可观测面板

Zapvol 内置的 4 个核心 Dashboard——LogQL 查询 + 判读标准 + 异常时的排查路径

面板的设计原则

不是”能看就加”,而是每个面板回答一个具体问题。问题越狭窄,出现异常时行动路径越短。

本章收录的 4 个面板全部围绕本次 cache 改造展开——不是碰巧,这正好是 Zapvol 第一次把”压缩 + cache + prepareStep”三件事联动编排的地方,最容易出错,也最值得监控。

每个面板按统一结构描述:

  1. 问题——这个面板回答什么具体疑问
  2. 查询——LogQL 代码(可直接复制到 Grafana Explore)
  3. 正常值域——什么叫健康
  4. 异常信号——什么叫要紧急排查
  5. 关联事件——这个面板读哪些 log event

面板 1:Cache 命中率趋势

问题

“Anthropic prompt cache 在本部署里真的在省钱吗?”

这是 cache 改造的首要验证指标。没有它,所有其他”断点落位正确、压缩边界稳定”的理论推演都只是理论。

查询

avg_over_time(
  {job="zapvol-server", event="stream.step_finished"}
    | json
    | unwrap cacheHitRatio
  [5m]
)

正常值域

阶段预期 ratio
任务首步(step 0)< 10%——首次请求,没东西可 cache
第 2 步起≥ 60%——system prompt + stable prefix 都该命中
多步任务后段(step 5+)70-85%——理想区间

异常信号

现象可能原因排查入口
持续 < 30%AI SDK propagation 假设不成立,或 Anthropic 端 5 分钟 TTL 在步间隙过期看面板 2 的断点分布
首步就 > 30% 但后续掉到 < 20%system prompt 被改变(prompt caching 失效),或 Anthropic 账户 cache 被禁用检查 createCachedInstructions 是否在所有请求里返回相同的 system
长期维持在 40-55% 区间有效断点数不足 4(Rule 2 没触发)面板 2 定位
某些任务 ratio = 0非 Anthropic provider(OpenAI / Google),cache 字段来自 provider raw过滤 provider != "anthropic",正常

关联事件

  • stream.step_finished——读 cacheHitRatio 字段
  • 交叉检查:cache.breakpoints_placed(设计端) vs stream.step_finished.cacheHitRatio(观测端)

面板 2:断点落位分布

问题

“applyCacheControl 的三条规则 + extraBreakpointAt 都在正确触发吗?”

面板 1 告诉你”cache 在工作吗”,面板 2 告诉你”为什么在 / 不在工作”。

查询

sum by (placed_count) (
  count_over_time(
    {job="zapvol-server", event="cache.breakpoints_placed"}
      | json
      | label_format placed_count="{{len .placedAt}}"
    [1h]
  )
)

输出:每 1 小时窗口内,每种”断点数量”分别出现多少次。

正常值域

Anthropic 允许每请求 4 个 cache_control 断点(system prompt 不占此 4 个,由 createCachedInstructions 单独管理)。applyCacheControlmessages[] 里最多放 3 个:

  • Rule 1(last message)
  • Rule 2(last user,仅 last ≠ user 时)
  • Rule 3 extraBreakpointAt(compactedPrefixEnd 或 length/2 回退)

所以 placed_count 的正常分布:

placed_count场景比例
3压缩已触发 + last ≠ user(典型 agent tool-loop 步)应占 60-80%
2无压缩(短对话)+ last ≠ user,或有压缩 + last = user15-30%
1首步,last = user 且无压缩5-10%
0异常——messages 为空应为 0
4+不可能——marked: Set 去重,上限 3应为 0

异常信号

现象可能原因排查入口
placed_count = 1 占比 > 30%Branch B 场景(last=tool_result)里 Rule 2 被伪 user 骗过 → applyCacheControl 在 reminder 注入之后被调用agent-round.ts prepareStep 里 applyCacheControl 是否在 reminder 注入之前
extraBreakpointUsed: false 持续 true 但压缩本应触发compactedPrefixEnd = -1 异常——appliedCrCount 没在 activateMoreRoundReplacements 里递增compaction.step_triggered 事件是否正常,再查 compaction.round_degradedactivatedCount
分布整体偏低(大部分 = 1)applyCacheControl 压根没被调用,或 isAnthropicModel() 返回 false检查 provider 配置;Gemini / OpenAI 路径本来就不走这里

关联事件

  • cache.breakpoints_placed——主数据源
  • compaction.round_degraded——确认压缩确实在消费 fallback
  • agent.created——确认模型确实是 Anthropic

面板 3:Cache 读/写 token 比

问题

“cache 写入的内容被重复利用了吗?还是在空耗?”

cacheWriteTokens 是把新 prefix 写入 Anthropic cache 的成本(相当于 1.25× input token 价)。cacheReadTokens 是从 cache 读出的收益(相当于 0.1× input token 价)。比值 R = read / write

  • R >> 1:cache 命中率高,写一次读多次——理想
  • R ≈ 1:每次写都没怎么被读——orphan write
  • R < 1:大量 cache 写但读不回——严重浪费

