Normalize citations from Bedrock, OpenRouter, and xAI
Merged
AD
9d ago+11637+11637+01234567890123456789012345678901234567890123456789-219-219-012345678901234567890123456789
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
| Provider | What's cited | How to turn it on | Anchor |
|---|---|---|---|
| Bedrock, Anthropic models | TXT and PDF documents, in the prompt or returned by tools | Citations() | Whole text part |
| OpenRouter | Web pages, including Perplexity's | Turn on web search | None: OpenRouter's offsets are 0/0 |
| xAI | Web, X and collection citations | Citations() | The [[1]](https://...) marker the model writes |
When Citations() asks for citations. The same rule as #7434, because these providers fail the same way:
| Situation | Bedrock (Anthropic models) | xAI |
|---|---|---|
| Text output | Asks | Asks |
| Structured output | Doesn't ask: markup in fields 3 of 3 raw, outputConfig gets a 400 | Doesn'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_choice | Raises UserError when the request has a document: markup in the arguments 3 of 3, 0 of 3 without | Asks: forced tool arguments stayed clean 3 of 3 live |
| Function tools the model chooses | Asks; 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). Abedrock_supports_citationsprofile 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 whereCitations()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
webmember, 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_tokensstreams, so theConverseshape 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 withoutCitations().
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.