Skip to content

1.4 产品数据模型:先读产品,再读 OSS 实现

学习目标

完成本节后,你将能够:

  1. ../langfuse-docs 的产品视角解释 Langfuse 的完整数据抽象。
  2. 区分产品模型、当前 OSS 实现、Cloud / Fast Preview 演进方向这三层。
  3. 解释为什么当前架构不能用“trace 是最低单位”来理解。
  4. 理解 eval 里 trace/datasetevent/experiment 两套语义为什么并存。

1.4.1 先给结论:OSS repo 是产品模型的一个实现切片

langfuse 这个 repo 展示的是 OSS/self-hosting 代码实现。它很重要,但它不是理解 Langfuse 数据抽象的最高入口。

更高一层应该先看 ../langfuse-docs

产品视角回答什么问题代表文档
Observability应用运行数据如何组织?content/docs/observability/data-model.mdx
Evaluation评价结果、评价方法、实验如何组合?content/docs/evaluation/core-concepts.mdx
Scores所有评价结果最终以什么对象存储?content/docs/evaluation/scores/data-model.mdx
Experimentsdataset、run、run item、trace、score 如何串起来?content/docs/evaluation/experiments/data-model.mdx
Fast Preview / v4为什么系统走向 observation-centric?content/docs/v4.mdx

本书讲的是当前 OSS repo,但读法必须放在产品层之下:先知道 Langfuse 想表达哪些对象,再看这个 repo 如何用 Postgres、ClickHouse、Redis、S3、Zod、queue 和 worker 实现它。

这也是为什么不能只从 schema.prisma 或 ClickHouse migration 反推系统。数据库表只能告诉你“当前怎么存”,产品文档才告诉你“这些对象为什么存在、应该如何组合”。

1.4.2 产品层对象图

Langfuse 的核心不是一张 traces 表,而是一套围绕 AI 应用运行、评价和迭代的对象网络。

这张图有三个重点:

  1. Observation 是运行事实的细粒度节点:trace 是容器和相关性边界,observation 才是 LLM call、tool call、retrieval、span 这些具体操作。
  2. Score 是评价结果的统一落点:人工标注、用户反馈、LLM-as-a-Judge、code evaluator、外部 pipeline 最终都写成 score。
  3. Experiment 是运行快照,不只是 dataset 的子页:dataset 是测试材料,experiment/run 是一次执行历史,run item 把 dataset item 和 trace/observation 结果连起来。

1.4.3 “最低单位”要按视角回答

如果问“当前项目的组织最低单位是不是 trace”,答案是:不是。trace 很重要,但它主要是相关性边界,不是所有能力的最低单位。

更准确的拆法是:

视角更接近最低单位的对象为什么
运行观测Observation / span / event rowLLM 调用、tool、retrieval、generic span 都是 observation。
相关性聚合trace_id用来把一组 observations 聚合回一次请求或工作流。
会话分析session_id用来把多个 traces 聚合成多轮对话或长流程。
评价结果Scoreeval、人工标注、用户反馈最终都落成 score。
离线实验DatasetRunItem / experiment item把一个测试输入、一次运行产物、trace/observation 和 score 串起来。
异步执行JobExecution / queue payloadworker 实际处理的是 job,不是直接处理 UI 对象。

所以在源码里看到 traceId 很多,不代表 trace 是最低单位。更准确的运行模型是:

text
session_id  聚合多个 trace_id
trace_id    关联一组 observation
span_id     标识一个 observation
score       可以挂 trace、observation、session 或 dataset run
job         驱动 eval、ingestion、导出、删除等异步执行

这个理解能解释当前 repo 的很多设计:

现象只看 trace 会误解放到产品模型后的理解
observationsparentObservationId以为只是 trace 详情页的子表observation 本身是可嵌套执行树。
v4 events 宽表带 trace context以为 trace 字段重复冗余observation-centric 查询需要把上下文传播到每一行。
score 有 observationId以为 score 总是评价整条 tracescore 是统一评价结果,可挂到更细对象。
dataset item 有 sourceObservationId以为 dataset 只从 trace 生成生产问题可以从某个具体操作沉淀成测试样本。
eval job 有 jobInputObservationId以为 evaluator 只能处理 trace新 eval 路径可以直接针对 observation 调度。

