feat(harness): 成本/Token 预算护栏 —— 单任务硬上限 + 单用户日预算(恒温器最后一环)
token 用量估算计量(CJK≈1/字、ASCII≈1/4字,无需分词器,护栏够用)。 单任务硬上限(dispatcher):Budget 挂 ctx 沿图透传,各 LLM 节点(对话/ReAct/compose/ 报告)入口计输入 token、出口计输出,触顶即中止整图——防失控成本(死循环/超大报告)。 报告路径优雅降级:触顶跳过剩余章节出部分稿,不整体失败。预算来源 Meta.token_budget 或 env TASK_TOKEN_BUDGET(默认 20 万)。 单用户日预算(gateway):dispatcher 收尾经 NATS 回写 UsageEvent → 网关按用户按天累计 Redis(48h 过期自滚动)→ 提交前门控 USER_DAILY_TOKEN_BUDGET(0=不限,超额 402)。 /billing 升级为真实用量:当日已用 / 日预算 / 余额。 契约 UsageEvent + MetaTokenBudget + SubjectUsage;bus Publish/SubscribeUsage; orchestrator SetUsageSink + 预算触顶 failed(不计熔断)。harness budget 5 单测,三模块全绿。 live:单任务 budget=30 → failed(已用约689);用户日 budget=200 → /billing remaining=0 → 402。 至此 harness 由「测温计」完成向「恒温器」的演进(评测闭环/纠偏/忠实度/脱敏/输入护栏/预算六项)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,41 @@ func (r *Redis) Allow(ctx context.Context, key string, limit int64, window time.
|
||||
|
||||
// ---- Token 流持久化(Redis Stream:可回放的追加日志,根治 SSE 连晚/重连丢 token)----
|
||||
|
||||
// ---- Token 用量日预算(成本护栏:按用户按天累计 token,供提交前门控)----
|
||||
|
||||
// usageDailyKey 是某用户某天的累计 token key(day 形如 20260626,便于到期自然滚动)。
|
||||
func usageDailyKey(userID, day string) string { return "sundynix:usage:" + userID + ":" + day }
|
||||
|
||||
// AddUsage 给某用户当天累计 token,并返回累计后总量(首次设 48h 过期自动清理)。降级返回 (0,nil)。
|
||||
func (r *Redis) AddUsage(ctx context.Context, userID, day string, tokens int) (int64, error) {
|
||||
if r.rdb == nil || userID == "" {
|
||||
return 0, nil
|
||||
}
|
||||
rk := usageDailyKey(userID, day)
|
||||
n, err := r.rdb.IncrBy(ctx, rk, int64(tokens)).Result()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n == int64(tokens) { // 首次累计:设过期
|
||||
_ = r.rdb.Expire(ctx, rk, 48*time.Hour).Err()
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// GetUsage 取某用户当天已用 token(不存在/降级返回 0)。
|
||||
func (r *Redis) GetUsage(ctx context.Context, userID, day string) int64 {
|
||||
if r.rdb == nil || userID == "" {
|
||||
return 0
|
||||
}
|
||||
n, err := r.rdb.Get(ctx, usageDailyKey(userID, day)).Int64()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ---- Token 流持久化(Redis Stream:可回放的追加日志,根治 SSE 连晚/重连丢 token)----
|
||||
|
||||
const streamTTL = 10 * time.Minute
|
||||
|
||||
func streamKey(taskID string) string { return "sundynix:stream:" + taskID }
|
||||
|
||||
Reference in New Issue
Block a user