What Is Grounded Generation? Cited Output for AI Applications

TLDR: Grounded generation is generation constrained to evidence retrieved at request time, with citations in the response that point each claim to its source. RAG describes how evidence reaches the model. Grounded generation describes what the response must carry. As of October 2026, Anthropic, Google, OpenAI, Cohere, and You.com each return citations in a different shape, and any answer can be checked for coverage, valid pointers, and support.
A fluent answer with no sources cannot be verified, and an answer nobody can verify is hard to trust. Grounded generation is the response-side fix. This guide covers what a grounded response contains, how five providers format it, and a script that checks citations mechanically. For the broader goal, see our AI grounding guide. For the retrieval layer that feeds it, see the Grounding API guide. For a single-call cited endpoint, see the AI answer API guide. You.com sells two of the APIs below, so check those rows against the docs we link.
What is grounded generation?
Grounded generation is generation with a source constraint. The system retrieves evidence at request time, the model writes against that evidence instead of its parametric memory alone, and the response carries pointers from claims to sources. Retrieval can be a web search, a document index, or both. A pointer can be an inline marker, a character span, a URL, or a verbatim excerpt.
Three properties separate it from an ordinary completion. The evidence is fetched per request, so the answer reflects the current web or document set. The generation is constrained to that evidence. And the citations travel inside the response object, so code can display, audit, or verify them without parsing prose.
That makes it different from retrieval augmented generation, which describes how evidence reaches the model: retrieve, place in context, generate. You can run RAG and return an answer with no pointers, which is how teams end up with answers that cite nothing even though retrieval did most of the work. Grounded generation is the output contract on top: a reviewer can go from a sentence to the passage that supports it. Our RAG with web search guide covers the pipeline half.
The term also appears as a product name. Google's documentation describes a Grounded Generation API in Agent Search, built around a generateGroundedContent method, and marks it deprecated. The page says Google is not accepting new requests and points to grounding on its Gemini Enterprise Agent Platform instead. If you arrived from an older Google tutorial, the concept stands, but that endpoint is not the one to build on.
What does a grounded response look like at each provider?
Every provider below returns citations, and no two formats match. The differences decide how you display, store, and verify them. The table lists the request switch, where citations arrive, and what each pointer identifies, from each provider's own docs as of October 10, 2026.
| Provider and feature | Turn it on | Citations arrive as | Pointer |
|---|---|---|---|
| Anthropic Citations, over your documents | Set citations.enabled to true on every document block, or on none | citations arrays on response text blocks | char_location with cited_text, document_index, start_char_index, and an exclusive end_char_index; page_location for PDFs; content_block_location for custom content |
| Anthropic web search tool | Add the tool; citations are always on | citations on text blocks | web_search_result_location with url, title, encrypted_index, and cited_text of up to 150 characters |
| Gemini API, Grounding with Google Search | Enable the google_search tool | annotations on the text block, plus a google_search_result step with search_suggestions HTML | url_citation with url, title, start_index, and end_index |
| OpenAI Responses API web search | Add the web_search tool | annotations on message.content[0] | url_citation with url, title, start_index, and end_index |
| Cohere Chat v2 with documents | Pass documents to co.chat() | message.citations | Citation with start, end, text, and sources that name the documents |
| You.com Answer API | POST api.you.com/v1/answer with a query | answer Markdown with markers such as [[1, 2]], plus a citations array | A source URL and verbatim excerpts; results.web lists everything considered |
| You.com Research API | POST api.you.com/v1/research with a research_effort tier | output.content Markdown with markers such as [[1, 2]] | output.sources items with url, title, and snippets |
Four points follow from the table:
- Pointers differ in what they point at. Gemini, OpenAI, and Cohere give offsets into the reply text. Anthropic attaches each citation to the text block it supports and, for document citations, points into your source document. The You.com APIs embed numbered markers in the Markdown that resolve to a source list.
- Sources are not citations. OpenAI's optional
sourcesfield returns every URL the model consulted, which its docs say is often more than the cited set. You.com'sresults.weblikewise lists what was considered, cited or not. Store both and display the cited ones. - Display rules come with the contract. OpenAI says inline citations must be clearly visible and clickable when you show web results to users. Anthropic says citations must be included when you display web search output directly to end users. Google's response carries search suggestions HTML, and its terms set the usage requirements.
- Pointer validity is a vendor claim. Anthropic says citations are guaranteed to contain valid pointers to the documents you provide. You.com says its Answer API verifies that every citation exists in the source text and supports the answer before returning it. Keep your own check anyway, for the reason in the failure section below.
Which build level fits?
There are three build levels, and the tradeoff is control against plumbing.
- Level one, self-built. You retrieve, prompt the model, and parse citation markers from its answer. You own retrieval, the prompt, and the check. The You.com Web Search API fits because it returns raw results with URLs and snippets from
POST https://ydc-index.io/v1/search, at $5.00 per 1,000 calls as of October 2026 per its billing page. - Level two, provider-grounded. You pass documents or enable the provider's search tool, and structured citations come back. Anthropic, Cohere, Gemini, and OpenAI work this way. Anthropic lists web search at $10 per 1,000 searches plus token costs, and notes that
cited_textdoes not count toward output tokens, so its citation feature can cost less than prompting the model to quote. Google lists $14 per 1,000 search queries for Gemini 3 and newer, after 5,000 free requests a month shared across those models. - Level three, single-call synthesis. The Answer and Research APIs retrieve, read, and synthesize in one request. The Answer API lists $5.00 per 1,000 calls. The Research API runs from $12 per 1,000 at the
litetier to $50 per 1,000 at the defaultstandardtier and up. Our Research API guide covers choosing one that returns cited answers.
The meters differ enough that the build level changes the bill. This table prices 10,000 grounded answers a month at list price, counting tool fees only and assuming one search per answer. Level-one and level-two rows add model tokens.
| Option | List rule | 10,000 answers, tool fees |
|---|---|---|
| Level one: You.com Web Search API | $5.00 per 1,000 calls | $50, plus your model's tokens |
| Level two: Anthropic web search | $10 per 1,000 searches | $100, plus tokens |
| Level two: Gemini with Google Search | $14 per 1,000 queries after 5,000 free a month | $70, plus tokens |
| Level three: You.com Answer API | $5.00 per 1,000 calls, synthesis included | $50 |
| Level three: You.com Research API | $12 per 1,000 at lite, $50 at standard | $120 or $500 |
Gemini and Anthropic bill per search the model runs, so a prompt that triggers several searches costs several times the row above. Google's pricing page gives the example of two queries in one call counting as two billable uses. OpenAI bills search actions as tool calls, at the rate on its own pricing page.
Pick level one when retrieval quality or source control is your product. Pick level two when the corpus is yours or you want the provider's citation format. Pick level three when you want a cited answer and not a pipeline.
How do you build level one and check it?
The script below is a level-one skeleton. It retrieves with the Web Search API, numbers the sources, builds a prompt that demands bracketed citations, and then checks the model's answer. It calls no model: ground() takes your own generate(prompt) function, so any provider fits. It uses only the Python standard library. Run it with no arguments and it executes fixture and mocked-HTTP checks. --live "question" runs the retrieval step alone, with YDC_API_KEY set, and prints the prompt your model would receive.
"""Level-one grounded generation: retrieve, number the sources, prompt, then check the citations.
Offline by default: `python3 grounded_check.py` runs fixture and mocked-HTTP checks.
No model is called here. `ground()` takes your own `generate(prompt) -> str` function.
`--live "question"` runs only the retrieval step (YDC_API_KEY) and prints the prompt.
"""
import argparse
import json
import os
import re
import sys
import urllib.request
SEARCH_URL = "https://ydc-index.io/v1/search"
STOPWORDS = {"that", "this", "with", "from", "have", "which", "their", "there", "about", "would",
"could", "should", "these", "those", "than", "then", "into", "also", "been", "were"}
class NoSourcesError(RuntimeError):
"""Raised when retrieval returns nothing, so we refuse to generate an ungrounded answer."""
def search(query, count=5, key=None, opener=urllib.request.urlopen):
key = key or os.environ.get("YDC_API_KEY", "").strip()
if not key:
raise SystemExit("Set YDC_API_KEY before using --live")
request = urllib.request.Request(
SEARCH_URL, data=json.dumps({"query": query, "count": count}).encode("utf-8"),
headers={"Content-Type": "application/json", "X-API-Key": key}, method="POST")
with opener(request, timeout=30) as response:
payload = json.load(response)
sources = []
for hit in (payload.get("results") or {}).get("web") or []:
text = " ".join(hit.get("snippets") or []) or hit.get("description") or ""
if hit.get("url") and text:
sources.append({"url": hit["url"], "title": hit.get("title") or "", "text": text})
return sources
def build_prompt(question, sources):
if not sources:
raise NoSourcesError("no sources retrieved, refusing to generate")
numbered = "\n\n".join("[%d] %s (%s)\n%s" % (i, s["title"], s["url"], s["text"])
for i, s in enumerate(sources, 1))
return ("Answer using only the numbered sources. End every sentence with its source numbers "
"in brackets, such as [1] or [1, 2]. If the sources do not answer the question, say so.\n\n"
"Sources:\n%s\n\nQuestion: %s" % (numbered, question))
def words(text):
return {w for w in re.findall(r"[a-z0-9]{4,}", text.lower()) if w not in STOPWORDS}
def check_citations(answer, sources, min_overlap=0.5):
"""Per sentence: uncited, bad_index, weak (little lexical overlap with the cited text), or ok."""
rows = []
answer = re.sub(r"([.!?])\s+(\[\d+(?:\s*,\s*\d+)*\])", r" \2\1", answer.strip()) # ". [1]" -> " [1]."
for sentence in re.split(r"(?<=[.!?])\s+", answer):
if not sentence:
continue
cited = [int(n) for group in re.findall(r"\[(\d+(?:\s*,\s*\d+)*)\]", sentence)
for n in re.split(r"\s*,\s*", group)]
claim = words(re.sub(r"\[[\d,\s]+\]", " ", sentence))
if not cited:
status = "uncited"
elif any(n < 1 or n > len(sources) for n in cited):
status = "bad_index"
else:
support = words(" ".join(sources[n - 1]["text"] for n in cited))
overlap = len(claim & support) / float(len(claim) or 1)
status = "ok" if overlap >= min_overlap else "weak"
rows.append({"sentence": sentence, "cited": cited, "status": status})
cited_rows = [r for r in rows if r["cited"]]
return {"rows": rows, "coverage": len(cited_rows) / float(len(rows) or 1),
"ok": bool(rows) and all(r["status"] == "ok" for r in rows)}
def ground(question, generate, search_fn=search, count=5):
sources = search_fn(question, count)
answer = generate(build_prompt(question, sources))
return {"answer": answer, "sources": sources, "report": check_citations(answer, sources)}
SOURCES = [
{"url": "https://example.com/a", "title": "A", "text": "Emperor penguins are the tallest penguin species."},
{"url": "https://example.com/b", "title": "B", "text": "Emperor penguins breed only in Antarctica during winter."},
]
class FakeResponse:
def __init__(self, payload):
self._raw = json.dumps(payload).encode("utf-8")
def read(self, *args):
return self._raw
def __enter__(self):
return self
def __exit__(self, *args):
return False
def check():
good = "Emperor penguins are the tallest species [1]. They breed only in Antarctica [2]."
report = check_citations(good, SOURCES)
assert report["ok"] and report["coverage"] == 1.0
uncited = check_citations(good + " They are also very friendly.", SOURCES)
assert uncited["rows"][-1]["status"] == "uncited" and not uncited["ok"]
assert round(uncited["coverage"], 2) == 0.67
assert check_citations("Penguins breed in Antarctica [3].", SOURCES)["rows"][0]["status"] == "bad_index"
assert check_citations("Emperor penguins breed only in Antarctica [1].", SOURCES)["rows"][0]["status"] == "weak"
assert check_citations("Emperor penguins breed only in Antarctica [1, 2].", SOURCES)["ok"]
assert check_citations("", SOURCES) == {"rows": [], "coverage": 0.0, "ok": False}
assert check_citations("Emperor penguins are the tallest species. [1]", SOURCES)["ok"] # marker after the period
prompt = build_prompt("Where do emperor penguins breed?", SOURCES)
assert "[1] A (https://example.com/a)" in prompt and "[2] B" in prompt
calls = []
out = ground("q", lambda p: calls.append(p) or good, search_fn=lambda q, n: SOURCES)
assert out["report"]["ok"] and len(calls) == 1
try:
ground("q", lambda p: calls.append(p) or good, search_fn=lambda q, n: [])
except NoSourcesError:
assert len(calls) == 1 # the model was never called
else:
raise AssertionError("generated without sources")
seen = []
def opener(req, timeout):
seen.append(req)
return FakeResponse({"results": {"web": [
{"url": "https://example.com/a", "title": "A", "snippets": ["s1", "s2"], "description": "d"},
{"url": "https://example.com/b", "title": "B", "description": "only a description"},
{"url": "https://example.com/c", "title": "C"}]}})
found = search("q", 3, key="secret-key", opener=opener)
assert [s["text"] for s in found] == ["s1 s2", "only a description"]
sent = seen[0]
assert sent.get_method() == "POST" and sent.full_url == SEARCH_URL
assert json.loads(sent.data) == {"query": "q", "count": 3}
assert sent.get_header("X-api-key") == "secret-key"
assert "secret-key" not in json.dumps(found)
assert search("q", opener=lambda req, timeout: FakeResponse({}), key="k") == []
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--live", metavar="QUESTION")
args = parser.parse_args()
check()
if not args.live:
print("checks passed, 0 network calls")
return 0
print(build_prompt(args.live, search(args.live)))
return 0
if __name__ == "__main__":
sys.exit(main())
The check returns one status per sentence. uncited means no marker. bad_index means a marker that points past the source list. weak means the cited text shares fewer than half of the sentence's content words, which is a screening signal and not a verdict. Coverage is the share of sentences with a marker. The guard in build_prompt matters as much as the check: with no sources it raises, so the model never answers from memory.
Here is what the check returns for the fixture sources in the script, two short passages about emperor penguins:
| Model sentence | Status | Why |
|---|---|---|
| Emperor penguins are the tallest species [1]. | ok | All four content words appear in source 1 |
| They breed only in Antarctica [2]. | ok | Three of four content words appear in source 2 |
| Emperor penguins breed only in Antarctica [1]. | weak | The right fact with the wrong source: two of five words appear in source 1 |
| They are also very friendly. | uncited | No marker, so nothing to check |
| Penguins breed in Antarctica [3]. | bad_index | Only two sources exist |
The third row is easy to miss on a skim: a true sentence that cites the wrong passage. Citing source 2 would have passed.
We checked the script with py_compile, its assertions, and a mocked-HTTP test of the request method, URL, header, and body. No live requests were made and no model was called, so retrieval quality, prompt wording, and the overlap threshold still need your own test.
What fails first, and how do you catch it?
Grounding makes failures findable. It does not remove them. The ALCE benchmark paper, accepted at EMNLP 2023, reported that on its ELI5 questions even the top models it tested lacked complete citation support 50% of the time. Those are older models, so read the number as the reason to check and not as a current rate.
- A pointer without support. A citation can resolve and still not back the claim. The
weakstatus catches crude cases. For legal, financial, or medical surfaces, follow each source and compare the excerpt before showing the claim, as both the Answer and Research API guides advise. - Uncited sentences. Coverage drops when the model drifts past its context. Alert on coverage by query template, and treat a falling trend as a retrieval or prompt problem before blaming the model.
- Silent retrieval narrowing. A freshness filter, a domain allowlist, or a stale cache quietly shrinks the evidence set, and the model grounds itself confidently in three sources instead of thirty. Log the number of unique sources per answer and alert when the distribution shifts.
- Staleness dressed as grounding. A page cached months ago is still a citable source. The Web Search API's
freshnessparameter acceptsday,week,month,year, or a date range, and results can carrypage_age. It is optional on web results, so treat it as a hint and not a guarantee.
A grounded answer is only auditable if you keep what produced it. Log the question, the retrieval request (query, freshness, domain filters), every source considered with its URL and fetch time, the prompt version, the model and its settings, the answer, and the citation report. Keep considered and cited sources apart, since OpenAI's sources field and You.com's results.web exist for exactly that split. When a customer disputes an answer, that record turns the dispute into a lookup.
For the metrics side of the same problem, our hallucination prevention guide covers how teams track hallucination rates.
Frequently Asked Questions
It means a model's answer is constrained to evidence retrieved at request time, and the response carries citations that point each claim to its source. The evidence can come from a web search or your own documents. The pointer can be a marker, a character span, a URL, or a verbatim excerpt, so a reviewer can check the claim.
No. RAG describes how evidence reaches the model: retrieve, place it in context, generate. Grounded generation describes what the response must carry, which is pointers from claims to sources. You can run RAG and return an answer with no citations, and a grounding feature can sit on top of any retrieval pipeline.
Several do, in different shapes. Anthropic returns document and web search citations on text blocks. Gemini and OpenAI return url_citation annotations with start and end offsets. Cohere returns spans with document sources, and the You.com Answer and Research APIs return Markdown with numbered markers plus a source list.
Check three things. Coverage: every claim sentence carries a citation. Validity: each citation points at a real source. Support: the cited passage backs the claim, which a reviewer should confirm for high-stakes answers. The script in this guide automates the first two and flags weak support with a word-overlap screen.
No, it makes them findable. A grounded system can still attach a citation that does not support the claim or ground an answer in a stale page. The ALCE benchmark found that in 2023 even the best models lacked complete citation support half the time on ELI5 questions, which is why a check belongs in the pipeline.
LI Test
LI Test
Share Article:
Related resources.

What Is Answer Engine Optimization? A Practical Guide for Content Teams
October 7, 2026
Blog

What Are LLM Citations? How They Are Produced and What They Prove
October 6, 2026
Blog

Vector Search vs Keyword Search for RAG Pipelines
September 29, 2026
Blog

5 RAG Chunking Strategies in 2026: Fidelity, Cost, and Complexity
September 29, 2026
Blog

What Is a Legal Research API? Building Cited Legal Research Into Applications
September 16, 2026
Blog
