适配器契约
本文档定义宿主系统如何把自己的事件流接到 @yingdao-ai/agent-observability。
目标:让所有宿主系统按同一套生命周期接入,而不是各自发明一套约定。
边界划分
| 层 | 职责 |
|---|---|
| SDK 核心 | run / generation / tool span / score 状态机、Langfuse transport、脱敏、flush gate |
| adapter(宿主侧) | 把宿主事件映射到 SDK 调用、提供 run id / user id / tags / metadata、提供鉴权和 environment |
SDK 不应该知道:宿主事件名、宿主 session token、宿主代理地址、宿主的 plugin/hook 系统。
最小生命周期
宿主必须保证一条 run 至少按这个顺序闭合:
startRun
→ startGeneration
→ appendText / appendReasoning
→ endGeneration
→ startTool / endTool
→ score
→ endRun
→ gate.waitAll允许省略某些步骤,但不允许跳过收尾:
- 没有 tool 调用,可以不
startTool/endTool - 没有 reasoning,可以不
appendReasoning - 没有 score,可以不
score - 但必须有
endRun - 进程回收前必须等待
gate.waitAll()
宿主必须提供的信息
Run 唯一标识
宿主要提供稳定的 RunInput.id:
- 同一次用户请求内稳定
- 能唯一对应一条 trace
- 子 session 如果要并到父 trace,adapter 自己维护 alias,并调用
observer.link(childId, rootId)
名称
RunInput.name 建议:
- 直接用 agent 类型或 agent 名称
- 不要把临时 UI 文案当 name
上下文
宿主负责决定是否写入 userId、tags、metadata。
延迟可得的上下文,可以:
ts
// 方式一:先 set context,再 startRun
observer.context.set(id, ctx)
const run = observer.startRun({ id, name })
// 方式二:run 过程中随时更新
run.setUserId("user-1")
run.setTags(["tag"])
run.setMetadata({ key: "val" })Generation 约定
开始时机
宿主应在"已知模型和 LLM 调用输入"时调用 startGeneration():
- 最小要求:
model - 尽量传
input
文本流
- assistant 输出增量 →
run.appendText() - reasoning 增量 →
run.appendReasoning() - 不要把用户原文再写回 output
结束时机
宿主应在 LLM 调用完成时调用 endGeneration(),并尽量带上 usage、cost。
若未调用 endGeneration() 而 run 已结束,SDK 会自动兜底结束 generation,但这只是保底,不推荐。
Named Generation
辅助调用走 startNamedGeneration(key, input):
- 适用于 title / summary / route / classify 等场景
key在单条 run 内稳定- 同一个
key再次开始时,SDK 自动结束旧 generation
Tool Span 约定
开始时机
ts
run.startTool({
id: callId, // 必须稳定且与 endTool 一致
name: toolName,
input,
})结束时机
ts
// 成功或失败都要调用
run.endTool(callId, { output, error })callId必须前后一致- 不要因工具报错就漏掉
endTool
若 run 结束时 tool 未结束,SDK 自动标为:
span leaked: run ended before tool completed这说明 adapter 生命周期接法有问题。
Score 约定
score 是 run 级别附加信息,adapter 不需要传 environment(来自 transport)。
宿主只需决定:
- 何时打分
- 用什么 score name
- score 类型(数值、布尔、类别)
推荐命名:
| 场景 | name |
|---|---|
| 结果状态 | agent_outcome |
| 执行时长 | session_duration_ms |
| 工具错误数 | tool_error_count |
并发和子 Session
若存在根 run + 多个子 agent / 子 session:
ts
observer.link(childId, rootId)
// 之后 observer.getRun(childId) 返回根 run要求:
- 子 session 不单独开新 trace,除非产品明确要求拆开
- 一个 child id 只能指向一个 root id
退出与 Flush
ts
await observer.endRun(id) // 结束 run,触发 transport flush
await observer.gate.waitAll() // 等待所有数据上报完成gate.waitAll() 是 adapter contract 里最不能省的一步。
错误处理要求
adapter 必须满足两个原则:
- 观测失败不能影响主业务链路
- 生命周期错误不能悄悄吞掉
建议:
- transport 网络错误打日志,不抛回主流程
- adapter 发现缺少
callId、sessionId、事件顺序错乱时,至少打 warn
新宿主接入 Checklist
新宿主接入前,至少确认:
- [ ] 有稳定的 run id
- [ ] 能识别 LLM 调用开始和结束
- [ ] 能识别 assistant text delta 和 reasoning delta
- [ ] 能识别 tool begin/end
- [ ] 能拿到 user/tags/metadata
- [ ] 退出前能
await observer.gate.waitAll() - [ ] transport 能提供稳定的
environment()