Langfuse の公式ドキュメントに掲載されている Cookbook「Migrating Data Between Langfuse Projects (Python SDK v4) 」を、4 つの移行パターンで実際に実行してみました。移行はおおむね成功しましたが、UI に出るものと出ないものが分かれ、見落としやすい落とし穴がいくつかありました。本記事では、特定の検証環境における観測結果と再現時の注意点を整理します。Cloud / セルフホスト / リージョンによる差分が Langfuse の正式仕様なのか、Feature flag や Preview UI・検証時点の実装差なのかは、本記事の範囲では切り分けていません。
想定読者は、Langfuse のプロジェクト間移行(クラウド間・セルフホスト ↔ クラウド)を検討しているが、公式スクリプトでどこまで再現できるかを把握したい方です。
1. 公式移行スクリプトの概要#
この Cookbook は、Langfuse Python SDK v4 を使い、移行元プロジェクトから移行先プロジェクトへデータをコピーする Jupyter ノートブックです。移行元・移行先それぞれの API キーとベース URL を設定し、セクション順に実行していきます。
トレースと Observation は、元の開始・終了時刻を保つために OTLP エンドポイント経由で再取り込みします。Score、プロンプト、データセットなどは公開 API 経由です。
移行できるもの#
Cookbook 冒頭では、次の 6 種類が移行対象として列挙されています。
- Score Configs(スコアの名前・型などの定義)
- Custom Model Definitions(ユーザー定義モデル)
- Prompts(すべてのバージョン)
- Observations(トレースとネストした Observation)
- Scores
- Datasets(items と run items)
移行できないもの・スクリプト対象外#
Cookbook 冒頭の「What is not migrated」では、Langfuse 公開 API が現時点でプログラムから作成できないものとして、次が挙げられています。これらは手動で再作成するか、UI レベルのエクスポート経由でコピーする必要があります。
- LLM-as-a-Judge Evaluator 設定
- カスタムダッシュボード
- ユーザー / RBAC / SSO
- プロジェクト・組織の設定
2. 試した移行パターン#
いずれも同一の Cookbook を使用し、移行元/先のAPI キーのみ変更しました。
| # | 移行元 | 移行先 |
|---|---|---|
| 1 | Langfuse Cloud | Langfuse Cloud |
| 2 | セルフホスト | Langfuse Cloud |
| 3 | Langfuse Cloud | セルフホスト |
| 4 | セルフホスト | セルフホスト |
検証環境#
移行結果の差分は、Langfuse 本体・SDK・Cloud 側の Preview 状態に依存する可能性があります。本記事の再現性判断の前提は次のとおりです。
| 項目 | 値 |
|---|---|
| Langfuse(Cloud / セルフホスト) | v3.198.0 |
| Python SDK | 3.8.1、4.0.0 |
| Cloud リージョン | JP |
| Fast Preview | 有効 |
認証に使うのは、各 Langfuse プロジェクトで発行する API キー(公開鍵・秘密鍵)です。移行元と移行先が同一組織内の別プロジェクトか、組織をまたぐかは、今回の試行範囲では挙動差はほぼ観測されませんでした。
パターン 2・4 は移行元がセルフホストのため v2 Observations API が使えず、3章で述べる v1 取得への手動差し替えが前提です。パターン 1・3 は移行元が Cloud のため、Cookbook 原本のまま実行できます。パターン 1 は、JP ↔ US ↔ EU などのクラウド間移行に相当します。
3. セルフホスト移行元の注意:Observation 取得の差し替え#
Cookbook の Observations セルには、次の注記があります。
Self-hosted deployments: the v2 Observations API is currently Cloud-only. If you are migrating from a self-hosted
Langfuse instance, replace the observations.get_many(...) calls below with src.api.trace.get(trace.id).observations
(which hits the v1 endpoint). A Langfuse self-hosted migration path for v2 is on the roadmap.これは、移行元がセルフホストで v2 の Observation 一覧 API が使えない場合(パターン 2・4)を想定しています。 変更するのは Observation を集めるヘルパーだけで足ります。OTLP 送出や trace 単位の移行ロジックはそのままで構いません。
置き換える場所#
_fetch_observations_v2関数本体(observations.get_manyとカーソルページングの部分)
公式の差し替え例#
デフォルトの _fetch_observations_v2 は、v2 API の observations.get_many をカーソルでページングする実装です。セルフホスト移行元(パターン 2・4)では、この関数本体を Cookbook が示す v1 経路に差し替えます。
Cookbook の Section 4 説明文では src.api.trace.get(trace.id).observations、_fetch_observations_v2 の docstring では次の 1 行が示されています。どちらも同じ意味で、今回の試行でもこの公式の書き方どおりに差し替えました。
def _fetch_observations_v2(source_trace_id: str) -> List[Any]:
return with_retries(lambda: src.api.trace.get(source_trace_id)).observationsobservations が None になるケースだけ or [] を足すと安全です。リストが返る通常の trace であれば、公式の 1 行と挙動は同じです。
def _fetch_observations_v2(source_trace_id: str) -> List[Any]:
return with_retries(lambda: src.api.trace.get(source_trace_id)).observations or []関数名を _fetch_observations_v2 のままにして関数本体だけ差し替えれば、呼び出し側はそのままで動きます。
クラウド移行元のとき(パターン 1・3)#
移行元が Langfuse Cloud で v2 API が使えるなら、Cookbook 原本の observations.get_many とカーソルページングのままで構いません。本章の差し替えは、移行元がセルフホストのとき(パターン 2・4)に限ります。
4. パターン共通でわかったこと#
4 パターンいずれでも、今回の検証環境では次のような傾向が観測されました。取得 API が v1 か v2 かにかかわらず、Cookbook 本体の OTLP 再取り込みや API の制約に起因するものがほとんどです。 詳細は7章以降に記載しています。
- Observation(Span / Generation など)単位のデータは移行できるが、Trace ツリーの見え方は元と一致しない(合成ルート Span の追加、親子リンクのずれ)
- Score はスクリプトログ上は migrated と出ても、公開 API で取得できず、UI の一覧や Trace 上のバッジにも出ないことがあった
- Observation 型(AGENT / TOOL / CHAIN 等)はコピーされず、SPAN や GENERATION として再登録される
- プロンプトの版番号は移行先で新しい連番になる(元版は commit message 等に残る)
- Dataset の schema(inputSchema / expectedOutputSchema)は移行されない
- Langfuse Media 参照付きの dataset item は、Media 本体が移行されないため失敗しうる
- dataset item は最新バージョン中心で、古いバージョンに紐づく run item は失敗しうる
- 移行 Trace には OTLP 由来の metadata(
langfuse-migration等)が付く - データセットやプロンプトの一覧並びが、作成順の影響で元と逆に見えることがある
5. 移行先によって分かれたこと:Experiments(Dataset run)の UI#
Experiments(Dataset run)ページに run item が表示されるかは、今回の検証環境では移行元より移行先で観測が分かれました。正式仕様としての断定はせず、以下は検証時点の観測です。詳細は7章以降に記載しています。
| 移行先 | 該当パターン | スクリプトログ | Experiments / Dataset run UI | 公開 API |
|---|---|---|---|---|
| Langfuse Cloud | 1, 2 | run item が migrated / linked と表示される | 何も表示されない(空) | dataset run / run item を取得できなかった |
| セルフホスト | 3, 4 | 同様に成功ログ | run item が表示される(latency / cost 集計は空になりうる。7章参照) | 一覧取得は可能だったが、集計値は UI 上で空になることがあった |
今回の検証環境では、「ログ上は移行できたのに Experiments の UI には出てこない」のは移行先が Cloud のときに限って観測されました。セルフホスト移行先では run item 自体は UI に出ます。移行先が Cloud の場合、UI だけでなく公開 API でも dataset run 関連データを取得できなかったため、保存の有無までは本記事の範囲では確認できていません。Dataset run まで要件に含める場合は、移行先が Cloud ならドライランで UI と API の両方を確認することをおすすめします。

