Skip to content

Budget & Limits

Ongrid erzwingt einen globalen Per-UTC-Tag-Token-Cap über jeden Provider. Der Default ist unbegrenzt; eine Umgebungsvariable schaltet ihn ein:

bash
ONGRID_LLM_DAILY_TOKEN_LIMIT=2000000   # 2 million tokens per UTC day

<=0 deaktiviert den Cap. Einzelner Wert, nicht pro Provider — das ist der MVP-Scope. Wenn Tenants landen, wandert er zu Per-Org-Settings; dieser Knopf bleibt als Safety-Net-globaler Cap.

Wie er verdrahtet ist

Drei Teile, in internal/pkg/llm/:

go
// 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)

Die Graph-Kernel-Runtime installiert den Callback-Handler in ihre eino-Callback-Chain. Bei jedem ChatModel OnStart:

  1. Prompt-Tokens schätzen: len(text)/4 (konservativ).
  2. BudgetChecker.Check(ctx, userID, estPromptTokens).
  3. Bei Ablehnung — speichere ErrBudgetExceeded im Kontext, sodass der Downstream-Knoten kurzschließen kann; nachfolgender Code zeigt es an.

Bei OnEnd wird das tatsächliche Usage.TotalTokens gegen den aktuellen UTC-Tag-Bucket erfasst.

ErrBudgetExceeded

go
// 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
}

Der Fehler propagiert zu:

  • Dem Chat-Send-Endpunkt — gibt HTTP 429 mit einem { "error": "budget_exceeded", "message": "..." }-Body zurück, den die Chat-UI in-line rendert.
  • Dem RCA-Investigator-Worker — die Report-Zeile landet als status=failed mit status_reason="budget_exceeded".
  • Dem Translate-Pfad — fällt auf „Übersetzung nicht verfügbar (Budget überschritten)" zurück und der Originaltext wird gezeigt.

InMemoryBudget-Vorbehalte

Die MVP-Implementierung ist In-Memory:

go
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
}

Konsequenzen:

  • Keine Persistenz — ein Manager-Restart setzt den Tageszähler zurück. Wenn Sie tatsächlich einen harten Tages-Cap wollen, der Neustarts überlebt, tauschen Sie die Implementierung. Das BudgetChecker-Interface ist die Naht.
  • Single-Process — wenn Sie mehrere Manager hinter einem Loadbalancer betreiben (sollten Sie noch nicht, aber falls), hat jeder seinen eigenen Zähler.
  • Global, nicht pro BenutzeruserID fließt durch das Interface, sodass eine zukünftige MySQL-usage_daily-Tabelle ein Drop-in ist, aber heute ist der Cap dieselbe Zahl für jeden.

Der Pivot zu Single-Tenant verschob das Per-User-Backend; das Interface ist forward-kompatibel, wenn Multi-User zurückkommt.

Token-Schätzung

BudgetCallbackHandler.OnStart schätzt Prompt-Tokens durch Zeichenzahl / 4. Das ist absichtlich konservativ — echte Tokenisierung variiert per Provider / Modell, und das Budget soll lieber auf die Seite ablehnender Grenzaufrufe als drüberzugehen.

Bei OnEnd wird das tatsächliche vom Provider zurückgegebene Usage.TotalTokens erfasst — sodass das Budget die Ground Truth trackt, auch wenn die Schätzung daneben lag.

Wenn der Provider keine Token-Zählung zurückgibt (manche Custom-Endpunkte tun das nicht), fällt der Callback auf eine Response-Meta-Heuristik zurück; siehe OnEndUsesResponseMetaFallback in den Tests.

Das Budget beobachten

bash
curl -s localhost:9100/metrics | grep llm_budget
# llm_budget_daily_limit_tokens 2000000
# llm_budget_used_tokens_today 412847
# llm_budget_rejections_total 3

Die Metriken sind durch BudgetCallbackHandler.Stats() verdrahtet. Das Self-Obs-Prom-Dashboard rendert sie als Tages-Ausgaben-Graph plus einen Alarm bei 80% des Caps.

Deaktivierung für eine Workload

Es gibt keinen „Budget für den Investigator deaktivieren"-Knopf. Wenn RCA den Cap trifft und Sie lieber wollten, dass es weiterläuft als Chat, erhöhen Sie den Cap — dafür ist er da. Die Alternative (Per-Workload-Kontingente) ist zusammen mit Multi-Tenancy geparkt.

Siehe auch