Normal path: execute → tool → observe.
El problema (en producción)
“No cambiamos nada”.
Excepto que:
- alguien retocó un prompt “un poco”
- un tool empezó a devolver un campo nuevo
- el modelo tuvo un version bump
- tu índice de retrieval se actualizó
El agente sigue “funcionando”.
Pero es más lento. Llama tools diferentes. Toma decisiones distintas. Se come edge cases. Nadie lo nota hasta que un usuario lo nota — y los usuarios no son QA amable.
Esto es drift silencioso: el comportamiento cambia en producción sin un fallo obvio.
Por qué esto se rompe en producción
1) El output del modelo no es estable
Incluso sin cambios de versión, hay varianza. Con cambios de versión, el shift es seguro.
Si no mides el shift, no lo ves.
2) Los tools también derivan
Los outputs cambian:
- evoluciona el schema
- cambian payloads de error
- cambia el ordering
- cambian defaults
Si tu agente es sensible a eso, derivará.
3) Los prompts son código (pero rara vez se tratan como tal)
Edits de prompt suelen shippearse sin:
- tests
- rollbacks
- canaries
- métricas
Así obtienes “cambiamos una frase y ahora llama http.get 10x más”.
4) Drift aparece en coste/latencia antes que en correctness
Las señales tempranas suelen ser operacionales:
- tokens/request suben
- tool calls/run suben
- p95 latencia sube
- stop reasons cambian
Si solo miras “success rate”, lo pierdes.
5) El fix es un feedback loop: golden tasks + replay + canary
Necesitas un loop de eval con forma de producción:
- golden tasks que representan workload real
- replay de traces reales (redactado)
- canary para cambios de modelo/prompt/tool
- alertas por deltas de comportamiento
Ejemplo de implementación (código real)
Un harness mínimo de “golden tasks”:
- corre tareas contra baseline y candidato
- compara stop reasons y tool-call counts
- falla si el delta pasa el umbral
from dataclasses import dataclass
@dataclass(frozen=True)
class GoldenTask:
id: str
input: str
def run_agent(version: str, task: GoldenTask) -> dict:
# Pseudo: run your agent with pinned model/prompt/tools config.
return agent_run(version=version, input=task.input) # (pseudo)
def score(run: dict) -> dict:
return {
"stop_reason": run.get("stop_reason"),
"tool_calls": int(run.get("tool_calls", 0)),
"tokens": int(run.get("tokens_total", 0)),
}
def drift_check(tasks: list[GoldenTask], *, baseline: str, candidate: str) -> None:
for t in tasks:
b = score(run_agent(baseline, t))
c = score(run_agent(candidate, t))
if c["stop_reason"] != b["stop_reason"]:
raise RuntimeError(f"[{t.id}] stop_reason drift: {b['stop_reason']} -> {c['stop_reason']}")
if c["tool_calls"] > b["tool_calls"] + 3:
raise RuntimeError(f"[{t.id}] tool_calls drift: {b['tool_calls']} -> {c['tool_calls']}")export function score(run) {
return {
stopReason: run.stop_reason,
toolCalls: Number(run.tool_calls || 0),
tokens: Number(run.tokens_total || 0),
};
}
export function driftCheck(tasks, { baseline, candidate, runAgent }) {
for (const t of tasks) {
const b = score(runAgent(baseline, t));
const c = score(runAgent(candidate, t));
if (c.stopReason !== b.stopReason) {
throw new Error("[" + t.id + "] stop_reason drift: " + b.stopReason + " -> " + c.stopReason);
}
if (c.toolCalls > b.toolCalls + 3) {
throw new Error("[" + t.id + "] tool_calls drift: " + b.toolCalls + " -> " + c.toolCalls);
}
}
}Esto es crudo a propósito. Aun así captura el drift más común:
- stop reasons cambian (timeouts nuevos, loops nuevos)
- inflación de tool calls (drift de coste)
Luego añades checks de correctness específicos del dominio. Pero empieza con drift operacional — es más fácil de medir y suele ser la primera señal.
Incidente real (con números)
Upgradeamos una versión de modelo para un agente de soporte. Sin canary, sin golden tasks.
El modelo nuevo era “mejor siendo minucioso”.
También llamaba search.read más a menudo.
Impacto en 24 horas:
- tool calls/run: 2.8 → 9.6
- p95 latencia: 2.7s → 7.4s
- gasto: +$460 vs baseline
- la corrección no cayó de forma obvia, así que nadie lo vio hasta que lo vio la factura
Fix:
- golden tasks con umbrales de drift (tool calls, stop reasons)
- canary (1% de tráfico) con auto-rollback por spikes
- replay semanal de traces reales anonimizados
- dashboards: tokens, tool calls, stop reasons, latencia
El drift no es emocionante. Es como se rompe producción cuando nadie mira.
Trade-offs
- Mantener suites de golden tasks cuesta tiempo.
- Canary añade complejidad de rollout (vale la pena).
- Parte del drift es “bueno” (mejores respuestas). Igual necesitas medir para decidir.
Cuándo NO usarlo
- Si tu agente es informacional y de bajo riesgo, puedes ser más laxo (igual vigila gasto).
- Si aún no tienes distribución estable de tareas, empieza con smoke tests y crece a golden tasks.
- Si no puedes replay traces por PII, usa tareas sintéticas + budgets estrictos.
Checklist (copiar/pegar)
- [ ] Golden tasks representando workload real
- [ ] Replay set de traces reales (redactados)
- [ ] Canary con triggers de rollback
- [ ] Umbrales: tool calls, tokens, latencia, stop reasons
- [ ] Versiones de modelo/prompt/tool fijadas por release
- [ ] Review semanal de “qué cambió”
Config segura por defecto (JSON/YAML)
releases:
canary_percent: 1
rollback_on:
tool_calls_per_run_increase_pct: 50
tokens_per_request_increase_pct: 50
latency_p95_increase_pct: 50
eval:
golden_tasks_required: true
drift_thresholds:
tool_calls_delta: 3
stop_reason_changes: 0
FAQ (3–5)
Usado por patrones
Fallos relacionados
- AI Agent Infinite Loop (Detectar + arreglar, con código)
- Explosión de presupuesto (cuando un agente quema dinero) + fixes + código
- Tool Spam Loops (fallo del agente + fixes + código)
- Incidentes de exceso de tokens (prompt bloat) + fixes + código
- Corrupción de respuestas de tools (schema drift + truncation) + código
Gobernanza requerida
Q: ¿El drift siempre es malo?
A: No. Pero drift no medido sí es malo. Sin métricas no sabes si es mejora o regresión lenta y cara.
Q: ¿Qué monitorizo primero?
A: Tool calls/run, tokens/request, latencia p95 y stop reasons. Se mueven antes que quejas de correctness.
Q: ¿Necesito canary para cada cambio de prompt?
A: Para agentes con tráfico o impacto: sí. Trata prompts como cambios de código.
Q: ¿Cómo hago replay de traces de prod con seguridad?
A: Redacta PII, guarda hashes de args cuando puedas, y reusa resultados de tools desde snapshots.
Páginas relacionadas (3–6 links)
- Foundations: Por qué fallan agentes en producción · Cómo afectan los límites del LLM a los agentes
- Failure: Exceso de tokens · Explosión de presupuesto
- Governance: Tool permissions (allowlists)
- Production stack: Production agent stack