Explosión de presupuesto (cuando un agente quema dinero) + fixes + código

  • Detecta el fallo temprano antes de que suba el gasto.
  • Entiende qué se rompe en producción y por qué.
  • Copia guardrails: budgets, stop reasons, validación.
  • Sabe cuándo esto no es la causa raíz.
Señales de detección
  • Tool calls por run suben (o repiten mismo args hash).
  • Gasto/tokens suben sin mejorar el resultado.
  • Retries pasan de raros a constantes (429/5xx).
Los budgets no fallan de golpe: se filtran por retries, prompt bloat y tool spam. Cómo pasan en producción y cómo capar gasto por run.
En esta página
  1. El problema (en producción)
  2. Por qué esto se rompe en producción
  3. 1) Los tokens escalan con el contexto, no con la intención
  4. 2) Los retries multiplican coste
  5. 3) “Planning” es overhead puro
  6. 4) El tool spam vuelve inútil cualquier budget
  7. 5) Si no loggeas gasto, no lo ves
  8. Ejemplo de implementación (código real)
  9. Incidente real (con números)
  10. Trade-offs
  11. Cuándo NO usarlo
  12. Checklist (copiar/pegar)
  13. Config segura por defecto (JSON/YAML)
  14. FAQ (3–5)
  15. Páginas relacionadas (3–6 links)
Flujo interactivo
Escenario:
Paso 1/2: Execution

Normal path: execute → tool → observe.

El problema (en producción)

Shipeas un agente.

En testing cuesta “unos céntimos”.

Luego entra tráfico real y alguien escribe en Slack:

“¿Por qué gastamos $900 en el agente ayer?”

Las explosiones de presupuesto rara vez son un bug enorme. Son muerte por mil cortes:

  • el uso de tokens deriva hacia arriba
  • los retries se multiplican
  • las tool calls se convierten en loops
  • los prompts crecen “solo una vez más”

Si no mides y capás budgets, te enteras del gasto por finanzas. Finanzas no es un sistema de monitoreo.

Por qué esto se rompe en producción

En sistemas con agentes, el coste compone.

1) Los tokens escalan con el contexto, no con la intención

Intención: “resume esto”. Implementación: “pega los últimos 40 mensajes + 6 outputs de tools + 2 runbooks”.

El coste escala con lo que le das al modelo, no con lo que pidió el usuario.

2) Los retries multiplican coste

Si falla una llamada al modelo y retry:

  • pagas dos veces
  • sube la latencia

Si falla un tool y retry:

  • pagas en coste del tool
  • y a menudo pagas más tokens explicando el fallo

Los retries no son gratis. En loops de agentes son multiplicativos.

3) “Planning” es overhead puro

Agentes con mucho planning queman tokens antes de hacer algo útil. Eso está bien cuando previene tool spam. No está bien cuando es solo “más pensamiento”.

4) El tool spam vuelve inútil cualquier budget

Si no capás tool calls, el agente puede gastar $0.01 en tokens y $5 en tools. Tu “token budget” no te protegió. Porque no era el budget que necesitabas.

5) Si no loggeas gasto, no lo ves

Si tus logs no incluyen:

  • tokens in/out
  • conteo de tool calls
  • estimación de coste por run
  • stop reason

…no puedes alertar por drift de gasto.

Ejemplo de implementación (código real)

Un tracker mínimo por run:

  • frena por tiempo, pasos, tool calls
  • estima coste (aprox) y frena por gasto
  • devuelve stop reason para alertar
PYTHON
from dataclasses import dataclass
import time


@dataclass(frozen=True)
class Budget:
  max_steps: int = 25
  max_seconds: int = 60
  max_tool_calls: int = 12
  max_usd: float = 1.00


@dataclass
class Usage:
  tool_calls: int = 0
  model_tokens_in: int = 0
  model_tokens_out: int = 0
  estimated_usd: float = 0.0


class BudgetExceeded(RuntimeError):
  pass


