Langfuse Evaluators v2 数据模型深度解析:从 eval_templates 到 Evaluator / Rule / Rule Assignment 的架构演进
【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse
本指南以 web/src/features/evals/v2/CLAUDE.md 为骨架,深入剖析 Langfuse 新一代评测系统(Evaluators v2)的数据模型设计、迁移路径与工程实践。读者将掌握新旧两代数据模型的差异、Evaluator/Rule/Rule Assignment三者间的 n:m 关联结构、评测执行元数据的兼容策略,以及该模块的测试规范与源码组织方式。
为什么需要 Evaluators v2
Langfuse 的评测(Evals)能力早期依赖一套"模板 + 作业配置"的模型。在旧设计中,一个可复用的评测器定义与它的运行规则被拆散在不同表中,导致"评测器"这一核心业务实体缺乏一等公民地位:
eval_templates:负责存放评测器的定义(提示词、模型、输出 schema、源码等);job_configurations:负责存放"变量映射(variable mapping)"以及"该评测器要跑在哪些事件上(which events it runs against)"。
这种拆分带来的问题是:模板与运行配置是一对多、却又通过外部引用强耦合的关系,当需要把"一个评测器"同时挂到多条运行规则上、并为每条规则定制变量映射时,关系表达不够自然,也难以在 UI 中形成清晰的"评测器详情"视图。
Evaluators v2 用三个实体重构了这一领域模型(参见 packages/shared/prisma/schema.prisma):
- Evaluator(评测器):评测能力的定义本体;
- Rule(规则):旧
job_configuration的继承者,负责描述"在什么条件下跑、按什么采样率跑"; - Rule Assignment(规则关联):处理 Evaluator 与 Rule 之间 n:m 关系的关联表。
新数据模型详解
Evaluator:评测器定义本体
Evaluator模型(schema.prisma)落库为evaluators表,字段包括:
| 字段 | 说明 |
|---|---|
name | 评测器名称 |
type | 评测类型,取值见EvalTemplateType枚举:LLM_AS_JUDGE(LLM 作为裁判)或CODE(代码评测) |
description | 可选描述 |
createdByUserId | 创建者,删除用户时置空(onDelete: SetNull) |
blockedAt/blockReason/blockMessage | 阻断状态:当 LLM 连接鉴权失败、账单耗尽、端点不可达、默认评测模型缺失或模型配置无效时,评测器会被标记为BLOCKED |
评测器的"定义内容"本身是可版本化的,存放在evaluator_versions表(EvaluatorVersion,schema.prisma)中。每个版本通过@@unique([evaluatorId, version(sort: Desc)])约束实现单调递增的版本号,并保存:
prompt/promptMessages/vars:LLM-as-Judge 的提示词体系;provider/model/modelParams:模型配置;outputDefinition:输出(评分)定义;variableMapping:变量映射(JSON);sourceCode/sourceCodeLanguage:代码评测的源码,sourceCode为VarChar(262144),语言枚举PYTHON/TYPESCRIPT。
在服务端,评测器定义通过 discriminated union 进行强校验(web/src/features/evals/v2/server/evaluators/evaluatorTypes.ts):LLM_AS_JUDGE分支要求promptMessages、modelConfig、outputDefinition,并会从提示词中自动抽取变量(extractVariables)写入vars;CODE分支则要求sourceCode与sourceCodeLanguage,且不允许携带variableMapping(z.never().optional()),因为代码评测的变量映射语义与 LLM 评测不同。
Rule:旧 job_configuration 的继承者
EvaluationRule(schema.prisma)落库为evaluation_rules表,保留了JobConfiguration的核心运行时语义:
| 字段 | 说明 |
|---|---|
name | 规则名称 |
status | JobConfigState枚举:ACTIVE/INACTIVE |
targetObject | 目标对象类型,v2 中通过过滤器表达,规则的targetObject字段已被标记为 deprecated |
filter | JSON 形式的条件过滤器,决定"哪些事件会被评估" |
sampling | Decimal 采样率,取值范围 0..1,控制作业实际执行的比例 |
delay | 执行延迟(毫秒) |
timeScope | 时间范围,默认["NEW"] |
从类型层看(ruleTypes.ts),RuleMetadataSchema将sampling约束为z.number().min(0).max(1),filter为singleFilter数组;CreateRuleSchema中targetObject的默认值为EVENT,并明确注释:"modern rules are event rules and experiment scope is expressed through filters"(现代规则即事件规则,实验(experiment)范围通过过滤器表达)。
Rule Assignment:n:m 关联表
EvaluationRuleEvaluatorAssignment(schema.prisma)落库为evaluation_rule_evaluator_assignments表,是 v2 数据模型相对旧版最关键的结构性变化:
- 同时持有
evaluationRuleId与evaluatorId两个外键,均级联删除; @@unique([evaluationRuleId, evaluatorId])保证同一规则下每个评测器只出现一次;- 每个关联可以携带独立的
variableMapping(JSON),这意味着"同一个评测器挂在多条规则上时,每条规则都能有各自的变量映射"; - 额外的
evaluatorId索引用于支撑"按评测器反查其关联的所有规则"。
这套设计把旧模型中"模板定义 + 作业配置"的一对多耦合,升级为"评测器 ⇄ 规则"的干净多对多关系,也正是文档中"association table handling the n:m relationship"所指的内容。
新旧数据模型对照
| 维度 | v1(旧版) | v2(新版) |
|---|---|---|
| 评测器定义 | EvalTemplate(eval_templates) | Evaluator+EvaluatorVersion(evaluators / evaluator_versions) |
| 运行配置 | JobConfiguration(job_configurations) | EvaluationRule(evaluation_rules) |
| 关联方式 | JobConfiguration 通过evalTemplateId外键指向模板 | EvaluationRuleEvaluatorAssignment关联表(n:m) |
| 变量映射 | 存放在 JobConfiguration.variableMapping | 存放在 Assignment.variableMapping,可逐规则定制 |
| 版本管理 | EvalTemplate 以@@unique([projectId, name, version])约束版本 | EvaluatorVersion 以@@unique([evaluatorId, version])约束版本 |
| 执行元数据 | 仅记录job_configuration_id | 记录evaluator_id+evaluation_rule_id(+ assignment id) |
需要说明的是,旧表并未被删除:EvalTemplate、JobConfiguration、JobExecution仍保留在 schema.prisma 中,用于支撑存量数据与兼容旧执行链路。
评测执行的元数据:job_configuration_id 与 evaluator_id 的兼容
原文档特别强调了一个向后兼容细节:过去捕获的评测执行 trace 只记录了job_configuration_id,只有新的运行才会记录evaluator_id和evaluation_rule_id。
在源码中,这一元数据约定被集中定义在 packages/shared/src/features/evals/evalExecutionMetadata.ts,完整键集合包括:
EVALUATOR_ID: "evaluator_id", EVALUATOR_VERSION_ID: "evaluator_version_id", EVALUATOR_TEST: "evaluator_test", EVALUATION_RULE_ID: "evaluation_rule_id", EVALUATION_RULE_ASSIGNMENT_ID: "evaluation_rule_assignment_id", JOB_EXECUTION_ID: "job_execution_id", JOB_CONFIGURATION_ID: "job_configuration_id", TARGET_TRACE_ID: "target_trace_id", TARGET_OBSERVATION_ID: "target_observation_id", TARGET_DATASET_ITEM_ID: "target_dataset_item_id",其中evaluator_test标记该次执行是否为测试运行,evaluator_version_id精确到版本,evaluation_rule_assignment_id精确到关联记录——这些字段共同支撑"这次评分是由哪个评测器、哪个版本、哪条规则、哪个关联产生的"完整追溯链。
为了让新旧执行记录在查询层面统一可检索,事件表为两者都暴露了过滤列:Evaluator ID(e.evaluator_id)与Rule ID(e.evaluation_rule_id),见 packages/shared/src/eventsTable.ts。而查询层(packages/shared/src/features/query/dataModel.ts)使用coalesce(nullIf(evaluator_id, ''), evaluation_rule_id)将新字段回退到旧字段:当某条记录缺少新的evaluator_id(即旧运行)时,自动用evaluation_rule_id兜底,从而让新旧执行在同一个过滤器下都能被命中。
评测器版本管理与创建/更新流程
v2 将"评测器定义"与"评测器元信息"分层:Evaluator表保存name/type/description等元数据与阻断状态,而EvaluatorVersion表保存每一版定义内容。创建与更新的入参 schema 定义在 evaluatorTypes.ts:
CreateEvaluatorSchema:projectId+ 可选evaluatorId(用于从已存在评测器创建新版本)+definition;UpdateEvaluatorSchema:projectId+evaluatorId+definition,同时支持部分更新(PatchEvaluatorInput)。
版本分页使用 base64url 编码的游标(evaluatorTypes.ts):游标 JSON 形如{ v: 1, version: 42 },解码失败会抛出InvalidRequestError;EvaluatorVersionsSchema默认limit = 50。定义内容在持久化时会区分数据库 NULL 与 JSON null(Prisma.DbNullvsPrisma.InputJsonValue),参见 evaluatorRepository.ts 的versionData函数。
前端对应的版本体验组件包括 EvaluatorVersionHistorySheet(版本历史抽屉)与 EvaluatorVersionConflictDialog(版本冲突提示);评测器创建/编辑走 EvaluatorSetupEditor 的三步向导:DefinitionStep(定义)→NameStep(命名)→VariableMappingStep(变量映射),每一步都有独立的 Container 组件负责状态管理。
Rule 的生命周期与激活语义
规则模块的 tRPC 路由(web/src/features/evals/v2/server/rules/ruleRouter.ts)覆盖了规则的完整生命周期:
list/get/filterOptions/reusableFilters:规则的查询与筛选;create/update/delete/deleteMany:增删改;setEnabled/setManyEnabled:启用/停用(JobConfigState.ACTIVE/INACTIVE);attach/detach:为规则挂载/卸载评测器(写入关联表);createOrAttachFromEvaluatorFilters:从一个评测器的过滤器一键派生规则;recentExecutions/costByRuleIds:最近执行与成本估算;suggestName:根据过滤器与采样率自动生成规则名。
规则激活是 v2 的一个设计亮点。SetRuleEnabledSchema(ruleTypes.ts)允许在确认激活时一并调整采样率,注释说明了原因:激活对话框可以在确认时微调采样,二者落在同一个事务与同一条审计日志中。与之配套的前端是 ActivationConfirmationDialog 及其成本估算视图 ActivationCostEstimateView;激活前的成本预估由服务端 activationCostService.ts 计算,其入参 schema(evaluatorTypes.ts)包含过滤器、采样率、是否扣除已知测试运行成本(knownTestRunCostUsd),并对时间范围做了约束:起始不能早于 6 个月前,结束不能晚于今天。
规则的过滤器还支持"复用":reusableFilters过程返回可复用的过滤器预设;ruleFilterMatching.ts 提供filterStateKey/filtersMatch,通过稳定序列化(数组排序 +stableJsonStringify)实现两个过滤器状态的等价比较,以及fallbackRuleName将过滤器渲染为可读的默认规则名(空过滤器时返回 "All observations",多条件用·连接并截断到 200 字符)。
n:m 关联在前端与批量操作中的体现
关联表的存在直接影响了 UI 与批量操作的设计:
- 按规则视角:RuleSetup 向导依次引导用户完成
RuleNameStep(命名)→RuleFilterStep(过滤器)→RuleEvaluatorsStep(选择评测器并逐一配置变量映射),并通过 RuleEvaluatorCostEstimate 展示成本估算; - 按评测器视角:EvaluatorRuleRelationships 展示某个评测器被哪些规则引用,对应服务端
listRulesForEvaluator过程; - 批量删除限制:仓库中的
batchEligibleEvaluatorWhere(evaluatorRepository.ts)明确规定,带有TRACE/DATASET目标对象规则的评测器不能参与观察级批量删除,因为"trace/dataset 关联携带规则特定的映射,仅凭评测器 ID 无法在观察批量中解析"。
这种"一个评测器可挂多条规则、每条规则可挂多个评测器"的能力,正是 n:m 关联表相比旧版外键设计的价值所在。
API 层:权限、审计与测试运行
权限与审计
服务端路由全部基于 tRPC 的protectedProjectProcedure构建(evaluatorRouter.ts),每个过程先调用throwIfNoProjectAccess校验 RBAC scope:评测器使用evaluator:read等 scope,规则使用evaluationRule:read等 scope。审计日志方面,评测器写操作沿用旧的evalTemplate资源类型(evaluatorRouter.ts),而规则写操作使用JOB_CONFIGURATION_AUDIT_LOG_RESOURCE_TYPE(ruleRouter.ts),可以看到审计资源类型上同样保留了新旧迁移的痕迹。
测试运行
test过程(evaluatorRouter.ts)接受observationId/traceId/startTime与一份完整的评测器定义,针对真实样本执行一次评测,无需先落库正式版本。前端对应 EvaluatorTestPanel 与 TestSection,支持选取样本观察(SampleObservationSelectorBase)、过滤样本(ObservationFilterBuilder)、重跑(TestRerunButton)与查看结果 trace(TestResultTraceActions)。测试执行的元数据中EVALUATOR_TEST标记会区分测试运行与正式运行。
列表与筛选
评测器列表支持按name/creator/model进行字符串过滤,按status/type进行选项过滤,并支持分页与排序(name/type/createdAt/updatedAt),schema 校验在 evaluatorTypes.ts;规则列表则支持name/creator/enabled/upgradeRequired过滤,见 ruleTypes.ts。评测器画廊(listGallery)采用基于createdAt + id的游标分页,便于在 EvaluatorGalleryView 中无限滚动浏览模板。
测试规范:避免无意义的 tautological 测试
原文档在 Testing 一节给出了两条明确约束,这也是 v2 模块工程规范的核心:
- 不要编写同义反复(tautological)的 React 客户端测试:即测试逻辑只是在重复实现本身,例如断言"点击按钮后 state 变成了按钮 onClick 里写死的值"这类不验证真实行为的用例;
- 不要测试像素定位:避免对布局坐标、样式位移这类与业务行为无关的断言;
- 只有当组件测试能约束有意义的行为时才添加:测试的价值在于守护真实业务规则,而非凑覆盖率。
从 v2 目录中已有的测试文件可以看到这一规范的落地形态:行为导向的测试如 EvaluatorSavedDialog.clienttest.tsx(校验保存对话框的交互行为)、RuleNameStep.clienttest.tsx、EvaluatorVersionHistorySheet.clienttest.tsx;纯函数逻辑则有配套的单元测试,如 prepareModernRuleVariableMapping.clienttest.ts、buildSampleQueryFilters.clienttest.ts、managedTemplatesCatalog.clienttest.ts。服务端侧则通过 servertest 覆盖事务与成本计算等关键路径,例如 activationCostService.servertest.ts。
小结
Evaluators v2 是 Langfuse 评测体系的一次数据模型重构:用Evaluator(含版本化定义)、Rule(运行条件与采样)和Rule Assignment(n:m 关联,逐关联变量映射)替换旧的EvalTemplate+JobConfiguration组合,同时通过执行元数据双写(evaluator_id/evaluation_rule_id与兼容旧记录的job_configuration_id)和查询层coalesce兜底,保证了新旧执行记录的统一可观测。如果你需要深入某一部分,建议从 web/src/features/evals/v2/server/evaluators/evaluatorTypes.ts 与 web/src/features/evals/v2/server/rules/ruleTypes.ts 两份 schema 定义入手,再对照 packages/shared/prisma/schema.prisma 的物理表结构与 packages/shared/src/features/evals/evalExecutionMetadata.ts 的元数据约定,即可完整还原这套模型的实现全貌。
【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考