Repository navigation
Python: fix(core): keep AgentResponse usage when streaming a workflow agent - #9720
Open
Somay kousis (Somay-kousis) wants to merge 2 commits into
Open
Somay kousis (Somay-kousis) wants to merge 2 commits into
Somay kousis (Somay-kousis) wants to merge 2 commits into
Conversation
… agent WorkflowAgent turned an AgentResponse output into stream updates without its usage, so a streamed run lost the token counts that a non-streamed run reports. Carry the usage as usage content on the last update, the way AgentResponse.to_updates() does. When none of the response's messages is shown to the caller (only internal calls, or no messages), emit one update for the usage and the response-level fields. It has no message_id or author_name, so it joins the message in progress instead of splitting it. Fixes microsoft#9564
Somay kousis (Somay-kousis)
requested review from
SergeyMenshykh,
Tao Chen (TaoChenOSU),
Eduard van Valkenburg (eavanvalkenburg),
Giles Odigwe (giles17),
Jose Alvarez (jpalvarezl),
Evan Mattson (moonbox3),
Roger Barreto (rogerbarreto),
sophia-ramsey and
westey (westey-m)
as code owners
October 26, 2026 16:12
Somay kousis (Somay-kousis)
temporarily deployed
to
github-app-auth
October 26, 2026 16:12 — with
GitHub Actions
Inactive
Somay kousis (Somay-kousis)
temporarily deployed
to
github-app-auth
October 26, 2026 16:13 — with
GitHub Actions
Inactive
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Usage-only updates expose filtered internal messages through their raw representation.
1 open issue
What changed in this PR
This PR addresses #9564 by preserving token usage when workflow agents stream AgentResponse outputs, aligning streaming behavior with non-streaming runs.
Changes:
- Appends usage content to the final update for each response.
- Emits a usage-only update when no messages remain after filtering, preserving response metadata.
- Adds regression tests for usage totals, filtered responses, message continuity, and continuation tokens.
| File | Description |
|---|---|
python/packages/core/tests/workflow/test_workflow_agent.py |
Adds streaming usage and metadata regression tests. |
python/packages/core/agent_framework/_workflows/_agent.py |
Preserves response usage in normal and usage-only stream updates. |
Review effort: Balanced
| response_id=data.response_id or response_id, | ||
| created_at=data.created_at | ||
| or datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ"), | ||
| raw_representation=cast(AgentResponse[Any], data), |
Contributor
Author
There was a problem hiding this comment.
Right, fixed in 56d3841: the usage-only update no longer sets raw_representation, so the filtered messages don't reach the caller through it or the final response. The edge-case test now also asserts that no streamed update has a raw representation and that the final response's raw representation holds no AgentResponse; it fails on aa19c4f and passes now.
…pdate The usage-only update carried the whole AgentResponse as its raw representation, so the internal messages that the caller filter drops reached the caller through it and through the final response. Leave it unset, and assert that no update or final raw representation carries the response.
Somay kousis (Somay-kousis)
temporarily deployed
to
github-app-auth
October 26, 2026 16:21 — with
GitHub Actions
Inactive
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Motivation & Context
A workflow executor that yields an
AgentResponsewithusage_details(for exampleresponse = await agent.run(...)thenawait ctx.yield_output(response)) reports that usage throughworkflow.as_agent().run(...), butrun(..., stream=True)ends withusage_details=Noneand no update carries a usage content. Streaming consumers of a workflow agent (hosting, DevUI, telemetry, cost tracking) lose the token counts. Details and repro in #9564.Description & Review Guide
WorkflowAgent's conversion of anAgentResponseoutput into stream updates (_workflows/_agent.py), the usage is appended asContent.from_usage(...)to the last update, next to the other response-level fields copied there. That is howAgentResponse.to_updates()carries usage, andfrom_updatesreads it back from contents. When none of the response's messages is shown to the caller (only internal function-call messages, or no messages), one update is emitted that carries the usage and the same response-level fields (continuation_token,finish_reason,agent_id, ...). It has nomessage_idorauthor_name, so it joins the message in progress instead of splitting it when it lands between chunks of another stream.usage_details. Usage from severalAgentResponseoutputs adds up the same way in both modes. Responses without usage are unchanged, and the usage-only update adds no message to the final response.Tests in
tests/workflow/test_workflow_agent.py:test_workflow_as_agent_stream_preserves_agent_response_usage: usage only on the last update, final response matches non-streaming.test_workflow_as_agent_stream_sums_usage_of_multiple_agent_responses: two outputs, same total in both modes.test_workflow_as_agent_stream_keeps_usage_without_caller_facing_messages[only_internal_call|no_messages]: usage, text and message count match non-streaming.test_workflow_as_agent_stream_usage_only_update_does_not_split_a_message: a usage-only response between two chunks of one streamed message leaves it as one message.test_workflow_as_agent_stream_usage_only_update_keeps_continuation_token: the usage-only update carries the response'scontinuation_token.With
_agent.pyfrom main, 6 of these 7 fail. Removing only the usage-only branch fails the 4 tests that need it.tests/workflow: 1464 passed, 1 skipped, 2 xfailed;packages/orchestrations/tests: 297 passed, 6 skipped. Ruff check is clean on the changed files, and the coretypingandtest-typingtasks (pyrefly, ty, zuban, pyright, mypy) pass.Related Issue
Fixes #9564
AI Assistance
AI assistance details: AI tools helped find the bug and write the code, the tests and this description. I reviewed all of it and ran the repro, the workflow tests, and the lint and typing checks myself.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.