toolcall() ← all concepts

// concept · rag

Your RAG answer cannot prove where it came from

Your assistant quoted your documentation and gave you no way to check which page it used. That is not a model problem. It is a consequence of the shape you handed the retrieved text back in.

// plain text has no handle

The default RAG wiring concatenates the top-k chunks into a prompt and asks the question. The model answers well. But the answer is a block of prose, and nothing in the response says which chunk any sentence came from — so verifying a claim means a human going back to the corpus and searching for it.

# the usual shape prompt = "Context:\n" + "\n\n".join(chunks) + f"\n\nQuestion: {q}" → the answer is text. there is no mapping back.

Search result content blocks change the shape rather than the prompt:

Search result content blocks let Claude cite your own content the same way it cites web search results: each citation carries the source and title you provided.

Three fields you already have

A search result is not a new document format. It is the thing your retriever already returns, labelled.

{ "type": "search_result", "source": "https://docs.company.com/pricing", # required "title": "Pricing and plans", # required "content": [{ "type": "text", "text": "..." }], # required "citations": { "enabled": true } # optional, and OFF by default }

source does not have to be a URL — the docs say "Any stable string works: a URL, or an internal identifier such as kb://article-1234". So a private corpus with no public pages is fine.

They can be returned two ways: from a tool call, for live retrieval, or as top-level content in a user message for pre-fetched results. Either way:

Claude cites the search results automatically when citations are enabled. No special prompting is needed: ask your question, and citations appear on the text blocks that draw on your content.

What comes back is a mapping, not a footnote

The answer arrives as text blocks. A block that leaned on a result carries a citations array, and each entry identifies exactly what it used.

{ "type": "text", "text": "The default timeout is 30 seconds.", "citations": [{ "type": "search_result_location", "source": "https://docs.company.com/pricing", "title": "Pricing and plans", "cited_text": "...the default timeout is 30 seconds, but can be adjusted...", "search_result_index": 0, "start_block_index": 0, "end_block_index": 1 }] }

That is enough to render a footnote, highlight the passage in a viewer, or diff the sentence against the text it was built from — without asking the model to format anything, and without trusting it to format it consistently.

One useful detail: cited_text is "Not counted toward output tokens", so the quoted passage coming back does not cost you output.

Granularity is the block, not the sentence

This is the limit worth knowing before you promise anyone sentence-level attribution:

The text block is the minimal citable unit: Claude cites whole blocks, not substrings within a block. To get finer-grained citations, split your search result content into smaller blocks.

So citation precision is a chunking decision. One 2000-character block means every citation returns all 2000 characters; the same content split into paragraphs means a citation points at a paragraph.

# coarse — every citation returns the whole thing "content": [{ "type": "text", "text": "<the entire article>" }] # finer — a citation can land on one paragraph "content": [ { "type": "text", "text": "Authentication: all requests need an API key..." }, { "type": "text", "text": "Rate limits: 1000 requests per hour per key." } ]

Which makes this the same decision as chunking, pointed at a second purpose.

Off by default, and all or nothing

Two things will catch a migration.

It is off. "By default, citations are disabled for search results." You have to set citations: {"enabled": true} explicitly — moving to search result blocks without that field gets you the new shape and none of the benefit.

Citations are all-or-nothing: either all search results in a request must have citations enabled, or all must have them disabled. Mixing search results with different citation settings results in an error.

So you cannot roll it out one retriever at a time inside a single request. If two code paths contribute results to the same call, both have to agree.

availability every active model except Claude Haiku 3 beta header none — standard Messages API platforms Claude API · Amazon Bedrock · Google Cloud media text only, no images inside a search result placement user messages only, including inside tool results

And on empty searches, the docs advise returning a plain text block rather than raising: "Claude explains the empty result to the user, and the conversation continues."

Checkable, not true

Worth being precise about what this buys, because it is easy to oversell. A citation tells you which block a sentence leaned on. It says nothing about whether that block is correct, current, or should still exist.

Which is exactly why it is useful: it makes the other RAG failures visible. When a page you deleted is still in the index, a citation is what shows you that the answer came from it — without one, a stale copy and a fresh one produce identical-looking prose.

Related: working out where your RAG is broken is the diagnosis you run once, by hand; this is the same information arriving with every answer. Why models make things up is a different failure and citations are not a cure for it.

Source: Claude Platform docs — Search results. Verified 2026-09-13.

One concept a week. Free.

The deeper, copy-paste version of each ToolCall short — in your inbox.

// total: 0.00 · spam: void · unsubscribe: one click