多 agent 并行批次的协调提交(消除全部共享文件冲突面): - 迁移 0004:id_session + ob_event 两表(T3/T4 用,任务不再写迁移) - ORM:UserSession / ObEvent;错误码:九个新码(40xxx/60006,含 meta) - 配置:四档 tiers / refresh TTL / 任务奖励注册表 / 埋点上限 / COS 直传全套 - 骨架:packages/analytics + packages/storage + events/uploads 空路由(已注册) - deps:AnalyticsServiceDep / StorageServiceDep;DailyBudgetGuard 接收 tiers - uv add cos-python-sdk-v5;import-linter/CI/Makefile 纳入两个新包
packages/llm/ — LLM 网关
架构 §4.3、§8.1。V1 是进程内包,V2 拆成独立服务。 因此接口必须按远程调用来设计:无共享内存假设、明确超时、可序列化的入参出参。
唯一入口
所有 LLM 调用都走 LLMGateway.complete()。业务代码不许自己 httpx.post 到任何
模型供应商 —— 那样会绕过成本归因、预算闸门、故障转移和 Prompt 版本记录,而这四件事
一旦有缺口就补不回来(历史数据是补不出来的)。
已实现
| 能力 | 位置 |
|---|---|
场景化路由(scene → provider/model/温度/超时/预算,全配置化) |
routing.py |
| 故障转移 + 熔断(超时/5xx/限流切备用;连续失败熔断,冷却后半开探测) | gateway.py circuit.py |
| 结构化输出(Pydantic Schema 约束 + 校验失败重试) | gateway.py |
| Prompt 版本(文件化,按 id+version 加载,版本随每次调用落库) | prompts.py /prompts |
| 缓存前缀纪律(固定前缀含时间戳/随机 ID/昵称 → 拒绝或告警) | prompts.py |
成本归因(每次 provider 调用写一行 ob_ai_call,含缓存命中 token) |
cost.py recording.py |
| 全局日成本硬熔断(超预算即拒绝非关键场景,不是只告警) | budget.py |
未实现(按 §8.1 还差)
- 流式输出 SSE(FR-AGT-01 要求首字 p95 < 2s,非流式做不到)
- 审核联动:输入输出审核应在网关内统一完成,业务方无需关心(CR-06)
- 用户级频控:目前只有平台级日预算与业务侧配额
- A/B 分流:按用户哈希选择 prompt 版本
- 智谱 GLM provider:仅结构占位,见
providers/zhipu.py
加一个新场景
- 在
types.Scene加枚举值。 - 在
routing._DEFAULTS加默认配置,想清楚critical该不该为真 ——critical=True意味着日预算耗尽时它仍然会花钱。 - 在
prompts/<scene>/v1.txt写模板。[system]段不许有任何变量。 - 跑
pytest tests/unit/test_prompt_cache_prefix.py,它会扫描仓库里全部 prompt 的固定前缀。
加一个新 provider
- 实现
providers/base.LLMProvider协议(name/default_model/complete/aclose)。 - HTTP 错误必须映射到
errors.py的异常族,retryable决定要不要切备用。 映射错了会导致「明明该切备用却没切」或者「明明是我们请求写错了却把备用也打一遍」。 - usage 字段必须映射出 缓存命中 token 数,否则 NFR-COST-03 的缓存命中率 看板是空的。
- 在
apps/api/deps.build_llm_gateway()注册。
费率
RateTable 来自配置(LLM_COST_RATES_JSON),不是常量。未知模型走 default 费率
而不是 0 —— 成本上的盲区比不精确更危险。上线前必须用供应商实际价目表校准。