事件
只想把上报接起来:看 接入上报,一页讲完代码、会收到什么、看板怎么算。 这一页是完整参考:全部事件与字段、错误来源、扩充属性。指标口径见 指标。
出口
useChatRuntime 返回的 runtime 自带 events,交给 <Chatkit runtime={runtime}> 后自动转发到 onEvent。
用会话层(useChatSessions + <Chatkit sessions={sessions}>)时,onEvent 收到的是全部已打开会话的事件,每个事件带着所属会话的 sessionId:切到别的会话之后,原会话在后台跑完的那一轮照样报出来。不经过 <Chatkit> 时订阅 sessions.events.subscribe(listener)。
onEvent 写在 <ChatkitProvider> 上也一样;里面放了 <Chatkit /> 时同一个事件只报一次。
回调抛错不会影响对话:错误交给 onError(来源 event-listener)。
错误出口:onError
onError(error, context) 收非致命错误,给错误监控用。第二个参数说明错误从哪来、属于哪个会话与哪一轮、能不能自愈,可以按 runId 关联到 onEvent 的事件:
接一处就够:把 onError 写在 <ChatkitProvider>(或 <Chatkit>)上。会话 hook(useChatSessions / useChatRuntime)上没写 onError 时,
它的错误(拉历史、续接、派发、事件订阅方抛错…)也交到这里;里面的 <Chatkit /> 与 SessionList 没写时用 Provider 的。
只有不挂界面(headless)时才需要在 hook 或 ChatCoreRuntime 的 onError 选项上接;都没接时,Core 侧的错误异步抛出,交给宿主的全局错误监控。
后端明确上报的运行失败要看 run_settled / turn_summary 的 outcome 与 errorCode,不会另有一次 onError。
结构
载荷里只有结构化字段(状态、耗时、错误码、个数),不带用户输入、回答正文、工具参数与结果、原始错误文本,没有例外。
这一条有测试守着:构造带敏感文本的完整对话,断言所有事件里都搜不到。错误的原文走 onError,不进事件。
工具名、错误码、系统提示标识等字符串仍来自后端或业务配置,上报前需要接入方确认;自定义 attributes 也应避免敏感内容。
耗时都用单调时钟(performance.now):不受系统时间调整影响。刷新后重新接上、远端发起的一轮没有本地的受理时点,这类轮次的耗时退回墙钟计算。
扩充属性
上报时总要带上自己的维度:应用版本、场景、Agent、模型、实验分组、入口……chatkit 留了三个入口,结果合并进事件的 attributes:
eventAttributes 是 useChatSessions 与 useChatRuntime 的参数,两边写法相同;用 createTelemetry 时写在它的 attributes 里,经 sessionOptions 带过去。
它是事件属性的唯一入口:<Chatkit> 与 <ChatkitProvider> 上没有这个参数。
- 合并顺序:
common→run→send(input, { attributes })→annotateRun,同名后者覆盖前者。 - 同一轮的事件都带这一轮的属性:
send_*、run_*、tool_call_settled、turn_summary,以及界面层按runId继承的first_paint。 - 只收扁平的基础类型(字符串、有限数字、布尔、null);对象、数组、
NaN在进事件之前丢掉,所以能直接交给任何埋点 SDK。 - 属性函数抛错:这次不带它的属性,对话照常,错误交给
onError(来源attributes)。 - 属性由接入方提供,隐私也由接入方负责:不要把用户输入放进去。
一轮的生命周期
- 首字延迟 = 受理到
first_paint,即turn_summary.firstPaintMs(也是first_paint.sinceAcceptedMs); - 首次响应 = 受理到
run_first_output,即turn_summary.firstOutputMs; - 会话完成耗时 = 受理到终态,即
turn_summary.totalMs。
短回复可能先落终态、界面随后才画出正文:turn_summary 会在终态之后最多等一会儿首次绘制(firstPaintGraceMs,默认 1000 毫秒),等到或到点再发出。
没有正文、或者终态时会话在后台(background: true)时不等。
目录
发送(Core)
via是这一轮从哪个命令来的:send(普通发送)、regenerate(重新生成)、edit(编辑后重发)。三个命令共用这组事件, 统计发送量时按via分开看,重新生成不算用户的新提问。- 重新生成 / 编辑不受理时
send_result.reason的取值:run-active(有一轮在跑或在排队)、not-supported(适配器没实现)、run-not-found(目标轮不存在)、input-unknown(目标轮的输入未知,比如从历史里读出来的轮次)。
运行(Core)
run_settled的outcome是这一轮的结论,落进且只落进一类:completed/empty/failed/aborted/interrupted/cancelled,判定规则见 指标。cancelled(排队中被撤下、从没派发)不算一轮,没有turn_summary;turn_summary.outcome只会是前五类。content只数有无和个数:{ text, reasoning, toolCalls, media, data };workItems是{ total, failed }。totalMs从受理(用户点发送)算起、含排队;durationMs从派发算起。- 卡住的阈值用
useChatRuntime({ stallThresholdMs })(或ChatCoreRuntime的同名选项)调,0关掉。 run_abandoned之后同一 run 如果又落了终态(从后台恢复、重新接上),以最后一个结论为准。
每轮汇总(Core)
turn_summary 是由原始事件归并出来的派生事件,原始事件照常发出。两套口径做看板时二选一,不要相加。
排队中被撤下(cancelled)的不算一轮,不发 turn_summary,所以它的 outcome 不含 cancelled。
工具与历史(Core)
tool_call_settled.durationMs 从 Core 第一次看到这次调用算起;第一次出现就已经有结果的(比如协议一帧里同时给了调用和结果)没有耗时。
history_loaded 只在后端有历史接口(适配器实现了 history)时发。
性能与错误(界面层)
render_error 只带错误的类名;完整的错误(含堆栈和渲染上下文)仍然走 onError,给错误监控用。
scope 为 slot 时是插槽里的一项出错:这一项不显示,同一位置的其他项与对话不受影响,slot 是出错的位置名。
交互(界面层)
conversation_event_action 的 kind 是提示的类型(如 chatkit.notice),actionId 是后端在提示里给这个按钮的标识;
提示的标题与说明属于对话内容,不进事件。要在点击后做事(打开摘要、跳转),在 onEvent 里按这两个字段分发。
执行流(界面层)
折叠组、工具行、展开区的曝光与交互。曝光要求元素 50% 进入视口并驻留 300ms,同一身份在同一页面、同一会话里只报一次:切走再切回、按会话重挂载 Chatkit 都不重报。
载荷类型是 ChatkitFlowEventPayloads。
工具字段(FlowToolTrackingFields):toolName(协议给的原始名)、renderer、toolState、renderPipeline。
renderPipeline 的取值:registered_renderer、generic_tool_card、fallback_tool_card、static_summary、summary_only;
summaryMode:active_tool(运行中)/ action_set(已结束);stepCountBucket:1、2、3、4_5、6_10、gt_10;
actionType 如 switch_tab、open_web_result、copy_input。复制失败是诊断信息,走 onError(来源 clipboard),不进事件。
类型与工具
ChatkitEvent:onEvent收到的全部事件的联合类型;ChatkitEventName是全部事件名,ChatkitEventOf<'turn_summary'>按名字取类型。CHATKIT_EVENT_NAMES:全部事件名的只读数组。isChatkitEvent(event, 'turn_summary'):按名字收窄,收窄后载荷有完整类型。
createTelemetry 参考
选项:
summary 预设报 turn_summary、render_error、history_loaded,以及发送失败的 send_result(口径见 METRICS.md 的「发送失败」)。
拍平规则:载荷字段原样展开,嵌套对象的字段名用下划线连接(content.text 展开成 content 加下划线加 text);信封字段
eventId、seq、pageVisitId、schemaVersion、ts、sessionId、runId 一并带上;接入方属性放在最底下,与载荷或信封同名时加前缀 attr 和下划线保留。
flattenChatkitEvent(event) 单独导出,自己写上报层时也能用。
同一个 eventId 只报一次。开发模式下,没给 reportError、属性值会被丢掉或疑似用户输入、页面隐藏时 track 返回 Promise 又没有 beacon,都会在控制台提示一次。
自己写上报层
不用 createTelemetry 时,在 onEvent 里映射成自己的事件名。页面关闭时浏览器不会等异步请求,
run_abandoned(page-hidden)与同时发出的 turn_summary 要用 navigator.sendBeacon 或埋点 SDK 的离开页面通道发出。
开发调试
@volcengine/chatkit/devtools 是开发调试用的子入口,不进根入口的体积:
createTurnMetrics():在页面里按 METRICS.md 的口径实时归并指标,把它的record接在onEvent上;<ChatkitTelemetryInspector metrics={metrics} />:浮层,显示核心指标与最近的事件,生产构建不渲染。
验收接入是否完整:Mock 适配器的 telemetry-acceptance 场景按发送次数轮流跑出每一种结论,
跑完后把上报函数收到的调用交给 @volcengine/chatkit/testing 的 verifyTelemetry,它检查每种结论都有汇总、属性齐全、没有对话内容、没有重复上报。