Home Langfuse公式解説: OSSプロンプト管理基盤とA/Bテスト実装の技術詳細
投稿
キャンセル

✍️ Langfuse公式解説: OSSプロンプト管理基盤とA/Bテスト実装の技術詳細

ブログ概要(Summary)

Langfuseは、LLMアプリケーションのためのOSS(MITライセンス)オブザーバビリティ・評価基盤である。プロンプトのバージョン管理・ラベルベースのA/Bテスト・リアルタイムトレーシング・自動評価を統合的に提供する。GitHub Stars 19,000超、Y Combinator W23出身で、OpenTelemetry・LangChain・OpenAI SDK・LiteLLMなど主要フレームワークとの統合をサポートしている。

この記事は Zenn記事: LLMプロンプト管理CI/CD:Langfuse×LaunchDarklyでA/Bテストと安全ロールアウト の深掘りです。

情報源

技術的背景(Technical Background)

LLMアプリケーションの本番運用において、プロンプトはビジネスロジックそのものである。しかし、プロンプトをソースコードに埋め込む従来のアプローチには以下の問題がある:

  1. デプロイ結合: プロンプト変更のたびにアプリケーションの再デプロイが必要
  2. バージョン不明: 本番で稼働しているプロンプトの正確なバージョンが追跡困難
  3. 品質測定困難: プロンプト変更の効果を定量的に測定する手段がない
  4. 協業の壁: プロンプトエンジニアがコードリポジトリにアクセスする必要がある

Langfuseはこれらの課題を、プロンプト管理とオブザーバビリティの統合プラットフォームとして解決する。Zenn記事ではLangfuseのラベル機能によるA/Bテスト実装を紹介したが、本記事ではその技術的基盤を公式ドキュメントベースで詳解する。

実装アーキテクチャ(Architecture)

プロンプト管理のデータモデル

Langfuseのプロンプト管理は、以下の概念で構成される:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Prompt
├── name: "summarizer"          # プロンプト名(一意)
├── type: "text" | "chat"       # プロンプト種別
├── versions: [                 # 線形バージョニング
│   ├── version 1: { prompt: "..." }
│   ├── version 2: { prompt: "..." }
│   └── version 3: { prompt: "..." }  # 最新
│ ]
└── labels: {                   # ラベル→バージョンのマッピング
    "production": version 3,
    "staging": version 2,
    "prod-a": version 2,      # A/Bテスト用
    "prod-b": version 3       # A/Bテスト用
  }

線形バージョニング: 同名のプロンプトを作成すると自動的にバージョン番号がインクリメントされる。ブランチモデルではなく線形モデルを採用しているため、バージョンの前後関係が明確。

ラベルシステム: バージョンにラベル(文字列タグ)を付与することで、アプリケーションは「バージョン番号」ではなく「ラベル」でプロンプトを取得できる。これにより、デプロイなしでのプロンプト切り替えが実現する。

graph LR
    subgraph 開発サイクル["プロンプト開発サイクル"]
        Dev["プロンプト編集\nLangfuse UI / API"] -->|"自動インクリメント"| V1["version 1"]
        V1 --> V2["version 2"]
        V2 --> V3["version 3 最新"]
    end

    subgraph ラベル管理["ラベル管理"]
        V2 -->|"ラベル付与"| L_staging["staging ラベル"]
        V3 -->|"ラベル付与"| L_prod["production ラベル"]
        V2 -->|"A/Bテスト"| L_a["prod-a ラベル"]
        V3 -->|"A/Bテスト"| L_b["prod-b ラベル"]
    end

    subgraph アプリ["アプリケーション"]
        App["get_prompt\nlabel='production'"] -->|"キャッシュ TTL:60秒 経由"| L_prod
        AB_App["A/Bテストロジック\nランダム選択"] --> L_a
        AB_App --> L_b
    end

    subgraph トレーシング["オブザーバビリティ"]
        L_prod -->|"リクエスト記録"| Trace["Langfuseトレース\nレイテンシ・トークン・品質スコア"]
        L_a --> Trace
        L_b --> Trace
    end

SDK統合パターン

Python SDK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from langfuse import Langfuse

langfuse = Langfuse()

# プロンプト作成(バージョン自動インクリメント)
langfuse.create_prompt(
    name="movie-critic",
    type="text",
    prompt="As a  movie critic, do you like ?",
    labels=["production"]
)

# チャット型プロンプト作成
langfuse.create_prompt(
    name="summarizer",
    type="chat",
    prompt=[
        {"role": "system", "content": "あなたは技術記事の要約エキスパートです。"},
        {"role": "user", "content": "以下の記事を要約してください: "}
    ],
    labels=["staging"]
)

# プロンプト取得(productionラベル版)
prompt = langfuse.get_prompt("movie-critic")
compiled = prompt.compile(criticlevel="expert", movie="Dune 2")
# → "As a expert movie critic, do you like Dune 2?"

TypeScript SDK

1
2
3
4
5
6
7
8
9
10
import Langfuse from "langfuse";

const langfuse = new Langfuse();

// プロンプト取得
const prompt = await langfuse.prompt.get("movie-critic");
const compiled = prompt.compile({
  criticlevel: "expert",
  movie: "Dune 2"
});

キャッシュ戦略

Langfuse SDKはクライアントサイドキャッシュを実装しており、プロンプト取得時のレイテンシを最小化する:

  • サーバーサイド: CDNキャッシュによる高速配信
  • クライアントサイド: SDK内メモリキャッシュ。get_prompt()呼び出しはメモリ読み取りと同等速度
  • TTL(Time-to-Live): デフォルト60秒。本番環境ではcache_ttl_secondsで調整可能
1
2
# キャッシュTTLの設定
prompt = langfuse.get_prompt("summarizer", cache_ttl_seconds=300)