1.4.4 v4 的方向:observation-centric

Fast Preview / v4 的关键变化是:从“traces + observations 两类实体”走向“observation-centric”。trace ID 仍然存在,但更多承担 correlation identifier 的角色。

经典模型:

text
traces table 保存 trace 属性
observations table 保存操作节点
查询时 join / 补上下文

observation-centric 模型:

text
每个 observation 行都携带必要 trace context
列表、筛选、聚合直接从 observation 行开始
trace_id 用来把同一组 observations 重新聚合成 trace 视图

这就是为什么当前 OSS repo 里会同时看到:

实现形态说明
legacy traces / observations / scores经典模型的 ClickHouse 表。
v4 events_full保存完整 observation/event 上下文和大字段。
v4 events_core面向列表、筛选、聚合的轻量投影。
observations_batch_staging从 legacy observations 到 v4 events 的过渡 staging。
FilterState + EventsQueryBuilder把 UI/API 过滤语义统一 lower 到 observation-centric 查询。

当前 repo 的 packages/shared/clickhouse/scripts/dev-tables.sh 注释里已经能看到这个迁移方向:events_full 是 full-fidelity event table,events_core 是 query-optimized projection,backfill 会把 legacy traces 转成 synthetic observation。

注意:../langfuse-docs/content/docs/v4.mdx 明确说明 Fast Preview 当前是 Langfuse Cloud 体验,OSS/self-hosting 迁移路径仍在推进。本书读的是 OSS repo,所以要同时理解两件事:

  1. 产品方向:Langfuse 的数据模型正在走向 observation-centric。
  2. 当前实现:OSS repo 中 legacy 表、v4 events、dual-write/backfill 和兼容逻辑会并存。

1.4.5 Evaluation 的产品模型

langfuse-docs 的 Evaluation 概念层可以拆成四个对象:

产品对象含义读源码时要找什么
Score评价结果,统一落点packages/shared/src/domain/scores.ts、ClickHouse scores
Evaluation method产生 score 的方法EvalTemplate、LLM-as-a-Judge、code evaluator、annotation
Online evaluation对线上数据自动评分JobConfiguration、filter、sampling、worker eval queue
Experiment对一组输入做离线运行和比较DatasetRunsDatasetRunItems、experiment UI/service

Score 是关键,因为它把不同评价来源统一了。产品 docs 里说 score 可以挂到 trace、observation、session 或 dataset run;当前 repo 里也能看到 ClickHouse scorestrace_idobservation_idsession_iddataset_run_id 以及 source=API/EVAL/ANNOTATION

Experiments 也要注意两层语义:

怎么理解
经典数据模型Dataset 是测试集,DatasetRun 是一次 run,DatasetRunItem 连接 dataset item、trace 和可选 observation。
新产品语义Experiments 成为一等概念,dataset 是参考材料,experiment 是可比较、不可变的执行快照。

因此读当前 OSS repo 时,看到 DatasetRuns 不要只理解成“dataset 页面下的一张表”。它是当前实现里承载 experiment run 语义的物理模型。

1.4.6 Eval 的两套语义:legacy 与新路径并存

回到源码,会看到历史语义和新语义同时存在:

旧 target新 target产品含义当前实现状态
traceevent从评价整条 trace,转向评价某个 observation。旧 trace eval 仍可工作,新 observation eval 是主要演进方向。
datasetexperiment从 dataset run 语义,转向 experiment/run item 语义。Prisma 仍有 DatasetRuns 命名,新 UI/API 倾向 experiment。

这里的 event 不是普通日志事件,而是 observation-scoped evaluator:按 observation 的 type、name、metadata、input、output、trace context 命中规则,创建 eval job,最后写出 score。

两条路径可以这样读:

这张图解释了几个源码现象:

源码现象说明
EvalTargetObject 同时有 TRACE/DATASET/EVENT/EXPERIMENT系统还在兼容 legacy target,同时支持新 target。
mapLegacyToModernTarget(trace)=eventUI/配置层会把旧语义映射到新语义。
fetchObservationEvalConfigs 只查 EVENT/EXPERIMENT新 scheduler 面向 observation eval。
evalService.createEvalJobs 只查 TRACE/DATASET老 scheduler 仍处理 trace/dataset eval。
scheduleObservationEvals 会上传 observation 到 S3eval 执行时不再重新查整条 trace,而是拿命中的 observation 快照。
completeEvalExecution 最后仍写 score无论新旧路径,评价结果统一沉淀为 score。

