ScavioScavio
ProductPricingDocs
Sign InGet Started
  1. Home
  2. Tutorials
  3. Build a Real-Time Fact-Checker That Grounds an LLM on Live Search
Tutorial

Build a Real-Time Fact-Checker That Grounds an LLM on Live Search

Ground an LLM on live Google results to fact-check claims in real time. Runnable Python and JS pipeline using Scavio's SERP API, 1 credit per claim.

Get Free API KeyAPI Docs

To build a real-time fact-checker that grounds an LLM on live search, split incoming text into individual claims, keep only the check-worthy ones (numbers, named entities, policy or statistical assertions), search each claim against live Google results, then ask the model to judge supported, contradicted, or unverifiable and cite the actual result links. The grounding step is what stops the hallucinated citations people complain about. As one builder put it after wiring a Google search API into their pipeline: they used it precisely because the model tends to invent sources that don't exist. Once every verdict has to point at a real URL the model just retrieved, fabricated citations mostly stop. This tutorial gives you a runnable pipeline: claim extraction, a check-worthiness filter, a POST to Scavio's /api/v1/google endpoint per claim, and a strict judging prompt that returns a verdict plus citations. It costs 1 credit ($0.005) per claim checked, so a 40-sentence transcript with 12 check-worthy claims runs about $0.06. Be honest about what this does and doesn't do: SERP grounding kills fabricated citations and keeps the model current, but it does not adjudicate truth. You still need source-quality filtering and human judgment, and a claim that's simply absent from search is unverifiable, not false.

Prerequisites

  • A Scavio API key (free signup gives 50 credits to test with)
  • An LLM API key (any chat-completion model with a JSON-capable response works)
  • Python 3.9+ or Node 18+
  • Basic familiarity with HTTP requests and JSON

Walkthrough

Step 1: Split text into atomic claims

Break the transcript into individual sentences, then into atomic claims. One sentence can carry several claims; keep them separate so each gets its own search. A naive sentence split is enough to start; swap in a model-based claim splitter later if you need finer granularity.

Python
import re

def split_claims(text: str) -> list[str]:
    sentences = re.split(r'(?<=[.!?])\s+', text.strip())
    return [s.strip() for s in sentences if len(s.strip()) > 0]

Step 2: Keep only check-worthy claims

Most sentences aren't worth checking. Keep the ones that make a falsifiable assertion: numbers, percentages, dates, named entities, or policy and statistical claims. This filter is what keeps your credit spend down, since you only search claims that can actually be verified.

Python
import re

NUM = re.compile(r'\d')
ENTITY = re.compile(r'\b[A-Z][a-z]+\b')
KEYWORDS = ('percent', '%', 'billion', 'million', 'increased', 'decreased', 'banned', 'passed', 'voted', 'rate', 'tax', 'budget')

def is_check_worthy(claim: str) -> bool:
    has_number = bool(NUM.search(claim))
    has_entity = bool(ENTITY.search(claim))
    has_keyword = any(k in claim.lower() for k in KEYWORDS)
    return (has_number or has_keyword) and has_entity

Step 3: Search each claim against live Google

POST the claim text to /api/v1/google. Pull the top organic results and the people_also_ask entries; those snippets are the evidence the model will reason over. Each call costs 1 credit when light_request is true, 2 credits with the full SERP feature set.

Python
import os, requests

H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}

def retrieve(claim: str) -> list[dict]:
    r = requests.post("https://api.scavio.dev/api/v1/google", headers=H,
        json={"query": claim, "light_request": True})
    data = r.json()
    evidence = []
    for row in data.get("organic_results", [])[:5]:
        evidence.append({"title": row["title"], "snippet": row.get("snippet", ""), "link": row["link"]})
    for paa in data.get("people_also_ask", [])[:3]:
        evidence.append({"title": paa.get("question", ""), "snippet": paa.get("snippet", ""), "link": paa.get("link", "")})
    return evidence

Step 4: Judge the claim against the evidence

Pass the claim plus the retrieved snippets to an LLM with a strict prompt: return supported, contradicted, or unverifiable, a one-line reason, and citations drawn only from the links you passed in. The rule never invent a source is the whole point. If the evidence doesn't cover the claim, the answer is unverifiable.

Python
PROMPT = '''You are a fact-checking judge. Given a CLAIM and EVIDENCE (search snippets with links), return strict JSON:
{"verdict": "supported|contradicted|unverifiable", "reason": "one sentence", "citations": ["<link from evidence>"]}
Rules: cite ONLY links present in EVIDENCE. Never invent a source. If evidence does not address the claim, verdict is "unverifiable".

CLAIM: {claim}
EVIDENCE: {evidence}'''

def judge(claim, evidence, llm):
    msg = PROMPT.format(claim=claim, evidence=evidence)
    return llm.complete(msg, response_format="json")

Python Example

Python
import os, re, json, requests

H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}
NUM = re.compile(r'\d')
ENTITY = re.compile(r'\b[A-Z][a-z]+\b')
KEYWORDS = ('percent', '%', 'billion', 'million', 'increased', 'decreased', 'banned', 'passed', 'voted', 'rate', 'tax', 'budget')

def split_claims(text):
    return [s.strip() for s in re.split(r'(?<=[.!?])\s+', text.strip()) if s.strip()]

def is_check_worthy(claim):
    return (bool(NUM.search(claim)) or any(k in claim.lower() for k in KEYWORDS)) and bool(ENTITY.search(claim))

