t.calledTool、t.maxTokens 和耗时判定都读取 Adapter 在 send 中返回的 Turn(events 与 usage),见接入你的 Agent。
OTel span 用于生成 niceeval view 中的调用瀑布图。瀑布图按轮显示模型调用、工具执行、耗时和 token,帮助定位评估用例失败或轮次变慢的具体步骤。
如果你的应用已经在发 OTel trace——AI SDK 的 telemetry、LangGraph 的 LangSmith 导出、OpenLLMetry / OpenInference 自动埋点,或自己按 GenAI 语义埋的点——那瀑布图的数据你已经在生产了:让应用把 span 也发给 NiceEval 一份即可,应用代码一行不改,仍是无侵入(见 Tier)。
原理(一段话)
NiceEval 运行时启动本机 OTLP 接收器。应用发送的 span 会归属到对应send 轮次,归一成 GenAI 语义后写入 EvalResult.trace,并由 npx niceeval view 显示为瀑布图。span 只用于瀑布图,不进入事件流,也不参与断言。埋点缺失、span 迟到或丢批只影响瀑布图完整性,不影响判定。
接法
1. adapter 侧——send 照常写(事件映射还是你的映射),只多一行:把本轮的 traceparent 随请求带过去:
uiMessageStreamAgent 总会自动把 ctx.telemetry.headers 并入请求头。
2. 向应用提供端点。 NiceEval 的接收端点属于启动期配置,不从 send 传递。标准 OTel SDK 只在进程启动时读取一次 OTEL_* 环境变量。按部署形态选择配置方式:
-
你自己长驻的服务(最常见):用固定端口模式,在
niceeval.config.ts里钉住接收端口——写了这个配置就等于打开了 OTel 接入:服务启动时一次性配OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces,之后跑多少次评估用例都不用改。代价:端口共享意味着同一台机器同时只能跑一个 niceeval 进程;OTel Collector 扇出场景同理指向这个固定端点。port被其它进程占用时 NiceEval 会直接报错提示换一个空闲端口,不会静默失败。 报给应用的接收端 hostname 默认是127.0.0.1;docker 型 sandbox tracing 需要host.docker.internal、或配了隧道的远程接入需要别的 hostname 时,加host覆盖:telemetry: { host: "host.docker.internal", port: 4318 }。这两个字段是 NiceEval 里配置 OTLP 接收的唯一入口,不读环境变量。 -
子进程 / 由 NiceEval 拉起的进程(CLI 型 agent):什么都不用做。
ctx.telemetry.env(标准OTEL_*环境变量,ready-to-spread)注入进程环境,每次 run 是新进程、读到新端点。
- AI SDK
- LangGraph / LangChain
- OpenLLMetry
- OpenInference
- 自己埋的 gen_ai
推荐官方 OTel 集成(exporter 走标准 OTel Node SDK,endpoint 指向 NiceEval(注入的 env 或固定端口)。可跑示例:应用侧埋点见
@ai-sdk/otel,产标准 GenAI 语义);老的 experimental_telemetry(ai.*)也能画:examples/zh/origin/ai-sdk-v7(src/backend/otel.ts,官方 @ai-sdk/otel),接入后的完整评测项目见 examples/zh/tier1/ai-sdk-v7。把 span 归属到 Turn
并行跑多条评估用例时,同一个接收器会同时收到多条会话的 span,NiceEval 按两条路把它们归到各自的轮:- traceparent(推荐,并发安全):
send发请求时把ctx.telemetry.headers(W3C trace context,每轮一个新traceparent)spread 进请求头。应用的埋点支持 context 传播的话(标准 OTel HTTP 服务端埋点都支持),本轮 span 自动挂到 NiceEval 给的 trace 下,按 traceId 精确归属。 - 时间窗口(兜底):应用不传播 trace context 时,按 send 前后的时间窗归属。窗口只在串行下可靠,所以这种情况 NiceEval 会把这个 agent 的轮次串行执行并在日志里提示,不会静默混流;一旦确认 traceparent 生效,自动恢复并发。
SimpleSpanProcessor(或每轮 flush)——BatchSpanProcessor 的缓冲会让 span 跨轮迟到,瀑布图偶发缺尾巴多半是它。
保留现有 OTel 后端并双发
应用多半已经把 trace 发给自己的观测后端(Langfuse / SigNoz / 生产 collector)。接 NiceEval 不需要换后端、也不需要第二套埋点:TracerProvider 支持挂多个 SpanProcessor——同一批 span,两个出口:用语义映射控制瀑布图内容
NiceEval 在绘制瀑布图前把每条 span 归一到 GenAI 语义。映射读取gen_ai.operation.name(标准操作名)和归一后的 kind(语义角色):
应用直接按 GenAI semconv 埋点(上文「自己埋的 gen_ai」tab)时这一切自动成立;主流格式(AI SDK、LangSmith、OpenLLMetry / OpenInference)的常见形状也在通用兜底的识别范围内。
私有埋点:自己写映射
埋点是应用私有形状、通用兜底认不出时,两条路:- 改埋点(推荐):在应用侧给 span 补一个
gen_ai.operation.name属性——一行改动,你自己的观测后端也同样受益。 - 写
spanMapper:不方便动应用时,在 agent 上声明一个纯函数,渲染前把私有 span 翻成上表的语义。tagSpan(把判定写回 span,原有属性只增不改)和heuristicTag(通用兜底判定)都从niceeval/adapter导出:
niceeval/adapter 导出的 mapCodexSpans 是一个可参考的 spanMapper 实现。spanMapper 只影响观测展示;映射错误会导致瀑布图分类或着色不准确,不影响断言。
边界
- 断言数据全部来自
send。工具调用需要映射进events(使用内置转换器或手写映射,见编写 Send);用量需要包含在send返回值中。Span 中存在但events中缺失的数据不会参与断言。 - 多轮会话、HITL 不归 span 管。span 没有”等人输入”语义,会话续接也是应用协议的事——这两样照常在
send里做(会话续接见写 send,HITL 概念见 HITL)。 - 收不到 span 会有提示。整个 run 0 span 通常是端点没接上(env 没注入、服务没重启),NiceEval 会在日志里提示;瀑布图为空,断言照常判。
相关阅读
- 接入你的 Agent ——
send事件映射与断言的数据来源。 - 编写 Send —— 手写 Adapter 的完整教程,第六步是本页的 Adapter 侧配置。
- 事件流参考 —— 断言读取的事件结构。