Normalize citations from Bedrock, OpenRouter, and xAI

Merged
AD
9d ago
+11637-219

Adds citations from Bedrock and xAI in the TextPart.citations shape from #7434, plus recordings and docs for OpenRouter (including Perplexity models), whose citations come from #7434's OpenAI Chat mapping.

How it works, at a glance

ProviderWhat's citedHow to turn it onAnchor
Bedrock, Anthropic modelsTXT and PDF documents, in the prompt or returned by toolsCitations()Whole text part
OpenRouterWeb pages, including Perplexity'sTurn on web searchNone: OpenRouter's offsets are 0/0
xAIWeb, X and collection citationsCitations()The [[1]](https://...) marker the model writes

When Citations() asks for citations. The same rule as #7434, because these providers fail the same way:

SituationBedrock (Anthropic models)xAI
Text outputAsksAsks
Structured outputDoesn't ask: markup in fields 3 of 3 raw, outputConfig gets a 400Doesn't ask: tool output failed 6 of 6 live (the model answers in prose instead of calling the output tool; 5 of 5 passed without), and markers reached NativeOutput fields 2 of 6
A function tool forced with tool_choiceRaises UserError when the request has a document: markup in the arguments 3 of 3, 0 of 3 withoutAsks: forced tool arguments stayed clean 3 of 3 live
Function tools the model choosesAsks; the docs warn (markup 2 of 9 on Haiku 4.5)Asks: tools are called as usual (6 of 6) and their arguments stayed clean (0 of 4)

xai_include_inline_citations still takes precedence over citations when set.

Decisions

  • Bedrock citations only on models that support them. A live probe (2026-10-20) found Claude returns citations, while Nova, Llama, Mistral Pixtral and DeepSeek reject the whole request (This model doesn't support citations). A bedrock_supports_citations profile flag is set for Anthropic models, and the setting is ignored elsewhere.
  • Bedrock leaves out documents it can't cite. Plain-text documents go as text rather than bytes, so with citations on a text document that isn't UTF-8 raises a UserError (in the docs' provider table). Other formats (docx, csv, md, html) and S3 documents go without citations, because Bedrock rejects S3 with citations on (S3 as a document source is not allowed when citations are enabled).
  • Bedrock document titles are positional (Document 1), the name Pydantic AI gives each document. The docs say this.
  • A Bedrock document with citations keeps its place in the message, like a PDF, so a cache point after it still works.
  • xAI collection citations are document sources; their chunk and collection IDs stay in provider_details['citations']. xAI is the one provider where Citations() changes the answer text, because of the markers.
  • xAI streaming attaches citations only when it's unambiguous: to the last text part, only when exactly one output has citations and its text equals that part. Otherwise they're left off rather than guessed.

Limits

  • Structured output on Bedrock and xAI gets no citations from Citations() (above).
  • A streamed xAI answer followed by a tool call gets no citations; the docs say so.
  • xAI: only X citations are recorded. Web and collection citations are mapped from the SDK types; collection citations couldn't be run live. Anchors are checked against the text's bounds only.
  • Bedrock web locations are mapped from the documented web member, but aren't recorded.

Verification

  • Recorded tests: Bedrock text, PDF, and a document returned by a tool; OpenRouter and OpenRouter with Perplexity, streamed and not; xAI streamed and not. Since main's #10845, Bedrock Claude without max_tokens streams, so the Converse shape is covered by unit tests and the stream parity test.
  • Recordings starting with emoji and accented text check that OpenAI Responses, OpenAI Chat and xAI anchors select the right text. A live probe (2026-10-22) showed Anthropic and Bedrock count document positions in Python characters.
  • Live acceptance on 2026-10-22 fixed three Bedrock problems: S3 documents with citations got a 400, the prompt cache missed on the second turn (now 8,754 cached tokens, against 0), and structured output fields got markup.
  • Live check on 2026-10-23 of every output mode with Citations() on and off, on every provider, and of function tools with documents. It found the xAI structured-output failure and the Bedrock forced-tool markup, both fixed above. On every other provider structured output behaves the same with and without Citations().

Checklist

  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • No breaking changes in accordance with the version policy.
  • Any permitted compatibility impact has a label, warning, migration, release note, and exact API-check waiver.
  • PR title is fit for the release changelog.