6. うまくいったこと#
Score config とカスタム Model 定義#
4 パターンいずれでも、Score config とカスタム Model 定義は意図どおり移行できました。移行先に同名の Score config がある場合は既存を再利用し、無ければ新規作成する動きです。
プロンプト#
プロンプトも、移行元で版を削除してバージョン番号が非連続になっている場合を除けば、正常に移行できました。版を削除したあとに移行すると、移行先では版番号が振り直されます。通常の連番のままであれば、本文・ラベル・タグは問題なくコピーされます。
Observation(Span)単位の移行#
公式の差し替え例どおり v1 取得に差し替えた後、Observation 単位の入出力・コスト・時刻などは移行できました。ただし Trace ツリー全体として元と同じ見え方になるわけではなく、7章で述べるとおり構造のずれは残ります。セルフホスト移行元向けの差し替え方は、3章を参照してください。
7. 気づいたこと・想定される原因#
Trace のツリー構造が変わる#
移行元では、Observation が Trace 内で階層構造になっていても、移行先では、同名の Span が一段多くネストしたり、task が親から外れてルート付近に並んだりします。
これは Cookbook が trace ごとに必ず行う OTLP 再構築が主因です。Trace 一覧と Observation 一覧を取得したうえで、合成ルート Span を常に 1 本追加し、各 Observation を別 Span として再送します。
厳密に元の Trace ツリーを再現したい場合は、合成ルート Span を付けない、親 ID の読み方を直すなど、スクリプト改修が必要です。本番移行前に、代表 trace を 1 本選び、移行元・移行先でツリーを並べて比較することをおすすめします。

