Python: Fix #3970: Python: [Bug]: AgentResponse.value is None when streaming workflow#4283
Python: Fix #3970: Python: [Bug]: AgentResponse.value is None when streaming workflow#4283moonbox3 wants to merge 1 commit intomicrosoft:mainfrom
AgentResponse.value is None when streaming workflow#4283Conversation
microsoft#3970) The streaming path in Agent.run() was reading response_format from the raw options parameter, which doesn't include values from default_options. The non-streaming path correctly read from ctx["chat_options"] which merges default_options with runtime options. Changed the streaming finalizer to lazily read response_format from the prepared run context (ctx_holder), ensuring default_options.response_format is properly propagated when the stream is finalized. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AgentResponse.value is None when streaming workflowAgentResponse.value is None when streaming workflow
There was a problem hiding this comment.
Pull request overview
Fixes a Python streaming bug where AgentResponse.value is not populated when response_format is provided via an agent’s default_options (reported in #3970), by ensuring the streaming finalizer uses the merged run context options.
Changes:
- Update the streaming
Agent.run(..., stream=True)finalizer to deriveresponse_formatfrom the prepared run context (chat_options) rather than the rawoptionsargument. - Add a regression test verifying structured output parsing works in streaming when
response_formatis set indefault_options. - Minor test-only formatting cleanup for long assertion messages.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| python/packages/core/agent_framework/_agents.py | Fixes streaming finalization to use merged context options (including default_options response_format). |
| python/packages/core/tests/core/test_agents.py | Adds regression coverage for streaming + default_options["response_format"] parsing (#3970). |
| python/packages/core/tests/core/test_function_invocation_logic.py | Collapses multi-line f-strings in assertions (formatting only). |
|
|
||
| def _finalize_with_context(updates: Sequence[AgentResponseUpdate]) -> AgentResponse: | ||
| ctx = ctx_holder["ctx"] | ||
| response_format = ctx["chat_options"].get("response_format") if ctx else None |
There was a problem hiding this comment.
Streaming finalizer pulls response_format from ctx["chat_options"] but doesn’t apply the same BaseModel subclass validation that the non-streaming path does (where non-BaseModel values are normalized to None). This can lead to inconsistent AgentResponse.response_format contents between streaming and non-streaming runs and can violate the annotated type. Consider reusing the non-streaming validation logic here (or inside _finalize_response_updates) so only type[BaseModel] is propagated for parsing.
| response_format = ctx["chat_options"].get("response_format") if ctx else None | |
| raw_response_format = ctx["chat_options"].get("response_format") if ctx else None | |
| # Normalize response_format so only BaseModel subclasses are propagated, matching non-streaming behavior. | |
| if isinstance(raw_response_format, type) and issubclass(raw_response_format, BaseModel): | |
| response_format = raw_response_format | |
| else: | |
| response_format = None |
Automated fix for #3970.
Verification passed after 0 retries.