Cost Limits para agentes IA (detener el gasto) + Código

Limitar tokens no alcanza. Cómo poner un cap duro de costo total (LLM + tools), con stop reasons claros para evitar sorpresas en producción.
En esta página
  1. El problema (en producción)
  2. Por qué esto se rompe en producción
  3. 1) Se limitan tokens y se olvidan los tools
  4. 2) Costo sin stop reason no es operable
  5. 3) Sin caps por tenant, un cliente puede romper tu mes
  6. Ejemplo de implementación (código real)
  7. Incidente real (con números)
  8. Trade-offs
  9. Cuándo NO usarlo
  10. Checklist (copiar/pegar)
  11. Config segura por defecto (JSON/YAML)
  12. FAQ (3–5)
  13. Páginas relacionadas (3–6 links)
Flujo interactivo
Escenario:
Paso 1/3: Execution

Action is proposed as structured data (tool + args).

El problema (en producción)

Miras el uso del LLM y piensas: “todo bien”.

Luego llega la factura del vendor de browser/scraping/datos. Y te das cuenta: pusiste límite de velocidad… en una sola rueda.

Los cost limits no son glamorosos. Son la diferencia entre:

  • “a veces sale caro”
  • y “esto es un generador de costo sin límites”

Por qué esto se rompe en producción

1) Se limitan tokens y se olvidan los tools

En agentes reales:

  • tokens son más previsibles
  • tools son lo salvaje (retries, rate limits, trabajo variable)

Si un tool cuesta $0.20/call, 10 calls = $2. Eso pasa más rápido de lo que piensas.

2) Costo sin stop reason no es operable

Si solo ves “timeout”, nadie entiende que fue “demasiado caro”. El usuario reintenta y empeora.

3) Sin caps por tenant, un cliente puede romper tu mes

Multi-tenant = un solo tenant puede destruir el gráfico si no cappeas.

Ejemplo de implementación (código real)

Un cost guard simple:

  • tokens + costo tools en un state
  • check después de cada step / tool call
  • stop reason max_usd
PYTHON
from dataclasses import dataclass, field
import time


TOOL_USD = {
  "browser.run": 0.20,
  "search.read": 0.00,
}


@dataclass(frozen=True)
class CostPolicy:
  max_usd: float = 1.00
  max_seconds: int = 60


@dataclass
class CostState:
  started_at: float = field(default_factory=time.time)
  tokens_in: int = 0
  tokens_out: int = 0
  tool_usd: float = 0.0

  def elapsed_s(self) -> float:
      return time.time() - self.started_at


def estimate_model_usd(tokens_in: int, tokens_out: int) -> float:
  return (tokens_in + tokens_out) * 0.000002


class CostExceeded(RuntimeError):
  def __init__(self, stop_reason: str, *, state: CostState):
      super().__init__(stop_reason)
      self.stop_reason = stop_reason
      self.state = state


class CostGuard:
  def __init__(self, policy: CostPolicy):
      self.policy = policy
      self.state = CostState()

  def total_usd(self) -> float:
      return estimate_model_usd(self.state.tokens_in, self.state.tokens_out) + self.state.tool_usd

  def check(self) -> None:
      if self.total_usd() > self.policy.max_usd:
          raise CostExceeded("max_usd", state=self.state)
      if self.state.elapsed_s() > self.policy.max_seconds:
          raise CostExceeded("max_seconds", state=self.state)

  def on_model(self, *, tokens_in: int, tokens_out: int) -> None:
      self.state.tokens_in += tokens_in
      self.state.tokens_out += tokens_out
      self.check()

  def on_tool(self, *, tool: str) -> None:
      self.state.tool_usd += float(TOOL_USD.get(tool, 0.0))
      self.check()
JAVASCRIPT
const TOOL_USD = { "browser.run": 0.2, "search.read": 0.0 };

export class CostExceeded extends Error {
constructor(stopReason, { state }) {
  super(stopReason);
  this.stopReason = stopReason;
  this.state = state;
}
}

