Verify Agent Answers with Live Search: A Grounding Pattern
Grounding an LLM with search results means checking each factual claim in a draft answer against live sources before it reaches the user. Extract the claim, run one web search and one news search, hand the model only those results, and make it return a verdict with URLs and dates. Two calls cost 2 credits per claim, and the hard part is not the retrieval but deciding what to do when the sources are old or disagree.
What does grounding an LLM with search results mean?
Grounding means the model's answer is tied to retrieved sources that a reader can open, instead of resting on training data alone. Retrieval-augmented generation, introduced in Lewis et al.'s 2020 paper, supplies documents to the model at generation time. Hallucination, where fluent output is not supported by any source, is surveyed in Ji et al.'s 2022 review.
Most grounding guides retrieve first and generate second. That works for questions you know in advance. This article covers the other direction: the agent has already drafted an answer, and a second pass checks the claims in it. It's cheaper to reason about, because each check is small, bounded and has an explicit pass or fail.
If you want the retrieve-first version, read RAG with live web data. The two combine well: retrieve to write, verify before you send.
Why check claims after the draft instead of before?
A draft shows you which claims the model actually made, so you only spend calls on those. Pre-retrieval fetches pages for the topic and hopes the right fact is in them. Post-draft verification queries the exact statement, and a miss is visible: no supporting result means the claim stays unverified.
It also separates two jobs. The first pass writes. The second pass acts as a reviewer that sees only the claim and the evidence, not the conversation, so it can't be talked into agreeing with the draft.
The cost is latency. You make a second round trip, and you'll want to run the two searches concurrently. If you hit the request rate, concurrent requests under rate limits covers the loop. Rates are counted per minute, and every response carries RateLimit-Remaining and RateLimit-Reset; the rate limits docs cover the rest.
How does the pattern work step by step?
Five steps: pull checkable claims out of the draft, turn each into a search query, fetch web and news results, compact them to the fields the judge needs, and ask the model for a verdict that cites the evidence. The model never sees raw JSON, only a short list of titles, URLs, snippets, sources and dates.
- Extract claims. Ask the model for atomic, checkable statements: names, numbers, dates, "latest" and "first" claims. Skip opinions.
- Write a query per claim. Short and specific beats a pasted sentence.
- Fetch two views. The Search endpoint returns web results; the News endpoint returns articles with a publication timestamp.
- Compact. Keep title, link, snippet, source and date. Drop thumbnails, tokens and pagination. Trimming API responses for LLM context shows how much that saves.
- Judge. Force a verdict from a fixed list and require a URL for every verdict except "unverified".
The evidence fetcher
This Python collects both result sets for one query. It uses only parameters from the endpoint tables: query and hl for search, q and hl for news.
import os
import requests
BASE = "https://api.perceptlayer.com"
HEADERS = {"X-API-Key": os.environ["API_KEY"]}
def get(path, **params):
r = requests.get(BASE + path, headers=HEADERS, params=params, timeout=30)
if r.status_code in (429, 503):
wait = r.headers.get("Retry-After")
raise RuntimeError(f"retry after {wait}s")
r.raise_for_status()
return r.json()
def evidence(query):
web = get("/v1/google-search/search", query=query, hl="en")
news = get("/v1/google-news/news", q=query, hl="en")
return {
"web": [
{
"title": x.get("title"),
"url": x.get("link"),
"snippet": x.get("snippet"),
"source": x.get("source"),
}
for x in web.get("organic_results", [])
],
"news": [
{
"title": x.get("title"),
"url": x.get("link"),
"snippet": x.get("snippet"),
"source": (x.get("source") or {}).get("name"),
"date": x.get("iso_date"),
}
for x in news.get("news_results", [])
],
}
Authentication is the X-API-Key header (authentication). A rate-limited (429) or temporarily unavailable (503) response carries Retry-After with the seconds to wait, and failed calls aren't billed. Production code should retry; handling errors in Python has the full version.
The judge prompt
The prompt is where most grounding fails, so keep it strict:
Claim: {claim}
Today's date: {today}
Evidence (web results and news results, each with source and date when known):
{evidence}
Using ONLY the evidence, return JSON:
{"verdict": "supported|contradicted|outdated|conflicting|unverified",
"reason": "one sentence",
"urls": ["the result URLs your verdict rests on"]}
Rules:
- If sources disagree, use "conflicting" and list both sides.
- If the evidence is newer than the claim implies, use "outdated".
- If no result addresses the claim, use "unverified". Do not guess.
- A snippet that ends in "..." may be cut off; do not infer what follows.
Pass today's date explicitly. Models don't reliably know it, and "outdated" is meaningless without it.
What does a worked check look like?
Two checks, using excerpts of real recorded responses. In both, the claim is what a model might plausibly say; the evidence is what the API returned.
Example 1: a stale "latest" claim
Suppose the draft says: "Gartner's most recent strategic technology trends report covers 2025." The recorded sample comes from a broader query, q=technology, which is enough to show the mechanics; in your agent you'd search for the claim's own wording. The news call:
curl "https://api.perceptlayer.com/v1/google-news/news?q=technology" \
-H "X-API-Key: $API_KEY"
The recorded response for that request includes this first result:
{
"news_results": [
{
"position": 1,
"title": "Top Strategic Technology Trends for 2027",
"link": "https://www.gartner.com/en/articles/top-technology-trends-2027",
"snippet": "Explore Gartner's Top Strategic Technology Trends for 2027 — 10 trends that will shape autonomy, resilience and governance in the years...",
"iso_date": "2026-10-20T15:59:45+00:00",
…
"source": {
"name": "Gartner",
…
}
}
],
…
}
A Gartner page titled for 2027 and dated 2026-10-20 strongly suggests the draft's "most recent" claim is out of date as of 2026-10-24, since a 2027 report implies a newer one than 2025. The verdict is outdated, with the Gartner URL as the citation. It's an inference, not proof: the snippet is truncated and the title alone doesn't rule out something newer still, so a stricter pipeline would fetch the page before committing. The source is the publisher itself and the timestamp is machine-readable, which keeps the judge's guessing to a minimum.
Example 2: a number that a snippet disputes
Now suppose the draft says: "Tim Raue, the Berlin restaurant, holds three Michelin stars." The search call is the one from the recorded sample:
curl "https://api.perceptlayer.com/v1/google-search/search?query=best+restaurants+in+Berlin&hl=en&safe=off" \
-H "X-API-Key: $API_KEY"
Two organic results from the recorded response:
{
"organic_results": [
{
"position": 1,
"title": "The Best Restaurants in Berlin - Food Guide & Map",
"link": "https://andershusa.com/best-restaurants-berlin-germany-guide/",
"snippet": "Dec 3, 2025 — Tim Raue is the most internationally renowned restaurant in Berlin. It holds two Michelin stars, has been frequently ranked on The World's 50 ...",
"source": "Anders & Kaitlin",
…
},
{
"position": 2,
"title": "Restaurants - Berlin",
"link": "https://guide.michelin.com/us/en/berlin-region/berlin/restaurants",
"snippet": "Berlin and surroundings: 1-48 of 79 restaurants · POTS · Rutz · GOLVET · Bandol sur mer · Nobelhart & Schmutzig · Oukan · 136 · FACIL.",
"source": "MICHELIN Guide",
…
}
],
…
}
Result 1 says two stars, which differs from the draft's three. This article doesn't vouch for either number; the point is what the evidence says. That result is a travel blog, the snippet is dated Dec 3, 2025, and it's a secondary source. Result 2 is the Michelin Guide's own listing page, the kind of primary source you'd want, and its snippet doesn't mention stars at all.
An honest verdict here is conflicting, leaning contradicted: one dated secondary source disagrees with the draft, and the primary source's snippet neither confirms nor denies. Nobody has checked the Michelin page itself. A good pipeline doesn't quietly rewrite "three" to "two". It flags the claim, links both results, and, if the stakes justify it, fetches the Michelin page itself.
Where does this pattern fail?
It fails in four predictable ways, and each one needs a decision in your code rather than a better prompt. Stale sources look authoritative, sources disagree, snippets are cut off, and ranking is not truth. Stating these in the output is part of the pattern; a verifier that always sounds certain is a hallucination generator with citations.
| Failure | What you see | What to do |
|---|---|---|
| Stale source | A well-ranked result is months old; the web snippet has no date | Prefer news results with iso_date for time-sensitive claims. Return "outdated" or "unverified", never "supported", when the only match is undated |
| Conflicting sources | Two results give different numbers | Return "conflicting" with both URLs. Don't average, and don't pick the higher-ranked one |
| Truncated snippet | The text ends in ... |
Treat it as a lead, not a quote. Fetch the page if the claim hinges on the missing words |
| Rank is not truth | The top result is a blog; the primary source is third | Tell the judge to prefer primary sources (the company, the registry, the publisher) and to say which kind it relied on |
| No match | Neither call returns anything on the claim | "Unverified". Absence of results is not a contradiction |
Two limits are specific to the tools. News coverage depends on what Google News indexes, so a niche claim may have no news result at all. And data is returned as found at the time of the call: a result that's correct today can change tomorrow, so store the retrieval date next to each verdict.
What does it cost per answer?
Each claim costs 2 credits: one Search call and one News call, 1 credit each. An answer with 5 checkable claims costs 5 × 2 = 10 credits, and an agent that verifies 1,000 such answers a month spends 10,000 credits. Failed calls cost nothing and return X-Credits-Used: 0.
The free tier gives 1,000 credits every month after you verify an email, which covers 500 claim checks. Beyond that, credits come in prepaid packs starting at 10,000 credits; pricing has what they cost, and the credits docs cover how credits work.
To keep spend predictable, verify only claims that matter: numbers, dates, "latest", "first", names. Skip claims the user supplied and anything with no external referent. For caps and budgets across a whole agent, see AI agent API cost control, and caching responses helps when the same claim recurs.
How do you give an agent this as a tool?
Expose the two calls as tools and let the agent choose, or wrap evidence() in one verify_claim tool of your own. The MCP server at https://api.perceptlayer.com/mcp offers search_endpoints, get_endpoint and call_endpoint. The first two only read the catalogue and cost nothing; call_endpoint runs an endpoint and spends its credits on success only.
For Claude Code, add the server with the command from the AI agents docs:
claude mcp add --transport http perceptlayer https://api.perceptlayer.com/mcp --header "Authorization: Bearer $API_KEY"
A single wrapped tool is usually the better design for verification, because it fixes the query shape, the compaction and the two-call budget. Open-ended tool access makes sense when the agent must choose among many sources. The trade-offs are covered in MCP vs function calling and tool schema design. For a full MCP walkthrough, see Claude MCP web search.
Your next step: get a free API key, run the News call from example 1 in the playground on the Google News API page, then try one claim from your own agent's last answer.
APIs in this post
Frequently asked questions
Does grounding with search results stop hallucinations?
It reduces unsupported claims but doesn't remove them. The model can still misread a snippet, cite a result that doesn't say what it claims, or accept a wrong source. Requiring a URL per verdict, a fixed verdict list and an explicit "unverified" option makes errors visible and checkable instead of hidden.
Should I use Google Search or Google News for verification?
Use both. News results include iso_date, so they suit time-sensitive claims such as latest releases and recent events. Web results cover reference pages, primary sources and older facts, but snippets may carry no date. One call to each costs 2 credits. See the Google Search API and Google News API.
What happens if a verification call fails?
A failed call is not billed and returns X-Credits-Used: 0. For 429 and 503, wait the seconds in Retry-After and retry. Other failures return a JSON error with a code and request_id; see errors. If a lookup still fails, mark the claim unverified rather than passing it unchecked.
Can I fetch the full page behind a result?
The Search and News endpoints return titles, links, snippets and dates, not page bodies. If a claim depends on text past a truncated snippet, fetch the linked page with your own HTTP client, or use another endpoint from the catalogue that fits that website. Keep the retrieval date with whatever you store.
How many claims should I verify per answer?
Only the ones a wrong answer would hurt: figures, dates, names, and anything phrased as "latest" or "first". Each verified claim costs 2 credits, so 5 claims cost 10 credits. Verifying every sentence adds cost and latency without adding much, because most sentences aren't checkable facts.