- Published on
Langfuseトレーシングのデータモデル — trace、observation、scoreが一度の実行を収める形
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — ダッシュボードよりデータモデルが先
- 三つの層に分かれる — trace、observation、score
- observationが分かれる三つの型 — span、generation、event
- 入れ子が必要な理由 — RAG一回の実行が残すツリー
- traceの上に乗る二つの層 — sessionとuser
- score — 評価結果が付く場所
- 保存形式を知れば問い合わせがずれない
- おわりに — 先に決めるものと後で直せるもの
- 試してみる
- シリーズ
- 参考資料
はじめに — ダッシュボードよりデータモデルが先
LLMアプリケーションにトレーシングを入れる作業はたいてい同じ流れで始まります。SDKを入れ、キーを設定し、画面にトレースが届くのを確認して満足します。そして二か月ほど経った頃に「先週、回答品質が落ちたリクエストはどの検索ステップを通ったのか」といった問いが来て、そこで詰まります。
このシリーズはLangfuseを実装の観点から扱います。ツール比較ではなく、データが実際にどう収集され、どこに保存され、どう取り出されるのかを見ます。第一回はデータモデルです。ここを理解して初めて残り五本が読めます。
構成と設定名は2026-08-15に公式ドキュメントで確認しました。Langfuseはバージョンによってアーキテクチャが変わるため、利用中のバージョンのドキュメントを再確認してください。本シリーズが基準としたのはセルフホストのメジャーバージョンv4です。バージョンポリシーのページはv2をサポート終了、v3を非推奨、v4を正式提供と示しています。
三つの層に分かれる — trace、observation、score
データモデルのドキュメントが定義する中心的なオブジェクトは三つです。
traceは一度のリクエストまたは操作を表します。チャットボットにユーザーが一度話しかけて答えを受け取るまでが一つのtraceです。ドキュメントの表現では、同じtrace_idを共有するobservationの論理的なまとまりです。
observationはその中でアプリケーションが実行した個々のステップです。モデル呼び出し、ツール実行、検索ステップがそれぞれ一つのobservationになります。重要なのは、これらがアプリケーションの構造を反映して入れ子になる点です。平坦なリストではなくツリーです。
scoreは評価結果です。スコアのドキュメントはこれを評価結果を保存する汎用オブジェクトと説明します。人手の採点、LLM審査の出力、コード評価の出力がすべて同じ形でここに集まります。
よくある誤解は、traceをobservationを収めるコンテナテーブルとして想像することです。実際の保存形式は異なり、この点は後の節で改めて扱います。
observationが分かれる三つの型 — span、generation、event
observationは一つの型ではありません。ドキュメントはLLMに特化した型としてgenerationとeventを挙げ、Python SDKはLangfuseSpan、LangfuseGeneration、LangfuseEventの三クラスを公開しています。
| 型 | 何を持つか | 時間 | 典型的な用途 |
|---|---|---|---|
| span | 一般的な作業区間 | 開始と終了がある | 検索、前処理、ツール呼び出し |
| generation | モデル呼び出し | 開始と終了がある | モデル名、トークン使用量、コストを持つ |
| event | ある一点の出来事 | 時点が一つ | ガードレール発動、キャッシュヒット |
generationが別にある理由は明確です。コストとトークンという軸がここにしかないからです。トークンとコスト追跡のドキュメントによれば、使用量はusage_detailsに、コストはcost_detailsに入ります。使用量の種別はプロバイダごとに異なる任意の文字列で、もっとも単純には入力と出力の二つです。使用量が取り込まれるか推定され、価格を持つモデル定義に一致すれば、Langfuseは取り込み時点でコストを計算します。
実務上の含意は明快です。モデル呼び出しをspanとして残すとコスト集計から消えます。セルフホストのモデルを使うなら、プロジェクト設定のモデル定義に正規表現パターンと価格を登録して初めてコストが付きます。
入れ子が必要な理由 — RAG一回の実行が残すツリー
なぜ平坦なログではなくツリーなのかは、実際の実行を一つ描けばすぐ見えます。ユーザーの質問一つがRAGパイプラインを通るときに残る構造はおおよそ次のとおりです。以下は概念を示す例です。
trace chat-request [user_id, session_id, tags, metadata]
└─ span rag-pipeline 1,840 ms
├─ span retrieve 420 ms
│ ├─ generation embed-query 90 ms usage_details, cost_details
│ └─ span vector-search 310 ms metadata: top_k, index
├─ span rerank 260 ms
│ └─ generation rerank-call 250 ms
├─ generation answer 1,090 ms model, usage_details, cost_details
└─ event guardrail-hit metadata: rule=pii
このツリーが答える問いに平坦なログは答えられません。全体で1.8秒かかったうち検索が0.42秒、回答生成が1.09秒という分解は、親子関係があって初めて計算できます。失敗も同じです。どのステップで例外が出たのか、そのステップの入力が何だったのかは、そのステップが独立したobservationである場合にだけ残ります。
エージェントはここからさらに一段進みます。ツール呼び出しが何回起きるかは実行前に分からないため、同じ名前の兄弟ノードが繰り返し付きます。ループを三周した実行と十周した実行が同じtrace名で残り、その差は子observationの個数と深さにしか現れません。「ツール呼び出しが五回を超えた実行」を探す問い合わせは、この構造の上でのみ成立します。
traceの上に乗る二つの層 — sessionとuser
trace一つでは会話を説明できません。複数ターンのやり取りはtraceが複数残るからです。Langfuseはこれをsessionでまとめます。ドキュメントは関係を一つのsessionが複数のtraceを持つと表記し、session識別子は200文字未満のUS-ASCII文字列なら何でも使えると説明します。
ユーザーの軸は別の属性として付きます。データモデルのドキュメントが明示するtraceレベルの属性は四つです。
user_id— エンドユーザーの識別子session_id— 会話または相互作用のまとまりtags— 機能やワークフローの分類metadata— 任意のキーと値
これに配備の文脈を分けるenvironmentと、アプリケーションのバージョンを示すrelease、versionが加わります。これらの値をいつ決めるかが、後で問い合わせ可能な軸を決めます。後から付けても過去のデータにはありません。
ドキュメントが明示する重要な挙動がもう一つあります。traceレベルの属性は、そのtraceに属するすべてのobservationへ自動的に伝播します。この一文が次の節と、本シリーズ第三回の保存層の話へ直接つながります。
score — 評価結果が付く場所
scoreは四つのフィールドからなります。名前、値、データ型、そして任意のコメントです。データ型はドキュメント基準で四種類です。
| データ型 | 値の形 | 用途 |
|---|---|---|
| NUMERIC | 実数 | 正確さや関連度のような連続的な測定 |
| CATEGORICAL | あらかじめ決めた文字列 | 既知の選択肢のいずれかへの分類 |
| BOOLEAN | 0または1 | 合格と不合格 |
| TEXT | 1文字から500文字の自由文字列 | 定性的な注釈 |
TEXTには制約があります。ドキュメントはTEXTのscoreを実験、LLM審査の評価器、分析で使えないと明示しています。自由記述は人が読むためのものであって集計の軸ではないという意味です。
付く場所も四つあります。trace、observation、session、そしてデータセット実行です。もっとも多いのはtraceに付く形で、これが端から端までの評価にあたります。v4で注目すべき変化は、評価器がtraceレベルからobservationレベルへ降りたことです。バージョンポリシーのドキュメントはv3からv4への変化の一つとしてこれを挙げています。パイプライン途中の検索ステップだけを単独で評価できるようになったという意味です。
保存形式を知れば問い合わせがずれない
先送りにしていた部分です。データモデルのドキュメントは保存をこう説明します。概念的には一つのobservationsテーブルがあり、各行はobservationレベルのデータに加えてtraceレベル属性の複製を持ちます。
この一文が実務で三つを決めます。
- 非正規化が既定です。
user_idで絞り込む問い合わせは結合なしで処理されます。関係データベースを想像して結合を設計すると方向がずれます。 - trace属性の事後変更は安くありません。複製がobservationの行ごとに存在するからです。
- 観測データに合う保存エンジンが要ります。幅が広く繰り返しの多い行を大量に走査する処理は、まさにカラム指向ストレージが得意とするところです。Langfuseがtrace、observation、scoreをClickHouseに置く理由がここにあり、第三回で詳しく見ます。
転送方式も知っておくと役に立ちます。ドキュメントはLangfuseがtraceをローカルでバッチにまとめ、バックグラウンドで送ると説明します。リクエスト経路を同期的に塞がないためです。そしてSDK全体がOpenTelemetry標準の上に立っています。この二つの実際の挙動は第二回で扱います。
おわりに — 先に決めるものと後で直せるもの
データモデルでも後から直しやすいものと難しいものが分かれます。scoreを追加すること、ダッシュボードを描き直すこと、タグを増やすことはいつでもできます。一方でuser_idとsession_idをどんな値で埋めるか、どこまでを一つのtraceと見るか、どのステップを独立したobservationとして残すかは初期に決める必要があります。後で変えると過去のデータとのつながりが切れます。
今できる点検は簡単です。先月受けた問いを三つ書き出し、今残している構造でそれに答えられるかを確認してください。答えられないなら、足りていないのはダッシュボードではなく計測です。
ツール選定そのものがまだ終わっていないなら、LLMプロダクションモニタリングプラットフォーム比較を先に読むほうがよいでしょう。本シリーズはLangfuseを使うと決めた後の話です。
試してみる
- DuckDBデータ分析プレイグラウンド — 入れ子のツリーを平坦なテーブルに展開し、親子関係を問い合わせで解いてみてください。
- PostgreSQLプレイグラウンド — traceとobservationを関係モデルで組んでみると、なぜ非正規化が選ばれるかが体感できます。
シリーズ
参考資料
- Langfuse データモデル: https://langfuse.com/docs/observability/data-model
- Langfuse Scores 概要: https://langfuse.com/docs/evaluation/scores/overview
- Langfuse Sessions: https://langfuse.com/docs/observability/features/sessions
- Langfuse トークンとコスト追跡: https://langfuse.com/docs/observability/features/token-and-cost-tracking
- Langfuse バージョンポリシー: https://langfuse.com/self-hosting/versioning