def retrieve(claim):
    r = requests.post("https://api.scavio.dev/api/v1/google", headers=H,
        json={"query": claim, "light_request": True})
    data = r.json()
    ev = [{"title": x["title"], "snippet": x.get("snippet", ""), "link": x["link"]}
          for x in data.get("organic_results", [])[:5]]
    ev += [{"title": p.get("question", ""), "snippet": p.get("snippet", ""), "link": p.get("link", "")}
           for p in data.get("people_also_ask", [])[:3]]
    return ev

PROMPT = '''You are a fact-checking judge. Given a CLAIM and EVIDENCE, return strict JSON:
{{"verdict":"supported|contradicted|unverifiable","reason":"one sentence","citations":["<link>"]}}
Cite ONLY links present in EVIDENCE. Never invent a source. No coverage => "unverifiable".
CLAIM: {claim}
EVIDENCE: {evidence}'''

def fact_check(text, llm):
    results = []
    for claim in split_claims(text):
        if not is_check_worthy(claim):
            continue
        evidence = retrieve(claim)
        verdict = llm.complete(PROMPT.format(claim=claim, evidence=json.dumps(evidence)), response_format="json")
        results.append({"claim": claim, **json.loads(verdict)})
    return results

# transcript = "..."
# for r in fact_check(transcript, my_llm):
#     print(r["verdict"], r["claim"], r["citations"])

JavaScript Example

JavaScript
const H = {
  Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`,
  "Content-Type": "application/json",
};
const KEYWORDS = ["percent", "%", "billion", "million", "increased", "decreased", "banned", "passed", "voted", "rate", "tax", "budget"];

const splitClaims = (text) =>
  text.trim().split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter(Boolean);

const isCheckWorthy = (c) =>
  (/\d/.test(c) || KEYWORDS.some((k) => c.toLowerCase().includes(k))) && /\b[A-Z][a-z]+\b/.test(c);

async function retrieve(claim) {
  const r = await fetch("https://api.scavio.dev/api/v1/google", {
    method: "POST",
    headers: H,
    body: JSON.stringify({ query: claim, light_request: true }),
  });
  const data = await r.json();
  const ev = (data.organic_results || []).slice(0, 5).map((x) => ({ title: x.title, snippet: x.snippet || "", link: x.link }));
  (data.people_also_ask || []).slice(0, 3).forEach((p) => ev.push({ title: p.question || "", snippet: p.snippet || "", link: p.link || "" }));
  return ev;
}

const PROMPT = (claim, evidence) => `You are a fact-checking judge. Given a CLAIM and EVIDENCE, return strict JSON:
{"verdict":"supported|contradicted|unverifiable","reason":"one sentence","citations":["<link>"]}
Cite ONLY links present in EVIDENCE. Never invent a source. No coverage => "unverifiable".
CLAIM: ${claim}
EVIDENCE: ${JSON.stringify(evidence)}`;

async function factCheck(text, llm) {
  const out = [];
  for (const claim of splitClaims(text)) {
    if (!isCheckWorthy(claim)) continue;
    const evidence = await retrieve(claim);
    const verdict = await llm.complete(PROMPT(claim, evidence), { responseFormat: "json" });
    out.push({ claim, ...JSON.parse(verdict) });
  }
  return out;
}

Expected Output

JSON
[
  {
    "claim": "The new budget increases infrastructure spending by 12 percent next year.",
    "verdict": "supported",
    "reason": "Two retrieved sources state a 12 percent infrastructure increase in the proposed budget.",
    "citations": ["https://example-news.test/budget-2026", "https://example-gov.test/budget-summary"]
  },
  {
    "claim": "Unemployment fell to its lowest level since 1969.",
    "verdict": "contradicted",
    "reason": "Retrieved sources report the lowest level since 2001, not 1969.",
    "citations": ["https://example-stats.test/unemployment"]
  },
  {
    "claim": "The committee met privately on Tuesday to discuss the proposal.",
    "verdict": "unverifiable",
    "reason": "No retrieved source addresses a private Tuesday meeting.",
    "citations": []
  }
]

Related Tutorials

    Frequently Asked Questions

    Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

    A Scavio API key (free signup gives 50 credits to test with). An LLM API key (any chat-completion model with a JSON-capable response works). Python 3.9+ or Node 18+. Basic familiarity with HTTP requests and JSON. A Scavio API key gives you 50 free credits on signup.

    Yes. The free tier includes 50 credits on signup, which is more than enough to complete this tutorial and prototype a working solution.

    Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

    Related Resources

    Best Of

    Best Real Time Search API in 2026

    Read more
    Solution

    Ground LLM Responses with Real-Time Search Data

    Read more
    Best Of

    Best Low-Latency Search APIs for AI Agents (2026)

    Read more
    Use Case

    Local LLM Search Grounding via API

    Read more
    Use Case

    Local LLM News and Search Grounding

    Read more
    Glossary

    LLM Grounding

    Read more

    Start Building

    Ground an LLM on live Google results to fact-check claims in real time. Runnable Python and JS pipeline using Scavio's SERP API, 1 credit per claim.

    Get Free API KeyRead the Docs
    ScavioScavio

    Real-time search API for AI agents. Search every platform, not just Google.

    Product

    • Features
    • Pricing
    • Dashboard
    • Affiliates

    Developers

    • Documentation
    • API Reference
    • Quickstart
    • MCP Integration
    • Python SDK

    Alternatives

    • Tavily Alternative
    • SerpAPI Alternative
    • Firecrawl Alternative
    • Exa Alternative

    Tools

    • JSON Formatter
    • cURL to Code
    • Token Counter
    • All Tools

    © 2026 Scavio. All rights reserved.

    Featured on TAAFT
    Terms of ServicePrivacy Policy