@@ -299,6 +299,7 @@ def _restore_compaction_annotation_in_additional_properties(
299299AgentResponseT = TypeVar ("AgentResponseT" , bound = "AgentResponse" )
300300ResponseModelT = TypeVar ("ResponseModelT" , bound = BaseModel | None , default = None , covariant = True )
301301ResponseModelBoundT = TypeVar ("ResponseModelBoundT" , bound = BaseModel )
302+ StructuredResponseFormat = type [BaseModel ] | Mapping [str , Any ] | None
302303
303304CreatedAtT = str # Use a datetimeoffset type? Or a more specific type like datetime.datetime?
304305
@@ -1949,6 +1950,24 @@ class ContinuationToken(TypedDict):
19491950# endregion
19501951
19511952
1953+ def _parse_structured_response_value (text : str , response_format : Any | None ) -> Any | None :
1954+ if response_format is None :
1955+ return None
1956+ if isinstance (response_format , type ) and issubclass (response_format , BaseModel ):
1957+ return response_format .model_validate_json (text )
1958+ if isinstance (response_format , Mapping ):
1959+ try :
1960+ return json .loads (text )
1961+ except json .JSONDecodeError as exc :
1962+ raise ValueError (f"Response text is not valid JSON: { exc } " ) from exc
1963+ logger .warning (
1964+ "Unable to parse structured response value, use either a Pydantic model or a dict defining the schema, "
1965+ "received response_format type: %s" ,
1966+ type (response_format ), # type: ignore[reportUnknownArgumentType]
1967+ )
1968+ return None
1969+
1970+
19521971class ChatResponse (SerializationMixin , Generic [ResponseModelT ]):
19531972 """Represents the response to a chat request.
19541973
@@ -2014,7 +2033,7 @@ def __init__(
20142033 finish_reason : FinishReasonLiteral | FinishReason | None = None ,
20152034 usage_details : UsageDetails | None = None ,
20162035 value : ResponseModelT | None = None ,
2017- response_format : type [ BaseModel ] | None = None ,
2036+ response_format : StructuredResponseFormat = None ,
20182037 continuation_token : ContinuationToken | None = None ,
20192038 additional_properties : dict [str , Any ] | None = None ,
20202039 raw_representation : Any | None = None ,
@@ -2058,7 +2077,7 @@ def __init__(
20582077 self .finish_reason = finish_reason
20592078 self .usage_details = usage_details
20602079 self ._value : ResponseModelT | None = value
2061- self ._response_format : type [ BaseModel ] | None = response_format
2080+ self ._response_format : StructuredResponseFormat = response_format
20622081 self ._value_parsed : bool = value is not None
20632082 self .additional_properties = (
20642083 _restore_compaction_annotation_in_additional_properties (additional_properties ) or {}
@@ -2087,6 +2106,15 @@ def from_updates(
20872106 output_format_type : type [ResponseModelBoundT ],
20882107 ) -> ChatResponse [ResponseModelBoundT ]: ...
20892108
2109+ @overload
2110+ @classmethod
2111+ def from_updates (
2112+ cls : type [ChatResponse [Any ]],
2113+ updates : Sequence [ChatResponseUpdate ],
2114+ * ,
2115+ output_format_type : Mapping [str , Any ],
2116+ ) -> ChatResponse [Any ]: ...
2117+
20902118 @overload
20912119 @classmethod
20922120 def from_updates (
@@ -2101,7 +2129,7 @@ def from_updates(
21012129 cls : type [ChatResponseT ],
21022130 updates : Sequence [ChatResponseUpdate ],
21032131 * ,
2104- output_format_type : type [ BaseModel ] | None = None ,
2132+ output_format_type : StructuredResponseFormat = None ,
21052133 ) -> ChatResponseT :
21062134 """Joins multiple updates into a single ChatResponse.
21072135
@@ -2124,10 +2152,10 @@ def from_updates(
21242152 updates: A sequence of ChatResponseUpdate objects to combine.
21252153
21262154 Keyword Args:
2127- output_format_type: Optional Pydantic model type to parse the response text into structured data.
2155+ output_format_type: Optional Pydantic model type or JSON schema mapping used to parse the
2156+ response text into structured data.
21282157 """
2129- response_format = output_format_type if isinstance (output_format_type , type ) else None
2130- msg = cls (messages = [], response_format = response_format )
2158+ msg = cls (messages = [], response_format = output_format_type )
21312159 for update in updates :
21322160 _process_update (msg , update )
21332161 _finalize_response (msg )
@@ -2142,6 +2170,15 @@ async def from_update_generator(
21422170 output_format_type : type [ResponseModelBoundT ],
21432171 ) -> ChatResponse [ResponseModelBoundT ]: ...
21442172
2173+ @overload
2174+ @classmethod
2175+ async def from_update_generator (
2176+ cls : type [ChatResponse [Any ]],
2177+ updates : AsyncIterable [ChatResponseUpdate ],
2178+ * ,
2179+ output_format_type : Mapping [str , Any ],
2180+ ) -> ChatResponse [Any ]: ...
2181+
21452182 @overload
21462183 @classmethod
21472184 async def from_update_generator (
@@ -2156,7 +2193,7 @@ async def from_update_generator(
21562193 cls : type [ChatResponseT ],
21572194 updates : AsyncIterable [ChatResponseUpdate ],
21582195 * ,
2159- output_format_type : type [ BaseModel ] | None = None ,
2196+ output_format_type : StructuredResponseFormat = None ,
21602197 ) -> ChatResponseT :
21612198 """Joins multiple updates into a single ChatResponse.
21622199
@@ -2175,10 +2212,10 @@ async def from_update_generator(
21752212 updates: An async iterable of ChatResponseUpdate objects to combine.
21762213
21772214 Keyword Args:
2178- output_format_type: Optional Pydantic model type to parse the response text into structured data.
2215+ output_format_type: Optional Pydantic model type or JSON schema mapping used to parse the
2216+ response text into structured data.
21792217 """
2180- response_format = output_format_type if isinstance (output_format_type , type ) else None
2181- msg = cls (messages = [], response_format = response_format )
2218+ msg = cls (messages = [], response_format = output_format_type )
21822219 async for update in updates :
21832220 _process_update (msg , update )
21842221 _finalize_response (msg )
@@ -2198,15 +2235,12 @@ def value(self) -> ResponseModelT | None:
21982235
21992236 Raises:
22002237 ValidationError: If the response text doesn't match the expected schema.
2238+ ValueError: If the response text is not valid JSON for a non-Pydantic structured format.
22012239 """
22022240 if self ._value_parsed :
22032241 return self ._value
2204- if (
2205- self ._response_format is not None
2206- and isinstance (self ._response_format , type )
2207- and issubclass (self ._response_format , BaseModel )
2208- ):
2209- self ._value = cast (ResponseModelT , self ._response_format .model_validate_json (self .text ))
2242+ if self ._response_format is not None :
2243+ self ._value = cast (ResponseModelT , _parse_structured_response_value (self .text , self ._response_format ))
22102244 self ._value_parsed = True
22112245 return self ._value
22122246
@@ -2397,7 +2431,7 @@ def __init__(
23972431 created_at : CreatedAtT | None = None ,
23982432 usage_details : UsageDetails | None = None ,
23992433 value : ResponseModelT | None = None ,
2400- response_format : type [ BaseModel ] | None = None ,
2434+ response_format : StructuredResponseFormat = None ,
24012435 continuation_token : ContinuationToken | None = None ,
24022436 raw_representation : Any | None = None ,
24032437 additional_properties : dict [str , Any ] | None = None ,
@@ -2438,7 +2472,7 @@ def __init__(
24382472 self .created_at = created_at
24392473 self .usage_details = usage_details
24402474 self ._value : ResponseModelT | None = value
2441- self ._response_format : type [BaseModel ] | None = response_format
2475+ self ._response_format : type [BaseModel ] | Mapping [ str , Any ] | None = response_format
24422476 self ._value_parsed : bool = value is not None
24432477 self .additional_properties = (
24442478 _restore_compaction_annotation_in_additional_properties (additional_properties ) or {}
@@ -2460,15 +2494,12 @@ def value(self) -> ResponseModelT | None:
24602494
24612495 Raises:
24622496 ValidationError: If the response text doesn't match the expected schema.
2497+ ValueError: If the response text is not valid JSON for a non-Pydantic structured format.
24632498 """
24642499 if self ._value_parsed :
24652500 return self ._value
2466- if (
2467- self ._response_format is not None
2468- and isinstance (self ._response_format , type )
2469- and issubclass (self ._response_format , BaseModel )
2470- ):
2471- self ._value = cast (ResponseModelT , self ._response_format .model_validate_json (self .text ))
2501+ if self ._response_format is not None :
2502+ self ._value = cast (ResponseModelT , _parse_structured_response_value (self .text , self ._response_format ))
24722503 self ._value_parsed = True
24732504 return self ._value
24742505
@@ -2492,6 +2523,16 @@ def from_updates(
24922523 value : Any | None = None ,
24932524 ) -> AgentResponse [ResponseModelBoundT ]: ...
24942525
2526+ @overload
2527+ @classmethod
2528+ def from_updates (
2529+ cls : type [AgentResponse [Any ]],
2530+ updates : Sequence [AgentResponseUpdate ],
2531+ * ,
2532+ output_format_type : Mapping [str , Any ],
2533+ value : Any | None = None ,
2534+ ) -> AgentResponse [Any ]: ...
2535+
24952536 @overload
24962537 @classmethod
24972538 def from_updates (
@@ -2507,7 +2548,7 @@ def from_updates(
25072548 cls : type [AgentResponseT ],
25082549 updates : Sequence [AgentResponseUpdate ],
25092550 * ,
2510- output_format_type : type [ BaseModel ] | None = None ,
2551+ output_format_type : StructuredResponseFormat = None ,
25112552 value : Any | None = None ,
25122553 ) -> AgentResponseT :
25132554 """Joins multiple updates into a single AgentResponse.
@@ -2516,7 +2557,8 @@ def from_updates(
25162557 updates: A sequence of AgentResponseUpdate objects to combine.
25172558
25182559 Keyword Args:
2519- output_format_type: Optional Pydantic model type to parse the response text into structured data.
2560+ output_format_type: Optional Pydantic model type or JSON schema mapping used to parse the
2561+ response text into structured data.
25202562 value: Optional pre-parsed structured output value to set directly on the response.
25212563 """
25222564 msg = cls (messages = [], response_format = output_format_type , value = value )
@@ -2534,6 +2576,15 @@ async def from_update_generator(
25342576 output_format_type : type [ResponseModelBoundT ],
25352577 ) -> AgentResponse [ResponseModelBoundT ]: ...
25362578
2579+ @overload
2580+ @classmethod
2581+ async def from_update_generator (
2582+ cls : type [AgentResponse [Any ]],
2583+ updates : AsyncIterable [AgentResponseUpdate ],
2584+ * ,
2585+ output_format_type : Mapping [str , Any ],
2586+ ) -> AgentResponse [Any ]: ...
2587+
25372588 @overload
25382589 @classmethod
25392590 async def from_update_generator (
@@ -2548,15 +2599,16 @@ async def from_update_generator(
25482599 cls : type [AgentResponseT ],
25492600 updates : AsyncIterable [AgentResponseUpdate ],
25502601 * ,
2551- output_format_type : type [ BaseModel ] | None = None ,
2602+ output_format_type : StructuredResponseFormat = None ,
25522603 ) -> AgentResponseT :
25532604 """Joins multiple updates into a single AgentResponse.
25542605
25552606 Args:
25562607 updates: An async iterable of AgentResponseUpdate objects to combine.
25572608
25582609 Keyword Args:
2559- output_format_type: Optional Pydantic model type to parse the response text into structured data
2610+ output_format_type: Optional Pydantic model type or JSON schema mapping used to parse the
2611+ response text into structured data.
25602612 """
25612613 msg = cls (messages = [], response_format = output_format_type )
25622614 async for update in updates :
0 commit comments