Normal path: execute → tool → observe.
El problema (en producción)
Todo va bien.
Hasta que tu agente se vuelve “más listo”.
Empieza a incluir:
- más historial
- más output de tools
- más “contexto útil”
La latencia sube poco a poco. El coste sube poco a poco. Y un día se trunca la policy y el agente empieza a comportarse raro.
El exceso de tokens no es teórico. Es un incidente que puedes graficar.
Por qué esto se rompe en producción
1) El contexto crece por defecto
Si no lo capás, el contexto crece:
- crece el chat history
- crece la memory
- crecen outputs de tools (especialmente HTML)
El modelo no conoce tus budgets. Se comerá feliz todo lo que le des.
2) El prompt bloat causa truncation
Prompts grandes aumentan la probabilidad de que:
- se caiga la parte superior del prompt
- el “system/policy” sea la primera víctima
Cuando falta la policy, el modelo no se vuelve malvado. Se vuelve sin límites.
3) Los outputs de tools son bombas de tokens
HTML crudo, logs y stack traces son enormes. Si los pegas tal cual:
- los tokens explotan
- y el modelo a menudo ni usa bien ese contenido
Extrae → resume → incluye una porción pequeña.
4) Memory lo empeora
Sistemas de memory que “guardan todo” tienden a “mostrar todo”. Eso no es memory. Eso es inflación de prompt.
5) No se arregla con “usa un modelo con más contexto”
Ventanas más grandes:
- cuestan más
- son más lentas
- y también truncan (solo más tarde)
El fix es un context budget y un context builder.
Ejemplo de implementación (código real)
Un “budgeter” barato y práctico:
- capa el contexto total
- mantiene lo más nuevo
- resume (placeholder) si se pasa del budget
Aquí usamos caracteres; en prod querrás token counting.
from dataclasses import dataclass
from typing import Iterable
@dataclass(frozen=True)
class ContextBudget:
max_chars: int = 12_000
def summarize(text: str) -> str:
# In real code: a dedicated summarization step with its own budget.
return text[:2000] + "…"
def build_context(chunks: Iterable[str], *, budget: ContextBudget) -> str:
parts: list[str] = []
total = 0
for c in chunks:
parts.append(c)
total += len(c)
ctx = "\n\n".join(parts)
if len(ctx) <= budget.max_chars:
return ctx
# Over budget: summarize oldest parts first.
# This is simplistic, but it prevents unbounded growth.
over = len(ctx) - budget.max_chars
head = ctx[: over + 1000]
tail = ctx[over + 1000 :]
return summarize(head) + "\n\n" + tailexport function summarize(text) {
// Real code: dedicated summarization with its own budget.
return text.slice(0, 2000) + "…";
}
export function buildContext(chunks, { maxChars = 12_000 } = {}) {
const ctx = chunks.join("\\n\\n");
if (ctx.length <= maxChars) return ctx;
// Summarize the oldest part.
const over = ctx.length - maxChars;
const head = ctx.slice(0, over + 1000);
const tail = ctx.slice(over + 1000);
return summarize(head) + "\\n\\n" + tail;
}Esto no te da “memory perfecta”. Te da lo primero que necesitas: un budget que evita prompts desbocados.
Incidente real (con números)
Teníamos un agente que respondía “¿por qué falló este job?”. Incluía el stack trace y logs completos dentro del prompt.
Funcionaba… hasta que un cliente pegó un blob de logs de 2MB.
Impacto:
- tokens/request saltaron de ~4k → 45k
- p95 latencia subió de 3.2s → 19s
- gasto subió ~$520 en un día
- peor: se truncó la policy y el agente empezó a sugerir tools peligrosos
Fix:
- caps de input (max chars) en logs aportados por el usuario
- extraer campos estructurados (errores, timestamps) en vez de dumps crudos
- context budgeter + tier de summarization
- métricas/alertas de tokens/request
Los logs son útiles. Los logs crudos no son un formato de prompt.
Trade-offs
- Los resúmenes pierden detalles (pero los dumps crudos tampoco ayudaban).
- Caps estrictos pueden rechazar requests de “power users”. Ofrece uploads async.
- Token counting añade complejidad. Se paga rápido a escala.
Cuándo NO usarlo
- Si necesitas razonamiento exacto sobre documentos largos, quizá un agent loop es la herramienta equivocada. Usa retrieval dirigido + workflows.
- Si no puedes resumir texto no confiable con seguridad, no lo metas entero.
- Si no puedes medir tokens, empieza hoy con char budgets y arregla tokens después.
Checklist (copiar/pegar)
- [ ] Cap tamaño de texto del usuario (logs, HTML, PDFs)
- [ ] Cap tamaño de output de tools antes de entrar al contexto
- [ ] Context builder con budget duro (tokens/chars)
- [ ] Tier de summarization con su propio budget
- [ ] Repite constraints críticos cada turno (sobrevive truncation)
- [ ] Métricas: tokens/request, latencia, spend/run
- [ ] Alertas por spikes y drift
Config segura por defecto (JSON/YAML)
context:
max_prompt_tokens: 2500
max_untrusted_chars: 8000
summarize_when_over_budget: true
policy:
repeat_critical_constraints_every_turn: true
metrics:
track: ["tokens_per_request", "latency_p95", "spend_per_run"]
FAQ (3–5)
Usado por patrones
Fallos relacionados
- Explosión de presupuesto (cuando un agente quema dinero) + fixes + código
- Corrupción de respuestas de tools (schema drift + truncation) + código
- Prompt Injection en agentes (fallo + defensas + código)
- AI Agent Infinite Loop (Detectar + arreglar, con código)
- Tool Spam Loops (fallo del agente + fixes + código)
Gobernanza requerida
Q: ¿No puedo comprar un modelo con más contexto?
A: Puedes, pero pagarás en latencia y coste, y aún truncarás algún día. Presupuestar es más barato.
Q: ¿Cómo cuento tokens con precisión?
A: Usa el tokenizer del proveedor. Si no puedes, empieza con caps por caracteres y añade token counting después.
Q: ¿Debo guardar todo en memory?
A: Guarda eventos, sí. Mostrar todo al modelo, no. Memory ≠ tamaño de prompt.
Q: ¿Por qué repetir constraints de policy?
A: Porque truncation se lleva el inicio del prompt. Repetir mantiene las reglas vivas.
Páginas relacionadas (3–6 links)
- Foundations: Tipos de memoria de agente · Cómo afectan los límites del LLM a los agentes
- Failure: Explosión de presupuesto · Fuentes alucinadas
- Governance: Tool permissions (allowlists)
- Production stack: Production agent stack