Skip to content

预算与限额

Ongrid 强制一个全局按 UTC 日的 token 上限,跨所有 provider。默认无限; 一个 env 变量打开它:

bash
ONGRID_LLM_DAILY_TOKEN_LIMIT=2000000   # 2 million tokens per UTC day

<=0 禁用上限。一个值,不是按 provider —— 这是 MVP 范围。租户落地时它移 到按组织设置;这个旋钮留作兜底全局上限。

怎么接的

三块,在 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)

graph-kernel 运行时把 callback handler 装到 eino callback 链里。每次 ChatModel OnStart

  1. 估 prompt token:len(text)/4(保守)。
  2. BudgetChecker.Check(ctx, userID, estPromptTokens)
  3. 拒绝时 —— 在 context 里存 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
}

错误传播到:

  • chat 发送 endpoint —— 返回 HTTP 429,body 是 { "error": "budget_exceeded", "message": "..." },chat UI 内联渲染。
  • RCA investigator worker —— 报告行落成 status=failedstatus_reason="budget_exceeded"
  • 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 接口是缝。
  • 单进程 —— 如果你在多个 manager 之间挂负载均衡器(你还不该这么干, 但如果),每个有自己的计数。
  • 全局,不分用户 —— userID 流过接口,所以未来一张 MySQL usage_daily 表是直接换零件,但今天上限对所有人都是同一个数。

转单租户时延后了按用户后端;接口对多用户回来时前向兼容。

token 估计

BudgetCallbackHandler.OnStart 按字符数 / 4 估 prompt token。这故意保守 —— 真实分词按 provider / model 变,预算应当宁可拒边界调用也别超。

OnEnd 时,provider 返回的实际 Usage.TotalTokens 被记下来 —— 所以即使 估算偏了,预算追踪的还是 ground truth。

如果 provider 不返 token 计数(有些自定义端点不返),callback 回退到一个 response-meta 启发式;见测试里的 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() 接。self-obs Prom dashboard 把它们 渲染成日度花销图加一条上限 80% 的告警。

给某个工作负载关闭

没有"给 investigator 关掉预算"的旋钮。RCA 撞上限了又宁愿它继续跑而不是 chat 的话,把上限抬上去 —— 它就是干这个的。备选方案(按工作负载配额)和 多租户一起暂缓了。

另见