Skip to content

fix(anthropic): resolve document_index citations to source URLs - #382

Open
cpsievert wants to merge 14 commits into
mainfrom
fix/anthropic-web-fetch-citations
Open

fix(anthropic): resolve document_index citations to source URLs#382
cpsievert wants to merge 14 commits into
mainfrom
fix/anthropic-web-fetch-citations

Conversation

@cpsievert

Copy link
Copy Markdown
Collaborator

Summary

Anthropic's web_fetch/document citations reference sources via document_index, an index into every document-shaped block sent in the request (spanning prior turns), not just the current response. Chatlas wasn't resolving that index to anything, so document_index-based citations had no attached source. This PR resolves those citations to their WebSource (URL/title), including:

  • Web-fetch results streamed in the current response.
  • Documents/PDFs/uploads attached earlier in the conversation (cross-turn), and attached in the same turn as the citing text.
  • Correct index accounting during streaming, where content arrives incrementally.

As a side effect of threading turn history through the streaming path, stream_content()/stream_turn()/value_turn() now accept the request's turns across all providers (previously only some providers received them), which fixes a related bug where turns supplied at request time weren't reaching provider content/turn resolution during streaming.

Known limitation

document_index accounting doesn't yet look inside custom tool results that embed raw Anthropic document blocks (an advanced, undocumented usage pattern via ContentToolResult(model_format="as_is")). Filed as a follow-up: #381.

Test plan

  • uv run pytest tests/test_provider_anthropic.py tests/test_provider_stream_contract.py tests/test_stream_thinking.py tests/test_echo_display.py tests/test_otel.py — 242 passed
  • Reviewed via /code-review; verified findings against SDK types and existing patterns

@cpsievert
cpsievert force-pushed the fix/anthropic-web-fetch-citations branch from 4aaebae to 9c82c01 Compare August 8, 2026 01:07
ellmer wraps its price list in an object (schema_version,
min_ellmer_version, updated_at, data) instead of a bare array. The
bot-updated prices.json on this branch already reflects that, but
pricing_list still parsed the whole file as the list, so every price
lookup raised "string indices must be integers".

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 Anthropic document_index citations by resolving them to concrete WebSource URLs/titles across the full request history (including prior turns) and threads the full request turns through the streaming hooks so providers can resolve cross-turn references during streaming.

Changes:

  • Resolve Anthropic document_index citations to source URLs by mapping document_index across prior-turn attachments and current-turn web_fetch results.
  • Pass full request turns into Provider.stream_content() / stream_turn() / value_turn() and update providers + tests accordingly.
  • Update token pricing loading to match the current wrapped prices.json schema ({"data": [...]}).

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_stream_thinking.py Updates fake provider streaming hook signatures to accept turns.
tests/test_provider_stream_contract.py Extends stream contract tests to assert turns propagation during streaming.
tests/test_provider_anthropic.py Adds coverage for document_index citation source resolution, including cross-turn cases.
tests/test_otel.py Updates OTEL streaming monkeypatches for new turns hook signature.
tests/test_echo_display.py Updates echo display test providers for new turns hook signature.
chatlas/types/anthropic/_submit.py Updates generated Anthropic submit typed dict model literals.
chatlas/_tokens.py Adjusts pricing loader to read prices.json["data"].
chatlas/_provider.py Adds turns to streaming/value hook signatures and documents purpose.
chatlas/_provider_snowflake.py Updates streaming/value hook signatures to accept turns.
chatlas/_provider_openai.py Updates streaming/value hook signatures to accept turns.
chatlas/_provider_openai_completions.py Updates streaming/value hook signatures to accept turns.
chatlas/_provider_google.py Updates streaming/value hook signatures to accept turns.
chatlas/_provider_bedrock_converse.py Updates streaming/value hook signatures to accept turns.
chatlas/_provider_anthropic.py Implements document_indexWebSource resolution via request-history document slot mapping.
chatlas/_chat.py Threads request_turns through streaming/value hooks when emitting content/turns.
CHANGELOG.md Documents the document_index citation resolution fix.

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

Comment on lines 56 to 61
"claude-haiku-4-5-20251001",
"claude-opus-4-5",
"claude-opus-4-5-20251101",
"claude-sonnet-4-5",
"claude-sonnet-4-5-20250929",
"claude-opus-4-1",
"claude-opus-4-1-20250805",
],
Comment thread CHANGELOG.md
Comment on lines +13 to +16
### Bug fixes

* `ChatAnthropic()` citations backed by `document_index` (from `tool_web_fetch()` results and document/PDF attachments) now resolve to a source URL, instead of always coming back without one. Anthropic counts that index across every document-shaped block in the whole request, including ones from prior turns, which chatlas wasn't accounting for. (#382)

ellmer's prices.json (see tidyverse/ellmer#968, the same PR that
introduced the envelope schema) no longer guarantees an `input` key
per row -- output-only models (e.g. Bedrock's TwelveLabs Pegasus video
models) omit it. get_token_cost() indexed price["input"] directly,
so pricing these models raised KeyError.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants