数据截至 (上游 commit d3b797b4c5cc)
评测↔可观测闭环、巧妙之处、边界与对比
本章讲什么: 前三章把评测和可观测分别讲透了。它们不是两座孤岛——CozeLoop 用两条桥把"线上发生 的事"和"离线评测的事"接成一个闭环。本章讲清这两条桥,再统一收口全局的巧妙之处、边界与横向对比。
1. 闭环全景:为什么要接起来
光有评测:你只能评"自己造的测试集",脱离真实流量。 光有可观测:你只能看"线上发生了什么",但没有"好不好"的判断。
接起来才完整:用真实线上 trace 当评测数据,用评测打分给线上质量做体检。两条桥:
可观测(线上 trace) 评测(离线/在线)
│ │
│ 桥①:trace_export │
│ 选中一批 span ─导成─▶ 评测数据集 ─────▶ 拿去跑实验
│ │
│ 桥②:在线评测 task │
│ 采样线上 span ─触发评估器打分─┐ │
│ ▼ │
└◀── auto_evaluate annotation 回写 span ┘(分数贴回 trace)
2. 桥① trace → 评测数据集
ITraceExportService.ExportTracesToDataset(trace_export_service.go:95)。
输入是一批 SpanID{TraceID, SpanID} + 字段映射(FieldMappings)+ 目标数据集配置(新建或追加)。它把选中 span 的输入/输出按映射抽成数据集的列,写入评测数据集。请求结构 ExportTracesToDatasetRequest(:44)里 ExportType(append/overwrite,:39-42)和 FieldMappings(:54)是关键——用 trace 的真实字段对齐数据集 schema,和评测侧的 FieldConf 映射思路一脉相承。
还有 PreviewExportTracesToDataset(:96)让你导之前先看几条,避免映射错了批量污染数据集。
这条桥的价值:把"线上真实失败 case"一键沉淀成回归测试集——这是 LLM 应用迭代的核心工作流。
3. 桥② 在线评测 → annotation 回写
这是更深的耦合。在线评测(observability 的 task 域)对线上 span 持续采样并触发评估器,结果写回 span。
流程
TaskCallbackServiceImpl.AutoEvalCallback(task_callback.go:59)是回调落点:评估器对某 span 打完分后,回调拿着 AutoEvalEvent(含 trace_id/span_id/score/reason/evaluator_record_id/expt_id)做三件事:
// 示意,非源码:AutoEvalCallback 主干(语义取自 task_callback.go:59-139)
for _, turn := range event.TurnEvalResults {
span := getSpan(turn.traceID, turn.spanID) // ① 把原始 span 捞回来
updateTaskRunDetailsCount(...) // ② Redis 计数(成功/失败统计)
span.AddAutoEvalAnnotation(taskID, evaluatorRecordID,
evaluatorVersionID, score, reasoning, ..., exptID) // ③ 造一条 auto_evaluate 标注
traceRepo.InsertAnnotations(... AnnotationTypeAutoEvaluate) // 落库
}
annotation 是什么
annotation(loop_span/annotation.go)是"贴在 span 上的结构化标签"。类型枚举(annotation.go:42-48)很说明问题:
| AnnotationType | 来源 |
|---|---|
auto_evaluate | 自动评测分数(桥②写的) |
manual_feedback | 人工反馈 |
manual_evaluation_set / manual_dataset | 人工标注入集 |
coze_feedback / openapi_feedback | 平台/接口反馈 |
AutoEvaluateMetadata(annotation.go:78)把这条标注反向连回评测世界:带 EvaluatorRecordID、EvaluatorVersionID、ExptID、ExptTemplateID。于是在 trace 详情页看到一个自动评分,能一路点回"是哪个实验、哪个评估器版本、哪条评估记录"打的。
还能修正
AutoEvalCorrection(task_callback.go:143)+ AnnotationCorrection(annotation.go:64,类型 llm/manual)支持对自动评分做人工纠偏,纠偏值带 UpdatedBy/UpdateAt。这和第一章"行分优先取 Correction"前后呼应——人工修正在评测与可观测两侧是统一的一等公民。
4. 全局巧妙之处(跨子系统)
| 妙在哪 | 一句话 | 锚点 |
|---|---|---|
| 统一"大字段外移" | span 与评估输入都用 omitted/对象存储策略控制主表行宽与消息体 | span.go:195、expt_run_item_turn_impl.go:1090 |
| 评测自身可观测 | Prompt 评估器执行时也开 trace span,打分链路在 ob 里能回放 | evaluator_source_prompt_impl.go:84 |
| 容错优先于洁癖 | LLM 输出四级降级解析、CK 写入重试容许重复,都选"尽量出结果" | evaluator_source_prompt_impl.go:373、spans.go:54 |
| 人工修正贯通两端 | Correction 在评测聚合与 annotation 两侧都覆盖模型分 | expt_score_calculator.go:190、annotation.go:64 |
| 复用成熟抽象 | trace 落库直接套 OTel Collector 管道形态 | ingestion.go:33 |
| 仅记录型 target | 在线评测不重跑被测对象,复用 trace 里已有输出 | target.go:85 |
| 事件驱动 + 幂等 | 实验调度与 span 落库都走 MQ + 幂等键,可中断可重试 | expt_run_scheduler_mode_impl.go |
5. 边界与局限(诚实)
- 两模块不直接互调(根
ARCHITECTURE.md不变量:模块间不直接互调)。评测↔可观测的耦合走 RPC + MQ + annotation,而非进程内调用——好处是边界清晰,代价是闭环链路更长、最终一致而非强一致。 - 强依赖外部组件:MySQL/ClickHouse/Redis/RocketMQ 缺一不可;Code 评估器还需独立 FaaS 服务(
python-faas/js-faas镜像)。本地最小化跑不起来。 - 落库异步:trace 上报成功不代表立即可查;在线评测分数回写也有延迟。
- LLM-as-judge 固有不确定性:四级解析能兜底"取到分",但兜不了"分对不对"——裁判模型本身的偏差是方法论边界,非工程能解。
- 开源版 vs 商业版:代码里大量
Commercial*错误码与 adapter(根ARCHITECTURE.md的 adapter 模式)暗示部分能力在商业版;开源版是核心子集。
6. 横向对比(同 shelf 兄弟项目)
CozeLoop 在 evals-observability 这一格的定位,相对常见兄弟项目:
| 维度 | CozeLoop | 典型纯评测框架(如 evals/promptfoo 类) | 典型纯 trace 平台(如 Langfuse/Phoenix 类) |
|---|---|---|---|
| 评测 | ✅ 四类评估器 + 实验调度引擎 + 加权聚合 | ✅ 核心 | 弱/无 |
| 可观测 | ✅ OTel 风格管道 + CK 存储 | 弱/无 | ✅ 核心 |
| 闭环 | ✅ trace↔数据集↔annotation 双向 | 无 | 部分(trace→dataset 有,自动评测回写不一定) |
| 形态 | 平台(Go 后端 + React 前端 + 部署件) | 多 为库/CLI | 平台 |
| 在线评测 | ✅ 采样线上 span 自动打分回写 | 无 | 部分 |
一句话取舍:CozeLoop 不是"最轻的评测库"也不是"最专的 trace 平台",它的差异化是把两者做进同一平台并打通闭环——代价是重(依赖多、部署复杂),收益是"线上→评测→改进"的工作流不出系统。
7. 全局代码地图(总导航)
| 子系统 | 主题 | 文件 | 符号 |
|---|---|---|---|
| 评测 | 实验聚合根 | modules/evaluation/domain/entity/expt.go | Experiment、ExptStatus_* |
| 评测 | 执行引擎 | modules/evaluation/domain/service/expt_run_item_turn_impl.go | DefaultExptTurnEvaluationImpl.Eval |
| 评测 | 调度器 | modules/evaluation/domain/service/expt_run_scheduler_mode_impl.go | NewSchedulerMode、ExptSubmitExec |
| 评测 | 行分聚合 | modules/evaluation/domain/service/expt_score_calculator.go | CalculateWeightedScore |
| 评测 | LLM 裁判解析 | modules/evaluation/domain/service/evaluator_source_prompt_impl.go | parseContentOutput 四策略 |
| 评测 | Code 沙箱 | modules/evaluation/infra/runtime/ | http_faas_runtime.go |
| 可观测 | 上报/查询 | modules/observability/domain/trace/service/trace_service.go | IngestTraces、GetTrace、GetMetrics |
| 可观测 | 落库管道 | modules/observability/domain/trace/service/ingestion.go | IngestionService |
| 可观测 | span 实体 | modules/observability/domain/trace/entity/loop_span/span.go | Span |
| 可观测 | CK 存储 | modules/observability/infra/repo/ck/spans.go | SpansCkDaoImpl |
| 闭环 | trace→数据集 | modules/observability/domain/trace/service/trace_export_service.go | ExportTracesToDataset |
| 闭环 | 评分回写 | modules/observability/domain/task/service/task_callback.go | AutoEvalCallback、AutoEvalCorrection |
| 闭环 | annotation | modules/observability/domain/trace/entity/loop_span/annotation.go | AnnotationType*、AutoEvaluateMetadata、AnnotationCorrection |
至此,CozeLoop 的 evals-observability 全貌已闭环:评测把 target×evaluator 跑遍数据集出分(ch.1-2),可观测把线上链路记成 trace 存查(ch.3),两条桥让线上与离线互相喂养(ch.4)。