Files
meida_backend/packages/llm/README.md
T
xsl 54dc741811 feat(llm): provider-abstract gateway with cost and failure controls
DeepSeek (OpenAI-compatible) as primary with a 智谱 GLM slot that is a real
routing path but raises until the 算法备案 model list is confirmed. Adds scene
routing, failover, a per-provider circuit breaker, structured output with
retry-on-validation-failure, prompts loaded by id+version from prompts/, and
cost attribution written to ob_ai_call for every attempt -- successes,
retries and failures alike.

Two controls exist because they are cheap now and expensive later: a
cache-prefix validator that rejects timestamps, uuids and nicknames in the
cached system prefix (a volatile prefix silently multiplies input cost), and
a hard platform-wide daily budget that rejects non-critical scenes instead of
merely alerting, since a retry storm outruns any human on-call.
2026-08-14 00:26:39 +08:00

55 lines
2.9 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `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`
## 加一个新场景
1. 在 `types.Scene` 加枚举值。
2. 在 `routing._DEFAULTS` 加默认配置,想清楚 `critical` 该不该为真
—— `critical=True` 意味着日预算耗尽时它仍然会花钱。
3. 在 `prompts/<scene>/v1.txt` 写模板。**`[system]` 段不许有任何变量**。
4. 跑 `pytest tests/unit/test_prompt_cache_prefix.py`,它会扫描仓库里全部
prompt 的固定前缀。
## 加一个新 provider
1. 实现 `providers/base.LLMProvider` 协议(`name` / `default_model` /
`complete` / `aclose`)。
2. HTTP 错误必须映射到 `errors.py` 的异常族,`retryable` 决定要不要切备用。
映射错了会导致「明明该切备用却没切」或者「明明是我们请求写错了却把备用也打一遍」。
3. usage 字段必须映射出 **缓存命中 token 数**,否则 NFR-COST-03 的缓存命中率
看板是空的。
4. 在 `apps/api/deps.build_llm_gateway()` 注册。
## 费率
`RateTable` 来自配置(`LLM_COST_RATES_JSON`),不是常量。未知模型走 default 费率
而不是 0 —— 成本上的盲区比不精确更危险。**上线前必须用供应商实际价目表校准。**