なぜデータモデルから理解するのか
前章まででLangfuseの立ち位置を俯瞰しました。本章からは内部に深く潜ります。 まず データモデル から始めるのは、Langfuseの全機能(Tracing、Prompt Management、Evaluation、Dataset)が 共通のエンティティ上に積み重ねられた設計だからです。ここを理解せずにSDKやUIの使い方だけ覚えても、 「なぜこの機能はこう動くのか」が腹落ちしません。
5大エンティティの全体像
erDiagram
USER ||--o{ TRACE : has
SESSION ||--o{ TRACE : groups
TRACE ||--o{ OBSERVATION : contains
OBSERVATION ||--o{ OBSERVATION : nests
TRACE ||--o{ SCORE : has
OBSERVATION ||--o{ SCORE : has
SESSION ||--o{ SCORE : has
PROMPT ||--o{ GENERATION : linked
DATASET ||--o{ DATASETITEM : contains
DATASET ||--o{ DATASETRUN : executes
DATASETRUN ||--o{ DATASETRUNITEM : records
DATASETRUNITEM ||--|| TRACE : produces
DATASETRUNITEM ||--o{ SCORE : has| エンティティ | 役割 | 親子関係 |
|---|---|---|
| Trace | 1リクエスト/1操作の最上位コンテナ | 親なし。Observationを複数内包 |
| Observation | Trace内の個別ステップ(Span/Generation/Event) | Trace配下、再帰ネスト可 |
| Score | 評価結果の唯一の保存形式(numeric/categorical/boolean) | Trace/Observation/Session/DatasetRunItemに付与 |
| Prompt | バージョン管理されるプロンプトオブジェクト | 独立。Generationにリンクされる |
| Dataset / DatasetRun | ゴールドセットと実行記録 | Dataset → DatasetItem / DatasetRun → DatasetRunItem → Trace |
Trace — 最上位コンテナ
Trace はユーザーリクエストまたは単一操作に対応する最上位の観測単位です。 チャットボットなら「1問1答」、RAGアプリなら「1クエリの処理全体」、エージェントなら「1ユーザー指示の完了まで」が1 Traceに相当します。
Traceが持つ主要属性:
| 属性 | 型 | 用途 |
|---|---|---|
id | string (UUID互換) | ユニークID。W3C Trace Contextと互換 |
name | string | トレース名("chat"、"rag-query"等の論理名) |
user_id | string? | エンドユーザー識別子。集計・分析の軸 |
session_id | string? | 会話セッションID。マルチターン会話をグループ化 |
tags | string[] | 第一階層のフィルタ用タグ |
metadata | object? | 任意のkey-value属性(全observationに伝播) |
input / output | any | トレース全体の入力・出力 |
release | string? | アプリのリリース識別(v1.2.3、Gitハッシュ等) |
version | string? | プロンプト/モデル構成の論理バージョン |
environment | string | "production" / "staging" / "development" 等 |
Observation — Span / Generation / Event の3種
Traceの内部で「何が起きたか」を記録するのが Observation です。 Langfuseは OpenTelemetryのSpanを拡張し、Observationを以下の3種に分類しています。
| タイプ | 用途 | 追加属性 | 典型例 |
|---|---|---|---|
| Span | 汎用処理(非LLM) | なし(基本属性のみ) | DB クエリ、ツール実行、ビジネスロジック、前処理 |
| Generation | LLM呼び出し | model / model_parameters / usage_details(input/output tokens)/ cost_details / prompt_name / prompt_version | OpenAI chat.completions、Anthropic messages.create |
| Event | durationを持たない点イベント | なし | エラー発生、ユーザーアクション、システム状態変化 |
さらに2025年以降、UIの表示用に「意味的タイプ」も導入されました: agent / tool / chain /
retriever / evaluator / embedding / guardrail。
これらは内部的には Span/Generation の特殊化で、ダッシュボードでのアイコンとグルーピングに使われます。
graph TD T[Trace: user-chat-request] --> O1[Span: parse-input] T --> O2[Span: rag-pipeline] O2 --> O21[Span/retriever: vector-search] O2 --> O22[Span/retriever: bm25-search] O2 --> O23[Generation/embedding: embed-query] T --> O3[Span/chain: generate-answer] O3 --> O31[Generation: gpt-4o call] O3 --> O32[Event: validation-passed] T --> O4[Span/tool: send-response] style T fill:#3b82f6,stroke:#1d4ed8,color:#fff style O2 fill:#8b5cf6,stroke:#6d28d9,color:#fff style O3 fill:#8b5cf6,stroke:#6d28d9,color:#fff style O31 fill:#f97316,stroke:#ea580c,color:#fff style O23 fill:#f97316,stroke:#ea580c,color:#fff style O32 fill:#14b8a6,stroke:#0d9488,color:#fff
Generation の追加属性が重要な理由
Generation にはLLM呼び出しに必要な全情報が一級属性として格納されます。
model、input/output tokens、cost、prompt バージョンを汎用タグに詰め込むのではなく、
専用フィールドに型付きで保存するため、ダッシュボードで「モデル別コスト推移」「プロンプトバージョン別レイテンシ」といった集計が即座にできます。
Level — エラーパス可視化
Observationには level 属性があり、DEBUG / DEFAULT / WARNING / ERROR の4段階で重要度を示します。
statusMessage と併用することで、ダッシュボードでエラーパスを素早く絞り込めます。
Score — 評価結果の唯一の保存形式
Score はLangfuseで「評価」の結果を表現する唯一のオブジェクトです。 重要なのは「人間アノテーション」「LLM-as-a-Judge」「ユーザーフィードバック」「外部EvalツールからのAPI送信」が すべてScoreに統合される点。これによりソースを横断した比較や集計ができます。
| 属性 | 型 | 説明 |
|---|---|---|
name | string | スコア名("correctness"、"toxicity"等) |
value | number | string | boolean | スコア値 |
dataType | NUMERIC / CATEGORICAL / BOOLEAN | 値の型 |
source | API / ANNOTATION / EVAL | 発生源(APIから送信 / 人間アノテーション / Evaluator実行) |
comment | string? | 任意の理由説明 |
trace_id / observation_id / session_id / dataset_run_item_id | string? | 付与対象(いずれか) |
Prompt — Immutableバージョン + 可変Label
Prompt はLangfuseでバージョン管理されるプロンプトオブジェクトです。text(単一文字列)と chat(role/content配列)の2種類があり、
バージョンは整数(1, 2, 3...)で immutable(不変)に蓄積されます。完全な監査履歴です。
バージョンが immutable だと「本番が参照するバージョンを切り替える仕組み」が必要です。これを担うのが Label です。
| Label | 性質 | 用途 |
|---|---|---|
production | デフォルト・本番参照先 | SDK.get_prompt()のデフォルトで参照される |
latest | 自動更新 | 最新バージョンを自動的に指す |
staging | 任意 | 本番前の段階的リリース |
| カスタム | 任意 | A/Bテスト、機能フラグ、チーム別など |
| Protected Label | 権限ロック | ロールベースの変更制御 |
graph LR
subgraph Immutable[Immutable バージョン]
V1[v1]
V2[v2]
V3[v3]
V4[v4]
end
subgraph Labels[可変Label(ポインタ)]
LP[production] -.指す.-> V2
LS[staging] -.指す.-> V3
LL[latest] -.自動.-> V4
LA[custom:experiment-a] -.指す.-> V4
end
style V1 fill:#8b5cf6,stroke:#6d28d9,color:#fff
style V2 fill:#3b82f6,stroke:#1d4ed8,color:#fff
style V3 fill:#f97316,stroke:#ea580c,color:#fff
style V4 fill:#14b8a6,stroke:#0d9488,color:#fff
style LP fill:#3b82f6,stroke:#1d4ed8,color:#fff
style LS fill:#f97316,stroke:#ea580c,color:#fff
style LL fill:#14b8a6,stroke:#0d9488,color:#fff
style LA fill:#14b8a6,stroke:#0d9488,color:#fffLinked Generation — プロンプトと生成の紐付け
プロンプトを取得して使うだけではLangfuseはバージョン追跡できません。
Generation 実行時に prompt パラメータを渡して明示的にリンクすることで、
Promptバージョン単位のレイテンシ・コスト・スコア中央値が自動集計されるようになります。
Dataset / DatasetRun / Experiment
Dataset(データセット)は入力と期待出力のテストケース集合(ゴールドセット)です。 これに対してLLMアプリを流して実行結果を記録するのが DatasetRun(実験)です。
| エンティティ | 役割 | 主要属性 |
|---|---|---|
| Dataset | テストケース集合 | name / description / metadata |
| DatasetItem | 個別テストケース | input / expectedOutput / metadata / sourceTraceId / status(ACTIVE/ARCHIVED) |
| DatasetRun | Dataset全体の実行 | name / metadata / Dataset参照 |
| DatasetRunItem | 個別実行結果 | Trace参照 / DatasetItem参照 |
graph LR D[Dataset\nname: qa-gold-set] --> I1[Item 1\ninput / expectedOutput] D --> I2[Item 2] D --> I3[Item N] D --> R1[DatasetRun\nname: v2-prompt-test] R1 --> RI1[RunItem → Trace\n+ Score] R1 --> RI2[RunItem → Trace\n+ Score] D --> R2[DatasetRun\nname: v3-prompt-test] R2 --> RI3[RunItem → Trace\n+ Score] style D fill:#3b82f6,stroke:#1d4ed8,color:#fff style R1 fill:#8b5cf6,stroke:#6d28d9,color:#fff style R2 fill:#14b8a6,stroke:#0d9488,color:#fff
詰まりやすい概念 Top 5
①: Trace vs Span vs Generation
初学者が最も混乱するポイント。正しい関係は:
- Trace = 1リクエスト全体(ユーザーの1操作に1:1対応)
- Observation = Trace内の作業単位の親型(Span/Generation/Event の共通親)
- Span = Observation の一種(汎用処理)
- Generation = Observation の一種(LLM呼び出し専用、model/usage/cost追加)
- Event = Observation の一種(durationなし)
「Span と Generation は別物」ではなく、「Observation という共通親の型ヴァリアント」と理解するのが正解です。 OpenTelemetryのspanに type属性を加えて拡張したもの、とも言えます。
②: Score と Evaluation の関係
Evaluation は「評価する行為・仕組み」、Score は「評価結果の唯一の永続形式」。
LLM-as-a-Judge・人間アノテーション・ユーザーフィードバック・外部Evalツールからの送信 — 全てがScoreとして記録されます。
source フィールド(API / ANNOTATION / EVAL)でソースを区別します。
③: Session と User の境界
user_id は「誰」の恒常的識別子、session_id は「1つの会話セッション」の識別子です。
1 Userが複数 Sessionを持つのは自然ですが、Sessionは Userの子エンティティではなく、両方ともTraceレベルで独立に付く「タグ的」属性です。
「Session テーブルに User 外部キーがある」わけではない、と覚えておくと混乱しません。
④: Prompt Linking のタイミング
Promptを langfuse.get_prompt() で取得した時点ではリンクされません。
Generation 実行時に prompt パラメータを渡して初めてリンクが作成されます。
LangChain統合では PromptTemplate.metadata["langfuse_prompt"] にセットすることで自動リンクされます。
⑤: V3のIngestion Flow(非同期性)
v3アーキテクチャでは、SDKが送信したトレースは以下のフローで処理されます(詳細は第5章):
- Webコンテナが受信 → S3 に書き込み → Redis にポインタをキューイング
- Workerコンテナが Redis からデキュー → S3 から本体取得 → ClickHouse へバルクINSERT
そのため 「送った直後はClickHouseに存在しない」タイミングがあります。E2Eテストで送信→即検索はレースが起きるので、 適切な待機またはflush後のポーリングが必要です。
OpenTelemetry との用語対応
LangfuseはOTelに準拠しつつ、LLMアプリ固有の概念を独自に追加しています。対応表は以下の通りです。
| OpenTelemetry | Langfuse | 備考 |
|---|---|---|
| Span | Observation(Span/Generation/Event) | Langfuseは3亜種に拡張 |
| Span attributes | Langfuse専用属性 + gen_ai.* + metadata.attributes | 優先順位: langfuse.* > gen_ai.* > metadata.attributes |
| Resource attributes | metadata.resourceAttributes | 単なるメタデータ扱い |
| trace_id / span_id | 同じ形式(32/16 hex) | そのまま互換 |
| (なし) | Session / User | Langfuse独自、Trace属性として伝播 |
| (なし) | Score | Langfuse独自の評価専用エンティティ |
| SpanKind | Observation type(agent/tool/retriever等) | LLMアプリ特化の意味付け |
理解度チェック
Langfuseの Observation について正しい記述はどれですか?
キーボード: 1〜4 で選択、Enter で回答