Skip to content

Surface Anthropic text citation annotations on the streaming path - #681

Merged
George Adams (gdams) merged 2 commits into
microsoft:mainfrom
PratikDhanaveFork:surface-streaming-text-citations
Jul 29, 2026
Merged

Surface Anthropic text citation annotations on the streaming path#681
George Adams (gdams) merged 2 commits into
microsoft:mainfrom
PratikDhanaveFork:surface-streaming-text-citations

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

The Anthropic streaming path dropped all text citation annotations. In provider/anthropicprovider/agent.go the streaming loop's content_block_stop handler only converted tool_use blocks, and buildDelta has no citations_delta case, so streamed text arrived without the message.CitationAnnotation values that the non-streaming buildBlock produces from TextBlock.Citations.

This change surfaces those citations on the streaming path. Citations are only populated on the accumulated text block (the SDK appends each citations_delta to cb.Citations), so when a text block stops we inspect accumulated.Content[index]; if it is a TextBlock with citations we emit an annotations-only TextContent. The text itself was already streamed via text_delta, so Text is left empty to avoid duplicating it. The citation-to-CitationAnnotation mapping is extracted into a small citationAnnotations helper shared by both paths.

Why

Streamed and non-streamed responses should carry identical citation metadata. This matches the non-streaming behavior already covered by TestTextCitationsBecomeAnnotations, and aligns with the .NET and Python Agent Framework SDKs, where citation annotations are attached to streamed content updates as well as final messages.

Tests

Adds TestStreamingTextCitationsBecomeAnnotations in the canonical agent_test.go, using the existing httptest SSE harness: message_start -> text content_block_start -> text_delta -> citations_delta (a web_search_result_location with cited_text/title/url) -> content_block_stop -> message_delta/message_stop. Run with Stream(true), it asserts the collected TextContent carries a *message.CitationAnnotation with the expected Snippet/Title/URL. The test fails before the fix (no annotation) and passes after. go build ./..., go vet ./provider/anthropicprovider/..., and go test ./provider/anthropicprovider/... all pass.

Copilot AI review requested due to automatic review settings July 24, 2026 01:28
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner July 24, 2026 01:28

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

Ensures Anthropic streaming responses surface the same citation metadata as the non-streaming path by emitting citation annotations derived from accumulated text blocks when a streaming content block completes.

Changes:

  • Emit citation annotations on the streaming path when a TextBlock stops (without duplicating already-streamed text).
  • Factor citation-to-message.CitationAnnotation conversion into a shared citationAnnotations helper used by both streaming and non-streaming paths.
  • Add a new SSE-based streaming test to validate citation annotations are present in streamed TextContent.

Reviewed changes

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

File Description
provider/anthropicprovider/agent.go Adds citation annotation emission on streaming content_block_stop and refactors citation conversion into a helper shared with non-streaming.
provider/anthropicprovider/agent_test.go Adds a streaming SSE test asserting citations become CitationAnnotation updates during streaming collection.

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

Comment on lines +167 to +171
contents = append(contents, &message.TextContent{
ContentHeader: message.ContentHeader{
Annotations: annotations,
},
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in e996d12: the streamed citation-only TextContent now sets ContentHeader.RawRepresentation to the accumulated anthropic.TextBlock, matching buildBlock/buildDelta for consistent inspection of the underlying block.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
@PratikDhanave
PratikDhanave (PratikDhanave) force-pushed the surface-streaming-text-citations branch from 9bc7407 to e59d1eb Compare July 24, 2026 01:40
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

The streaming loop dropped all citation annotations: content_block_stop
only handled tool_use blocks and buildDelta had no citations_delta case,
so streamed text lost the CitationAnnotation values the non-streaming
buildBlock produces. Citations are only available on the accumulated
text block, so emit an annotations-only TextContent (empty Text, to
avoid duplicating the already-streamed text) when the block stops.

This aligns the streaming path with the non-streaming path and with the
.NET/Python SDKs, where streamed responses carry the same citation
annotations as non-streamed ones.
@PratikDhanave
PratikDhanave (PratikDhanave) force-pushed the surface-streaming-text-citations branch from e996d12 to 001d2e8 Compare July 24, 2026 09:34
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — parity-approved

Scope: provider/anthropicprovider/agent.go and agent_test.go

Change summary: This PR fixes a bug where Anthropic text citation annotations (mapped to message.CitationAnnotation) were silently dropped on the streaming path. The fix emits an annotations-only TextContent at content_block_stop for text blocks that carry citations, matching the behavior already present on the non-streaming path. A small citationAnnotations helper is extracted and shared between both paths.

Public API impact: None. No exported identifiers, types, option shapes, or observable public contracts were added, removed, or changed. The citationAnnotations function is unexported.

Cross-repo parity: The PR description correctly notes that .NET and Python Agent Framework SDKs attach citation annotations to streamed content as well as final messages. This fix brings the Go streaming path into parity with those implementations — it resolves a divergence rather than introducing one. No new Go-only public feature is introduced.

Conclusion: The change preserves semantic parity with upstream .NET and Python implementations. No parity issues found; public-api-change label is not warranted.

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 · 26.9 AIC · ⌖ 5.58 AIC · ⊞ 5.9K ·

@gdams
George Adams (gdams) added this pull request to the merge queue Jul 29, 2026
Merged via the queue into microsoft:main with commit e0b7724 Jul 29, 2026
20 checks passed
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.

3 participants