Para que un agente deje de inventar datos como estrellas de GitHub o numero de seguidores, oblígale a llamar a una herramienta de busqueda y a razonar solo sobre el JSON devuelto, nunca a producir numeros de memoria. Dale al modelo una herramienta que llame al endpoint Google SERP de Scavio (POST https://api.scavio.dev/api/v1/google) y a la busqueda de Reddit, y escribe un prompt de sistema que prohíba inventar cualquier numero, fecha o nombre propio que no haya vuelto en la respuesta. El modelo pasa a narrar datos con fuente en lugar de adivinar. Esto solo ancla hechos publicos e indexados. Una API SERP confirma lo que Google ya muestra en publico; no puede leer un panel privado ni una metrica detras de un login, así que dejaselo claro al modelo.
Requisitos previos
- Una clave API de Scavio en scavio.dev (el plan gratuito da 50 creditos unicos, 1 peticion/seg)
- Python 3.10+ o Node 18+ con un cliente LLM que soporte llamadas a herramientas/funciones
- Saber como tu LLM expone herramientas y procesa los resultados de las llamadas
- Entender que hay que indicarle al modelo que nunca devuelva un numero que no este en el JSON de la herramienta
Guia paso a paso
Paso 1: Define la herramienta de busqueda que el modelo puede llamar
Expon al modelo exactamente una herramienta de obtencion de datos: una funcion de busqueda que hace POST al endpoint Google SERP de Scavio. La herramienta recibe una cadena de consulta y devuelve JSON estructurado. Manten el esquema cerrado para que el modelo solo pueda pedir una busqueda, no improvisar. La idea es que toda afirmacion factual del agente sea trazable hasta una llamada que puedas auditar. Si quiere un numero de estrellas de GitHub, tiene que buscarlo, no recordarlo.
TOOLS = [{
"type": "function",
"function": {
"name": "web_search",
"description": "Search Google for current public facts. Returns sourced JSON. Use this for any number, date, or name.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"]
}
}
}]Paso 2: Implementa la herramienta contra el endpoint de Google de Scavio
Cuando el modelo llame a web_search, haz un POST a https://api.scavio.dev/api/v1/google con Authorization: Bearer {API_KEY}. Pon light_request en false para obtener resultados organicos, people_also_ask, knowledge_graph y related_searches en una sola llamada (esto cuesta 2 creditos en vez de 1). Devuelve solo los campos que el modelo necesita, cada valor emparejado con su URL de fuente para que el modelo pueda citar de donde salio cada numero.
import requests
def web_search(query: str) -> dict:
r = requests.post(
"https://api.scavio.dev/api/v1/google",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"query": query, "light_request": False},
timeout=30,
)
r.raise_for_status()
data = r.json()
return {
"organic": data.get("organic", [])[:5],
"knowledge_graph": data.get("knowledge_graph"),
"people_also_ask": data.get("people_also_ask", []),
}Paso 3: Anade busqueda en Reddit para senal de la comunidad
Algunos datos viven en la conversacion, no en paginas indexadas. Anade una segunda herramienta que hace POST a https://api.scavio.dev/api/v1/reddit/search para reportes de primera mano, quejas y opiniones. Trata los resultados de Reddit como afirmaciones de personas, no como hechos verificados, e indica al modelo que los atribuya ('un usuario de Reddit reporto...') en vez de afirmarlos como verdad. Asi el agente es honesto sobre el origen de la senal mas blanda.
def reddit_search(query: str) -> dict:
r = requests.post(
"https://api.scavio.dev/api/v1/reddit/search",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"query": query},
timeout=30,
)
r.raise_for_status()
return {"posts": r.json().get("posts", [])[:5]}Paso 4: Escribe el prompt de sistema de anclaje
Esta es la parte que de verdad frena la alucinacion. Instruye al modelo: nunca devuelvas un numero, fecha, version o nombre propio salvo que aparezca literal en un resultado de herramienta. Si una busqueda no devuelve nada, di que no pudiste verificarlo en vez de adivinar. Cita la URL de fuente junto a cada cifra. Deja claro que la herramienta de busqueda cubre solo datos publicos indexados y no puede ver paneles privados ni metricas tras login, así que para eso la respuesta es 'no puedo acceder a eso'.
SYSTEM = '''You are a research agent. Rules:
1. Never state a number, date, version, or named entity unless it came back in a tool result. No recalling from memory.
2. For any factual claim, call web_search first, then narrate ONLY the returned values.
3. Put the source URL next to each figure.
4. If a search returns nothing useful, say "I could not verify this" instead of guessing.
5. The search tool sees public indexed data only. It cannot read private dashboards or login-gated metrics. For those, reply that you cannot access them.'''Paso 5: Ejecuta el bucle y verifica que el modelo solo narra valores devueltos
Envia la pregunta del usuario con las herramientas y el prompt de sistema, ejecuta las llamadas, devuelve el JSON y deja que el modelo escriba su respuesta. Luego comprueba: cada numero de la salida debe poder encontrarse en una respuesta de herramienta. Si ves una cifra que no esta, tu prompt es demasiado laxo, aprieta la regla 1. El modo de fallo honesto que buscas es que el agente diga 'no pude verificar el numero de seguidores' en lugar de imprimir uno equivocado con seguridad.
messages = [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": "How many GitHub stars does the project X have, and what do people say about it?"},
]
# 1. model -> requests web_search("project X github stars")
# 2. you run web_search, append the JSON as a tool message
# 3. model -> may also call reddit_search for sentiment
# 4. model writes final answer using ONLY those returned values, with source URLsEjemplo en Python
import os
import requests
API_KEY = os.environ["SCAVIO_API_KEY"]
def web_search(query: str) -> dict:
r = requests.post(
"https://api.scavio.dev/api/v1/google",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"query": query, "light_request": False},
timeout=30,
)
r.raise_for_status()
data = r.json()
return {
"organic": data.get("organic", [])[:5],
"knowledge_graph": data.get("knowledge_graph"),
"people_also_ask": data.get("people_also_ask", []),
}
def reddit_search(query: str) -> dict:
r = requests.post(
"https://api.scavio.dev/api/v1/reddit/search",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"query": query},
timeout=30,
)
r.raise_for_status()
return {"posts": r.json().get("posts", [])[:5]}
SYSTEM = (
"You are a research agent. Never state a number, date, version, or named "
"entity unless it came back in a tool result; do not recall from memory. "
"For any factual claim, call web_search first and narrate only the returned "
"values, with the source URL next to each figure. If a search returns nothing "
"useful, say you could not verify it. The search tool sees public indexed data "
"only; it cannot read private dashboards or login-gated metrics."
)
if __name__ == "__main__":
# The LLM calls web_search('project X github stars'); you return this JSON;
# the model then narrates ONLY these values. Example of the grounding call:
result = web_search("project X github stars")
print(result["knowledge_graph"])
print([item["link"] for item in result["organic"]])
Ejemplo en JavaScript
const API_KEY = process.env.SCAVIO_API_KEY;
async function webSearch(query) {
const res = await fetch("https://api.scavio.dev/api/v1/google", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, light_request: false }),
});
if (!res.ok) throw new Error(`Scavio ${res.status}`);
const data = await res.json();
return {
organic: (data.organic || []).slice(0, 5),
knowledge_graph: data.knowledge_graph,
people_also_ask: data.people_also_ask || [],
};
}
async function redditSearch(query) {
const res = await fetch("https://api.scavio.dev/api/v1/reddit/search", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query }),
});
if (!res.ok) throw new Error(`Scavio ${res.status}`);
const data = await res.json();
return { posts: (data.posts || []).slice(0, 5) };
}
const SYSTEM =
"You are a research agent. Never state a number, date, version, or named " +
"entity unless it came back in a tool result; do not recall from memory. " +
"For any factual claim, call web_search first and narrate only the returned " +
"values, with the source URL next to each figure. If a search returns nothing, " +
"say you could not verify it. The search tool sees public indexed data only; " +
"it cannot read private dashboards or login-gated metrics.";
// The LLM calls webSearch('project X github stars'); feed the JSON back so the
// model narrates ONLY these returned values:
webSearch("project X github stars").then((r) => {
console.log(r.knowledge_graph);
console.log(r.organic.map((o) => o.link));
});
Salida esperada
{
"organic": [
{
"title": "project X - GitHub",
"link": "https://github.com/org/project-x",
"snippet": "project X is an open-source ... 12.4k stars"
}
],
"knowledge_graph": {
"title": "project X",
"type": "Software repository"
},
"people_also_ask": [
{ "question": "Is project X actively maintained?" }
]
}