この設計により、Langfuseサーバーがダウンしてもキャッシュからプロンプトを提供でき、本番環境の可用性に影響しない。

パフォーマンス最適化(Performance)

A/Bテスト実装の詳細

Zenn記事で紹介したA/Bテストの技術的実装を深掘りする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import random
from langfuse import Langfuse
from openai import OpenAI

langfuse = Langfuse()
openai_client = OpenAI()

def summarize_with_ab_test(article_text: str, trace_id: str) -> dict:
    """A/Bテスト付きの記事要約

    Args:
        article_text: 要約対象のテキスト
        trace_id: Langfuseトレース識別子

    Returns:
        要約結果とバリアント情報
    """
    # ラベルでバリアントを取得
    prompt_a = langfuse.get_prompt("summarizer", label="prod-a")
    prompt_b = langfuse.get_prompt("summarizer", label="prod-b")

    # ランダム選択
    selected = random.choice([prompt_a, prompt_b])

    # テンプレート変数を埋め込み
    compiled = selected.compile(article=article_text)

    # Langfuseトレーシングと紐付けてOpenAI呼び出し
    response = openai_client.chat.completions.create(
        model=compiled.get("model", "gpt-4o"),
        messages=compiled["messages"],
        # Langfuseのprompt linkingで自動トレーシング
        langfuse_prompt=selected
    )

    return {
        "summary": response.choices[0].message.content,
        "variant": selected.label,
        "version": selected.version
    }

トレーシングとメトリクス追跡

Langfuseのトレーシングは各リクエストの以下を自動記録する:

メトリクス説明A/Bテストでの用途
レイテンシLLM呼び出しの応答時間バリアント間のp95比較
トークン使用量入力/出力トークン数コスト効率の比較
コストUSD換算の推論コストROI算出
品質スコアLLM-as-a-Judge評価出力品質の定量比較
ユーザーフィードバック👍/👎等のフィードバック実ユーザー満足度

Promptfoo統合

LangfuseはPromptfooとの直接統合をサポートしており、CI/CDパイプラインでの品質ゲートを構成できる:

1
2
3
4
5
6
7
8
9
10
# promptfooconfig.yaml
prompts:
  - id: langfuse:summarizer:production
  - id: langfuse:summarizer:staging
tests:
  - vars:
      article: "React 19のServer Componentsの新機能..."
    assert:
      - type: llm-rubric
        value: "要約は3-5文で、技術用語を保持している"

PromptfooはLangfuseからプロンプトを直接取得し、評価結果をLangfuseにフィードバックする双方向統合が可能。

運用での学び(Production Lessons)

統計的有意性の確保

Zenn記事で「サンプル数を決めずにn=47で有意差なしと結論してしまった」失敗を紹介した。Langfuseのダッシュボードでは以下が推奨される:

  • 最低サンプル数: 500リクエスト/バリアント
  • 有意水準: p < 0.05
  • 効果量: Cohen’s d ≥ 0.2(実用的な差異として意味がある)

バージョンロールバック

障害発生時のロールバックはラベルの付け替えだけで完了する:

1
2
3
4
5
6
7
# 緊急ロールバック: production ラベルを前バージョンに戻す
langfuse.create_prompt(
    name="summarizer",
    type="chat",
    prompt=previous_version_content,
    labels=["production"]  # 新バージョンとして作成、productionラベル付与
)

コードデプロイ不要で、即座にロールバックが完了する。Zenn記事で紹介したLaunchDarklyのFeature Flagとの二重ロールバック機構が構成できる。

監視アラートの設定

Langfuseのメトリクスから以下のアラート条件を構成:

  1. 品質スコア低下: 直近1時間の平均品質スコアが閾値(0.8)を下回った場合
  2. レイテンシ急上昇: p95レイテンシが前日比150%を超えた場合
  3. コスト異常: 日次トークン使用量が前週平均の200%を超えた場合

学術研究との関連(Academic Connection)

DSPyとの統合

DSPy(arXiv: 2310.11511)のTeleprompterで最適化されたプロンプトをLangfuseに格納し、バージョン管理する設計パターン:

  1. DSPyでcompiled_program.save("v2.3.json")
  2. JSONからプロンプト文字列を抽出
  3. langfuse.create_prompt(name="rag-pipeline", prompt=extracted, labels=["staging"])
  4. Promptfooで品質ゲート通過後、labels=["production"]に昇格

OPROとの統合

OPRO(arXiv: 2309.03409)で生成されたプロンプト候補をLangfuseのA/Bテストバリアントとして設定し、本番トラフィックで検証する。OPROの訓練データとしてLangfuseに蓄積されたスコアデータを還元することで、継続的改善ループが成立する。

TextGradとの統合

TextGrad(arXiv: 2406.07496)の最適化ループの各ステップをLangfuseのトレースとして記録し、テキスト勾配の品質とプロンプト改善の相関を分析する。

まとめと実践への示唆

Langfuseは、LLMプロンプト管理に必要な機能(バージョニング・ラベル・A/Bテスト・トレーシング・評価)を単一のOSSプラットフォームで提供する。Zenn記事で紹介した3層防御アーキテクチャの核心を構成するツールであり、DSPy・OPRO・TextGradなどの学術的手法との統合ポイントも豊富。

MITライセンスのセルフホスト版とクラウド版の両方が利用可能で、月間10万リクエスト以下のチームはクラウド版の無料プランで十分にスタートできる。

参考文献

この投稿は CC BY 4.0 でライセンスされています。

論文解説: OPRO — LLMをプロンプト最適化器として活用するフレームワーク

LaunchDarkly公式解説: AI ConfigsによるLLMプロンプトの段階的ロールアウトとランタイム制御