5.4 监控三张仪表盘:命中率、成本、质量 本节摘要:缓存省没省,不能靠感觉。本节设计命中率、成本、质量三张仪表盘,给出每张该画什么曲线、埋点记哪些字段、告警怎么设阈值,以及三张图如何互相印证定位问题。最后给一段可直接跑的聚合统计代码。 5.3 把 Agent 的命中率做上去了,但"做上去了"三个字本身不是结论,是待验证的假设。这一节我们把前面所有动作产出的数据,变成三张能贴在监控大屏上的仪表盘。它们既是本章的收尾,也是第 6 章财务账的原料——没有这些数,下一章的"每回收一个 token 值多少钱"就无从算起。 看不见的回收率怎么算清 缓存的本质是"回收已经付过的算力",可算力付没付、回收多少,默认是看不见的。引擎只报 token 总量,网关只报命中次数,业务只关心延迟和成本。
本节摘要:缓存省没省,不能靠感觉。本节设计命中率、成本、质量三张仪表盘,给出每张该画什么曲线、埋点记哪些字段、告警怎么设阈值,以及三张图如何互相印证定位问题。最后给一段可直接跑的聚合统计代码。
5.3 把 Agent 的命中率做上去了,但"做上去了"三个字本身不是结论,是待验证的假设。这一节我们把前面所有动作产出的数据,变成三张能贴在监控大屏上的仪表盘。它们既是本章的收尾,也是第 6 章财务账的原料——没有这些数,下一章的"每回收一个 token 值多少钱"就无从算起。
缓存的本质是"回收已经付过的算力",可算力付没付、回收多少,默认是看不见的。引擎只报 token 总量,网关只报命中次数,业务只关心延迟和成本。三股信息各说各话,结论就永远模糊。所以我们不画一张大图,而是画三张各有侧重、又能互相印证的图:一张说命中了多少,一张说省了多少钱,一张说质量有没有塌。
命中率仪表盘回答"缓存生效没有"。它至少画三条线:整体命中率(命中 token / 总输入 token)、分层命中率(引擎前缀缓存、网关精确缓存、语义缓存各自占比)、以及按业务线的命中率曲线。再加一个"未命中原因分桶"——键漂移、缓存逐出、前缀本就不同,各占多少。
只看整体命中率会骗人:它可能很高,但其实是因为某一条长前缀请求刷高了均值。按业务线拆开,才能看到"哪条线根本没接住"。未命中原因分桶则直接告诉你是键算错了、块不够了、还是流量本身就不重复。
成本仪表盘回答"省了多少钱"。核心三个数:节省金额(命中 token × 单位算力成本)、有效单价(总花费 / 有效生成 token)、缓存写溢价占比(为写缓存多占的显存/计算 ÷ 总缓存开销)。
有效单价这个概念很关键。开了缓存,你的"每次请求平均成本"会下降,但"为维持缓存多付的写溢价"在上升。成本仪表盘的价值,就是让这两条线同框:当命中率足够高时,溢价被摊薄,净节省为正;命中率一旦跌破某个点,溢价反而吃掉收益。第 6 章会把这个临界点算成具体数字。这里提前给一个直觉:写溢价不是固定成本,它和缓存容量、命中率都有关。命中率低时,你为维持一份很少被命中的缓存持续付费,溢价占比就高;命中率高时,同一份缓存被反复命中,单次命中的边际溢价趋近于零。所以成本仪表盘最该画的是"写溢价占比随命中率变化"的曲线,而不是两个孤立的数字。
成本降了,回答质量不能塌。质量仪表盘看三件事:命中回答的采纳率(用户是否真的用了缓存返回的回答)、用户负反馈率(被采纳的命中回答里有多少被点踩)、以及语义缓存误命中抽检(语义层把"看似相同"的请求判命中,有没有返回错误内容)。
这一张最容易被省略,却最关键。前缀缓存是字节级精确,几乎不会错;但如果你在 5.2 网关里加了语义层缓存,误命中就会真实发生。质量仪表盘就是给"降本"这把刀装个护手——降得狠了,负反馈先报警。质量仪表盘最难做的是"语义误命中抽检"——你不可能逐条人工看,也没法全量跑一遍正确性校验。实务做法是分层抽样:每天从命中回答里抽千分之一,用更强的模型或规则做一致性核对,估算误命中率上限。这个数字哪怕只是区间,也远比"我们相信语义缓存不会错"这种假设靠谱。采纳率则要从前端埋点拿,别用"返回了就算采纳",那会把所有命中都算成有效,掩盖质量问题。
三张仪表盘的数据,来自每条请求的统一埋点。一张字段表:
| 字段 | 类型 | 用途 |
|---|---|---|
| request_id | 字符串 | 串联一次请求的全程日志 |
| model | 字符串 | 区分多模型路由,成本分桶 |
| biz_line | 字符串 | 按业务线拆命中率 |
| input_tokens | 整数 | 计算命中率分母 |
| cache_hit_tokens | 整数 | 命中率分子 |
| cache_layer | 枚举 | 命中发生在引擎/网关/语义哪层 |
| miss_reason | 枚举 | 未命中归因:漂移/逐出/本就不同 |
| cost_saved | 浮点 | 本次节省金额,成本仪表盘直接取 |
| adopted | 布尔 | 回答是否被用户采纳,质量用 |
| negative_fb | 布尔 | 用户负反馈,质量用 |
这些字段在 5.2 网关的命中/未命中分支里各补一行就能产出;引擎层的前缀命中 token 从引擎暴露的指标端点同步过来。字段齐了,三张图只是不同的聚合视角。
阈值要随业务线分别设,不能全网一个值。长前缀、高重复的业务线天然命中率高,用低基线会一直误报;短前缀、强个性化的线命中率本就低,用高基线会永远不报。

