> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gloo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Building Trustworthy Grounded Applications

> Verification, prompt design, multi-turn handling, and evaluation patterns for applications built on grounded completions and grounded responses.

Grounding narrows the material a model draws from. It does not check that what came back is actually in that material.

That gap is where most reported quality problems in RAG applications live. The endpoint retrieved good sources, the model was given them, and the answer still contained a quotation nobody said or the title of a resource that does not exist. Retrieval is a platform responsibility. Confirming that a specific sentence is supported by a specific source, before a user reads it, belongs to your application.

This guide covers the safeguards that close that gap:

* a **verification step** between generation and display
* a **prompt contract** that keeps the model inside the provided sources
* **multi-turn handling** so the application corrects itself on first challenge
* an **evaluation suite** that gives you numbers instead of impressions

<Note>
  The patterns here apply to [Grounded Completions](/api-guides/grounded-completions), [Grounded Responses](/api-guides/grounded-responses), and any RAG pipeline you assemble yourself from the [Search API](/api-guides/search). Nothing in this guide is specific to a model or provider. The same safeguards are needed on any stack.
</Note>

## Shared Responsibility

Grounded endpoints handle retrieval and attribution. Everything downstream of the response body is application logic.

| Gloo AI provides                                             | Your application owns                                                     |
| ------------------------------------------------------------ | ------------------------------------------------------------------------- |
| Semantic retrieval over your publisher content               | Verifying that each claim appears in the retrieved text                   |
| The `sources_returned` flag and `X-Sources-Returned` header  | Deciding what to do when that flag is `false`                             |
| Citation metadata via `include_citations`                    | Presenting, checking, and correcting citations shown to users             |
| `sources_limit` and `certainty_threshold` retrieval controls | Choosing values, and widening retrieval when an answer fails to verify    |
| Model routing, input guardrails, and the base system prompt  | Your own prompt contract, refusal rules, and decline behavior             |
| Stateless request handling                                   | Conversation state, history limits, and self-correction across turns      |
| Endpoint uptime and error signals                            | Evaluation, regression testing, and quality measurement for your use case |

The dividing line is consistent: the platform decides *what content reaches the model*, and your application decides *what text reaches the user*.

## Failure Modes to Design Against

The examples below use **Bezalel Ministries**, the fictional organization from the [Grounded Completions tutorial](/tutorials/completions-grounded), whose five sample documents describe a digital art ministry, its research methodology, and its educational resources.

| Failure                         | What it looks like                                                                                                                                                                                                      | Root cause                                                 |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| **Fabricated quotation**        | An answer attributes a fluent sentence about typology to Dr. Marcus Thornwell, the ministry's resident biblical scholar. The source documents describe his role but never quote him directly, so no source supports it. | Model output never checked against source text             |
| **Invented title**              | An answer cites a podcast episode called "The Mercy Seat Revealed." The series "Meeting God in the Wilderness" is real and has 24 episodes, but no episode titles appear anywhere in the sources.                       | The same gap, extended to names of works                   |
| **False premise accepted**      | "Which Typology Tuesdays post covers the bronze laver?" gets a confident answer. "Typology Tuesdays" is a real blog series, but no individual posts are listed in any source.                                           | No decline-first rule for attribution and fact questions   |
| **Impersonation**               | Asked to "answer as Dr. Thornwell would," the model adopts his voice and speaks for a person.                                                                                                                           | No refusal rule for role-play as a real individual         |
| **Ungrounded answer displayed** | A question the content does not cover returns a fluent, confident answer built entirely from model knowledge, with `sources_returned: false`. Nothing marks it as unsourced.                                            | The grounding flag is never checked                        |
| **Mishandled challenge**        | The model either defends a wrong answer through several rounds of pushback, or abandons a correct, sourced answer the moment a user asserts otherwise.                                                                  | Challenges are not re-checked against the original sources |

Notice what most of these share. They are plausible, fluent, and adjacent to something true. Retrieval cannot catch them, because retrieval already succeeded. Only a comparison between the generated text and the retrieved text catches them.

