Potato で Langfuse のトレースをレビューする
Langfuse のトレースをポーリングまたはエクスポートの変換で Potato に取り込み、1 つのトレースに複数のレビュアーを割り当て、回答をスコアとして Langfuse に返します。
Potato は Langfuse のトレースを 2 通りの方法で読み込みます。Langfuse プロジェクトをポーリングして完了したトレースを取得する方法と、python -m potato.trace_converter --input-format langfuse でトレース・ファイルを変換する方法です。ポーリングで取得したトレースについては、レビュアーの統合された回答が、トレースと判定対象の各オブザベーションに対するスコアとして Langfuse に返されます。変換したトレースの結果は Potato のエクスポートに残ります。
Langfuse は、LLM とエージェントのアプリケーションをデバッグ・評価するための、オープンソースでセルフホスト可能な AI エンジニアリング・プラットフォームです。Langfuse のリポジトリは、ee フォルダ以外が MIT ライセンスです。Langfuse のデータモデルでは、トレースは 1 つのリクエストまたは操作を表します。オブザベーションはトレース内の 1 ステップで、LLM 呼び出し、ツール呼び出し、検索ステップなどが該当し、アプリケーションの構造に沿って入れ子になります。セッションは 1 回のユーザーとのやり取りに含まれるトレースをまとめ、スコアはトレース、オブザベーション、セッションに付く値です。
Langfuse 自身のアノテーション・キューでは、レビュアーがスコア設定に沿ってトレース、オブザベーション、セッションにスコアを付けます。Potato は、トレースごとに複数の盲検レビュアーを置き、レビュアー間の一致度を計算し、不一致を裁定したうえで、結果を Langfuse の同じトレースに返します。LLM とエージェントのトレースのための人手レビューキューは、Langfuse、LangSmith、Braintrust、Arize、Potato のレビュー機能を比較しています。
ポーリングとエクスポート・ファイルの比較
次の表は 2 つの経路を比較したものです。
ポーリング(trace_ingestion) | エクスポート・ファイル(trace_converter) | |
|---|---|---|
| トレース | 遡及期間内に開始したプロジェクト内のすべてのトレース、その後は新しいトレースが完了するたびに 1 回ずつ | ファイル内のトレース |
| Langfuse API | Observations API v2(フォールバックとして従来の traces API) | なし(ファイルは利用者が用意) |
| 表示が読む項目フィールド | steps(オブザベーションごとに 1 エントリ) | conversation(思考、アクション、結果ごとに 1 ターン) |
| 保持されるオブザベーション・タイプ | すべてのタイプ | GENERATION、SPAN、EVENT |
| セッション ID | metadata.session_id に保持 | 保持されない |
| Langfuse に送られるスコア | あり | なし |
ポーリングは、レビュー済みのスコアを Langfuse の各トレースの隣に置きたいチームに向いています。エクスポート・ファイルは、結果を Potato 内に置いておけば足りる固定のサンプルに向いています。論文用に選んだトレースや、評価用の保持セットに入れるトレースは、典型的な固定サンプルです。
Langfuse プロジェクトをポーリングする
次の設定は、Langfuse Cloud を 1 分ごとにポーリングし、各トレースを 2 人のレビュアーに割り当てます。最初のポーリングでは、過去 7 日間に開始したすべてのトレースを取得します。2 人のレビュアーが回答し終えると、Potato は統合された各回答を、human.resolved、human.helpfulness、human.step_check という名前のスコアとしてトレースに送り返します。この設定は完結しており、python -m potato.validate_cli --strict に通ります。
annotation_task_name: Langfuse trace review
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/traces.jsonl
item_properties:
id_key: id
text_key: task_description
user_config:
allow_all_users: true
users: []
# Pull every trace that started in the last 7 days, then poll once a minute.
# Keys for write-back come from LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY.
trace_ingestion:
enabled: true
api_key: change-me
sources:
- type: langfuse
api_url: https://cloud.langfuse.com
public_key: pk-lf-...
secret_key: sk-lf-...
poll_interval: 60
lookback_hours: 168
writeback:
trigger: fully_annotated
min_annotators: 2
score_prefix: human
langfuse:
score_configs: true
num_annotators_per_item: 2
instance_display:
fields:
- key: steps
type: agent_trace
label: Langfuse trace
display_options:
collapse_observations: true
annotation_schemes:
- annotation_type: radio
name: resolved
description: Did the agent answer the user's question correctly?
labels: [correct, partly_correct, wrong]
- annotation_type: likert
name: helpfulness
description: How helpful was the final answer?
size: 5
min_label: Not helpful
max_label: Very helpful
- annotation_type: process_reward
name: step_check
description: Mark each step correct or incorrect.
steps_key: steps
mode: per_stepサーバーは potato start config.yaml -p 8000 で起動します。Potato は起動時にデータ・ファイルを必要とし、data/traces.jsonl にすでにあるトレースは、ポーリングで取得したトレースとともにキューに入ります。ファイル内のトレースが上の表示に現れるのは、そのトレースに steps リストがある場合に限られます。
trace_ingestion.sources の下にある type: langfuse の各エントリが、ポーラーを 1 つ起動します。ポーラーは次のキーを読みます。
| キー | 既定値 | 意味 |
|---|---|---|
api_url | https://cloud.langfuse.com | Langfuse のホスト。セルフホスト・サーバーは独自の URL を指定します |
public_key、secret_key | なし | プロジェクトの API キー |
poll_interval | 30 | ポーリング間隔(秒) |
lookback_hours | 24 | 最初のポーリングがどこまで遡るか |
settle_seconds | 30 | トレースが完了してから取得するまでの待機時間(秒) |
max_trace_seconds | 3600 | 未完了のトレースを強制的に取得するまでの秒数 |
ポーラーが取得するトレース
ポーラーは、対象の時間枠について GET /api/public/v2/observations を読み、行をトレース ID ごとにグループ化します。このエンドポイントを持たないサーバーでは、代わりに従来の GET /api/public/traces API を使います。Langfuse の移行ガイドによると、Langfuse Cloud は従来の読み取りエンドポイントを 2026 年 11 月 16 日まで提供し、この切り替えにポーラーの変更は不要です。ルート・オブザベーションに終了時刻があり、かつ settle_seconds が経過した時点で、トレースは完了したと見なされます。ルートが終了しないトレースは、max_trace_seconds が経過すると、metadata.complete を false に設定して取得されます。Potato はアイテム・ストアにすでにあるトレースをスキップするため、再起動してもトレースが二重に追加されることはありません。
各トレースは、ID が langfuse_<trace id> の 1 つのアイテムになります。アイテムの steps リストには、子オブザベーションが開始順に入り、各ステップはオブザベーションの入力、出力、タイプ、名前、モデル、オブザベーション ID を保持します。トレース名は task_description になり、名前のないトレースではトレースの入力が使われます。セッション ID、ユーザー ID、タグ、入力、出力は metadata の下に入ります。ポーラーは時間枠内のすべてのトレースを取得し、名前、タグ、環境による絞り込みは行いません。ポーリングで取得したトレースのうち、レビュアーにどれを先に見せるかは、自動化ルールとトリアージ・キューが決めます。
Langfuse のエクスポートを変換する
langfuse コンバーターは、JSON または JSONL 形式の 1 つのトレース・オブジェクト、またはトレース・オブジェクトのリストを読みます。各トレースには、完全なオブザベーション・オブジェクトの observations リストが必要です。従来の GET /api/public/traces/{traceId} エンドポイントはこの形式でトレースを返し、一覧エンドポイントの GET /api/public/traces はオブジェクトではなくオブザベーション ID を返します。v2 の Observations API の行は、変換の前に traceId ごとにグループ化し、オブザベーションを observations の下に置いた 1 トレース 1 オブジェクトの形にする必要があります。ファイルを変換するには次のようにします。
python -m potato.trace_converter \
--input langfuse_traces.json \
--input-format langfuse \
--output data/traces.jsonlこのコマンドはトレースごとに 1 行の JSON を書き出し、--auto-detect は、先頭のエントリに type がある observations リストからこの形式を認識します。コンバーターは各フィールドを次のように対応付けます。
| Langfuse のフィールド | Potato のフィールド |
|---|---|
トレースの id | id |
トレースの name | agent_name |
トレースの input.query、input.input、input.question、または文字列の input | task_description |
GENERATION オブザベーションの出力(content、text、completion) | conversation 内の Agent (Thought) ターン |
SPAN オブザベーションの名前と入力 | name(key=value, ...) 形式で書かれた Agent (Action) ターン |
SPAN オブザベーションの出力(result、output、またはオブジェクト全体) | Environment ターン |
EVENT オブザベーションの出力、出力がない場合は入力 | System (<name>) ターン |
| オブザベーション数、トレース名、モデル | metadata_table の行 |
コンバーターはオブザベーションをファイル内の順序のまま保持し、他のすべてのオブザベーション・タイプを破棄します。Langfuse のオブザベーション・タイプには、agent、tool、chain、retriever、evaluator、embedding、guardrail も含まれ、それらのタイプで計装されたトレースは、変換でそのステップを失います。トレースの出力、メタデータ、セッション ID、オブザベーションのレベルも引き継がれません。トレースがこれらのタイプやフィールドに依存している場合は、代わりにプロジェクトをポーリングしてください。
変換したファイルでは、表示とステップ単位のスキームを conversation に向けます。ポーリング用の設定で、instance_display ブロックと annotation_schemes の下の step_check エントリを、次のものに置き換えてください。
instance_display:
fields:
- key: task_description
type: text
label: User request
- key: conversation
type: agent_trace
label: Langfuse trace
annotation_schemes:
- annotation_type: process_reward
name: step_check
description: Click the first step that went wrong.
steps_key: conversation
mode: first_errorLangfuse トレース用の表示とスキーム
agent_trace 表示は各ステップを、タイプ・バッジ付きのカードとして表示し、ポーラーの steps リストとコンバーターの conversation リストの両方を読みます。3 ペインの eval_trace 表示は、同じステップを推論、関数呼び出し、最終回答に分割します。表示の key は、トレースが入ってきた経路に合わせて steps か conversation に設定してください。
書き戻しは、radio、likert、slider、number、select、multiselect の各スキームから、トレース単位の回答を送ります。ステップ単位の質問には、process_reward、trajectory_eval、tool_call_review、failure_attribution、gui_trajectory を使います。ポーリングで取得したトレースでは、min_annotators 人のレビュアーが付けた各ステップ・ラベルが、そのステップ自身のオブザベーションに対するスコアになります。テキストボックスは根拠を Potato のエクスポート用に記録し、送信されることはありません。Langfuse のセッション全体に関する質問には、enabled: true の sessions ブロックが、ポーリングしたトレースを metadata.session_id でグループ化し、session_level: true のスキームは、/sessions ページでセッションごとに 1 回回答されます(セッション単位のスコアリング)。
MAST 失敗分類と τ-bench エージェント評価のデザインは、エージェントの失敗に対する、すぐに使えるトレース単位のスキームを提供します。
Langfuse のスコア
書き戻しは、min_annotators 人のレビュアーが回答すると、スキームごとに 1 つのスコアを送ります。trigger: adjudicated では、Potato は代わりに裁定者の判断を待ち、どちらのトリガーでも、裁定された値が多数決に取って代わります。各スコアには <score_prefix>.<scheme name> という名前が付き、次の値を持ちます。
| スキーム | スコアの値 | Langfuse のデータ型 |
|---|---|---|
radio、select、labels 付きの likert | 最も多くのレビュアーが選んだラベル | Categorical |
multiselect | 過半数のレビュアーが選んだラベルをカンマ区切りにしたもの | Categorical |
labels なしの likert | 中央値の点 | Numeric |
slider、number | 平均 | Numeric |
| ステップ単位のスキーム | 最も多くのレビュアーがそのステップに付けたラベル(ステップのオブザベーションに送信) | Categorical |
同票の場合は、裁定者が判断するか別のレビュアーが決着をつけるまで、何も送信されません。コメントには値の決まり方が記され、たとえば Potato: 2 of 2 annotator(s) chose correct となります。メタデータには、レビュアー数、回答数、一致したレビュアー・ペアの割合が入ります。ユーザー名は送信されません。langfuse.score_configs: true を設定すると、Potato は各スコアを初めて送るときに、そのスコア名のスコア設定を Langfuse に作成します。これにより Langfuse は、フィルターに resolved のカテゴリと 1 から 5 の有用性の範囲を表示できます。
書き戻しは、LANGFUSE_PUBLIC_KEY と LANGFUSE_SECRET_KEY の両方が設定されていればそこから Langfuse のキーを読み、設定されていなければ langfuse ソースから読みます。Potato は、各スコアにトレースとスキームごとに固定の ID を付けます。Langfuse がスコアを置き換えるのは、ID、名前、日付がすべて一致する場合だけです(重複スコアの防止)。そのため、後日に回答が変わると、その日付の 2 つ目のスコアが追加されます。送信状況は、管理用 API キーで確認します。
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/api/trace_writebackレスポンスは、保留中、送信済み、失敗、保留扱いのスコアの数を示し、保留扱いの各トレースが待っている理由も示します。
Potato のエクスポートに残る結果
ポーリングで取得したトレースも変換したトレースも、アノテーションは annotation_output/<user>/user_state.json に保持されます。agent_eval エクスポートは回答をトレースごとに集計し、単一選択のスキームにはラベル分布と多数派を、数値のスキームには平均を出します。
python -m potato.export --config config.yaml --format agent_eval --output results/エクスポート形式には、jsonl、parquet、huggingface などの形式が挙げられています。セッション単位の回答は annotation_output/session_annotations.jsonl に出力されます。/admin/iaa ダッシュボードは、スキームごとにレビュアー間の一致度を報告し、アノテーター間一致度の解説は係数の選び方を扱っています。データセットのページでは、Import ingested traces を使って、ポーリングしたトレースからバージョン管理された評価セットを作れます。修正された軌跡は、軌跡の編集を通じて SFT と DPO のレコードになります。
制限事項
- 書き戻しの対象はポーリングしたトレースだけです。 データ・ファイル由来のトレースは、変換したものも含めて、結果が Potato 内に残ります。
- テキストの回答は Potato 内に残ります。 書き戻しは自由記述の回答を送らず、セッション単位の回答も送りません。
- ポーラーはプロジェクト全体を取得します。 サンプリングと優先度は、トレースが届いたあとに、自動化ルールとトリアージ・キューが決めます。
よくある質問
Langfuse とは何ですか?
Langfuse は、LLM とエージェントのアプリケーションをトレースし、そのトレースを評価し、プロンプトを管理する、オープンソースの AI エンジニアリング・プラットフォームです。リポジトリは ee フォルダ以外が MIT ライセンスで、Langfuse は Langfuse Cloud として、または自前のサーバー上で動作します。Potato は Langfuse に接続して、1 つのトレースに複数の人間のレビュアーを割り当て、統合された回答をスコアとして Langfuse に返します。
エージェントのオブザーバビリティのために、Langfuse は何を記録しますか?
Langfuse は、エージェントの 1 回の実行を 1 つのトレースとして、実行内の各ステップをそのトレースの下に入れ子になったオブザベーションとして記録します。ステップとは、LLM 呼び出し、ツール呼び出し、検索ステップ、または実行内のその他の操作です。オブザベーションは、generation、agent、tool、retriever、guardrail など 10 種類のタイプのいずれかを取り、セッションは 1 回のユーザーとのやり取りに含まれるトレースをまとめます。トレースに span、event、generation 以外のタイプのオブザベーションが含まれる場合、Langfuse はオブザベーションのタイミングと入れ子からエージェント・グラフを描きます。Potato のポーラーはそれらのオブザベーションを読み、それぞれをレビュアーがラベル付けできるステップに変換します。
関連資料
- LLM とエージェントのトレースのための人手レビューキューと AI エージェント評価ツールの比較
- エージェント型アノテーション(他の 14 種類のトレース・コンバーターについて)
- LLM ジャッジと人間のアノテーターの一致度を測る方法(Langfuse の LLM-as-a-judge スコアとレビュアーのラベルを比較するため)
- エージェント評価における Potato と LangSmith・Langfuse の比較
- トレース・スコアの書き戻し(Potato のソース・ドキュメント)