--- type: schema node_id: schema:llm-calls title: "表结构: llm_calls(遥测 20 字段)" date: 2026-07-20 --- # 表结构: llm_calls(遥测 20 字段) ## 列定义(冻结,M1 设计 §4.4 / ARCH §7.8) | 列 | 类型 | 说明 | |---|---|---| | call_id | TEXT PRIMARY KEY | 每次尝试独立 UUID;INSERT OR IGNORE 幂等 | | parent_call_id / session_id | TEXT | 调用链路(agent step → LLM call) | | model / provider / source_name | TEXT NOT NULL | 溯源;model 由旧 Protocol 的 model_name 更名(VT 迁移 §8) | | messages / response / thinking | TEXT NOT NULL | messages 落库前多模态 part 摘要(与缓存 key 共用 digest_messages) | | prompt_tokens / completion_tokens | INTEGER NOT NULL | usage 帧;帧缺失记 0/0(不编造估值,由 usage_source 标注) | | usage_source | TEXT NOT NULL | measured / estimated / unavailable(2026-07-30 起三态,见下) | | latency_ms | INTEGER NOT NULL | 尝试耗时;缓存命中 0 | | ttft_ms / max_inter_token_ms | REAL | 流式活性测量 | | cache_hit | INTEGER NOT NULL DEFAULT 0 | 命中标记 | | error | TEXT | 异常信息;取消记 "cancelled" | | cost | REAL | M2 起 pricing 换算;`usage_source='unavailable'` 的真实调用行为 NULL(缓存命中行例外,仍为 0.0) | | created_at | TEXT NOT NULL DEFAULT (datetime('now')) | 落库时刻 | | cached_prompt_tokens | INTEGER | 供应商 prompt cache 命中的输入 token(2026-07-31,issue #3);NULL = 该源未上报,`0` = 上报了真实零命中,两者不可混同 | | model_reported | TEXT | API 响应体实际返回的 model;NULL = 未上报。与 `model`(配置别名)可能分叉 | ## usage/成本口径(2026-07-30,est_tokens 解耦) | usage_source | 含义 | 生产者 | cost | |---|---|---|---| | `measured` | usage 帧完整可信 | 正常路径;OCR 成功行(0 token 是事实) | 按 token 换算 | | `estimated` | 有实测数字但可信度降级 | 打捞路径(收到 usage 帧但流被截断) | 按 token 换算 | | `unavailable` | 用量信息不可得 | usage 帧缺失、失败尝试、终态失败 | NULL | `SUM(cost)` 天然跳过 NULL,故账单汇总不再被虚构的估值污染;账目缺口的度量口径固定为 `WHERE usage_source = 'unavailable' AND cache_hit = false`。**`cache_hit` 限定不可省**:缓存命中行未产生新调用,cost 是事实上的 `0.0` 而非未知,本无账目缺口,漏掉该条件会让缺口度量偏高。 ## 供应商 prompt cache 口径(2026-07-31,issue #3) 新增两列排在 `created_at` **之后**——旧表只能经 `ALTER TABLE ADD COLUMN` 追加到末尾,DDL 里若插在前面,新建库与升级库的物理列序会分叉(列序断言无合规修法)。两个后端在初始化期幂等补列:`CREATE TABLE IF NOT EXISTS` 不会给旧表加列,不补则每行写入被逐行 warning 丢弃、遥测静默全失;补列失败只降级为逐行丢弃,绝不让 recorder 整体失能。 `cache_hit` 指 **PolyGateway 自身响应缓存**,与供应商 prompt cache 是两回事。缓存命中行的这两列是**原样回放**的历史值(与 `model`/`prompt_tokens` 同一口径),故命中率度量口径固定为: ```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; ``` `WHERE cache_hit = false` 不可省,理由与上面 cost 缺口口径同源:回放行计入即重复计数。 ## 埋点位置(单一 helper 铁律) - `middleware/telemetry.py::TelemetryEmitter` 是全库**唯一** `record_llm_call` 调用点; - `TelemetryMW`(最外层)记缓存命中与最终失败;`RetryMW._emit` 经同一 Emitter 逐次记尝试; - 写失败降级 warning 不冒泡;取消路径 finally 尽力记录。 ## 评估基线 首版无历史基线,标"待首次运行后建立";验收断言: 单测覆盖成功/失败/缓存命中/取消四路径各产生恰一行;并发 50 协程写全落库。