3.6 数据结构图谱
学习目标
完成本节后,你将能够:
- 用一张分层图解释 Langfuse 的产品对象如何落到契约和存储。
- 说清 trace、observation、score、dataset、experiment、eval job 的业务关系。
- 区分 Postgres、ClickHouse、Redis/BullMQ、S3/blob 中分别保存什么。
- 理解 v4 为什么把 observation/event row 作为事实底座,而不是继续让 trace row 承载所有事实。
3.6.1 先给结论:先分层,再看表
Langfuse 的数据结构不能直接从数据库表开始读。正确顺序是:
这张图是本节最重要的读法。比如 trace_id 在产品层表示“一次请求或工作流的相关性边界”,在 ClickHouse v4 里只是每个 observation row 上的关联字段,在 UI 查询里又可以被 uniq(trace_id) 聚合成 trace 计数。
如果直接看表,很容易误解成“Langfuse 就是 trace CRUD”。实际更准确的抽象是:
运行事实:observation/event row
相关性边界:trace_id、session_id
评价事实:score
实验事实:dataset run item 连接 dataset item 与 trace/observation
配置事实:prompt、score config、eval template、job configuration
执行状态:queue job、job execution、raw event pointer
查询投影:events_core、analytics_events_core、field sets3.6.2 业务实体关系图:对象先按语义分组
下面这张图不追求把所有字段画完,而是帮助你建立“谁连接谁”的脑图。
读这张图时抓住三条主线:
| 主线 | 最小闭环 | 含义 |
|---|---|---|
| 观测闭环 | Session -> Trace -> Observation tree | 用户请求被拆成一棵操作树。trace 负责把节点归组,observation 负责表达每个实际操作。 |
| 评价闭环 | JobConfiguration -> JobExecution -> Score | evaluator 的配置和执行状态在 Postgres,评价结果最终统一落成 score。 |
| 实验闭环 | DatasetItem -> DatasetRunItem -> Trace/Observation -> Score | dataset 是输入材料,experiment/run 是执行快照,run item 把样本、运行结果和评分连起来。 |
3.6.3 物理存储图:同一业务对象会跨多个存储
Langfuse 的存储不是按“对象名”一一对应拆的,而是按状态性质拆的。
这张图解释了一个常见疑问:为什么一个功能会同时碰 Postgres、ClickHouse、Redis 和 S3?
以 observation eval 为例:
| 状态 | 存在哪里 | 为什么 |
|---|---|---|
| evaluator 配置 | Postgres job_configurations、eval_templates | 需要管理 UI、事务更新、权限和版本关系。 |
| 命中的 observation 事实 | ClickHouse observations 或 events_* | 这是高吞吐运行事实,需要按时间、project、type、metadata 查询。 |
| 待执行任务 | Redis/BullMQ | evaluator 执行要异步、可重试、可限流。 |
| observation 快照 | S3/blob | 执行 evaluator 时不希望每个 job 再查一遍 ClickHouse,也避免队列 payload 过大。 |
| 最终评价结果 | ClickHouse scores + Postgres config 关联 | score 是分析事实,config 是事务型约束。 |
3.6.4 Observation tree:一条请求怎么拆成事实行
agent/workflow 的一次请求通常不是线性的。它可能同时调用模型、检索、工具、子 agent。Langfuse 用 observation tree 表达这件事。
落到 observation/event row 时,核心不是“更新一行 trace”,而是写多行事实:
| row | 关键字段 | 表达的事实 |
|---|---|---|
| O1 | trace_id=T1、span_id=O1、parent_span_id=''、type=SPAN | 这次应用调用的根操作。 |
| O2 | trace_id=T1、span_id=O2、parent_span_id=O1、type=GENERATION、model/cost/usage | 主 agent 的一次模型调用。 |
| O4 | trace_id=T1、span_id=O4、parent_span_id=O3、type=TOOL | retrieval 下的一次工具调用。 |
| O9 | trace_id=T1、span_id=O9、parent_span_id=O1、type=GENERATION、input/output | 最终回答生成。 |
因此 trace 页面不是从一行 trace 读出所有子对象,而是:
WHERE project_id = 当前项目
AND trace_id = T1
读取同一组 observation/event row
按 parent_span_id 组装成树这就是“trace 是相关性边界,observation 是事实节点”的具体含义。trace 仍然很重要,但它不必承担每个子步骤的输入、输出、成本、错误和评分。
3.6.5 经典模型到 v4:事实底座为什么变化
经典模型和 v4 observation-centric 模型的差异可以画成这样:
变化的关键不是“trace 消失”,而是 trace 的职责变了:
| 经典模型里 trace 常承担 | v4 更推荐的表达 |
|---|---|
| trace row 保存请求级 input/output | 根 observation 保存整体输入输出;子 observation 保存自己的输入输出。 |
| trace row 保存 user/session/tags/metadata,然后查询时 join 或补上下文 | SDK/ingestion 把 trace context 传播到每个 observation row。 |
| trace list 是默认入口,再点进去看操作 | observation table 是默认入口,trace 通过 trace_id 聚合出来。 |
| evaluator 常评价整条 trace | evaluator 可以直接命中某个 observation/event。 |
这个取舍符合 ClickHouse 的使用方式:排序键和查询过滤要围绕高频查询模式设计,重复 join 的上下文更适合在写入或预处理时铺到分析宽表里。Langfuse 的 events_full / events_core 正是把“写入时多带上下文”换成“查询时少 join、少扫大字段”。
3.6.6 ClickHouse 里的三种形态:legacy、full、core
当前 OSS repo 里会同时看到 legacy 表和 v4 events 表。它们不是互相矛盾,而是迁移期并存。
events_full 可以理解成“把一行 observation 需要的上下文都铺平”:
| 字段组 | 例子 | 为什么放在 event row 上 |
|---|---|---|
| 关联字段 | project_id、trace_id、span_id、parent_span_id | 过滤租户、还原 trace 树、定位 observation。 |
| trace context | trace_name、user_id、session_id、tags、release、version | 列表和聚合常按这些字段过滤,避免每次 join trace。 |
| observation fact | name、type、start_time、end_time、level、status_message | 每个操作本身的事实。 |
| model/usage/cost | provided_model_name、usage_details、cost_details、total_cost | LLM observability 的核心分析维度。 |
| I/O 和 metadata | input、output、metadata_names、metadata_values | 详情页、全文搜索、eval 需要。 |
| experiment context | experiment_id、experiment_item_id、experiment_item_expected_output | 让实验结果可以和 observation 查询组合。 |
events_core 则是给常见查询准备的轻量形状。列表页、filter options、count 和聚合不应该默认读取完整 input/output/metadata。需要完整内容时再从 events_full 按 key 取。
3.6.7 Experiment / Eval 的数据加工链路
实验和评价最容易读乱,因为它横跨“离线材料、线上运行、异步 evaluator、score”。可以按下面这张图理解:
这里有两个容易混淆的点:
| 容易混淆的点 | 正确理解 |
|---|---|
| Dataset 是不是 eval 的结果? | 不是。Dataset 是材料,DatasetRun/Experiment 是一次执行快照,Score 才是评价结果。 |
event target 是普通日志事件吗? | 不是。这里的 event 是 observation-scoped evaluator,实际评价的是某个 observation/event row。 |
3.6.8 读源码时的“实体翻译表”
下面这张表可以当成源码阅读的索引。
| 产品对象 | 业务含义 | 主要契约/实现 | 权威存储或投影 |
|---|---|---|---|
| Project | 租户内的项目边界 | Project Prisma model、project-scoped tRPC/API context | Postgres projects |
| API key | 外部写入和读取的认证凭证 | ApiKey Prisma model、Public API middleware | Postgres api_keys |
| Trace | 一次请求或工作流的相关性边界 | trace domain、trace_id、trace UI | legacy ClickHouse traces;v4 中是 event row 上的 context |
| Observation/Event | 一个实际操作节点 | observation domain、span_id、parent_span_id、events query builder | legacy observations;v4 events_full / events_core |
| Score | 统一评价结果 | score domain、score config、eval completion | ClickHouse scores,配置在 Postgres |
| Dataset | 测试材料集合 | Prisma Dataset、dataset API/UI | Postgres datasets |
| DatasetItem | 一个测试样本 | Prisma DatasetItem、版本字段、source links | Postgres dataset_items |
| Experiment / DatasetRun | 一次离线运行快照 | Prisma DatasetRuns、experiment UI/service | Postgres dataset_runs |
| DatasetRunItem | 样本与运行结果的连接 | Prisma DatasetRunItems、trace/observation links | Postgres dataset_run_items,v4 event row 可铺 experiment context |
| EvalTemplate | evaluator 的执行定义 | Prisma EvalTemplate | Postgres eval_templates |
| JobConfiguration | evaluator 何时运行、命中什么对象 | Prisma JobConfiguration、targetObject/filter/sampling | Postgres job_configurations |
| JobExecution | 一次 evaluator 执行状态 | Prisma JobExecution、queue processor | Postgres job_executions |
| Raw ingestion event | SDK/OTel 原始 payload | processEventBatch、queue payload pointer | S3/blob |
| Queue job | 异步执行指令 | packages/shared/src/server/queues.ts | Redis/BullMQ |
读代码时把“产品对象、契约、存储”三列同时打开,才不会把 ClickHouse 的宽表字段误认为产品模型本身。
3.6.9 本节对应源码
| 主题 | 源码入口 |
|---|---|
| 产品数据模型 | ../langfuse-docs/content/docs/observability/data-model.mdx、../langfuse-docs/content/docs/v4.mdx |
| Score / Experiment 产品语义 | ../langfuse-docs/content/docs/evaluation/scores/data-model.mdx、../langfuse-docs/content/docs/evaluation/experiments/data-model.mdx |
| Postgres 实体 | packages/shared/prisma/schema.prisma |
| legacy ClickHouse 表 | packages/shared/clickhouse/migrations/unclustered/0001_traces.up.sql、0002_observations.up.sql、0003_scores.up.sql |
| v4 events 表 | packages/shared/clickhouse/scripts/dev-tables.sh |
| v4 查询 | packages/shared/src/server/queries/clickhouse-sql/event-query-builder.ts |
| ingestion 请求侧 | packages/shared/src/server/ingestion/processEventBatch.ts |
| ingestion worker | worker/src/queues/ingestionQueue.ts、worker/src/services/IngestionService/index.ts |
| eval target 和执行 | packages/shared/src/features/evals/types.ts、worker/src/features/evaluation/observationEval/** |