Skip to content

バジェットと制限

Ongrid はすべてのプロバイダーをまたいで UTC 日ごとのグローバル トークン キャップを強制します。デフォルトは無制限。1 つの環境変数でオンになります:

bash
ONGRID_LLM_DAILY_TOKEN_LIMIT=2000000   # 2 million tokens per UTC day

<=0 でキャップ無効。単一値で、プロバイダーごとではありません —— これが MVP スコープです。テナント機能が乗ったときに組織ごと設定へ移行します。 このつまみは安全網としてのグローバルキャップとして残ります。

配線方法

3 つのピースが 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)

グラフカーネルランタイムはコールバックハンドラーを eino コールバック チェーンに設置します。ChatModel の OnStart ごとに:

  1. プロンプトトークンを見積もる:len(text)/4(保守的)。
  2. BudgetChecker.Check(ctx, userID, estPromptTokens)
  3. 拒否時 —— 下流ノードが短絡できるよう ctx に ErrBudgetExceeded を 保存。後続コードがこれを表面化。

OnEnd で実際の Usage.TotalTokens が現在の UTC 日バケットに記録されます。

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
}

エラーは以下に伝播します:

  • チャット送信エンドポイント —— チャット UI がインライン描画する { "error": "budget_exceeded", "message": "..." } ボディと共に HTTP 429 を返す。
  • RCA investigator worker —— レポート行が status_reason="budget_exceeded" 付きで status=failed で着地。
  • translate パス —— 「translation unavailable (budget exceeded)」に フォールバックし、元のテキストが表示される。

InMemoryBudget の注意点

MVP 実装はインメモリです:

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
}

帰結:

  • 永続性なし —— manager 再起動でその日のカウンターがリセットされ ます。再起動を生き延びるハード日次キャップが本当に必要なら実装を 入れ替えてください。BudgetChecker インターフェイスが seam です。
  • シングルプロセス —— ロードバランサー後ろで複数 manager を走らせる と(まだそうすべきではないですが、もしも)各々が独自のカウンターを 持ちます。
  • グローバル、ユーザーごとではない —— userID はインターフェイスを 流れるので、将来の MySQL usage_daily テーブルはドロップインで入り ますが、今日のキャップは全員に同じ数字です。

シングルテナントへのピボットがユーザーごとバックエンドを延期しました。 インターフェイスは多人数が戻ってきたときの前方互換性があります。

トークン見積もり

BudgetCallbackHandler.OnStart は文字数 / 4 でプロンプトトークンを 見積もります。これは意図的に保守的 —— 実際のトークン化はプロバイダー / モデルで異なり、バジェットは超過するより境界線の呼び出しを拒否する側に 誤る前提です。

OnEnd ではプロバイダーが返した実際の Usage.TotalTokens が記録されます —— なので見積もりがズレてもバジェットは ground truth を追跡します。

プロバイダーがトークン数を返さないとき(一部のカスタムエンドポイントは 返さない)、コールバックはレスポンスメタヒューリスティクスにフォール バックします。テスト内の OnEndUsesResponseMetaFallback を参照。

バジェットの観測

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

メトリクスは BudgetCallbackHandler.Stats() が配線します。セルフ obs Prom ダッシュボードがこれらを日次支出グラフ + キャップの 80% でのアラート として描画します。

1 ワークロードのみ無効化

「investigator のバジェットを無効化」つまみはありません。RCA がキャップに ヒットしていてチャットより RCA を生かしておきたいなら、キャップを上げて ください —— それがそこにある理由です。代替案(ワークロードごとクォータ) はマルチテナンシーと一緒に保留中です。

関連