Python: updated declarative samples and handling of non-pydantic response formats (#5022)

* updated declarative samples and handling of non-pydantic response formats

* fixed from comments

* update docstring
This commit is contained in:
Eduard van Valkenburg
2026-04-01 21:16:00 +02:00
committed by GitHub
Unverified
parent 6acab3d1d6
commit 519bb0cb2b
21 changed files with 370 additions and 90 deletions
+2 -6
View File
@@ -127,9 +127,7 @@ class MockChatClient:
yield ChatResponseUpdate(contents=[Content.from_text("another update")], role="assistant")
def _finalize(updates: Sequence[ChatResponseUpdate]) -> ChatResponse:
response_format = options.get("response_format")
output_format_type = response_format if isinstance(response_format, type) else None
return ChatResponse.from_updates(updates, output_format_type=output_format_type)
return ChatResponse.from_updates(updates, output_format_type=options.get("response_format"))
return ResponseStream(_stream(), finalizer=_finalize)
@@ -233,9 +231,7 @@ class MockBaseChatClient(
await asyncio.sleep(0)
def _finalize(updates: Sequence[ChatResponseUpdate]) -> ChatResponse:
response_format = options.get("response_format")
output_format_type = response_format if isinstance(response_format, type) else None
return ChatResponse.from_updates(updates, output_format_type=output_format_type)
return ChatResponse.from_updates(updates, output_format_type=options.get("response_format"))
return ResponseStream(_stream(), finalizer=_finalize)
@@ -301,6 +301,56 @@ async def test_chat_client_agent_streaming_response_format_from_run_options(
assert result.value.greeting == "Hi"
async def test_chat_client_agent_response_format_dict_from_default_options(
client: SupportsChatGetResponse,
) -> None:
"""AgentResponse.value should parse JSON dicts from default_options response_format."""
json_text = json.dumps({"greeting": "Hello"})
client.responses.append(ChatResponse(messages=Message(role="assistant", text=json_text))) # type: ignore[attr-defined]
agent = Agent(
client=client,
default_options={"response_format": {"type": "object", "properties": {"greeting": {"type": "string"}}}},
)
result = await agent.run("Hello")
assert result.text == json_text
assert result.value is not None
assert isinstance(result.value, dict)
assert result.value["greeting"] == "Hello"
async def test_chat_client_agent_streaming_response_format_dict_from_run_options(
client: SupportsChatGetResponse,
) -> None:
"""Agent streaming should preserve mapping response_format and parse the final value as a dict."""
json_text = json.dumps({"greeting": "Hi"})
client.streaming_responses.append( # type: ignore[attr-defined]
[
ChatResponseUpdate(
contents=[Content.from_text(json_text)],
role="assistant",
finish_reason="stop",
)
]
)
agent = Agent(client=client)
stream = agent.run(
"Hello",
stream=True,
options={"response_format": {"type": "object", "properties": {"greeting": {"type": "string"}}}},
)
async for _ in stream:
pass
result = await stream.get_final_response()
assert result.text == json_text
assert result.value is not None
assert isinstance(result.value, dict)
assert result.value["greeting"] == "Hi"
async def test_chat_client_agent_create_session(
client: SupportsChatGetResponse,
) -> None:
@@ -191,9 +191,7 @@ def mock_chat_client():
yield ChatResponseUpdate(contents=[Content.from_text(" world")], role="assistant", finish_reason="stop")
def _finalize(updates: Sequence[ChatResponseUpdate]) -> ChatResponse:
response_format = options.get("response_format")
output_format_type = response_format if isinstance(response_format, type) else None
return ChatResponse.from_updates(updates, output_format_type=output_format_type)
return ChatResponse.from_updates(updates, output_format_type=options.get("response_format"))
return ResponseStream(_stream(), finalizer=_finalize)
@@ -800,6 +800,19 @@ def test_chat_response_with_format_init():
assert response.value.response == "Hello"
def test_chat_response_with_mapping_response_format() -> None:
"""ChatResponse.value should parse JSON when response_format is a mapping."""
message = Message(role="assistant", text='{"response": "Hello"}')
response = ChatResponse(
messages=message,
response_format={"type": "object", "properties": {"response": {"type": "string"}}},
)
assert response.value is not None
assert isinstance(response.value, dict)
assert response.value["response"] == "Hello"
def test_chat_response_value_raises_on_invalid_schema():
"""Test that value property raises ValidationError with field constraint details."""
@@ -1004,6 +1017,22 @@ async def test_chat_response_from_async_generator_output_format_in_method():
assert resp.value.response == "Hello"
async def test_chat_response_from_async_generator_mapping_response_format() -> None:
async def gen() -> AsyncIterable[ChatResponseUpdate]:
yield ChatResponseUpdate(contents=[Content.from_text('{ "respon')], message_id="1")
yield ChatResponseUpdate(contents=[Content.from_text('se": "Hello" }')], message_id="1")
resp = await ChatResponse.from_update_generator(
gen(),
output_format_type={"type": "object", "properties": {"response": {"type": "string"}}},
)
assert resp.text == '{ "response": "Hello" }'
assert resp.value is not None
assert isinstance(resp.value, dict)
assert resp.value["response"] == "Hello"
# region ToolMode