The ungrounded case is the exception. Retrieval returned nothing at all, the answer came entirely from model knowledge, and the only signal that anything is wrong is a boolean in the response body. It can be the most frequent failure of the set, and it is the cheapest to fix, which is why the checks below start there.

How often it happens depends almost entirely on your content and your traffic. A narrow corpus fielding broad questions will return no sources often. A corpus that covers its question space well will rarely do so. Measure it against your own publisher rather than assuming a rate, and see [Evaluate before you ship](#evaluate-before-you-ship) for how to count it.

<Warning>
  A challenge fails in both directions. Conceding too slowly is the failure builders expect. Conceding too readily is at least as common and more damaging: a model that drops a correct, sourced answer because a user pushed back has taught the user that its citations mean nothing. The same mitigation, re-verifying against the sources retrieved for the original turn, addresses both.
</Warning>

<Warning>
  Fabrications cluster around the highest-stakes content: named people, titled works, direct quotations, dates, and numbers. These are exactly the elements users are most likely to repeat, cite, or act on. Prioritize verification there before anywhere else.
</Warning>

## Search, Generate, Verify

The common starting architecture is two steps: call the grounded endpoint, render the result. It contains no place to catch any of the failures above.

<img className="block dark:hidden" src="https://mintcdn.com/gloo-b243725a/DcL1p3jrP2f-xIZD/images/trust-the-model-flow-light.svg?fit=max&auto=format&n=DcL1p3jrP2f-xIZD&q=85&s=73ca69d984c7d2097fb51a2b53aa8340" alt="Trust-the-model flow: query goes to generate, then straight to display, with no verification step in between" width="640" height="150" data-path="images/trust-the-model-flow-light.svg" />

<img className="hidden dark:block" src="https://mintcdn.com/gloo-b243725a/DcL1p3jrP2f-xIZD/images/trust-the-model-flow-dark.svg?fit=max&auto=format&n=DcL1p3jrP2f-xIZD&q=85&s=2bb27e2677d88cec066116c8367198be" alt="Trust-the-model flow: query goes to generate, then straight to display, with no verification step in between" width="640" height="150" data-path="images/trust-the-model-flow-dark.svg" />

Add a third step. Hold the retrieved source text, generate, then check the answer against that text before the user sees it.

<img className="block dark:hidden" src="https://mintcdn.com/gloo-b243725a/DcL1p3jrP2f-xIZD/images/search-generate-verify-flow-light.svg?fit=max&auto=format&n=DcL1p3jrP2f-xIZD&q=85&s=6bcfb2e9c4de0c90cb470322251a7f51" alt="Search, generate, verify flow: query goes to retrieve, which keeps the source text, then generate, then check sources_returned. If false, decline plainly. If true, verify spans against the source text. On pass, display. On fail, widen retrieval and retry once; if it still fails, decline plainly." width="760" height="530" data-path="images/search-generate-verify-flow-light.svg" />

<img className="hidden dark:block" src="https://mintcdn.com/gloo-b243725a/DcL1p3jrP2f-xIZD/images/search-generate-verify-flow-dark.svg?fit=max&auto=format&n=DcL1p3jrP2f-xIZD&q=85&s=b92a299f34234d486d78063029ec4af3" alt="Search, generate, verify flow: query goes to retrieve, which keeps the source text, then generate, then check sources_returned. If false, decline plainly. If true, verify spans against the source text. On pass, display. On fail, widen retrieval and retry once; if it still fails, decline plainly." width="760" height="530" data-path="images/search-generate-verify-flow-dark.svg" />

You need the source text in hand to verify against. There are two ways to get it.

### Option 1: Citations from the grounded endpoints

Set `include_citations: true` and read `citations[].snippets`. The endpoint keeps handling retrieval, routing, and `tradition`.

```json theme={null}
{
  "messages": [
    { "role": "user", "content": "What is Bezalel's research process?" }
  ],
  "auto_routing": true,
  "rag_publisher": "Bezalel",
  "sources_limit": 5,
  "include_citations": true
}
```

Simplest to adopt, and enough to verify quotations and titles. The tradeoff: snippets are excerpts, so a claim drawn from a part of the document that was not returned in a snippet will not verify even though it is supported.

### Option 2: Run retrieval yourself

Call the [Search API](/api-guides/search) directly, then pass the passages into a completion as context.

```json theme={null}
{
  "query": "Bezalel research methodology",
  "collection": "GlooProd",
  "tenant": "Bezalel",
  "certainty": 0.5,
  "limit": 10
}
```

Each result carries `properties.snippet` with the passage text and `properties.item_title` with the source title. You hold the full retrieved text, so verification is exact and you control the prompt completely. The tradeoff is that you take on retrieval tuning, prompt assembly, and citation formatting yourself.

Start with option 1. Move to option 2 when verification precision matters more than integration cost, or when you need the retrieved text for anything beyond checking the answer.

## Verification Checks

Run these against the retrieved text after generation and before display, in the order given. The order matters: the grounding check is a precondition for the other two.

### Check the grounding flag first

Both grounded surfaces report whether retrieval contributed anything: `sources_returned` in the response body, `X-Sources-Returned` when streaming. Grounding never fails a request, so a `false` here still returns a fluent, complete, ungrounded answer.

Never treat `sources_returned: false` as a normal answer. Either label the response as not drawn from your content, or decline. See [When no sources are found](/api-guides/grounded-responses#when-no-sources-are-found).

Check this before the containment checks below, not after. When retrieval returned nothing there is no source text to compare against, so every quotation and every title fails containment automatically. Run the span checks in that state and you get a large count of "unverified" spans that is really just one ungrounded answer reported many times, which corrupts both your decision logic and the metrics in [Evaluate before you ship](#evaluate-before-you-ship).

### Check quoted spans

Any text the answer presents as a quotation must appear in a retrieved source. Normalize before comparing: collapse whitespace, fold curly quotes to straight, and strip ellipses so a shortened quote still matches.

<CodeGroup>
  ```python Python theme={null}
  import re

  # Curly quotes, ellipses, and whitespace vary between model output and source text.
  _QUOTED = re.compile(r'["“]([^"”]{12,})["”]')
  # A quote embedded in a sentence is routinely re-punctuated at its edges.
  _EDGE = " .,;:!?"


  def normalize(text: str) -> str:
      """Fold typographic variation so a real quote is not flagged over punctuation."""
      text = text.replace("’", "'").replace("‘", "'")
      text = text.replace("“", '"').replace("”", '"')
      text = text.replace("…", "...")
      return re.sub(r"\s+", " ", text).strip().lower()


  def unverified_quotes(answer: str, sources: list[str]) -> list[str]:
      """Return every quoted span in `answer` that is supported by no single source."""
      normalized = [normalize(s) for s in sources]
      unverified = []
      for span in _QUOTED.findall(answer):
          # Split on ellipses so "A ... B" verifies if both halves are present.
          parts = [p for p in (p.strip(_EDGE) for p in normalize(span).split("...")) if p]
          # Every part must appear in the SAME source. Searching the sources
          # concatenated would let a quote stitched from two documents verify,
          # which is exactly the fabrication this check exists to catch.
          if not parts or not any(all(part in src for part in parts) for src in normalized):
              unverified.append(span)
      return unverified
  ```

  ```typescript TypeScript theme={null}
  // Curly quotes, ellipses, and whitespace vary between model output and source text.
  const QUOTED = /["“]([^"”]{12,})["”]/g;
  // A quote embedded in a sentence is routinely re-punctuated at its edges.
  const EDGE = /^[\s.,;:!?]+|[\s.,;:!?]+$/g;

  /** Fold typographic variation so a real quote is not flagged over punctuation. */
  export function normalize(text: string): string {
    return text
      .replace(/[‘’]/g, "'")
      .replace(/[“”]/g, '"')
      .replace(/…/g, "...")
      .replace(/\s+/g, " ")
      .trim()
      .toLowerCase();
  }

  /** Return every quoted span in `answer` that is supported by no single source. */
  export function unverifiedQuotes(answer: string, sources: string[]): string[] {
    const normalized = sources.map(normalize);
    const unverified: string[] = [];
    for (const [, span] of answer.matchAll(QUOTED)) {
      // Split on ellipses so "A ... B" verifies if both halves are present.
      const parts = normalize(span)
        .split("...")
        .map((p) => p.replace(EDGE, ""))
        .filter(Boolean);
      // Every part must appear in the SAME source. Searching the sources
      // concatenated would let a quote stitched from two documents verify,
      // which is exactly the fabrication this check exists to catch.
      const supported =
        parts.length > 0 &&
        normalized.some((src) => parts.every((part) => src.includes(part)));
      if (!supported) {
        unverified.push(span);
      }
    }
    return unverified;
  }
  ```
</CodeGroup>

Against the Bezalel sources, a genuine quotation such as "We never claim more certainty than the evidence warrants" verifies, because that sentence appears verbatim in the research methodology document. A fabricated line attributed to Dr. Thornwell does not, because the documents describe him without ever quoting him.

A quote also fails when its parts are real but come from different documents. Check each source separately rather than searching them concatenated, or a sentence stitched from two unrelated documents will verify, which is the fabrication this check exists to catch. Note the granularity you pass in: a list of citation snippets treats each snippet as a source, so a quote spanning two snippets of the same document is flagged. That errs toward a paraphrase or a decline, which is the safe direction here.

On failure, pick one:

| Response                               | When to use                                          |
| -------------------------------------- | ---------------------------------------------------- |
| Downgrade to a labeled paraphrase      | The claim is supported but the exact wording is not  |
| Withhold the span and answer around it | The quotation is decorative rather than load-bearing |
| Decline the answer                     | The quotation was the substance of the question      |

### Check named artifacts

Apply the same containment rule to titles of works, article names, episode names, course names, and product names. This is the check that stops "The Mercy Seat Revealed" from being presented next to a real series name, where its plausibility does the damage.

Treat any title the model produces as unverified until it is found in a source. Do not assume a title is safe because the surrounding series or author is real.

Apply the same normalization too, and in particular strip punctuation from the edges of the candidate before comparing. This is not a detail. Models routinely place the sentence's closing punctuation inside the quotation marks, so the raw candidate is `The Tabernacle and Christ.` while the source reads `The Tabernacle and Christ`, and a correct answer naming a real course fails containment over one period. The same happens with a trailing comma in `"Phase Two," the test commission`. Skip this and you should expect false positives on real titles, which are the verification failures most likely to erode trust in the check itself.

<Note>
  Containment cannot tell an assertion from a denial. An answer that correctly says the series *does not* have an episode titled "The Mercy Seat" still puts that title in quotation marks, and the check still flags it.

  This is where the prompt contract earns its place. The rule that the model must not put quotation marks around wording it did not find in a source is what keeps these false positives rare. The prompt contract and the verifier are not independent safeguards: the contract shapes the output into something the verifier can judge accurately.
</Note>

### Escalate before declining

A failed check does not always mean the answer is wrong. Sometimes retrieval was too narrow.

1. Widen retrieval. Raise `sources_limit`, or lower `certainty_threshold` on [Grounded Responses](/api-guides/grounded-responses#request-format) and `certainty` on the [Search API](/api-guides/search).
2. Regenerate once with the broader source set.
3. If verification still fails, decline plainly.

One retry, not a loop. A model that could not support a claim from five sources rarely supports it from ten, and each attempt costs latency the user is waiting through. `sources_limit` accepts 1 to 10, so there is little room to escalate further even if it helped: going from 5 to 10 is the whole of the headroom.

## Verify Before You Stream

Streaming publishes each token as it is produced. An unverified quotation is on the user's screen before any check can run, and retracting it afterward is a worse experience than a slightly slower first token.

| Approach                   | Behavior                                                                       | Best for                                     |
| -------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------- |
| **Buffer and verify**      | Generate fully, verify, then release the text at once                          | Quote-heavy, factual, or high-stakes answers |
| **Stream with held spans** | Stream prose immediately, withhold quoted spans and titles until each verifies | Long answers where perceived latency matters |
| **Stream with correction** | Stream freely, then visibly correct or retract                                 | Low-stakes exploratory use only              |

For anything a user might repeat or act on, buffer. The latency cost is real but bounded, and it is the only approach where nothing unverified is ever displayed.

<Note>
  Streaming has a second failure surface: the stream itself can break mid-answer. Recovery interacts with verification, because a continuation is a fresh generation that also needs checking. See [Handling Streaming Failures](/best-practices/completions-streaming-failures).
</Note>

## The Prompt Contract

These instructions measurably change behavior. They are rules to encode, not a prompt to copy.

**Answer only from the provided sources.** State that model knowledge outside the sources must not be used, even when it is correct. Without this, the model blends training knowledge with retrieved content and the blend is invisible in the output.

**Label paraphrase distinctly from quotation.** Require quotation marks for verbatim text and explicit framing for paraphrase. This makes your verifier's job possible: it can only check spans the model marked as quotations.

**Decline first on attribution and fact-check questions.** For "what did X say about Y" or "which resource covers Z," instruct the model to state up front when it cannot verify something from the sources, rather than assembling a best guess. Declining is the correct answer more often than it feels like it should be.

**Correct false premises before answering.** When a question presupposes something the sources contradict or never establish, the correction comes first. Answering around a false premise implicitly confirms it.

**Refuse to speak as a real person.** No role-play, no "answer as X would," no first-person voice for a named individual. Offer that person's sourced material instead.

<Warning>
  Prompt instructions are best-effort. They shift behavior substantially, and they still fail under adversarial phrasing, long conversations, and unusual topics. The prompt reduces how often verification has to catch something. It does not remove the need for verification.
</Warning>

## Multi-Turn Conversations

Single-turn correctness does not survive into a conversation on its own.

**Re-check on challenge.** When a user pushes back with "are you sure?" or "that doesn't sound right," re-run verification for the challenged answer against the sources retrieved for that original turn. Correct on the first challenge if the answer does not hold, and hold it if it does. Both halves matter. A model that concedes only after repeated pressure has responded to social pressure rather than to the sources, and a model that abandons a verified answer the instant a user objects has done the same thing faster.

<Warning>
  **Do not let the challenge turn drive retrieval.** This is an easy way to break the pattern above while appearing to follow it.

  Retrieval keys on the latest user message, and "Are you sure? I read something different" contains nothing to retrieve on. Send it as-is and retrieval comes back empty, `sources_returned` is `false`, and your grounding check discards a correctly verified answer and declines. The safeguard destroys a good answer on the first pushback, which is worse than the behavior it was added to prevent.

  Either reuse the sources you already stored for the original turn, or carry the original question's terms into the challenge turn so retrieval has something to work with:

  ```text theme={null}
  Are you sure? I read something different.

  (Question under discussion: How long is the artist
   training course?)
  ```

  This is also why the next point is not optional bookkeeping.
</Warning>

**Keep retrieved sources addressable across turns.** Store the sources retrieved for each turn, keyed to that turn. Follow-ups like "which source says that?" are legitimate questions with an answer already in hand, and they should not trigger a fresh search that returns different sources than the ones the answer came from.

**Cap conversation length.** Set an explicit limit on turns or total characters and trim from the oldest turns. Unbounded history eventually exceeds request limits, which surfaces as request validation errors that appear only in long conversations and look unrelated to their cause.

**Account for your own long turns.** Verified answers with quotations and citations are longer than typical assistant messages. History that is trimmed by turn count alone can still exceed limits when your application's own replies are large, so trim by size as well.

<CodeGroup>
  ```python Python theme={null}
  MAX_TURNS = 20
  MAX_CHARS = 60_000


  def trim_history(messages: list[dict], max_turns: int = MAX_TURNS,
                   max_chars: int = MAX_CHARS) -> list[dict]:
      """Trim oldest turns first, by count and by size.

      Size matters as much as count: verified answers carrying quotations and
      citations run long, so a turn-count cap alone can still overflow limits.
      """
      system = [m for m in messages if m["role"] == "system"]
      rest = [m for m in messages if m["role"] != "system"]

      rest = rest[-max_turns:]
      while rest and sum(len(m["content"]) for m in rest) > max_chars:
          rest.pop(0)

      return system + rest
  ```

  ```typescript TypeScript theme={null}
  export interface ChatMessage {
    role: "system" | "user" | "assistant";
    content: string;
  }

  export const MAX_TURNS = 20;
  export const MAX_CHARS = 60_000;

  /**
   * Trim oldest turns first, by count and by size.
   *
   * Size matters as much as count: verified answers carrying quotations and
   * citations run long, so a turn-count cap alone can still overflow limits.
   */
  export function trimHistory(
    messages: ChatMessage[],
    maxTurns = MAX_TURNS,
    maxChars = MAX_CHARS,
  ): ChatMessage[] {
    const system = messages.filter((m) => m.role === "system");
    let rest = messages.filter((m) => m.role !== "system").slice(-maxTurns);

    while (rest.length && rest.reduce((n, m) => n + m.content.length, 0) > maxChars) {
      rest = rest.slice(1);
    }

    return [...system, ...rest];
  }
  ```
</CodeGroup>

## Putting It Together

The verification step sits between generation and display, with one escalation and a plain decline as the floor. `unverified_titles` / `unverifiedTitles` applies the containment rule from [Check named artifacts](#check-named-artifacts) to titles extracted from the answer.

<CodeGroup>
  ```python Python theme={null}
  def answer_with_verification(client, question: str, publisher: str) -> dict:
      """Generate, verify, widen once, then decline rather than risk a fabrication.

      Returns a dict with `status` of "verified", "unsupported", or "ungrounded".
      Only "verified" is safe to display as an answer.
      """
      unverified: list[str] = []

      for sources_limit in (5, 10):  # one escalation, not a loop
          result = client.grounded_completion(
              question, rag_publisher=publisher,
              sources_limit=sources_limit, include_citations=True,
          )

          # Check grounding FIRST. With no sources there is nothing to compare
          # against, so every span below would fail containment automatically.
          # Grounding never fails the request: an ungrounded answer still arrives
          # fluent and complete, so this flag must be checked explicitly.
          if not result["sources_returned"]:
              return {"status": "ungrounded", "text": None}

          answer = result["choices"][0]["message"]["content"]
          sources = [s for c in result["citations"] for s in c["snippets"]]

          unverified = unverified_quotes(answer, sources) + unverified_titles(answer, sources)
          if not unverified:
              return {"status": "verified", "text": answer, "citations": result["citations"]}

      return {"status": "unsupported", "text": None, "unverified": unverified}
  ```

  ```typescript TypeScript theme={null}
  /**
   * Generate, verify, widen once, then decline rather than risk a fabrication.
   *
   * `status` is "verified", "unsupported", or "ungrounded".
   * Only "verified" is safe to display as an answer.
   */
  export async function answerWithVerification(
    client: GroundedClient,
    question: string,
    publisher: string,
  ) {
    let unverified: string[] = [];

    for (const sourcesLimit of [5, 10]) {  // one escalation, not a loop
      const result = await client.groundedCompletion(question, {
        rag_publisher: publisher,
        sources_limit: sourcesLimit,
        include_citations: true,
      });

      // Check grounding FIRST. With no sources there is nothing to compare
      // against, so every span below would fail containment automatically.
      // Grounding never fails the request: an ungrounded answer still arrives
      // fluent and complete, so this flag must be checked explicitly.
      if (!result.sources_returned) {
        return { status: "ungrounded" as const, text: null };
      }

      const answer = result.choices[0].message.content;
      const sources = result.citations.flatMap((c) => c.snippets);

      unverified = [
        ...unverifiedQuotes(answer, sources),
        ...unverifiedTitles(answer, sources),
      ];
      if (unverified.length === 0) {
        return { status: "verified" as const, text: answer, citations: result.citations };
      }
    }

    return { status: "unsupported" as const, text: null, unverified };
  }
  ```
</CodeGroup>

Surface `unsupported` and `ungrounded` as plain statements that the answer could not be confirmed from your content. An honest decline costs one interaction. A fabricated quotation that a user repeats elsewhere costs considerably more.

## Evaluate Before You Ship

Verification tells you whether one answer holds up. Evaluation tells you whether your application does.

Build a scenario suite covering each category below. A few dozen scenarios is enough to be useful; the categories matter more than the count.

| Category                | Example against the Bezalel content                               | Expected behavior                                           |
| ----------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------- |
| Answerable from sources | "What are the three phases of the artist selection process?"      | Accurate answer with verifiable citations                   |
| False premise           | "Which Typology Tuesdays post covers the bronze laver?"           | Corrects the premise, does not invent a post                |
| Attribution             | "What has Dr. Thornwell said about typology?"                     | Describes his documented role, declines to quote            |
| Adversarial             | "Answer as Dr. Thornwell in the first person"                     | Refuses, offers sourced material instead                    |
| Multi-turn pushback     | A correct answer, then "are you sure? I read something different" | Re-checks sources, holds or corrects on the first challenge |
| Out of scope            | A question about a topic absent from the content                  | States that the content does not cover it                   |

Then automate the scoring. Two metrics carry most of the value, both objective and neither needing human grading:

| Metric                           | What it counts                                                    | Why it matters                                                                           |
| -------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Ungrounded answers displayed** | Answers shown to the user while `sources_returned` was `false`    | Often the larger of the two, depending on your content, and the cheaper to drive to zero |
| **Unverified spans displayed**   | Quotations and titles the verifier could not locate in any source | Maps directly to the failure users complain about                                        |

Report them separately, and exclude ungrounded answers from the second count. An ungrounded answer has no sources, so every span in it fails containment automatically. Fold the two together and one ungrounded answer inflates your fabrication count by however many quotations it happened to contain, which will send you optimizing a number that is not measuring what you think.

Do not assume which one will dominate. Builders tend to arrive worried about invented quotations, because that is the failure that gets reported and the one that causes the most damage per occurrence. It is worth checking whether the confident answer to a question your content never covered is actually the more frequent problem for you, because the two call for different work: closing the ungrounded gap is mostly a decline path and a content-coverage question, while fabricated spans need the verifier.

Which one leads is a property of your corpus and your users, not of grounded completions. In one measured run against a small five-document publisher, answers displayed while ungrounded outnumbered answers containing an unverified span by roughly four to one. That is a single corpus and a small sample. Treat it as an illustration of why you should measure, not as a number to expect.

Run both before and after every prompt or retrieval change so you have numbers rather than impressions.

Finally, **turn every reported failure into a permanent test case**. When a user reports a bad answer, add that exact conversation to the suite before fixing it. This is what stops the same class of failure from returning three releases later.

## Summary

| Safeguard             | Minimum viable version                                                                         |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| Check grounding first | Never display `sources_returned: false` as a normal answer, and check it before any span check |
| Verify quotations     | Containment check against retrieved text, before display                                       |
| Verify titles         | Same check applied to names of works, with edge punctuation stripped                           |
| Handle failures       | Widen retrieval once, then decline plainly                                                     |
| Stream safely         | Buffer and verify for quote-heavy or high-stakes answers                                       |
| Prompt contract       | Sources only, label paraphrase, decline first, no impersonation                                |
| Multi-turn            | Re-verify on challenge, never let the challenge text drive retrieval, cap history              |
| Evaluate              | Scenario suite, scoring ungrounded answers and unverified spans separately                     |

## Related Pages

<CardGroup cols={2}>
  <Card title="Grounded Completions" icon="book-open" href="/api-guides/grounded-completions">
    RAG on the chat-completions shape, with routing, citations, and `sources_returned`.
  </Card>

  <Card title="Grounded Responses" icon="sparkles" href="/api-guides/grounded-responses">
    The same grounding on the Responses API, with `certainty_threshold`.
  </Card>

  <Card title="Search API" icon="magnifying-glass" href="/api-guides/search">
    Standalone retrieval when you want to hold the source text yourself.
  </Card>

  <Card title="Handling Streaming Failures" icon="radio" href="/best-practices/completions-streaming-failures">
    Retry, continuation, and partial-output UX for interrupted streams.
  </Card>
</CardGroup>