def estimate_usd(tokens_in: int, tokens_out: int) -> float:
  # Replace with real pricing for your model(s).
  # This is intentionally simple: budgets need to be approximate, not perfect.
  return (tokens_in + tokens_out) * 0.000002  # $/token (placeholder)


class BudgetGuard:
  def __init__(self, budget: Budget) -> None:
      self.budget = budget
      self.usage = Usage()
      self.started = time.time()
      self.steps = 0

  def on_step(self) -> None:
      self.steps += 1
      if self.steps > self.budget.max_steps:
          raise BudgetExceeded("step budget exceeded")
      if time.time() - self.started > self.budget.max_seconds:
          raise BudgetExceeded("time budget exceeded")
      if self.usage.tool_calls > self.budget.max_tool_calls:
          raise BudgetExceeded("tool budget exceeded")
      if self.usage.estimated_usd > self.budget.max_usd:
          raise BudgetExceeded("cost budget exceeded")

  def on_tool_call(self) -> None:
      self.usage.tool_calls += 1

  def on_model_call(self, *, tokens_in: int, tokens_out: int) -> None:
      self.usage.model_tokens_in += tokens_in
      self.usage.model_tokens_out += tokens_out
      self.usage.estimated_usd = estimate_usd(
          self.usage.model_tokens_in, self.usage.model_tokens_out
      )


def run(task: str, *, budget: Budget) -> str:
  guard = BudgetGuard(budget)

  while True:
      guard.on_step()

      # model call (pseudo)
      action, tokens_in, tokens_out = llm_decide(task)  # (pseudo)
      guard.on_model_call(tokens_in=tokens_in, tokens_out=tokens_out)

      if action.kind == "tool":
          guard.on_tool_call()
          result = call_tool(action.name, action.args)  # (pseudo)
          task = update_state(task, action, result)  # (pseudo)
      else:
          return action.final_answer
JAVASCRIPT
export class BudgetExceeded extends Error {}

export class BudgetGuard {
constructor(budget) {
  this.budget = budget;
  this.started = Date.now();
  this.steps = 0;
  this.usage = { toolCalls: 0, tokensIn: 0, tokensOut: 0, estimatedUsd: 0 };
}

estimateUsd(tokensIn, tokensOut) {
  // Replace with real pricing. Approximate is fine for guards.
  return (tokensIn + tokensOut) * 0.000002;
}

onStep() {
  this.steps += 1;
  const elapsedS = (Date.now() - this.started) / 1000;
  if (this.steps > this.budget.maxSteps) throw new BudgetExceeded("step budget exceeded");
  if (elapsedS > this.budget.maxSeconds) throw new BudgetExceeded("time budget exceeded");
  if (this.usage.toolCalls > this.budget.maxToolCalls) throw new BudgetExceeded("tool budget exceeded");
  if (this.usage.estimatedUsd > this.budget.maxUsd) throw new BudgetExceeded("cost budget exceeded");
}

onToolCall() {
  this.usage.toolCalls += 1;
}

onModelCall({ tokensIn, tokensOut }) {
  this.usage.tokensIn += tokensIn;
  this.usage.tokensOut += tokensOut;
  this.usage.estimatedUsd = this.estimateUsd(this.usage.tokensIn, this.usage.tokensOut);
}
}

El detalle clave: los budgets se chequean continuamente, no solo al final. Quieres parar antes del precipicio.

Incidente real (con números)

Teníamos un agente que iba bien en dev con ~3k tokens/request.

Luego añadimos “contexto útil”:

  • últimos 20 mensajes de usuario
  • outputs completos de tools (incluyendo HTML)
  • un snippet de runbook

El tamaño del prompt derivó. Nadie lo vio.

Impacto en 48 horas:

  • mediana tokens/request: 3k → 16k
  • p95 latencia: 2.4s → 8.9s
  • gasto: +$740 vs baseline