查询

sum(rate({job="zapvol-server", event="stream.step_finished"} | json | unwrap cacheReadTokens [5m]))
  /
sum(rate({job="zapvol-server", event="stream.step_finished"} | json | unwrap cacheWriteTokens [5m]))

正常值域

R 值判读
> 3健康——每次写入被读 3+ 次
1.5-3可接受——读多于写,但 cache 被改写较频繁
1-1.5临界——写入几乎只被读一次就被下次写入覆盖
< 1告警——写得多读得少,cache 策略失效

异常信号

现象可能原因排查入口
R < 1 且面板 1 显示 hit ratio 正常Grafana Cloud 免费档时间窗错误聚合(分子分母 rate 窗口不对齐),不一定真异常改成 sum_over_time 替代 rate 再看
R < 1 且面板 1 hit ratio 也低断点位置漂移(length/2 启发 / reminder 污染),每步都在写新 prefix 但下步位置不同面板 2 查断点分布,回代码检查 reminder 注入顺序
R ≈ 1 持续5 分钟 TTL 在步间隙过期(任务节奏慢),Anthropic cache 被动失效监控步与步之间的间隔,考虑批量化步骤

关联事件

  • stream.step_finished——cacheReadTokens + cacheWriteTokens

面板 4:异常任务 Top-N

问题

“过去 1 小时哪些任务的 cache 行为反常?”

前三个面板是聚合视角,本面板给个案视角——按具体 taskId 列出低命中率步,供点进去深挖。

查询

{job="zapvol-server", event="stream.step_finished"}
  | json
  | cacheHitRatio < 0.3
  | line_format "taskId={{.taskId}} step={{.step}} ratio={{.cacheHitRatio}} inputTokens={{.inputTokens}}"

正常值域

首步 cache ratio < 0.3 是正常的,不要大惊小怪。真正值得关注的是:

  • 同一 taskId 在 step 3+ 仍然 < 0.3
  • 一个时间段内多个不同 taskId 同时跌破 0.3(系统性问题而非单任务问题)

异常信号

现象可能原因排查入口
单个任务持续低 ratio该任务的 system prompt 或 context 有非确定性内容(时间戳、随机 ID 进 system)复制 traceId,拉出该任务的 cache.breakpoints_placed 序列,看断点位置是否稳定
某时间窗内大量任务同时低 ratio版本发布 / 配置变更 / Anthropic 侧 cache 异常看发布时间戳是否对齐;查 Anthropic 状态页
特定模型 ID 的任务低 ratio该模型 provider 不支持 cache,或 cache 定价/行为不同检查 createCachedInstructions 对该 provider 的处理

关联事件

  • stream.step_finished——主数据源
  • 点入某个 taskId 后,切换到:{traceId="<id>"} | json——看该任务的全部日志

扩展新面板的工作流

Step 1:先加 event,再加面板

绝不要把 Grafana 当代码写。 面板是数据的视图,不是业务逻辑的载体。先在 Zapvol 代码里 emit 一个结构化 event:

log.info("your_event.name", {
  taskId,
  /* 低基数字段 */ kind: "...",
  /* 数值字段 */ someMetric: 42,
});

运行几次,在 Grafana Explore 里用 {event="your_event.name"} | json 确认能查到、字段对不对。

Step 2:定义面板四要素

在 Dashboard 创建之前,在文档里写下来(可以先在 PR 描述里):

  • 问题:这个面板回答什么
  • 查询:LogQL 原型
  • 正常值域:不加入我的判读,别人也能看懂”这个数字该是多少”
  • 异常信号:至少 2 条可操作的排查路径

写不出来说明问题没想清楚,不要开始画面板。

Step 3:Dashboard JSON 提交到 repo

Grafana 的 Dashboard → Settings → JSON Model 复制出来,放到:

ops/grafana/dashboards/{domain}-{name}.json

在同目录的 README.md 里追加导入说明和判读要点(或链接到本章对应小节)。

为什么要 commit JSON:Grafana 单实例丢了数据就没了。commit 到 repo 后,新人 15 分钟能在本地拉起等效 Grafana。也是 code review 的好载体——别人改了一个查询你能在 PR 里看到 diff。

Step 4:如果需要告警

用 Grafana 自带的 Alert rules(声明式 YAML,不要用点点点的 UI)。声明式规则也 commit 到 repo:

ops/grafana/alerts/cache-hit-degradation.yaml

告警规则要极度克制。Agent 系统里”可能出问题”的事很多,但”必须立刻处理”的事很少。默认阈值:

  • for: 15m 以上——短暂波动不告警
  • 只告结果性指标(cache hit ratio、error rate、latency 尾部),不告过程性指标(断点数、压缩频率)

相关章节

这页有帮助吗?