El problema
El paso de busqueda web de tu agente recibe un limite de tasa 429 o un timeout colgado, y toda la ejecucion se detiene. Es el fallo del hilo de r/ClaudeWorkflows sobre depurar busqueda web y extraccion de contenido en un agente de Claude Code: el paso de buscar y extraer agota el tiempo o queda limitado, no hay via de recuperacion y el agente muere a mitad de tarea. Las dos causas de raiz casi siempre son las mismas. Ignoras la cabecera Retry-After en un 429, y descargas HTML crudo para analizarlo en vivo en lugar de pedir JSON estructurado.
La solucion de Scavio
Tres arreglos, por orden de impacto. Primero, respeta la cabecera Retry-After en cada 429. La mayoria de las docs de proveedores la esconden, asi que los ingenieros descubren a las malas que el servidor ya les dijo cuanto esperar. Lee la cabecera, duerme esos segundos, y si no esta usa backoff exponencial con jitter para que una flota de agentes no reintente al unisono. Segundo, deja de descargar HTML y analizarlo en tiempo de peticion. El analisis de paginas en vivo es donde nacen la mayoria de los timeouts de extraccion: un render lento, una cadena de redirecciones, un muro anti-bots, y tu navegador headless se cuelga pasado el plazo. POST https://api.scavio.dev/api/v1/google de Scavio devuelve JSON tipado en una sola llamada, con campos organic, people_also_ask, knowledge_graph y related_searches ya analizados en el servidor, sin navegador headless de tu lado. Tercero, fija un timeout duro por peticion y un proveedor de respaldo, para que una sola llamada lenta no bloquee la ejecucion, y cachea consultas identicas para que un reintento no vuelva a facturarte. Limite honesto: una API SERP estructurada no ayuda cuando tu objetivo es una SPA cargada de JavaScript o contenido tras un login. Para eso aun necesitas un navegador real o una sesion autenticada.
Antes
El agente descarga HTML crudo y lo analiza en vivo sin backoff. Un 429 o un render lento cuelga el navegador headless pasado el plazo, y la ejecucion muere sin recuperacion.
Después
El agente llama a un endpoint SERP en JSON estructurado, respeta Retry-After con backoff exponencial mas jitter, impone un timeout duro con proveedor de respaldo, y cachea consultas identicas para que los reintentos no vuelvan a facturar.
Para quien es
Ingenieros que construyen agentes con Claude Code, LangChain o CrewAI cuyo paso de busqueda o extraccion sigue agotando el tiempo o chocando con 429, y que quieren una llamada SERP en JSON estructurado con backoff correcto en vez de un analisis de HTML en vivo que se cuelga.
Beneficios clave
- Retry-After mas backoff con jitter convierte un 429 en una pausa corta en vez de una ejecucion muerta
- El JSON estructurado de /api/v1/google elimina el analisis de HTML en vivo que causa la mayoria de timeouts
- Sin navegador headless que vigilar: organic, people_also_ask y knowledge_graph llegan analizados en una respuesta
- Un timeout duro por peticion mas un proveedor de respaldo evita que una llamada lenta bloquee al agente
- El cache de consultas hace que los reintentos golpeen la cache, no tu saldo de creditos
Ejemplo en Python
import time, random, requests
API = "https://api.scavio.dev/api/v1/google"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
_cache = {}
def search(query, light_request=False, timeout=15, max_retries=5):
if query in _cache:
return _cache[query]
body = {"query": query, "light_request": light_request}
for attempt in range(max_retries):
try:
r = requests.post(API, json=body, headers=HEADERS, timeout=timeout)
except requests.Timeout:
time.sleep(min(2 ** attempt + random.random(), 30))
continue
if r.status_code == 429:
retry_after = r.headers.get("Retry-After")
wait = float(retry_after) if retry_after else min(2 ** attempt + random.random(), 30)
time.sleep(wait)
continue
r.raise_for_status()
data = r.json()
_cache[query] = data
return data
raise RuntimeError("search failed after retries")
result = search("agent web search timeout fix")
for item in result.get("organic", []):
print(item["title"], item["link"])Ejemplo en JavaScript
const API = "https://api.scavio.dev/api/v1/google";
const HEADERS = { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" };
const cache = new Map();
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function search(query, { lightRequest = false, timeout = 15000, maxRetries = 5 } = {}) {
if (cache.has(query)) return cache.get(query);
const body = JSON.stringify({ query, light_request: lightRequest });
for (let attempt = 0; attempt < maxRetries; attempt++) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), timeout);
try {
const r = await fetch(API, { method: "POST", headers: HEADERS, body, signal: ctrl.signal });
clearTimeout(t);
if (r.status === 429) {
const retryAfter = r.headers.get("Retry-After");
const wait = retryAfter ? parseFloat(retryAfter) * 1000 : Math.min(2 ** attempt * 1000 + Math.random() * 1000, 30000);
await sleep(wait);
continue;
}
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const data = await r.json();
cache.set(query, data);
return data;
} catch (e) {
clearTimeout(t);
await sleep(Math.min(2 ** attempt * 1000 + Math.random() * 1000, 30000));
}
}
throw new Error("search failed after retries");
}
const result = await search("agent web search timeout fix");
for (const item of result.organic ?? []) console.log(item.title, item.link);