Budget & límites
Ongrid refuerza un tope global por UTC-día de tokens a través de cada provider. El default es ilimitado; una env var lo enciende:
ONGRID_LLM_DAILY_TOKEN_LIMIT=2000000 # 2 million tokens per UTC day<=0 deshabilita el tope. Un único valor, no por-provider — este es el scope MVP. Cuando aterricen los tenants se mueve a settings por-org; esta perilla se queda como un tope global de safety-net.
Cómo está cableado
Tres piezas, en internal/pkg/llm/:
// 1. The interface
type BudgetChecker interface {
Check(ctx context.Context, userID uint64, estPromptTokens int) error
Record(ctx context.Context, userID uint64, usage Usage) error
}
// 2. The MVP implementation
budget := llm.NewInMemoryBudget(cfg.LLM.DailyTokenLimit)
// 3. The eino callback that bridges to the graph kernel
handler := llm.NewBudgetCallbackHandler(budget, userID)El runtime del graph-kernel instala el callback handler en su cadena de callbacks de eino. En cada OnStart de ChatModel:
- Estima tokens de prompt:
len(text)/4(conservador). BudgetChecker.Check(ctx, userID, estPromptTokens).- Ante rechazo — almacena
ErrBudgetExceededen el contexto para que el nodo downstream pueda cortocircuitar; el código subsiguiente lo emerge.
En OnEnd, el Usage.TotalTokens real se graba contra el bucket de UTC-día actual.
ErrBudgetExceeded
// internal/pkg/llm/budget.go:37
func (b *InMemoryBudget) Check(ctx context.Context, userID uint64, estPromptTokens int) error {
if b.dailyLimit <= 0 {
return nil
}
b.mu.Lock()
defer b.mu.Unlock()
key := b.dayKey()
if b.used[key]+estPromptTokens > b.dailyLimit {
return ErrBudgetExceeded
}
return nil
}El error se propaga a:
- El endpoint de chat send — devuelve HTTP 429 con un body
{ "error": "budget_exceeded", "message": "..." }que la UI de chat renderiza in-line. - El worker investigator RCA — la fila de informe aterriza como
status=failedconstatus_reason="budget_exceeded". - La ruta translate — cae a "translation unavailable (budget exceeded)" y se muestra el texto original.
Caveats de InMemoryBudget
La implementación MVP es in-memory:
type InMemoryBudget struct {
mu sync.Mutex
dailyLimit int // tokens per UTC day; <=0 means unlimited
used map[string]int // key = "YYYY-MM-DD" (UTC)
now func() time.Time
}Consecuencias:
- Sin persistencia — un reinicio del manager resetea el contador del día. Si realmente quieres un tope diario duro que sobreviva a reinicios, swap la implementación. La interfaz
BudgetCheckeres el seam. - Single-process — si corres múltiples managers detrás de un load balancer (no deberías aún, pero si), cada uno tiene su propio contador.
- Global, no por-usuario —
userIDfluye a través de la interfaz para que una futura tabla MySQLusage_dailysea drop-in, pero hoy el tope es el mismo número para todos.
El pivot a single-tenant aplazó el backend por-usuario; la interfaz es forward-compatible para cuando vuelva multi-usuario.
Estimación de tokens
BudgetCallbackHandler.OnStart estima tokens de prompt por conteo de caracteres / 4. Esto es intencionalmente conservador — la tokenización real varía por provider / modelo, y el budget está supuesto a errar del lado de rechazar llamadas borderline en vez de pasarse.
En OnEnd, el Usage.TotalTokens real devuelto por el provider se graba — así que el budget trackea ground truth incluso cuando la estimación fue off.
Si el provider no devuelve conteos de token (algunos endpoints custom no lo hacen), el callback cae a una heurística de response- meta; ver OnEndUsesResponseMetaFallback en los tests.
Observando el budget
curl -s localhost:9100/metrics | grep llm_budget
# llm_budget_daily_limit_tokens 2000000
# llm_budget_used_tokens_today 412847
# llm_budget_rejections_total 3Las métricas las cablea BudgetCallbackHandler.Stats(). El dashboard de self-obs Prom las renderiza como un gráfico de gasto diario más una alerta al 80% del tope.
Deshabilitando para un workload
No hay perilla "deshabilitar budget para el investigator". Si RCA está pegando el tope y prefieres que siga corriendo en lugar del chat, sube el tope — para eso está. La alternativa (cuotas por-workload) está parqueada junto con multi-tenancy.
Ver también
- Overview de modelos.
- Routing — ortogonal al budget; el mismo tope aplica por-llamada sin importar qué provider se eligió.
- Variables de entorno — perillas
ONGRID_LLM_*.