Langfuse 评估系统(Evals)架构深度解析:变量映射、Observation 过滤与 v2 数据模型

发布时间:2026/9/10 1:13:39
Langfuse 评估系统(Evals)架构深度解析:变量映射、Observation 过滤与 v2 数据模型 Langfuse 评估系统Evals架构深度解析变量映射、Observation 过滤与 v2 数据模型【免费下载链接】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/langfuseLangfuse 是面向 LLM 应用的观测与评估平台其评估Evals子系统负责把生产环境中的 trace、span、generation 等观测数据送入裁判LLM-as-Judge 或代码评估器并产出分数。本文以仓库内 web/src/features/evals/AGENTS.md 为骨架结合 web/src/features/evals/v2/AGENTS.md 与源码实现系统讲解三条核心设计主线数据到 Prompt 变量的映射机制、基于 Observation Filters 的样本选择与内存过滤约束、以及从job_configurations到 Evaluator/Rule 的 v2 数据模型迁移。读完本文你将理解评估器在 Langfuse 中从配置、匹配到执行的完整链路以及新旧两代数据模型为何能共存。一、评估系统的三条核心主线从 AGENTS.md 可以提炼出评估系统的设计骨架它由三个正交的问题构成数据如何进入 Prompt—— 观测数据到模板变量的映射由谁完成。哪些数据需要被评估—— 通过 Observation Filters 选择样本。评估配置如何持久化与寻址—— 从 legacy 的job_configurations到 v2 的 Rule。三者分别对应仓库中的变量映射模块、InMemoryFilterService与过滤管线、以及 v2 的数据模型与兼容层。下面逐一展开。二、变量映射LLM-as-Judge 与代码评估器的两种哲学AGENTS.md 的第一条明确指出LLM as a judge 需要将数据映射到 prompt 变量——由用户完成第二条补充代码评估器向数据库写入硬编码映射——由服务器处理实际数据由用户在代码中映射。这是两种评估器类型在设计上的根本分歧。2.1 LLM-as-Judge用户驱动的声明式映射LLM-as-Judge 模板声明了一组变量如input、output、expectedOutput用户需要把观测数据中的列Column映射到这些模板变量。映射结构定义在 packages/shared/src/features/evals/types.ts// 面向 observation 的简化映射目标观测已确定无需 objectName export const observationVariableMapping z.object({ templateVariable: z.string(), // 模板中的变量名 selectedColumnId: z.string(), // 要提取的列须匹配 observationEvalVariableColumns.id jsonSelector: z.string().nullish(), // 可选的 JSON path 选择器 });而面向 trace/dataset 的完整映射legacy 结构则在同文件 L164-L185export const variableMapping z.object({ templateVariable: z.string(), objectName: z.string().nullish(), // trace/dataset_item 无需指定 langfuseObject: langfuseObject, // trace/span/generation/event/... 枚举 selectedColumnId: z.string(), jsonSelector: z.string().nullish(), }).refine( (value) value.langfuseObject trace || value.langfuseObject dataset_item || value.objectName ! null, { message: objectName is required for observation objects (generation, span, score) }, );可见 trace 级评估需要回答从 trace 里的哪个观测对象取值langfuseObjectobjectName而 observation 级event/experiment评估因为目标观测已由 Rule 的过滤器确定映射被简化为模板变量 → 列 → JSON 选择器三元组。2.2 代码评估器服务器注入的硬编码映射代码评估器EvalTemplateType.CODE没有模板变量其入参由系统固定提供。在 web/src/features/evals/v2/fns/variableMapping/prepareModernRuleVariableMapping.ts 中可以看到当评估器类型为CODE时映射不来自用户而是直接取getCodeEvalVariableMapping()if (evaluatorType EvalTemplateType.CODE) { const mapping getCodeEvalVariableMapping(); return { defaultVariableMapping: mapping, initialVariableMapping: null, }; }这与 AGENTS.md 的表述完全一致the code evaluators write a hardcoded mapping to the database - handled by the server。用户在代码评估器侧只需要写接收固定上下文如input、output、expectedOutput、metadata的评估函数服务器负责把匹配到的观测数据组装进这些固定入参。持久化时legacyCompatibilityService.ts 的evaluatorVersionData同样为 CODE 类型写入getCodeEvalVariableMapping()确保两种类型的版本记录都携带合法的映射。2.3 新旧映射的自动转换v2 的前端草稿需要区分现代映射与legacy 映射。prepareModernRuleVariableMapping在 Zod 解析之前做检测如果映射条目携带langfuseObject或objectName字段则判定为 legacy 结构剥离后生成仅含templateVariable的空列映射selectedColumnId: 、jsonSelector: null并同时写入initialVariableMapping强制持久化避免执行时回退到旧版本映射见 prepareModernRuleVariableMapping.ts。三、Observation Filters样本选择与 InMemoryFilterService 的能力边界AGENTS.md 的第三条是本文最值得展开的部分During the evaluator setup users can set all kind of observation filters to select a sampleFilters used in rules are limited to what theInMemoryFilterServicecan process3.1 两种过滤场景的差异Evaluator 设置阶段用户在配置评估器时可以自由组合各种 Observation Filters 来挑选一个样本观测sample observation用于在保存前试跑评估器、预览输出。这个阶段面向交互式预览过滤能力更宽。Rule 执行阶段Rule 持久化的过滤器会在 worker 侧对每条流入的观测做内存级判定因此只能使用InMemoryFilterService支持的过滤条件。这构成了一个明确的能力边界——UI 上能选的过滤器子集与内存过滤引擎的实现强绑定。3.2 InMemoryFilterService内存过滤引擎核心实现在 packages/shared/src/server/services/InMemoryFilterService.ts。入口为静态方法evaluateFilterT(data, filter, fieldMapper, options?)过滤器为空时返回true匹配全部否则逐条求值任一条件不满足即返回false发生异常时出于安全返回false并记录日志L19-L49。它支持的全部过滤器类型与操作符如下表过滤器类型支持的操作符语义说明string、contains、does not contain、starts with、ends with、is not empty字段先String()归一化datetime、、、要求字段本身是Date按时间戳比较number、、、、要求字段本身是 numberstringOptionsany of、none of单值字段是否命中候选列表arrayOptionsany of、none of、all of字段必须是数组all of要求候选全部命中boolean、严格相等比较categoryOptionsany of、none of从对象字段中取指定 key 的值与候选比对stringObject继承string的操作符要求字段是对象且键必须存在hasOwnProperty守卫numberObject、、、、对象键值做数值解析isNaN则不匹配booleanObject、按name:true\|false编码条目匹配nullis null、is not null配合emptyEqualsNullColumns把空串视为 nullpositionInTrace—内存过滤中忽略始终返回true3.3 两个值得注意的实现细节stringObject 的键存在性守卫evaluateStringObjectFilter使用Object.prototype.hasOwnProperty.call(record, key)而非直接取属性因为键若与Object.prototype上的名字如toString、constructor冲突直接取值会解析到继承属性从而绕过守卫将缺失键归并为空串还会让contains 误匹配从未携带该键的行。注释明确说明这与 ClickHouse 侧mapContains守卫PR #13369保持一致L335-L371。booleanObject 的编码对齐evaluateBooleanObjectFilter依赖encodeBooleanScoreEntry生成name:true|false编码条目调用方必须在 field mapper 中提供预转小写的条目因为原始 score 的string_value是True/False不转换无法匹配L417-L445。3.4 字段映射从 filter 列 ID 到观测字段内存过滤依赖fieldMapper把过滤器中的列 ID 解析为数据对象上的实际值。observation 场景的映射器是 packages/shared/src/features/evals/observationForEval.ts 中的mapEventEvalFilterColumnIdToField它基于eventsEvalFilterColumns的定义把 camelCase 列 ID如providedModelName、promptName映射到 snake_case 观测字段如provided_model_name、prompt_name并对isRootObservation、isExperimentItemRootSpan这类派生布尔列做特殊计算。3.5 运行时如何消费过滤器worker 侧的调度器 worker/src/features/evaluation/observationEval/scheduleObservationEvals.ts 是过滤器在规则执行期的落点function evaluateFilter(observation: ObservationForEval, config: ObservationEvalRule): boolean { const filterConditions config.filter as FilterState; const isEmptyFilter !filterConditions || !Array.isArray(filterConditions) || filterConditions.length 0; const fieldMapper (obs: ObservationForEval, column: string) mapEventEvalFilterColumnIdToField(obs, column); const isFilterMatch isEmptyFilter ? true : InMemoryFilterService.evaluateFilter(observation, filterConditions, fieldMapper, { emptyEqualsNullColumns: OBSERVATION_FILTER_EMPTY_EQUALS_NULL_COLUMNS, }); return isFilterMatch; }调度流程是先跳过不可执行的 Rule状态非 active 或已被 block→ 内存过滤判定是否命中 → 按sampling采样率决定是否执行L136-L161→ 命中后才把观测上传到 S3 并逐条处理 assignment。这条链路印证了 AGENTS.md 的约束规则中的过滤器必须能被InMemoryFilterService处理因为每条观测都将在内存中完成匹配判定。该服务的测试覆盖非常全面见 worker/src/tests/inMemoryFilterService.test.ts对上述每种类型与操作符的组合均有断言是理解过滤语义边界的最佳参考。四、v2 数据模型Evaluator / Rule / Rule Assignments4.1 新旧模型对比v2/AGENTS.md 给出了明确的新旧模型对照维度旧模型legacy新模型v2评估器定义eval_templatesevaluators 版本化的evaluator_versions运行配置job_configurations变量映射 目标事件evaluation_rulesRule多对多关系无独立关联表evaluation_rule_evaluator_assignmentsRule assignments新模型把评估器长什么样与何时对什么数据运行彻底解耦Evaluator 是定义提示词/代码/模型、评分名、版本Rule 是运行策略过滤器、采样率、延迟、目标对象、状态Rule assignment 是 n:m 关联表——一个 Rule 可以挂多个 Evaluator同一个 Evaluator 也可以被多个 Rule 复用。4.2 数据库结构建表 SQL 见迁移 packages/shared/prisma/migrations/20260821121000_add_evaluator_v2/migration.sqlevaluatorsname同时作为评分名、type、blocked_at/block_reason/block_message执行失败后的封禁信息evaluator_versionsversion递增、prompt、model/provider/model_params、vars、variable_mapping、output_definition、source_code/source_code_languageCODE 类型用varchar(262144)并有(evaluator_id, version DESC)的唯一索引evaluation_rulesstatus默认ACTIVE、target_object、filterJSONB、samplingDECIMAL(65,30)、delay、time_scope默认[NEW]evaluation_rule_evaluator_assignmentsevaluation_rule_idevaluator_idvariable_mappingJSONB允许每个 assignment 覆盖映射并有(evaluation_rule_id, evaluator_id)唯一索引防止重复挂载。前端 Rule 草稿的数据结构对应 web/src/features/evals/v2/types/rules.tsexport type RuleDraft { name: string; filter: FilterState; sampling: number; assignments: RuleDraftAssignment[]; };其中每个 assignment 携带defaultVariableMapping继承自 Evaluator 版本的默认映射与可选的variableMapping本 Rule 覆盖映射及requiredVariables与数据库中的variable_mapping列一一对应。4.3 JobConfigurationIDlegacy与 Rule IDnew是同一个AGENTS.md 最后一条是最容易被忽视的兼容性设计JobConfigurationID (legacy) and Rule ID (new) are the same (see eval migration)即 v2 迁移中legacy 的job_configurations行被原地转换为evaluation_rules行Rule 的 ID 就是原 JobConfiguration 的 ID。这样做的直接好处是执行历史不会断链过去写入 trace metadata 的job_configuration_id依然能通过同一个 ID 现在指向 Rule关联到新模型。配套的后台迁移为 packages/shared/prisma/migrations/20260821121500_backfill_evaluator_v2/migration.sql负责把job_configurations/eval_templates数据回填进新表DDL 与 backfill 分两个迁移避免长时间持有projects表上的SHARE ROW EXCLUSIVE锁阻塞写入热路径迁移头部的注释对此有详细说明。4.4 执行元数据的双轨兼容执行期写入的 metadata 见 packages/shared/src/server/evals/evalExecutionMetadata.ts。buildEvalExecutionData的JOB分支同时写入job_configuration_id旧键与evaluation_rule_id/evaluator_id/evaluation_rule_assignment_id新键并把evaluationRuleId ?? jobConfigurationId作为执行上下文中的 Rule ID——这正是新 run 同时携带新旧标识、旧 run 只有job_configuration_id的落地方式evaluationContext { evaluatorId: params.evaluatorId, evaluationRuleId: params.evaluationRuleId ?? params.jobConfigurationId, evaluatorExecutionIsTest: false, };v2/AGENTS.md 明确记载了这一事实Traces captured during the eval executions in the past only capturedjob_configuration_id. Only new runs captureevaluator_idandevaluation_rule_id.4.5 双 ID 的历史查询兼容查询在 web/src/features/evals/server/legacyCompatibilityService.ts 的resolveExecutionConfigIds中体现给定一组jobConfigurationIds它先为每个 ID 建立以自身为初始成员的 ID 集合再查evaluation_rule_evaluator_assignments把每个 Rule 关联的evaluatorId并入集合——这样按 Rule 或按 Evaluator 寻址的历史执行记录都能被检索到迁移不会隐藏任何历史。五、兼容层legacy 编辑器如何活在 v2 之上LegacyEvalCompatibilityServicelegacyCompatibilityService.ts是一层重要的翻译器让旧版评估配置界面继续运行在 v2 数据模型之上单向投影toLegacyConfig只投影恰好挂了一个 Evaluator的 Rule把evaluation_rules assignments evaluator_versions反解为 legacy 的job_configuration形状L170-L198SQL 投影查询legacyConfigIdsQuery用子查询统计每个 Rule 的 assignment 数并HAVING count(*) 1只保留单评估器 Rule 供 legacy 列表分页L539-L577定义去重definitionsMatch刻意排除变量映射——映射属于 assignment两个 Rule 可以共享一个 Evaluator 却映射不同变量L283-L309改名 forklegacy 表单的评分名即 Evaluator 名重命名时若该 Evaluator 只被当前 Rule 使用则原地改名否则 fork 一份私有副本并把 assignment 指向新副本L466-L528managed 模板折叠managed:前缀的目录模板与项目内副本按定义比对未修改的副本在列表中折叠回目录条目L401-L415。规则层面服务层 web/src/features/evals/v2/server/rules/ruleService.ts 负责创建/更新/激活 Rule并配套validateEvaluatorFiltersForTarget目标对象合法性校验与filterStateKey/filtersMatchruleFilterMatching.ts 中对过滤器做稳定序列化用于判断可复用的过滤器预设是否等价。六、代码评估器的预检与执行约束AGENTS.md 提到代码评估器由服务器处理映射而服务器侧对配置能否运行有专门的前置校验web/src/features/evals/server/codeEvalJobConfigValidation.ts 中的assertCodeEvalCanRun代码评估器只能作用于 observationevent或 experiment否则抛出invalid_target错误用z.array(observationVariableMapping).parse校验映射结构在LANGFUSE_MIGRATION_V4_ALLOW_PREVIEW_OPT_IN true时对样本执行一次真实试跑runCodeEvalTestForJobConfig把INVALID_TARGET、TEMPLATE_NOT_FOUND、UNSUPPORTED_LANGUAGE、DISPATCHER_NOT_CONFIGURED等错误码映射为面向用户的CodeEvalJobConfigError试跑无匹配样本或执行失败时给出Adjust the filters and try again之类的明确提示。此外isRunnableTemplatelegacyCompatibilityService.ts保证若某代码评估器的语言没有可用的执行器dispatcherlegacy 编辑器就不会把它列为可选模板避免保存一个永远无法执行的 Rule。七、测试与工程实践准则v2/AGENTS.md 对测试提出两条约束同样值得读者在为本模块贡献代码时遵守不写同义反复tautological的 React 客户端测试不写断言像素位置的测试只有组件测试能强制有意义的行为时才添加。与之呼应的是过滤器这类纯逻辑有非常扎实的单元测试InMemoryFilterService的用例集中在 worker/src/tests/inMemoryFilterService.test.ts变量映射转换有 prepareModernRuleVariableMapping.clienttest.ts字段映射有 packages/shared/src/features/evals/observationForEval.test.ts执行元数据双键兼容有 packages/shared/src/server/evals/evalExecutionMetadata.test.ts。这些测试文件是理解各模块行为边界的第一手资料。结语Langfuse 的评估系统通过三条设计主线保持自洽变量映射区分用户声明式映射LLM-as-Judge与服务器硬编码映射代码评估器Observation Filters把选择什么样本与规则执行时的内存过滤能力绑定在InMemoryFilterService上v2 数据模型用 Evaluator / Rule / Rule assignments 三张表取代eval_templates/job_configurations并通过Rule ID 复用 JobConfigurationID与执行元数据的双键写入实现平滑迁移。理解这三条主线无论是排查评估器不触发的过滤问题、还是在新旧模型之间调试配置都能快速定位到对应的源码层。【免费下载链接】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),仅供参考