指标

只想知道看板怎么算:看 接入上报 的「看板怎么算」。 这份文档定完整口径:每个指标怎么定义、用哪些事件算、分子分母是什么、哪些情况排除。 chatkit 只负责发出事件(事件),上报、存储、看板、告警由接入方做。口径写在这里, 结论字段(outcome)由 Core 直接算好,接入方可直接使用该字段,不必另写结论分类;最终指标仍取决于事件完整性、去重和归并。

两套口径,选一套

口径上报什么数据侧要做什么适合
每轮汇总(推荐)turn_summary(createTelemetry 的默认 summary 预设)按 sessionId + runId 取 revision 最大的一条大多数接入方
原始事件send_*、run_*、first_paint 等(core-metrics 预设)按本文的规则关联、归并(见文末「从原始事件到指标」)要自己做归并、做深度分析的

turn_summary 由 Core 按本文的口径从原始事件归并而成:结论、是否计入有效轮次、首字延迟、完成耗时都已经算好。 两套口径做看板时二选一,不要相加,否则同一轮会算两次。下面每个指标都同时给出两套算法。

统计单位:一轮

一轮 = 一次 Agent 执行,用 sessionId 与 runId 一起标识,可以由本地发送或远端触发;不要假设不同会话的后端轮次编号全局唯一。chatkit 里的「会话」指整个对话(sessionId), 下面叫「会话完成率」「会话完成耗时」的指标,统计单位都是一轮。

一轮的结论

每一轮落进下面一类且只落一类,取 turn_summary.outcome(原始事件口径取 run_settled.outcome,没有终态的取 run_abandoned):

结论判定进哪个指标
completed到达完成终态,并且有回答:非空白的正文、图片 / 文件等媒体、数据块(卡片)任一会话完成率
empty到达完成终态,但没有回答:只有思考,或只调了工具,或正文全是空白空回复率
failed失败终态,可从 errorCode 读取错误码任务失败率
aborted用户点了停止,且当时没有卡住用户中止率
interrupted没有正常收口:协议中断、无法续上、卡住之后被用户停止、运行时释放或页面触发 pagehide(run_abandoned)回复中断率
cancelled排队中被撤下,从没派发过不算一轮

连接断开或恢复失败本身不一定产生终态。没有收到 run_settled 或 run_abandoned 的轮次,应单独观察,不能自行补成失败或中断。completed 只表示有正文、媒体或数据片段,不代表内容正确、业务卡片成功渲染或用户任务已经解决。

「卡住」:在跑、却超过阈值(默认 60 秒,stallThresholdMs)没有被接受的新事件或状态更新,发 run_stalled;等待外部输入 (awaiting-external,比如等用户确认)不算。卡住之后又恢复并完成的,结论仍是 completed,只是 stallCount 大于 0。

通用规则

  • 按 sessionId + runId 归并,以最后一个结论为准。 汇总口径取 revision 最大的一条;原始事件口径收到 run_abandoned 后,若同一轮又收到 run_settled,用后者更新统计,不能累计成两轮。
  • 去重与排序用信封字段。 重传、离开页面通道与普通通道各发一次时按 eventId 去重;判断先后用 seq(同一次页面访问内递增),不用 ts。 关联同一次页面访问用 pageVisitId。
  • useChatSessions 内切换会话不会产生放弃事件;单对话 hook 换 sessionId 或卸载才释放旧运行时。页面侧监听 pagehide,并非每次切换浏览器标签页都会触发。
  • 有效轮次(稳定类的分母)= 结论为 completed、empty、failed、interrupted 的轮次。用户主动停止(aborted)是 用户的选择,不算系统不稳定,单独看用户中止率;cancelled 不算一轮。所以完成率 + 空回复率 + 失败率 + 中断率 = 100%。
  • 耗时看分位数(P50 / P90 / P99),不看平均值:长尾才是体验问题。
  • 耗时用载荷里的 *Ms,不用 ts 相减。 载荷里的耗时用单调时钟算,不受系统时间调整影响;ts 是墙钟,只用来对齐与排查。 刷新后重新接上、远端发起的一轮(origin: 'remote')没有本地受理时点,耗时退回墙钟计算,可能混入服务端时间,应与本地发起的一轮分开看。
  • 页面关闭时的事件要用离开页面通道发(navigator.sendBeacon 或埋点 SDK 的对应能力),否则 run_abandoned 会丢,中断率偏低。
  • 刷新页面后重新接上的一轮(origin: 'remote')没有 send_result,不进首字延迟、首次响应的样本(汇总里也没有 firstPaintMs / firstOutputMs)。
  • 前台与后台分开看。 终态时会话在后台的一轮(turn_summary.background 为 true)用户并没有在等,没有首字样本,完成耗时也不代表体验,体验类指标只取前台的一轮。