单看一张容易误判,三张对着看才能定位。举例:命中率仪表盘显示整体下跌,别急着改键——先看成本仪表盘,如果节省金额还是正的,说明只是长尾流量变化,不影响收益;再看质量仪表盘,如果负反馈没涨,说明下跌没伤质量。三张都稳,才是真稳。反过来,命中率跌、成本转负、质量报警三者同时出现,那基本可以断定是语义层误命中或键设计崩了,直接去查 miss_reason 和 negative_fb。
下面这段 Python 把埋点日志聚合成三张仪表盘的核心指标。假设日志是每行一个 JSON 的请求记录。
import json from collections import defaultdict rows = [] with open("access_log.jsonl") as f: for line in f: rows.append(json.loads(line)) # 命中率:按业务线聚合 by_line = defaultdict(lambda: [0, 0]) # [命中token, 输入token] for r in rows: b = r["biz_line"] by_line[b][0] += r["cache_hit_tokens"] by_line[b][1] += r["input_tokens"] print("== 各业务线命中率 ==") for b, (hit, tot) in by_line.items(): rate = hit / tot if tot else 0 print(f"{b}: {rate:.1%}") # 成本:总节省与写溢价占比 total_saved = sum(r["cost_saved"] for r in rows) write_premium = sum(r.get("write_premium", 0) for r in rows) print(f"总节省金额={total_saved:.2f} 写溢价占比={write_premium / (total_saved + write_premium):.1%}") # 质量:负反馈率与采纳率 neg = sum(1 for r in rows if r["negative_fb"]) adopt = sum(1 for r in rows if r["adopted"]) print(f"负反馈率={neg / len(rows):.2%} 采纳率={adopt / len(rows):.1%}")
同样的聚合用 SQL 也能写:SELECT biz_line, SUM(cache_hit_tokens)*1.0/SUM(input_tokens) ... GROUP BY biz_line。无论哪种,字段表对得上,三张仪表盘就都能直接生成。
本章从引擎开关一路做到监控大屏,手里现在有命中率曲线、节省金额、有效单价、负反馈率这一整套数。它们不是终点——第 6 章会把"节省金额"和"写溢价"换算成一张财务账,告诉你每回收一个 token 值多少真实成本,以及缓存要在多大命中率下才划算。工程跑通、度量齐全,账才能算得清。这也是"算力预付、缓存回收"这条主线,第一次完整闭环。三张盘联动的排障顺序如下: