@@ -2861,14 +2861,13 @@ def __init__(
28612861 self .created_at = created_at
28622862 self .finish_reason = finish_reason
28632863 self .usage_details = usage_details
2864- self .value = value
2864+ self ._value : Any | None = value
2865+ self ._response_format : type [BaseModel ] | None = response_format
2866+ self ._value_parsed : bool = value is not None
28652867 self .additional_properties = additional_properties or {}
28662868 self .additional_properties .update (kwargs or {})
28672869 self .raw_representation : Any | list [Any ] | None = raw_representation
28682870
2869- if response_format :
2870- self .try_parse_value (output_format_type = response_format )
2871-
28722871 @classmethod
28732872 def from_chat_response_updates (
28742873 cls : type [TChatResponse ],
@@ -2933,29 +2932,78 @@ async def from_chat_response_generator(
29332932 Keyword Args:
29342933 output_format_type: Optional Pydantic model type to parse the response text into structured data.
29352934 """
2936- msg = cls (messages = [])
2935+ response_format = output_format_type if isinstance (output_format_type , type ) else None
2936+ msg = cls (messages = [], response_format = response_format )
29372937 async for update in updates :
29382938 _process_update (msg , update )
29392939 _finalize_response (msg )
2940- if output_format_type and isinstance ( output_format_type , type ) and issubclass (output_format_type , BaseModel ):
2941- msg .try_parse_value (output_format_type )
2940+ if response_format and issubclass (response_format , BaseModel ):
2941+ msg .try_parse_value (response_format )
29422942 return msg
29432943
29442944 @property
29452945 def text (self ) -> str :
29462946 """Returns the concatenated text of all messages in the response."""
29472947 return ("\n " .join (message .text for message in self .messages if isinstance (message , ChatMessage ))).strip ()
29482948
2949+ @property
2950+ def value (self ) -> Any | None :
2951+ """Get the parsed structured output value.
2952+
2953+ If a response_format was provided and parsing hasn't been attempted yet,
2954+ this will attempt to parse the text into the specified type.
2955+
2956+ Raises:
2957+ ValidationError: If the response text doesn't match the expected schema.
2958+ """
2959+ if self ._value_parsed :
2960+ return self ._value
2961+ if (
2962+ self ._response_format is not None
2963+ and isinstance (self ._response_format , type )
2964+ and issubclass (self ._response_format , BaseModel )
2965+ ):
2966+ self ._value = self ._response_format .model_validate_json (self .text )
2967+ self ._value_parsed = True
2968+ return self ._value
2969+
29492970 def __str__ (self ) -> str :
29502971 return self .text
29512972
2952- def try_parse_value (self , output_format_type : type [BaseModel ]) -> None :
2953- """If there is a value, does nothing, otherwise tries to parse the text into the value."""
2954- if self .value is None and isinstance (output_format_type , type ) and issubclass (output_format_type , BaseModel ):
2955- try :
2956- self .value = output_format_type .model_validate_json (self .text ) # type: ignore[reportUnknownMemberType]
2957- except ValidationError as ex :
2958- logger .debug ("Failed to parse value from chat response text: %s" , ex )
2973+ def try_parse_value (self , output_format_type : type [_T ] | None = None ) -> _T | None :
2974+ """Try to parse the text into a typed value.
2975+
2976+ This is the safe alternative to accessing the value property directly.
2977+ Returns the parsed value on success, or None on failure.
2978+
2979+ Args:
2980+ output_format_type: The Pydantic model type to parse into.
2981+ If None, uses the response_format from initialization.
2982+
2983+ Returns:
2984+ The parsed value as the specified type, or None if parsing fails.
2985+ """
2986+ format_type = output_format_type or self ._response_format
2987+ if format_type is None or not (isinstance (format_type , type ) and issubclass (format_type , BaseModel )):
2988+ return None
2989+
2990+ # Cache the result unless a different schema than the configured response_format is requested.
2991+ # This prevents calls with a different schema from polluting the cached value.
2992+ use_cache = (
2993+ self ._response_format is None or output_format_type is None or output_format_type is self ._response_format
2994+ )
2995+
2996+ if use_cache and self ._value_parsed and self ._value is not None :
2997+ return self ._value # type: ignore[return-value, no-any-return]
2998+ try :
2999+ parsed_value = format_type .model_validate_json (self .text ) # type: ignore[reportUnknownMemberType]
3000+ if use_cache :
3001+ self ._value = parsed_value
3002+ self ._value_parsed = True
3003+ return parsed_value # type: ignore[return-value]
3004+ except ValidationError as ex :
3005+ logger .warning ("Failed to parse value from chat response text: %s" , ex )
3006+ return None
29593007
29603008
29613009# region ChatResponseUpdate
@@ -3141,6 +3189,7 @@ def __init__(
31413189 created_at : CreatedAtT | None = None ,
31423190 usage_details : UsageDetails | MutableMapping [str , Any ] | None = None ,
31433191 value : Any | None = None ,
3192+ response_format : type [BaseModel ] | None = None ,
31443193 raw_representation : Any | None = None ,
31453194 additional_properties : dict [str , Any ] | None = None ,
31463195 ** kwargs : Any ,
@@ -3153,6 +3202,7 @@ def __init__(
31533202 created_at: A timestamp for the chat response.
31543203 usage_details: The usage details for the chat response.
31553204 value: The structured output of the agent run response, if applicable.
3205+ response_format: Optional response format for the agent response.
31563206 additional_properties: Any additional properties associated with the chat response.
31573207 raw_representation: The raw representation of the chat response from an underlying implementation.
31583208 **kwargs: Additional properties to set on the response.
@@ -3180,7 +3230,9 @@ def __init__(
31803230 self .response_id = response_id
31813231 self .created_at = created_at
31823232 self .usage_details = usage_details
3183- self .value = value
3233+ self ._value : Any | None = value
3234+ self ._response_format : type [BaseModel ] | None = response_format
3235+ self ._value_parsed : bool = value is not None
31843236 self .additional_properties = additional_properties or {}
31853237 self .additional_properties .update (kwargs or {})
31863238 self .raw_representation = raw_representation
@@ -3190,6 +3242,27 @@ def text(self) -> str:
31903242 """Get the concatenated text of all messages."""
31913243 return "" .join (msg .text for msg in self .messages ) if self .messages else ""
31923244
3245+ @property
3246+ def value (self ) -> Any | None :
3247+ """Get the parsed structured output value.
3248+
3249+ If a response_format was provided and parsing hasn't been attempted yet,
3250+ this will attempt to parse the text into the specified type.
3251+
3252+ Raises:
3253+ ValidationError: If the response text doesn't match the expected schema.
3254+ """
3255+ if self ._value_parsed :
3256+ return self ._value
3257+ if (
3258+ self ._response_format is not None
3259+ and isinstance (self ._response_format , type )
3260+ and issubclass (self ._response_format , BaseModel )
3261+ ):
3262+ self ._value = self ._response_format .model_validate_json (self .text )
3263+ self ._value_parsed = True
3264+ return self ._value
3265+
31933266 @property
31943267 def user_input_requests (self ) -> list [UserInputRequestContents ]:
31953268 """Get all BaseUserInputRequest messages from the response."""
@@ -3215,7 +3288,7 @@ def from_agent_run_response_updates(
32153288 Keyword Args:
32163289 output_format_type: Optional Pydantic model type to parse the response text into structured data.
32173290 """
3218- msg = cls (messages = [])
3291+ msg = cls (messages = [], response_format = output_format_type )
32193292 for update in updates :
32203293 _process_update (msg , update )
32213294 _finalize_response (msg )
@@ -3238,7 +3311,7 @@ async def from_agent_response_generator(
32383311 Keyword Args:
32393312 output_format_type: Optional Pydantic model type to parse the response text into structured data
32403313 """
3241- msg = cls (messages = [])
3314+ msg = cls (messages = [], response_format = output_format_type )
32423315 async for update in updates :
32433316 _process_update (msg , update )
32443317 _finalize_response (msg )
@@ -3249,13 +3322,40 @@ async def from_agent_response_generator(
32493322 def __str__ (self ) -> str :
32503323 return self .text
32513324
3252- def try_parse_value (self , output_format_type : type [BaseModel ]) -> None :
3253- """If there is a value, does nothing, otherwise tries to parse the text into the value."""
3254- if self .value is None :
3255- try :
3256- self .value = output_format_type .model_validate_json (self .text ) # type: ignore[reportUnknownMemberType]
3257- except ValidationError as ex :
3258- logger .debug ("Failed to parse value from agent run response text: %s" , ex )
3325+ def try_parse_value (self , output_format_type : type [_T ] | None = None ) -> _T | None :
3326+ """Try to parse the text into a typed value.
3327+
3328+ This is the safe alternative when you need to parse the response text into a typed value.
3329+ Returns the parsed value on success, or None on failure.
3330+
3331+ Args:
3332+ output_format_type: The Pydantic model type to parse into.
3333+ If None, uses the response_format from initialization.
3334+
3335+ Returns:
3336+ The parsed value as the specified type, or None if parsing fails.
3337+ """
3338+ format_type = output_format_type or self ._response_format
3339+ if format_type is None or not (isinstance (format_type , type ) and issubclass (format_type , BaseModel )):
3340+ return None
3341+
3342+ # Cache the result unless a different schema than the configured response_format is requested.
3343+ # This prevents calls with a different schema from polluting the cached value.
3344+ use_cache = (
3345+ self ._response_format is None or output_format_type is None or output_format_type is self ._response_format
3346+ )
3347+
3348+ if use_cache and self ._value_parsed and self ._value is not None :
3349+ return self ._value # type: ignore[return-value, no-any-return]
3350+ try :
3351+ parsed_value = format_type .model_validate_json (self .text ) # type: ignore[reportUnknownMemberType]
3352+ if use_cache :
3353+ self ._value = parsed_value
3354+ self ._value_parsed = True
3355+ return parsed_value # type: ignore[return-value]
3356+ except ValidationError as ex :
3357+ logger .warning ("Failed to parse value from agent run response text: %s" , ex )
3358+ return None
32593359
32603360
32613361# region AgentResponseUpdate
0 commit comments