Score はログ上「成功」でも API で取得できず、UI にも出なかった#
今回の検証環境では、スクリプトログ上は Score 移行が成功(migrated)と出ても、移行先の公開 API で当該 Score を取得できず、Scores 一覧・Trace ツリー・Observation バッジのいずれにも表示されませんでした。UI 非表示だけでなく API 取得にも失敗したため、保存されていることを保証できません。
Cookbook 上、Score config は移行対象ですが Evaluator(LLM-as-a-Judge)の設定・実行履歴は対象外です。configId 付き Score や observation ID の不一致が影響している可能性はありますが、今回の環境では API でエビデンスを取れなかったため、原因までは特定していません。

Dataset 自体の設定は一部移行できない#
Dataset 本体の name / description / metadata は移りますが、inputSchema / expectedOutputSchema は送られません。移行元で schema を設定していた場合、移行先では空になり、item の中身だけがコピーされた状態です。schema 付き Dataset を運用している場合は、移行後に手で再設定してください。
Dataset run の latency / cost 集計が空になることがある#
今回の検証環境では、移行先がセルフホスト(パターン 3・4)では run item は UI に出ましたが、一覧の latency / total cost が空になることが観測されました。紐づく Trace を開くと cost は見えます。移行先が Cloud(パターン 1・2)では、Experiments ページ自体が空で、公開 API でも dataset run 関連データを取得できず、集計以前の問題でした。
run 一覧の集計が空になる件については、Cookbook が run item 作成時に createdAt を渡さず移行実行時刻が入る一方、Observation は元の start_time を保持するため、時間窓から外れて集計が null になりうる、という説明が考えられます。ただし Cloud 移行先で API 取得までできなかったケースについては、集計ロジック以前の問題として切り分けが必要です。

移行 Trace に OTLP 由来の metadata が付く#
Cookbook の OTLP パイプラインは service.name: langfuse-migration を固定で載せます。移行先 Trace を開くと、元の Prompt test 実行時とは違う metadata に見えることがあります。比較・監査で metadata を見ている場合は注意が必要です。

並び順が逆に見えることがある#
データセット、プロンプトなどで、一覧の並びが元プロジェクトと逆に感じられることがありました。移行処理が作成 API を順に叩くため、作成順がソートに影響している可能性があります。表示上の問題であり、データ欠損とは限りません。
Dataset item は最新バージョン中心で、古い run item が失敗しうる#
最新の dataset item バージョンしか移行されないため、古いバージョンにだけ存在していた run item は参照先が無く失敗することがあります。media 付き item では、JSON 内の @@@langfuseMedia:...@@@ 参照だけが送られ、Media 本体は移行されないため 400 エラーになり、run item が連鎖失敗することもありました。
Observation 型(AGENT / TOOL / CHAIN 等)はコピーされない#
移行元の AGENT 等は、Cookbook の OTLP 変換で SPAN や GENERATION に置き換えられます。
_OBS_TYPE_MAP = {
"AGENT": "span",
"TOOL": "span",
# ... 他の型も同様に span または generation へ
}中身(名前・入出力)は残ることが多いですが、UI の型アイコンは変わるため、Agent フィルタでは 0 件になり、移行漏れと勘違いしやすいです。確認時は型フィルタだけに頼らず、名前や入出力で突き合わせてください。