Fix:

  1. budgets duros (tokens, tool calls, tiempo, gasto)
  2. prompt builder con caps + summarization
  3. alertas en tokens/request y spend/run
  4. safe-mode cuando se llegue al budget

Esto no fue “el modelo empeoró”. Le dimos más texto y esperamos que la factura no se diera cuenta.

Trade-offs

  • Budgets tight aumentan respuestas “cortadas temprano”. Bien — mejor que gasto infinito.
  • La estimación de gasto es aproximada. No necesita ser perfecta para ser útil.
  • Los resúmenes ahorran tokens pero pueden perder matices. Úsalos donde sea seguro.

Cuándo NO usarlo

  • Si no puedes estimar coste (múltiples modelos/tools), empieza con budgets de tiempo/tools primero.
  • Si el workload es determinista, un workflow con coste fijo es mejor.
  • Si necesitas razonamiento de contexto largo, planea un budget mayor y hazlo explícito.

Checklist (copiar/pegar)

  • [ ] Budgets: steps, tool calls, segundos, USD
  • [ ] Track tokens in/out por run
  • [ ] Estima gasto por run y alerta por spikes
  • [ ] Capa retries (modelo + tool)
  • [ ] Capa tamaño de texto no confiable (HTML/tool dumps)
  • [ ] Resume o trunca contexto over-budget
  • [ ] Devuelve stop reason (no timeouts silenciosos)

Config segura por defecto (JSON/YAML)

YAML
budgets:
  max_steps: 25
  max_seconds: 60
  max_tool_calls: 12
  max_usd: 1.0
llm:
  retries: { max_attempts: 2 }
context:
  max_prompt_tokens: 2500
  summarize_when_over_budget: true

FAQ (3–5)

¿Necesito contabilidad exacta para budgets?
No. Los guards pueden ser aproximados. El objetivo es parar runs desbocados antes de que sean facturas.
¿Con qué budget empiezo?
Tiempo + tool calls. Luego añade token/spend cuando puedas medirlo.
¿Qué hago con requests que necesitan más budget?
Escala: pide confirmación, sube a un tier mayor o corre async con estado visible.
¿Puedo poner un budget enorme y olvidarme?
Puedes, pero vuelves a enterarte de fallos por finanzas y on-call.

Q: ¿Necesito contabilidad exacta para budgets?
A: No. Los guards pueden ser aproximados. El objetivo es parar runs desbocados antes de que sean facturas.

Q: ¿Con qué budget empiezo?
A: Tiempo + tool calls. Luego añade token/spend cuando puedas medirlo.

Q: ¿Qué hago con requests que necesitan más budget?
A: Escala: pide confirmación, sube a un tier mayor o corre async con estado visible.

Q: ¿Puedo poner un budget enorme y olvidarme?
A: Puedes, pero vuelves a enterarte de fallos por finanzas y on-call.

No sabes si este es tu caso?

Disena tu agente ->
⏱️ 7 min de lecturaActualizado Mar, 2026Dificultad: ★★☆
Implementar en OnceOnly
Guardrails for loops, retries, and spend escalation.
Usar en OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Integrado: control en producciónOnceOnly
Guardrails para agentes con tool-calling
Lleva este patrón a producción con gobernanza:
  • Presupuestos (pasos / topes de gasto)
  • Kill switch y parada por incidente
  • Audit logs y trazabilidad
  • Idempotencia y dedupe
  • Permisos de herramientas (allowlist / blocklist)
Mención integrada: OnceOnly es una capa de control para sistemas de agentes en producción.
Ejemplo de policy (concepto)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}
Autor

Esta documentación está curada y mantenida por ingenieros que despliegan agentes de IA en producción.

El contenido es asistido por IA, con responsabilidad editorial humana sobre la exactitud, la claridad y la relevancia en producción.

Los patrones y las recomendaciones se basan en post-mortems, modos de fallo e incidentes operativos en sistemas desplegados, incluido durante el desarrollo y la operación de infraestructura de gobernanza para agentes en OnceOnly.