diff --git a/Home.md b/Home.md index 76a6c29..7d49f57 100644 --- a/Home.md +++ b/Home.md @@ -2,7 +2,7 @@ 实验室统一的大语言模型调度与中转库:LLM / VLM / OCR / Embedding 共用同一套生产级治理栈(多源多账号、限流、错误分类重试、熔断、响应缓存、流式看门狗、遥测与成本)。治理单位是**一次模型调用**;任务编排与业务解析留在业务侧。 -当前版本 **v1.0.3**,已通过 GovDoc-SaaS 与 CHSAnalyzer 两个真实项目的全量迁移验收。 +当前版本 **v1.1.0**,已通过 GovDoc-SaaS 与 CHSAnalyzer 两个真实项目的全量迁移验收。 ## 文档地图(按你此刻要干什么选入口) diff --git a/参考-公共API.md b/参考-公共API.md index 35b1538..a5b2637 100644 --- a/参考-公共API.md +++ b/参考-公共API.md @@ -20,11 +20,13 @@ | prompt_tokens / completion_tokens | int | 用量 | | latency_ms | int | 总耗时 | | ttft_ms / max_inter_token_ms | float\|None | 流式时延指标 | -| cache_hit | bool | 是否缓存命中 | +| cache_hit | bool | **PolyGateway 自身响应缓存**是否命中(未产生网关调用);与供应商 prompt cache 无关,后者见 cached_prompt_tokens | | call_id | str | 遥测主键 | | cost | float\|None | 按价格表折算(缺表为 None) | | usage_source | str | measured / estimated / unavailable(v1.0.3 起三态,口径见 [[指南-遥测与成本]]) | | structured_data | Any\|None | `structured=` 时的校验结果 | +| cached_prompt_tokens | int\|None | 供应商 prompt cache 命中的输入 token 数(v1.1.0 新增)。`None` = 该源未上报;`0` = 上报了真实零命中——两者不可混同,口径见 [[指南-遥测与成本]] | +| model_reported | str\|None | API 响应体实际返回的 model(v1.1.0 新增);`None` = 未上报。与 `model`(配置别名)可能分叉,实验复现应认这个串 | ## EmbeddingClient diff --git a/参考-配置键.md b/参考-配置键.md index 9282a1b..5a6bb2a 100644 --- a/参考-配置键.md +++ b/参考-配置键.md @@ -37,7 +37,7 @@ | `PGW_CACHE_BACKEND` | none / memory / redis | 必填;redis 需 NAMESPACE + TTL_S + REDIS_URL | | `PGW_CACHE_NAMESPACE` / `PGW_CACHE_TTL_S` | | 缓存启用时必填;TTL 必须 > 0 | | `PGW_TELEMETRY_BACKEND` | none / sqlite / postgres | 必填;sqlite 需 `PGW_TELEMETRY_SQLITE_PATH`,postgres 需 `PGW_TELEMETRY_PG_DSN` | -| `PGW_PRICING_PATH` | 路径 | 可选,价格表 JSON;缺省 cost 恒 None | +| `PGW_PRICING_PATH` | 路径 | 可选,价格表 JSON;缺省 cost 恒 None。条目支持可选第三档 `cached_input_per_1m`(v1.1.0,供应商 prompt cache 命中部分的单价;不填则命中部分也按 input 全额计) | | `PGW_STRUCTURED_MAX_RETRIES` | int | 结构化重问上限,缺省 2 | | `PGW_LEASE_TTL_S` | float | permit 租约,缺省 1500;须 ≥ 最大源 timeout | | `REDIS_URL` | dsn | redis 后端共用 | diff --git a/指南-遥测与成本.md b/指南-遥测与成本.md index 7635145..742112f 100644 --- a/指南-遥测与成本.md +++ b/指南-遥测与成本.md @@ -12,7 +12,7 @@ PGW_TELEMETRY_SQLITE_PATH=logs/telemetry.db # sqlite 时必填 实验室纪律:PG DSN 只许指向专用库 `polygateway`,严禁在用业务库。 -## 表结构(`llm_calls`,18 字段冻结) +## 表结构(`llm_calls`,20 字段冻结) | 字段组 | 字段 | |---|---| @@ -22,6 +22,9 @@ PGW_TELEMETRY_SQLITE_PATH=logs/telemetry.db # sqlite 时必填 | 用量 | prompt_tokens / completion_tokens / usage_source(三态,见下) | | 时延 | latency_ms / ttft_ms / max_inter_token_ms | | 结果 | cache_hit / error(异常类名前缀,如 `TransientError: ...`)/ cost | +| 可观测(v1.1.0) | cached_prompt_tokens / model_reported(见下) | + +v1.1.0 新增的两列会**自动补到已存在的旧表上**(SQLite 与 Postgres 都在初始化期幂等 ALTER),无需手工迁移;历史行的新列为 NULL。 `session_id`/`parent_call_id` 由调用方传入(`client.chat(..., session_id=...)`),用于把一次业务任务下的多次调用串成链。 @@ -42,11 +45,13 @@ PGW_PRICING_PATH=config/prices.json ``` ```json -{"MiniMax-M3": {"input_per_1m": 2.1, "output_per_1m": 8.4}} +{"MiniMax-M3": {"input_per_1m": 2.1, "output_per_1m": 8.4, "cached_input_per_1m": 0.42}} ``` 配了价格表后每行遥测带 `cost`(元);缓存命中 token=0 天然零成本。缺价格表时 cost 恒 None,不报错。 +`cached_input_per_1m` 是**可选**的第三档(v1.1.0):供应商 prompt cache 命中的那部分输入按更低单价计费。配了它,cost 就按 `(prompt - cached) × input + cached × cached_input` 分段算;**不配就退化为全额输入价**——库不会替你猜一个折扣率,所以不配时 cost 会比实际账单偏高。命中数若超过输入总数(网关口径异常),按总数夹取并记一条 warning,不会算出负数。 + **cost 的口径**:产生了真实调用、但用量不可得的行 `cost` 为 NULL——库不会编一个数字,免得"免费"与"未知"在数据上混为一谈。缓存命中行**不在此列**:它没产生新调用,`0.0` 是事实,所以即便 `usage_source='unavailable'`,cost 仍是 `0.0`。 因此 `SUM(cost)` 天然跳过不可得的行,而账目缺口要这样量化: @@ -61,3 +66,27 @@ WHERE usage_source = 'unavailable' AND cache_hit = false; ## 降级方向 遥测后端不可用 → warning 后静默丢弃该行,**绝不影响业务调用**。共享后端注意:不要在真实批跑期间并发跑库的集成测试(时序隔离,详见主仓库 CLAUDE.md)。 + +## 供应商 prompt cache(v1.1.0) + +`cached_prompt_tokens` 是**供应商服务器**复用了你的提示词前缀、按更低单价计费的那部分输入 token 数。它和 `cache_hit` 是两件事: + +| | `cache_hit` | `cached_prompt_tokens` | +|---|---|---| +| 指的是 | **PolyGateway 自己的** Redis 响应缓存 | **供应商侧**的 prompt cache | +| 有没有联网 | 没有,直接返回旧答案 | 联了,只是对方省了算力 | +| 花不花钱 | 不花(cost 恒 0.0) | 花,但命中那部分打折 | + +`None` 和 `0` 必须分开看:`None` = 这个源不上报这个数(你无法对它做缓存成本校正,论文里该声明),`0` = 它上报了,这次真的一次都没命中。 + +**统计命中率时 `WHERE cache_hit = false` 不可省**: + +```sql +SELECT SUM(cached_prompt_tokens)::float / NULLIF(SUM(prompt_tokens), 0) +FROM llm_calls +WHERE cache_hit = false AND cached_prompt_tokens IS NOT NULL; +``` + +原因和上面 cost 缺口的口径一样:缓存命中行里这两个字段是**原样回放**的历史值(与 `model`、`prompt_tokens` 同一规则——命中时只有时延类字段被清零),计进去就是重复计数。 + +`model_reported` 是 API 响应体里实际返回的 model,和 `.env` 里配的别名可能不是一个东西——供应商把别名指向新权重时,只有它认得出当时真正跑的版本。要做可复现的实验快照,记这一列。