Files
PolyGateway/research-wiki/schemas/llm-calls.md
T
iomgaa 4c135075b3 test: verify the thinking switch against the live API (issue #5, #6)
A sixteen-row matrix over 127 real calls: disable and enable on
MiniMax-M3 in both streaming and non-streaming mode, extra_body winning
over the profile slot, qwen and deepseek still disabling correctly, a
drift sentinel that re-derives every registered capability from live
behaviour, and the assembly guard refusing the models that cannot
comply.

Two judgement criteria had to be corrected by the data they were meant
to judge. Output length cannot separate the two regimes at all -- the
disabled runs reach 46 tokens when the model narrates its working in
the visible answer, and the enabled runs drop to 13 when medium effort
barely thinks. reasoning_tokens separates them cleanly in both
directions, which is precisely what issue #6 was collected for. A
second anchor compares prompt_tokens between the two regimes: the
vendor injects a reasoning instruction when thinking is on, so the
input side grows, and comparing the two runs relatively avoids
hardcoding any vendor number.

Provider names are mapped explicitly rather than guessed from the model
string; guessing had silently skipped the qwen row behind a "source
unavailable" reason that was not true.
2026-08-02 06:55:38 -04:00

6.6 KiB

type, node_id, title, date
type node_id title date
schema schema:llm-calls 表结构: llm_calls(遥测 22 字段) 2026-07-20

表结构: llm_calls(遥测 22 字段)

列定义(冻结,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(配置别名)可能分叉
sampling TEXT 本次调用的采样参数 canonical JSON(2026-07-31,issue #4);NULL = 未传。见下方口径
reasoning_tokens INTEGER 推理消耗的输出 token(2026-08-02,issue #6);含在 completion_tokens 内,不影响成本总额,只补归因。NULL = 本次调用未上报

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 = falsecache_hit 限定不可省:缓存命中行未产生新调用,cost 是事实上的 0.0 而非未知,本无账目缺口,漏掉该条件会让缺口度量偏高。

供应商 prompt cache 口径(2026-07-31,issue #3)

新增两列排在 created_at 之后——旧表只能经 ALTER TABLE ADD COLUMN 追加到末尾,DDL 里若插在前面,新建库与升级库的物理列序会分叉(列序断言无合规修法)。两个后端在初始化期幂等补列:CREATE TABLE IF NOT EXISTS 不会给旧表加列,不补则每行写入被逐行 warning 丢弃、遥测静默全失。两侧都先探测缺列再 ALTER(ADD COLUMN IF NOT EXISTS 即使列已存在也先取 ACCESS EXCLUSIVE 锁,遥测是内联 await,锁共享审计表会拖垮业务调用),且补列失败只降级为逐行丢弃,绝不让 recorder 整体失能——两侧纪律必须对称。

cache_hitPolyGateway 自身响应缓存,与供应商 prompt cache 是两回事。缓存命中行的这两列是原样回放的历史值(与 model/prompt_tokens 同一口径),故命中率度量口径固定为:

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 缺口口径同源:回放行计入即重复计数。

采样参数口径(2026-07-31,issue #4)

reasoning_tokens 的 NULL 语义与 cached_prompt_tokens 不同: 后者的 NULL 是"该源不报这个数",前者只能读作"本次调用未上报"——中转在上游不返回 usage 时会用本地 tokenizer 补算并整体替换 usage 对象,把 completion_tokens_details 一并吃掉(实测同一请求 10 轮呈 6:4 双峰)。故统计口径须为 IS NULL OR = 0 才算"未推理",写 = 0 的条件永远不成立——实测三家供应商在未推理时都是整个 details 缺失,无人上报字面 0不可用 completion_tokens 反推是否推理: 两档的输出长度分布重叠(关闭档实测最高 46,开启档最低 13)。

sampling 列 = 「调用方采样意图 ⊎ 生效源 extra_body」的 canonical JSON,空则 NULL。不含结构化输出注入的 response_format——列名是采样参数,schema 不是,且数 KB schema 逐行落库会让审计表无谓膨胀。补列纪律与 issue #3 两列逐字相同(排在末尾、先探测再 ALTER、失败只逐行降级)。

三个 emit 入口的取值必须各自定死,否则同一列在不同行含义不同:

入口 调用者 有生效源? 记什么
emit_attempt RetryMW(最内) merge(source.extra_body, request.sampling)
emit_cache_hit TelemetryMW(最外) request.sampling
emit_terminal_failure TelemetryMW request.sampling

后两行缺 extra_body 是客观事实而非口径瑕疵——它们没有"生效源"可言,与 model/source_name 在终态行置空是同一先例;缓存命中行亦无损:sampling 已进缓存 key,能命中即意味调用级参数与历史那次逐字相同。三者统一读 request.sampling 而非 request.overlay(后者在 RetryMW 处已被结构化注入污染、在 TelemetryMW 处未被污染,直接用必然三行分叉)。

OCR / embedding 路径的该列恒为 NULL:两条路径的 transport 不发 extra_body(embed payload 硬编码 {model, input}、MonkeyOCR 只发 multipart),故其源在构造期就被剥离——不剥离则该列会记录一个从未发出的参数,那是数据造假而非参数失效。

复现某批实验的解码条件:

SELECT DISTINCT sampling FROM llm_calls
WHERE session_id = $1 AND cache_hit = false AND error IS NULL;

埋点位置(单一 helper 铁律)

  • middleware/telemetry.py::TelemetryEmitter 是全库唯一 record_llm_call 调用点;
  • TelemetryMW(最外层)记缓存命中与最终失败;RetryMW._emit 经同一 Emitter 逐次记尝试;
  • 写失败降级 warning 不冒泡;取消路径 finally 尽力记录。

评估基线

首版无历史基线,标"待首次运行后建立";验收断言: 单测覆盖成功/失败/缓存命中/取消四路径各产生恰一行;并发 50 协程写全落库。