Skip to content

Do not double-count usage when Response.ToUpdates re-emits per-message UsageContent#594

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanave:no-double-count-usage-toupdates
Open

Do not double-count usage when Response.ToUpdates re-emits per-message UsageContent#594
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanave:no-double-count-usage-toupdates

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

(*Response).ToUpdates now filters *message.UsageContent out of each per-message update's Contents, while keeping the single trailing aggregate UsageContent update.

Why

Providers embed usage inside message contents (e.g. anthropicprovider/agent.go puts UsageContent in msg.Contents). ToUpdates previously emitted the full msg.Contents (still holding that UsageContent) and appended a trailing update carrying resp.Usage(), which itself is the sum of per-message usage.

ResponseStream.Collect merges the empty-keyed trailing update into the last message, so Collect(resp.ToUpdates()).Usage() returned sum(per-message usage) + aggregate — double the real total.

This mirrors .NET ToAgentResponseUpdates, which emits usage exactly once. The filtering builds a fresh slice and never mutates msg.Contents, since the Response is caller-owned.

Testing

Added TestResponse_ToUpdates_RoundTripDoesNotDoubleCountUsage: builds a Response with a message containing a TextContent and a UsageContent{TotalTokenCount:100}, then asserts the per-message update carries no UsageContent, the trailing update reports 100, and Collect(resp.ToUpdates()).Usage().TotalTokenCount == 100 (was 200 before the fix). The test fails before the change and passes after.

go build ./..., go vet ./agent/..., and go test ./agent/... all pass.

@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 23, 2026 02:16
Copilot AI review requested due to automatic review settings July 23, 2026 02:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes usage double-counting when converting a completed agent.Response into streaming-style ResponseUpdates and then collecting them back into a Response.

Changes:

  • Filter *message.UsageContent out of per-message updates produced by (*Response).ToUpdates, while still emitting a single trailing aggregate usage update.
  • Add a regression test ensuring Collect(resp.ToUpdates()).Usage() matches the real total (no double-counting).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
agent/response.go Filters per-message Contents to omit embedded UsageContent so collection doesn’t double-count usage.
agent/response_test.go Adds a round-trip test covering the double-counting scenario and asserting correct aggregate usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread agent/response.go
Comment on lines +168 to +171
// contentsWithoutUsage returns a copy of contents with any *message.UsageContent
// removed. Per-message usage is re-emitted once as a trailing aggregate update in
// ToUpdates, so retaining it here would cause Collect to double-count usage. The
// input slice is not mutated, as the owning Response is caller-owned.
…e UsageContent

Response.ToUpdates emitted each message's full Contents (including any
UsageContent providers embed there, e.g. anthropicprovider) and then
appended a trailing aggregate UsageContent update. Collect merges the
trailing update into the last message, so the round trip counted usage
twice. Filter UsageContent out of the per-message updates without
mutating the caller-owned Response, keeping a single trailing aggregate
emission to match .NET ToAgentResponseUpdates semantics.
@PratikDhanave
PratikDhanave force-pushed the no-double-count-usage-toupdates branch from fe8b381 to 19cf425 Compare July 23, 2026 15:44
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review: No Cross-Repo Consistency Issues

This PR fixes a usage double-counting bug in (*Response).ToUpdates() and is in scope for parity review (it changes observable behavior of an exported method).

What changed

ToUpdates() previously included *message.UsageContent items inside per-message update Contents and appended a separate trailing aggregate UsageContent update. This caused Collect(resp.ToUpdates()).Usage() to return per-message usage + aggregate — twice the real total.

The fix strips *message.UsageContent from per-message update Contents, so usage is emitted exactly once via the trailing aggregate update. The unexported helper contentsWithoutUsage never mutates the caller-owned Response.

Upstream parity

The PR description correctly identifies the upstream reference: .NET ToAgentResponseUpdates already emits usage exactly once. The Go fix restores parity with .NET behavior rather than diverging from it.

No equivalent Python surface was found for ToUpdates/ToAgentResponseUpdates, so no Python parity gap applies here.

Conclusion

The exported ToUpdates() signature is unchanged. The behavioral correction aligns Go semantics with the .NET implementation. No cross-repo consistency issues found.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 38.4 AIC · ⌖ 5.54 AIC · ⊞ 5.7K ·

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants