Skip to content

Python: Surface Anthropic citations_delta annotations when streaming - #8978

Open
Zakaria Khchiche (Zakariakhchiche) wants to merge 3 commits into
microsoft:mainfrom
Zakariakhchiche:issue-8977-anthropic-streaming-citations
Open

Zakaria Khchiche (Zakariakhchiche) wants to merge 3 commits into
microsoft:mainfrom
Zakariakhchiche:issue-8977-anthropic-streaming-citations

Conversation

@Zakariakhchiche

Copy link
Copy Markdown
Contributor

Motivation & Context

In streaming mode Anthropic sends citations (web search results, search results, document citations) as content_block_delta events with delta.type == "citations_delta". AnthropicClient had no case for them, so streamed responses lost every citation, while non-streaming responses kept them as text annotations.

Description & Review Guide

  • What are the major changes?
    • _parse_contents_from_anthropic handles citations_delta by emitting an empty text Content that carries the parsed annotation. This is the same pattern the Responses client uses for response.output_text.annotation.added, and text coalescing merges it into the block's text.
    • _parse_citations_from_anthropic also accepts the delta's single citation field, so all citation types keep the existing mapping.
    • Regression test that streams real anthropic SDK events (BetaCitationsDelta + BetaTextDelta) through get_response(stream=True) and checks the final response annotations.
  • What is the impact of these changes? Streaming and non-streaming now return the same citation annotations. No change for responses without citations.
  • What do you want reviewers to focus on? Using an empty text content as the annotation carrier, consistent with the OpenAI Responses client.

Related Issue

Fixes #8977

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Adds support for Anthropic streaming citations_delta events so citation annotations are preserved in streaming mode, aligning behavior with non-streaming responses (fixing #8977).

Changes:

  • Handle citations_delta in _parse_contents_from_anthropic by emitting an empty text Content carrying parsed citation annotations.
  • Extend _parse_citations_from_anthropic to also accept the delta’s single citation field.
  • Add a regression test that streams real Anthropic SDK delta events and asserts final response annotations.
File Description
python/​packages/​anthropic/​agent_framework_anthropic/​_chat_client.py Parses citations_delta during streaming and maps it into standard text annotations.
python/​packages/​anthropic/​tests/​test_anthropic_client.py Adds a regression test validating streamed citations become response annotations.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1677 to +1679
content_blocks: Any = getattr(content_block, "citations", None)
if content_block.type == "citations_delta":
content_blocks = [content_block.citation]
client = create_test_anthropic_client(mock_anthropic_client)
mock_anthropic_client.beta.messages.create.side_effect = create
stream = client.get_response([Message(role="user", contents=["Weather in Paris?"])], stream=True)
_ = [update async for update in stream]
@Zakariakhchiche

Copy link
Copy Markdown
Contributor Author

Notes on the automated review:

  • The test now iterates the stream directly (pushed).
  • Any for content_blocks: narrowing with isinstance(content_block, BetaCitationsDelta) makes pyright and mypy reject the location-specific attribute access further down in _parse_citations_from_anthropic, which handles several citation location types. So I kept the existing duck-typed handling and dispatch on content_block.type; uv run poe pyright and uv run poe mypy report no new errors for the package.

Content.from_text(
text="",
raw_representation=content_block,
annotations=self._parse_citations_from_anthropic(content_block),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Zakaria Khchiche (@Zakariakhchiche), streaming a citation from a plain-text document now aborts here. BetaCitationCharLocation exposes document_title, not title, but _parse_citations_from_anthropic() reads citation.title in its char_location branch. This new dispatch therefore turns a valid document citation delta into an AttributeError, terminating get_response(..., stream=True) as a ChatClientException instead of returning the response. Please map document_title and add a streaming regression using a real BetaCitationCharLocation; the new test covers only web-search citations, and the existing character-citation test supplies an invented title attribute through MagicMock.

This branch was previously deployed

1 inactive deployment
github-app-auth — f34f3e59 Deployed Oct 2, 2026 by Zakariakhchiche via add_label #24309
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: AnthropicClient drops citations in streaming mode (citations_delta ignored)

3 participants