Si ChatGPT afirma datos incorrectos con el mismo tono de seguridad que los correctos es porque el modelo no tiene ninguna señal externa en el momento de generar el texto. Para añadir esa señal, pasa la respuesta por un bucle de grounding de tres pasos: extrae las afirmaciones fácticas atómicas, busca cada una en la web en vivo y etiquétala como apoyada, sin apoyo o contradicha, con las fuentes adjuntas. Esto es justo lo que hacen por dentro herramientas como TrustLens y los verificadores de datos del navegador, y puedes construirlo en unas 40 líneas. El paso de búsqueda es donde la gente se atasca, porque necesitas resultados limpios y estructurados, no una página de HTML que parsear. Este tutorial usa el endpoint de Google de Scavio (POST https://api.scavio.dev/api/v1/google, Authorization: Bearer) para que cada afirmación vuelva como fragmentos con título que puedes puntuar directamente. A todo el flujo lo llamamos la checklist de grounding de Scavio: extraer, recuperar, comparar, puntuar, citar.
Requisitos previos
- Una clave de API de Scavio (50 créditos gratis, sin tarjeta) desde scavio.dev
- Python 3.10+ con el paquete requests
- Un LLM al que puedas llamar para los pasos de extracción de afirmaciones y comparación (cualquier proveedor)
- Una respuesta que quieras comprobar (pega cualquier respuesta de ChatGPT/Claude/Gemini)
Guia paso a paso
Paso 1: Extraer afirmaciones atómicas de la respuesta
Divide la respuesta del LLM en afirmaciones fácticas independientes, cada una un enunciado verificable. 'La Torre Eiffel mide 330 m y se terminó en 1889' se convierte en dos afirmaciones. Las opiniones y los enunciados matizados ('probablemente', 'mucha gente cree') se omiten, porque no son comprobables. Pide a tu modelo que devuelva una lista JSON de cadenas con las afirmaciones para que el siguiente paso pueda recorrerlas.
CLAIM_PROMPT = '''Extract every standalone, checkable factual claim from the\ntext below. Ignore opinions, predictions, and hedged statements.\nReturn a JSON array of short claim strings.\n\nTEXT:\n{answer}'''Paso 2: Buscar cada afirmación en la web en vivo
Para cada afirmación, ejecuta una búsqueda que devuelva evidencia real. Usa la propia afirmación (o una versión reducida a palabras clave) como consulta. El endpoint de Google de Scavio devuelve los resultados orgánicos como JSON estructurado; poner light_request en false también trae el knowledge graph, que a menudo responde directamente afirmaciones fácticas (fechas, alturas, poblaciones). Toma los 5 primeros fragmentos como conjunto de evidencia de esa afirmación.
import os, requests
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}",
"Content-Type": "application/json"}
def evidence_for(claim: str) -> dict:
r = requests.post("https://api.scavio.dev/api/v1/google", headers=H,
json={"query": claim, "light_request": False}, timeout=30)
r.raise_for_status()
d = r.json()
return {
"snippets": [{"title": o["title"], "link": o["link"],
"text": o.get("snippet", "")}
for o in d.get("organic", [])[:5]],
"knowledge_graph": d.get("knowledge_graph"),
}Paso 3: Comparar la afirmación con la evidencia
Devuelve al LLM la afirmación junto con sus fragmentos de evidencia y pídele un veredicto: apoyada, sin apoyo o contradicha, con el enlace concreto de la fuente que lo decide. Mantén acotada la tarea del modelo. No está respondiendo la pregunta, solo juzga si la evidencia respalda la afirmación. Esa restricción es lo que hace fiable la segunda pasada del LLM allí donde la primera alucinó.
VERDICT_PROMPT = '''Claim: {claim}\n\nEvidence (search snippets):\n{evidence}\n\nReply as JSON: {{"verdict": "supported|unsupported|contradicted",\n"source": "<the link that decides it>", "why": "<one sentence>"}}'''Paso 4: Consolidar los veredictos en una puntuación de confianza
Convierte los veredictos por afirmación en un solo número para que una persona sepa si actuar sobre la respuesta. Una puntuación sencilla y defendible: apoyada = 1.0, sin apoyo = 0.4, contradicha = 0.0, promediada entre las afirmaciones y multiplicada por 100. Cualquier respuesta con una afirmación contradicha cae rápido, que es justo el comportamiento que buscas. Adjunta los enlaces de las fuentes para que la puntuación sea auditable y no una mera sensación.
Paso 5: Gestionar los casos límite antes de fiarte de la puntuación
Dos modos de fallo que vigilar: una afirmación con cero fragmentos relevantes debe ser 'sin apoyo', nunca 'apoyada por ausencia', y una afirmación que la búsqueda confirma pero con una fuente obsoleta (precios antiguos, versión superada) necesita una comprobación de frescura sobre la fecha del fragmento. El grounding reduce las alucinaciones, no las elimina, así que muestra las fuentes y deja que la persona tome la decisión final en afirmaciones de alto riesgo.
Ejemplo en Python
import os, json, requests
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}",
"Content-Type": "application/json"}
SCORE = {"supported": 1.0, "unsupported": 0.4, "contradicted": 0.0}
def search(claim):
r = requests.post("https://api.scavio.dev/api/v1/google", headers=H,
json={"query": claim, "light_request": False}, timeout=30).json()
return [f"{o['title']}: {o.get('snippet','')} ({o['link']})"
for o in r.get("organic", [])[:5]]
def fact_check(claims, verdict_fn):
# verdict_fn(claim, evidence) -> {'verdict','source','why'} via your LLM
results = []
for c in claims:
ev = search(c)
v = verdict_fn(c, ev)
results.append({**v, "claim": c})
trust = round(100 * sum(SCORE[r["verdict"]] for r in results) / len(results))
return {"trust_score": trust, "claims": results}
if __name__ == "__main__":
claims = ["Scavio's free tier gives 50 credits",
"Scavio returns Google AI Overviews"]
# plug in your own verdict_fn; second claim should come back contradicted
print(json.dumps(fact_check(claims, verdict_fn), indent=2))Ejemplo en JavaScript
const H = { Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`,
"Content-Type": "application/json" };
const SCORE = { supported: 1.0, unsupported: 0.4, contradicted: 0.0 };
async function search(claim) {
const r = await fetch("https://api.scavio.dev/api/v1/google", {
method: "POST", headers: H,
body: JSON.stringify({ query: claim, light_request: false }),
}).then((r) => r.json());
return (r.organic || []).slice(0, 5).map(
(o) => `${o.title}: ${o.snippet ?? ""} (${o.link})`);
}
async function factCheck(claims, verdictFn) {
const results = [];
for (const c of claims) results.push({ ...(await verdictFn(c, await search(c))), claim: c });
const trust = Math.round(100 * results.reduce((s, r) => s + SCORE[r.verdict], 0) / results.length);
return { trustScore: trust, claims: results };
}Salida esperada
Un informe JSON por respuesta: una puntuación de confianza global (trust_score, 0-100) más una lista de afirmaciones, cada una con su veredicto, el enlace de la fuente que lo decidió y una razón de una línea. Una respuesta limpia puntúa cerca de 100; una respuesta con una afirmación contradicha baja de 70 y se marca para revisión. El coste es una llamada a Google por afirmación; con light_request: false son 2 créditos ($0.01) cada una, así que una respuesta de 6 afirmaciones cuesta unos $0.06 verificar contra fuentes en vivo. Los 50 créditos gratis cubren aproximadamente cuatro respuestas completas de principio a fin.