8. Cookbook の移行マトリクス(移行される/されない)#
公式 Cookbook の移行範囲を出発点に、4 パターンの試行で著者が拡張整理したものです。以下は v3.198.0 / SDK 3.8.1・4.0.0 / Cloud JP / Fast Preview 有効の検証環境における観測であり、Langfuse の正式仕様であるとは限りません。1章「移行できないもの・スクリプト対象外」節より詳しい検証結果が載っています。
移行される項目・されない項目
項目別の整理#
早見#
おおむね移行されるもの:Trace / Observation の入出力・コスト・時刻・多くの metadata、Score config、カスタム model、prompt 本文、Dataset item の最新版、Trace 単位の public。
移行されない/変形するもの:evaluator 設定、dataset schema、Observation 型、Langfuse Media 本体、Trace ツリー完全一致、検証環境では移行先 Cloud で Experiments UI・API 取得に失敗、Score は API 取得不可、prompt 版番号の維持、item の全バージョン、run 集計(createdAt 未指定)など。
Score config#
移行される:name、data_type、description、カテゴリ定義、数値の min/max など。
移行されない・注意:アーカイブ状態。移行先に同名 config がある場合は既存を再利用。検証環境では configId 付き Score が API で取得できなかったケースあり(保存失敗かは未確認)。
カスタム Model 定義#
移行される:model 名、match pattern、価格設定など。
移行されない:Langfuse 管理モデル。移行先に既に存在する定義。
Prompt#
移行される:name、本文、labels、tags、config。
移行されない・変わる:版番号(移行先で新連番。commit message に元版を記載)。Cookbook 未対応の prompt 型。
Trace / Observation(OTLP)#
移行される:id、name、user_id、session_id、入出力、metadata、時刻など。
移行されない・変わる:Observation 型そのもの、Trace ツリー構造、Media 添付、OTLP 固有 metadata、Observation 単位の public。
Score#
移行される(Cookbook 上):id、name、value、comment、timestamp、trace_id / observation_id(マップ後)など。
検証環境での観測:スクリプトログは migrated でも、公開 API で Score を取得できず UI にも表示されなかった。source、dataset_run_id、evaluator 実行履歴との紐づけは未確認。
Dataset#
移行される:Dataset の name / description / metadata、item の入出力、run と run item のリンク(マップ後)。
移行されない・変わる:schema、Media 本体、item の過去バージョン、run item の createdAt、検証環境では移行先 Cloud で Experiments UI・API 取得に失敗。
Cookbook 対象外(公式記載の移行しない項目)#
LLM-as-a-Judge Evaluator 設定、カスタムダッシュボード、ユーザー・RBAC・SSO、プロジェクト・組織の設定。
9. まとめ#
この Cookbook は、クラウド間やセルフホストからデータの大部分を API で持っていくための実用的な出発点です。今回の検証環境(Langfuse v3.198.0、SDK 3.8.1 / 4.0.0、Cloud JP、Fast Preview 有効)では、Score config、カスタム Model、プロンプト、Observation 単位のデータは移行できました。一方で、Trace ツリーの見え方、Score の API 取得・UI 表示、Observation 型、Dataset schema や Media、移行先 Cloud での Experiments の API 取得・UI 表示などは、観測された範囲ではそのまま再現されませんでした。
本番移行前に、少ない件数で試し、特に移行先が Cloud なら Dataset run の UI と API の両方、移行先がセルフホストなら run 集計まで、運用要件を満たすかを確認することをおすすめします。本記事の結果は検証環境に依存するため、バージョンや Preview 状態が異なると見え方が変わる可能性があります。