export class CostGuard {
constructor(policy) {
  this.policy = policy;
  this.state = { startedAtMs: Date.now(), tokensIn: 0, tokensOut: 0, toolUsd: 0 };
}

totalUsd() {
  return estimateModelUsd(this.state.tokensIn, this.state.tokensOut) + this.state.toolUsd;
}

check() {
  if (this.totalUsd() > this.policy.maxUsd) throw new CostExceeded("max_usd", { state: this.state });
  if ((Date.now() - this.state.startedAtMs) / 1000 > this.policy.maxSeconds) {
    throw new CostExceeded("max_seconds", { state: this.state });
  }
}

onModel({ tokensIn, tokensOut }) {
  this.state.tokensIn += tokensIn;
  this.state.tokensOut += tokensOut;
  this.check();
}

onTool({ tool }) {
  this.state.toolUsd += Number(TOOL_USD[tool] ?? 0);
  this.check();
}
}

function estimateModelUsd(tokensIn, tokensOut) {
return (tokensIn + tokensOut) * 0.000002;
}

Incidente real (con números)

Vimos una loop de investigación que usaba tools de browser.

El vendor estaba inestable ese día. Los tools retryan. El agente retrya. Y un request cuesta más que tu promedio diario.

Impacto:

  • p95 gasto/request subió a $4.80
  • la queue se llenó (runs más largos)
  • soporte pasó ~2h con tickets “está lento”

Fix:

  1. cap duro max_usd + stop reason claro
  2. circuit breaker a nivel tool cuando el vendor está flaky
  3. caching/dedupe para URLs/queries repetidas

Trade-offs

  • Los caps cortan runs útiles a veces.
  • El costo exacto por tool call es difícil; el aproximado sirve como guardrail.
  • Cost limits sin budgets (steps/time/tool calls) es incompleto.

Cuándo NO usarlo

  • Si no puedes estimar costo, pon al menos max_seconds y max_tool_calls.
  • Aunque tengas tools internos “gratis”, cappea cuando haya vendors que cobren.

Checklist (copiar/pegar)

  • [ ] cap max_usd por run
  • [ ] stop reason max_usd en logs + respuesta
  • [ ] costos tools aproximados (conservador)
  • [ ] circuit breaker para vendor flaky
  • [ ] caps por tenant / tiers
  • [ ] alertas por spikes de stops max_usd

Config segura por defecto (JSON/YAML)

YAML
cost_limits:
  max_usd: 1.00
  max_seconds: 60
tool_costs_usd:
  browser.run: 0.20
  search.read: 0.00
stop_reasons:
  log: true
  surface_to_user: true

FAQ (3–5)

¿Alcanza con cappear solo tools?
No. Los tokens también pueden explotar (context largo, retries). Trackea ambos.
¿Cómo hago caps por tenant?
Con tiers. Y loggea spend por tenant, o lo ves recién a fin de mes.
¿Qué cap por defecto recomiendas?
Lo suficientemente baja para evitar sorpresas y suficientemente alta para runs normales. Empieza en $1 y ajusta con datos.
¿Por qué no elegir el modelo más barato?
Porque muchas veces el modelo no es lo más caro. Tool calls + retries suelen ser el driver real.

P: ¿Alcanza con cappear solo tools?
R: No. Los tokens también pueden explotar (context largo, retries). Trackea ambos.

P: ¿Cómo hago caps por tenant?
R: Con tiers. Y loggea spend por tenant, o lo ves recién a fin de mes.

P: ¿Qué cap por defecto recomiendas?
R: Lo suficientemente baja para evitar sorpresas y suficientemente alta para runs normales. Empieza en $1 y ajusta con datos.

P: ¿Por qué no elegir el modelo más barato?
R: Porque muchas veces el modelo no es lo más caro. Tool calls + retries suelen ser el driver real.

No sabes si este es tu caso?

Disena tu agente ->
⏱️ 5 min de lecturaActualizado Mar, 2026Dificultad: ★★★
Implementar en OnceOnly
Budgets + permissions you can enforce at the boundary.
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
writes:
  require_approval: true
  idempotency: true
controls:
  kill_switch: { enabled: 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)
  • Permisos de herramientas (allowlist / blocklist)
  • Kill switch y parada por incidente
  • Idempotencia y dedupe
  • Audit logs y trazabilidad
Mención integrada: OnceOnly es una capa de control para sistemas de agentes en producción.
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.