性能

指标口径汇总口径原始事件口径排除 / 说明
首字延迟运行时受理 → 助手非空正文首次渲染turn_summary.firstPaintMs(排除 background)first_paint.sinceAcceptedMs本地发送才有;正文渲染不等于进入视口
首次响应运行时受理 → 首次执行产出或活动turn_summary.firstOutputMsrun_started.queuedMs + run_first_output.latencyMs与首字延迟一起看:分清是模型慢,还是 Agent 先思考 / 调工具
会话完成耗时运行时受理 → 到达终态,含排队turn_summary.totalMs(outcome: 'completed')run_settled.totalMs只统计有耗时且结论为 completed 的轮次
历史加载耗时打开一个会话到历史拉回来—history_loaded.durationMs(kind: 'refresh',ok: true)从历史请求开始计时;翻页(load-more)单独看

上述口径不包含输入框校验或异步 onSend 的耗时。若要统计“点击发送到响应”,需要业务方另记点击时刻。first_paint 在正文组件渲染后触发,不是浏览器精确绘制时间;历史展示、后台会话切回后的延迟渲染应与持续可见的对话分开分析。

run_first_output 记录运行时的首次执行信号,正文、思考、工具阶段或工作项都可能触发,不保证此时正文已经显示。latencyMs 与 run_started.queuedMs 都用单调时钟,可以直接相加。

短回复可能先落终态、界面随后才画出正文:turn_summary 在终态之后最多等 firstPaintGraceMs(默认 1000 毫秒)的首次绘制再发出; 到点没等到(低端机、主线程繁忙)就不带 firstPaintMs。等待超时的一轮多了,首字延迟会偏乐观,可调大这个值。

稳定

有效轮次:汇总口径是 turn_summary.effective 为 true 的轮次;原始事件口径见上面的「通用规则」。

指标公式事件拆分维度
会话完成率结论 completed 的轮次 ÷ 有效轮次turn_summary;或 run_settled、run_abandoned属性(Agent、模型、场景)
空回复率结论 empty 的轮次 ÷ 有效轮次turn_summary;或 run_settled(content 看空在哪)content.reasoning、content.toolCalls
回复中断率结论 interrupted 的轮次 ÷ 有效轮次turn_summary;或 run_settled、run_abandonedabandonReason、interruptReason、stallCount
任务失败率结论 failed 的轮次 ÷ 有效轮次turn_summary;或 run_settlederrorCode、retryable
工具调用成功率status: 'completed' 的调用 ÷(completed + failed)的调用;cancelled 不进分母tool_call_settled;汇总口径用 toolCalls 的计数toolName、errorCode(汇总口径只有总数,不分工具)
工具调用耗时单次调用的 durationMs,看分位数tool_call_settledtoolName、status
发送失败率发送失败次数 ÷(send_attempt 次数 − send_blocked 次数)send_attempt、send_blocked、send_resultsend_result.reason
渲染错误率render_error 次数 ÷ 有效轮次,按每千轮render_errorscope、partType、errorName

发送失败:这里只统计运行时未受理或派发失败,不等于远端一定没有执行。onSend 拦截发生在运行时事件之前,不计入下面的分子或分母。算以下两种:

  • 受理阶段就失败:send_result 的 stage: 'accepted' 且 accepted: false;
  • 派发阶段失败:send_result 的 stage: 'dispatched'、accepted: false,且 reason 是 adapter-rejected(适配器拒收)或 error(派发时抛错)。

派发阶段的 aborted、aborted-before-dispatch、cancelled-before-dispatch、runtime-disposed 表示停止、撤下排队或运行时已释放,不算发送失败。 send_blocked 是接入方自己的中间件拦下的(比如内容审核),不进分母。

工具名是协议给的原始名(mcp__server__search、webSearch…)。同一个工具在不同协议下可能有不同名称;需要合并时,在自己的上报层维护明确的名称映射,不要依赖包内未导出的工具函数。

体验