所以如果问“现在实际 work 的是不是新语义”,更准确的回答是:活跃演进的路径是 event/experiment,legacy 的 trace/dataset 仍保留并继续工作。 读源码时要看具体入口走的是哪条 scheduler,不能只看枚举名。

1.4.7 docs-level 到 repo-level 的映射

下面这张表是读源码时最重要的翻译表。

产品对象OSS repo 中的主要实现存储倾向
Project / org / member / API keyPrisma models、tRPC context、Public API authPostgres
Tracelegacy trace domain、trace router、trace context、trace_idClickHouse legacy + v4 context
Observationobservation domain、events table、observation evalClickHouse legacy + v4 rows
Sessiontrace/session 字段和聚合查询Postgres + ClickHouse context
Scorescore domain、score config、score ingestion、score analyticsClickHouse + Postgres config
Dataset / DatasetItemPrisma models、dataset API/UIPostgres
Experiment / DatasetRundataset run models、experiment UI/servicePostgres + ClickHouse run item links
Eval config / template / executionJobConfigurationEvalTemplateJobExecution、worker eval queuesPostgres + Redis + S3 + Score
Promptprompt feature、prompt versions、playgroundPostgres

读源码时不要把这些对象都压成“表”。Langfuse 的抽象不是一组 CRUD 表,而是:

text
运行事实:Observation / Trace / Session
评价事实:Score / Evaluator / ScoreConfig
实验事实:Dataset / DatasetItem / ExperimentRun / RunItem
配置事实:Prompt / EvalTemplate / JobConfiguration
执行事实:Queue job / JobExecution / raw event / ClickHouse write
查询投影:events_core / analytics views / field maps

1.4.8 读 OSS repo 时的三层翻译法

以后看到一个字段、表或类型,不要马上问“它在哪个目录”。先按三层翻译:

第几层要问的问题例子
产品层这是观测、评价、实验、配置,还是执行状态?observation_id 是观测节点;dataset_run_id 是实验运行;score 是评价结果。
契约层谁需要共同理解这个结构?queue payload 需要 producer/consumer 兼容;FilterState 需要 UI/API/query builder 兼容。
存储层它的权威存储在哪里?是否只是投影?JobConfiguration 在 Postgres;event analytics 在 ClickHouse;raw payload 在 S3。

用这个方法读代码,你会自然知道先打开哪里:

想理解什么先读产品 docs再读 OSS 实现
trace / observationobservability/data-model.mdxpackages/shared/src/domain/observations.ts、ClickHouse observations / events_*
scoreevaluation/scores/data-model.mdxpackages/shared/src/domain/scores.ts、score ingestion、ClickHouse scores
experimentevaluation/core-concepts.mdxevaluation/experiments/data-model.mdxPrisma DatasetRuns / DatasetRunItems、experiment repositories
observation evalv4 docs、observation-level eval changelogfeatures/evals/types.tsobservationEval/*、worker eval queues
v4 querydocs/v4.mdx、Fast Preview FAQevents_fullevents_coreEventsQueryBuilder

1.4.9 本书后续如何使用这个视角

后面读 OSS repo 时,遇到任何数据结构都先问四个问题:

  1. 它在 langfuse-docs 的产品模型里是哪类对象?
  2. 它是事实数据、配置数据、执行状态,还是查询投影?
  3. 它的权威存储在哪里,Postgres、ClickHouse、Redis 还是 S3?
  4. 它是否处在 legacy model 和 observation-centric model 的迁移边界?

这样读 schema.prisma、ClickHouse migrations、Zod schema 和 worker queues 时,你看到的就不是散落的实现细节,而是一套产品数据模型在 OSS repo 里的物理落地。

本节先建立产品语义。要把这些对象进一步对照到 Postgres、ClickHouse、Redis/BullMQ、S3/blob 的实际存储,请接着读 数据结构图谱

下一节

架构原则