跳转到主要内容
OTel 接入不改变断言的数据来源t.calledToolt.maxTokens 和耗时判定都读取 Adapter 在 send 中返回的 Turneventsusage),见接入你的 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 是新进程、读到新端点。
3. 应用侧——按你的埋点生态各自几行配置:
推荐官方 OTel 集成(@ai-sdk/otel,产标准 GenAI 语义);老的 experimental_telemetryai.*)也能画:
exporter 走标准 OTel Node SDK,endpoint 指向 NiceEval(注入的 env 或固定端口)。可跑示例:应用侧埋点见 examples/zh/origin/ai-sdk-v7src/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 生效,自动恢复并发。
应用侧要及时导出:瀑布图在意的是”这一轮的 span 及时到齐”,用 SimpleSpanProcessor(或每轮 flush)——BatchSpanProcessor 的缓冲会让 span 跨轮迟到,瀑布图偶发缺尾巴多半是它。

保留现有 OTel 后端并双发

应用多半已经把 trace 发给自己的观测后端(Langfuse / SigNoz / 生产 collector)。接 NiceEval 不需要换后端、也不需要第二套埋点:TracerProvider 支持挂多个 SpanProcessor——同一批 span,两个出口:
不方便改应用代码的话,OTel Collector 扇出——应用只发给 collector,collector 配两个 exporter(你的后端 + NiceEval 的固定端点)。代价是多运维一个组件。

用语义映射控制瀑布图内容

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 侧配置。
  • 事件流参考 —— 断言读取的事件结构。