指标公式事件说明
用户中止率结论 aborted 的轮次 ÷(有效轮次 + aborted 的轮次)turn_summary;或 run_settled可结合耗时与用户反馈排查原因;卡住后停止的算中断,不在这里
重新生成率重新生成的次数 ÷ 有效轮次turn_summary.via;或 send_result可辅助观察用户重复尝试的行为;口径见下

重新生成的次数:turn_summary 里 via 为 regenerate 的轮次;原始事件口径是 send_result 里 stage: 'accepted'、accepted: true,且 via 为 regenerate 的条数。 编辑后重发单独统计,条件相同,via 为 edit;需要时两者相加作为“重新尝试”的次数;仅凭点击行为不能确定用户是否不满意。 普通发送的 via 是 send,统计提问量时只算它。

辅助观测

不作为核心指标,排查时有用:

  • 卡住率:有效轮次中 stallCount 大于 0 的轮次 ÷ 有效轮次;卡住时的阶段看 run_stalled.phase,是否已有输出看 hasOutput。
  • 排队等待:turn_summary.queuedMs 或 run_started.queuedMs,用户在上一轮没结束时连续发送的等待。
  • 首字等待超时:前台、有正文、却没有 firstPaintMs 的汇总 ÷ 前台有正文的汇总,用来校准 firstPaintGraceMs。
  • 阶段耗时:同一轮相邻 run_phase_changed 的时间差可估算中间阶段;首段需关联 run_started,末段需关联 run_settled。缺少边界事件的样本不应算作完整阶段。

维度与扩充属性

每个事件都带 sessionId、runId(运行相关的)和接入方扩充的 attributes。常用维度建议在 useChatSessions / useChatRuntime 的 eventAttributes 里给(入口与合并顺序见 事件):

维度放在哪例子
应用版本、场景eventAttributes.commonappVersion、scene
实验分组eventAttributes.commonexperiment
入口、是否带附件eventAttributes.runentry、withAttachments
实际模型、Agent在上报层关联业务服务确认的属性model、agent

属性名由接入方约定,chatkit 不解释。只收扁平的基础类型,不要放用户输入。

重新生成与编辑后重发沿用发送的事件,载荷里的 via 是 regenerate / edit。统计用户提问量、发送失败率时按 via 分开看, 重新生成不算新的提问。

从事件到指标

汇总口径:用 createTelemetry 接入(默认就是 summary 预设),数据侧每轮一条 turn_summary,按 sessionId + runId 取 revision 最大的一条, 上面的公式直接按字段算。开发时可以用 @volcengine/chatkit/devtools 的 createTurnMetrics 在页面里实时看到同样口径算出的结果。

import { createTelemetry } from '@volcengine/chatkit';

const telemetry = createTelemetry({
  track: (name, props) => sdk.track(name, props),
  beacon: (name, props) => navigator.sendBeacon('/collect', JSON.stringify({ name, props })),
});

原始事件口径:createTelemetry({ track, events: 'core-metrics' }) 上报 METRICS.md 用到的原始事件(不含 turn_summary)。数据侧按以下规则归并:

  • 按 sessionId + runId 关联运行事件,关联首字时再限定为同一个 pageVisitId。
  • 受理时刻只取 send_result 中 stage: 'accepted' 且 accepted: true 的事件;首字延迟直接取 first_paint.sinceAcceptedMs。
  • 结论更新按同一轮覆盖(按 seq 取最后一个),不能把 run_abandoned 和后续 run_settled 相加。
  • 没有 runId 的发送尝试、拦截、历史加载和渲染错误也要保留,分别按各自的指标统计。

常见问题

中断率比预期低。 页面关闭时的事件没发出去:run_abandoned 和同时发出的 turn_summary 要走离开页面通道(createTelemetry 的 beacon), 普通的异步请求在页面关闭时会被浏览器丢掉。

首字延迟的样本比轮次少。 正常:远端发起、刷新后接上的一轮没有受理时点;没有正文的一轮没有首次绘制;后台落定的一轮不进样本; 终态之后等了 firstPaintGraceMs 还没画出正文的一轮不带 firstPaintMs。

切走会话之后还在收到事件。 正常:切走的会话在后台继续接收数据,那一轮跑完照样报,用 sessionId 区分;它的 turn_summary 标着 background: true。

同一轮出现了两条 turn_summary。 先被放弃(页面隐藏、运行时释放)报了一条,之后又落了终态再报一条,revision 递增,取最大的一条。

完成率和失败率加起来超过 100%。 两套口径相加了:turn_summary 和 run_settled 只能选一套做看板。verifyTelemetry 会检查出这种情况。