LangfuseのExperiments Compare ViewのBaseline機能を解説
- Shogo Umeda
- 1 時間前
- 読了時間: 12分
はじめに
LLMアプリケーションの開発において、プロンプトの改善は避けて通れない作業です。しかし、プロンプトを変更するたびに、こんな不安を感じたことはありませんか?
このプロンプト変更、本当に改善になっているのか?
一部のケースで良くなったけど、他のケースで悪化していないか?
前のバージョンと比べて、どれくらい良くなったのか数字で示せない...
LangfuseのBaseline機能を使えば、変更前後の結果を定量的に比較し、改善した点と品質が下がった点を一目で把握できます。
本記事では、「京都の観光案内ボット」を題材に、感覚ではなくデータに基づいてプロンプトを改善する手順を紹介します。
本記事でわかること
LangfuseのExperiments機能の基本的な使い方
Baseline機能を使ったプロンプトの比較・評価手順
Compare Viewでのリグレッション検出方法
前提・想定対象読者
LLMアプリケーションの開発経験がある方
プロンプトの品質管理・改善に課題を感じている方
LangfuseのExperiments機能を使ったプロンプトの比較・評価手順を知りたい方
Langfuse Experimentsとは
Experiments機能の役割
LangfuseのExperiments機能は、プロンプトやモデル設定を変えたときに、出力が「良くなったか・悪くなったか」を、データセットを用いて定量的にテスト・比較できる機能です。具体的には以下のことが可能になります。
データセット管理 : テストケース(入力と期待される出力)を一元管理
実験の実行 : 同じデータセットに対して、異なるプロンプトやモデルで実験を実行
評価(スコアリング) : SDKの `evaluators` でスコアを付与、またはLangfuse上のEvaluator(LLM-as-a-Judge等)で評価を実行
結果の比較 : 複数の実験結果を並べて比較
Baseline機能の登場(2025年11月リリース)
従来のExperiments機能でも複数の実験結果を比較できましたが、「どれが基準(Baseline)なのか」が明示されていませんでした。
Langfuse v3.125.0でBaseline機能が追加され、以下が可能になりました。
基準バージョンの明示 : 現在の本番環境や、比較の基準となる実験を「Baseline」として指定
差分の可視化 : Baseline と Candidate(比較対象)の差分を緑(改善)・赤(悪化)で色分け表示
リグレッションの検出 : フィルター機能により、スコアが悪化した項目だけを抽出して確認
これにより、「新しいプロンプトは全体的には良いが、特定のケースで品質が低下している」といった状況を視覚的に素早く発見できるようになりました。
実務での活用シーン
Baseline機能は以下のようなシーンで特に有効です。
プロンプトの改善 : 新しいプロンプトが既存バージョンより優れているか検証
モデルの変更 : OpenAI GPTからGeminiへの移行など、モデル変更の影響を評価
継続的な品質管理 : 定期的に実験を実行し、品質の推移をトラッキング
A/Bテストの事前検証 : 本番投入前に、複数のバリエーションを比較
次のセクションでは、プロンプトの変更によるリグレッションを検出するために、実際にコードを書いて実験を実行してみましょう。
実装
今回のシナリオ
「京都の観光案内ボット」を題材に、以下の2つのプロンプトバージョンを比較します。
プロンプトV1(Baseline) : 標準的で真面目なトーン
プロンプトV2(Candidate) : 親しみやすく絵文字を使うトーン
目的は、「親しみやすさを向上させつつ、正確性を維持できるか」を検証することです。
以下の流れで実装していきます。
Dataset(評価用データ)の作成
↓
Baseline実験(Prompt V1)
↓
Candidate実験(Prompt V2)
↓
Evaluator(accuracy, emoji_count)
↓
Compare View(差分・リグレッション可視化)Tips: 本番運用では、Prompt Management機能 でプロンプトを管理するのがおすすめです(コードのデプロイなしで更新・バージョニングできます)。
ディレクトリ構成
今回のサンプルコードのディレクトリ構成は以下の通りです。
langfuse-experiments-demo/
├── src/
│ ├── data/
│ │ └── kyoto_tourism_dataset.json # テストデータセット
│ ├── run_experiment.py # 実験スクリプト
│ ├── upload_dataset.py # データセットアップロード
│ └── .env # 環境変数
└── pyproject.toml # 依存関係定義環境セットアップ
今回は uv を使ってPython環境を構築します。
UV環境設定ファイル作成
UVの環境設定ファイル`pyproject.toml` を作成します。
[project]
name = "langfuse-experiments-demo"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"langfuse>=3.0.0",
"google-genai>=1.0.0",
"python-dotenv>=1.0.0",
]依存関係インストール
uvで依存関係をインストールします。
# uvのインストール(まだの場合)
curl -LsSf https://astral.sh/uv/install.sh | sh
# プロジェクトディレクトリに移動
cd langfuse-experiments-demo
# 仮想環境を作成し、依存関係をインストール
uv sync環境変数設定
環境変数を設定します(`src/.env`ファイルを作成)。
# Langfuse設定
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_BASE_URL=https://cloud.langfuse.com
# Google Cloud / Vertex AI設定
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=asia-northeast1Google Cloud / Vertex AI の認証
Vertex AI(Gemini)を呼び出すために、Google Cloud の Application Default Credentials(ADC)を実行します。
# Application Default Credentials を設定
gcloud auth application-default login
# プロジェクトを設定
gcloud config set project your-project-id以上で環境構築完了です。
データセットの準備
テストケースを含むJSONファイルを作成します。以下のようなファイルを`src/data/kyoto_tourism_dataset.json` に保存します。
[
{
"input": "京都の有名な観光地を3つ教えてください。",
"expected_output": "清水寺、金閣寺、伏見稲荷大社などが有名です。",
"metadata": {
"category": "general_spots",
"difficulty": "easy"
}
},
{
"input": "京都で紅葉が綺麗な場所はどこですか?",
"expected_output": "東福寺、永観堂、嵐山などが紅葉の名所として知られています。",
"metadata": {
"category": "seasonal",
"difficulty": "medium"
}
}
]データセットのアップロード用コード
Langfuseにデータセットをアップロードするスクリプトを作成します。以下のコードを `src/upload_dataset.py` に保存します。
コードが長くなるのでセクションに折り畳んでいます。コード内容はセクションを展開して確認してください。
src/upload_dataset.py
"""
Langfuseにデータセットを登録するスクリプト
"""
import json
import os
from dotenv import load_dotenv
from langfuse import get_client
# 環境変数を読み込み
load_dotenv()
# Langfuseクライアントの初期化
langfuse = get_client()
DATASET_NAME = "kyoto-tourism-qa"
DATASET_DESCRIPTION = "京都観光案内ボットの評価用データセット"
def upload_dataset():
"""Langfuseにデータセットをアップロード"""
# ローカルデータセットの読み込み
with open('data/kyoto_tourism_dataset.json', 'r', encoding='utf-8') as f:
items = json.load(f)
# データセットを作成(既に存在する場合はそのまま使用)
try:
langfuse.create_dataset(
name=DATASET_NAME,
description=DATASET_DESCRIPTION,
metadata={"version": "1.0", "language": "ja"}
)
print(f" データセット '{DATASET_NAME}' を作成しました")
except Exception as e:
print(f" データセット '{DATASET_NAME}' は既に存在します")
# データセットアイテムを登録
for item in items:
langfuse.create_dataset_item(
dataset_name=DATASET_NAME,
input=item["input"],
expected_output=item["expected_output"],
metadata=item.get("metadata", {})
)
print(f" {len(items)}件のアイテムを登録しました")
if __name__ == "__main__":
upload_dataset()実験スクリプトの実装
Langfuse Python SDK V3の `dataset.run_experiment()` を使って、Baseline実験とCandidate実験の両方を実行できるスクリプトを実装します。
以下のコードを `src/run_experiment.py` に保存します。 コードが長くなるのでセクションに折り畳んでいます。コード内容はセクションを展開して確認してください。
`src/run_experiment.py
"""
京都観光案内ボットの実験スクリプト
Baseline(V1)と Candidate(V2)の両方を実行可能
Usage:
uv run python src/run_experiment.py baseline # Baseline実験を実行
uv run python src/run_experiment.py candidate # Candidate実験を実行
"""
import os
import sys
import json
import re
from dotenv import load_dotenv
from langfuse import get_client, Evaluation
from google import genai
from google.genai import types
# 環境変数を読み込み
load_dotenv()
# Langfuseクライアントの初期化
langfuse = get_client()
# Google Gen AI SDK クライアントの初期化
project_id = os.getenv("GOOGLE_CLOUD_PROJECT")
location = os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1")
client = genai.Client(vertexai=True, project=project_id, location=location)
# モデル名
MODEL_NAME = "gemini-2.5-flash"
# データセット名
DATASET_NAME = "kyoto-tourism-qa"
# ================================================================
# プロンプト定義
# ================================================================
# プロンプトV1: 標準的で真面目なトーン(Baseline)
PROMPT_V1 = """あなたは京都の観光案内を専門とするアシスタントです。
正確で丁寧な情報提供を心がけてください。
ユーザーの質問: {question}
上記の質問に対して、正確かつ簡潔に回答してください。"""
# プロンプトV2: 親しみやすく絵文字を使うトーン(Candidate)
PROMPT_V2 = """あなたは京都の観光案内を専門とするアシスタントです。
正確で丁寧な情報提供を基本としつつ、親しみやすい雰囲気で案内してください。
回答のポイント:
- 正確性を最優先に、信頼できる情報を提供する
- 適度に絵文字を添えて、読みやすく親しみやすい印象にする(1〜2個/段落程度)
- 「です・ます」調で丁寧に、かつ堅すぎない自然な文体で
ユーザーの質問: {question}
上記の質問に対して、正確かつ親しみやすく回答してください。"""
# ================================================================
# タスク関数
# ================================================================
async def baseline_task(*, item, **kwargs):
"""Baseline実験のタスク関数(プロンプトV1を使用)"""
question = item.input if hasattr(item, 'input') else item.get("input")
prompt = PROMPT_V1.format(question=question)
# generationとして記録(コスト計算のためにusage_detailsを設定)
with langfuse.start_as_current_observation(
as_type="generation",
name="gemini-generation",
model=MODEL_NAME,
input=prompt
) as generation:
response = await client.aio.models.generate_content(
model=MODEL_NAME,
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.7,
max_output_tokens=2048
)
)
# usage_detailsを設定(コスト計算に必要)
generation.update(
output=response.text,
usage_details={
"input": response.usage_metadata.prompt_token_count,
"output": response.usage_metadata.candidates_token_count,
}
)
return response.text
async def candidate_task(*, item, **kwargs):
"""Candidate実験のタスク関数(プロンプトV2を使用)"""
question = item.input if hasattr(item, 'input') else item.get("input")
prompt = PROMPT_V2.format(question=question)
# generationとして記録(コスト計算のためにusage_detailsを設定)
with langfuse.start_as_current_observation(
as_type="generation",
name="gemini-generation",
model=MODEL_NAME,
input=prompt
) as generation:
response = await client.aio.models.generate_content(
model=MODEL_NAME,
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.7,
max_output_tokens=2048
)
)
# usage_detailsを設定(コスト計算に必要)
generation.update(
output=response.text,
usage_details={
"input": response.usage_metadata.prompt_token_count,
"output": response.usage_metadata.candidates_token_count,
}
)
return response.text
# ================================================================
# 評価関数(共通)
# ================================================================
async def accuracy_evaluator(*, output, expected_output, **kwargs):
"""正確性評価: LLMを使って意味的な正確性を評価(LLM as a Judge)"""
if expected_output is None:
return Evaluation(name="accuracy", value=0.0, comment="期待される出力がないため評価不可")
judge_prompt = f"""あなたは回答の正確性を評価する審査員です。
以下の「期待される回答」と「実際の回答」を比較し、実際の回答が期待される内容を正確にカバーしているか評価してください。
【期待される回答】
{expected_output}
【実際の回答】
{output}
【評価基準】
- 1.0: 期待される内容を完全にカバーしている
- 0.7-0.9: 主要な内容はカバーしているが、一部欠けている
- 0.4-0.6: 部分的にカバーしているが、重要な情報が欠けている
- 0.1-0.3: ほとんどカバーできていない
- 0.0: 全く関係ない回答
以下のJSON形式のみで回答してください:
{{"score": 0.0〜1.0の数値, "reason": "評価理由を簡潔に"}}
"""
try:
response = await client.aio.models.generate_content(
model=MODEL_NAME,
contents=judge_prompt,
config=types.GenerateContentConfig(
temperature=0.0,
max_output_tokens=2048
)
)
response_text = response.text.strip()
json_match = re.search(r'\{[^{}]*\}', response_text)
result = json.loads(json_match.group()) if json_match else json.loads(response_text)
score = max(0.0, min(1.0, float(result.get("score", 0.0))))
reason = result.get("reason", "理由なし")
return Evaluation(name="accuracy", value=score, comment=reason)
except Exception as e:
return Evaluation(name="accuracy", value=0.0, comment=f"評価エラー: {str(e)}")
def emoji_count_evaluator(*, output, **kwargs):
"""絵文字の使用数を評価"""
emoji_count = sum(1 for char in output if ord(char) > 0x1F300) if output else 0
return Evaluation(
name="emoji_count",
value=emoji_count,
comment=f"絵文字を{emoji_count}個使用"
)
# ================================================================
# 実験実行関数
# ================================================================
def run_baseline_experiment():
"""Baseline実験を実行"""
dataset = langfuse.get_dataset(DATASET_NAME)
result = dataset.run_experiment(
name="Kyoto Tourism Bot - Prompt Comparison",
run_name="baseline-v1",
description="標準的で真面目なトーンのプロンプト(Baseline)",
task=baseline_task,
evaluators=[accuracy_evaluator, emoji_count_evaluator],
metadata={"prompt_version": "v1", "model": MODEL_NAME}
)
print(result.format())
langfuse.flush()
return result
def run_candidate_experiment():
"""Candidate実験を実行"""
dataset = langfuse.get_dataset(DATASET_NAME)
result = dataset.run_experiment(
name="Kyoto Tourism Bot - Prompt Comparison",
run_name="candidate-v2",
description="親しみやすく絵文字を使うトーンのプロンプト(Candidate)",
task=candidate_task,
evaluators=[accuracy_evaluator, emoji_count_evaluator],
metadata={"prompt_version": "v2", "model": MODEL_NAME}
)
print(result.format())
langfuse.flush()
return result
# ================================================================
# メイン
# ================================================================
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python run_experiment.py [baseline|candidate]")
sys.exit(1)
experiment_type = sys.argv[1].lower()
if experiment_type == "baseline":
run_baseline_experiment()
elif experiment_type == "candidate":
run_candidate_experiment()
else:
print(f"Unknown experiment type: {experiment_type}")
print("Usage: python run_experiment.py [baseline|candidate]")
sys.exit(1)
実装のポイント
1. `dataset.run_experiment()` の活用
Langfuse Python SDK V3では、`dataset.run_experiment()` という高レベルメソッドが提供されています。以下のように、実験を実行することができます。
result = dataset.run_experiment(
name="Experiment Name", # 実験名
run_name="baseline-v1", # Run名
task=baseline_task, # 各項目に対して実行する関数
evaluators=[ .. ], # Item-level評価関数
metadata={ .. } # メタデータ
)2. 非同期タスクと評価関数
`run_experiment()` はタスク関数と評価関数の両方で非同期(`async def`)をサポートしています。LLM APIなどを呼び出す処理は非同期処理にすることで、並列実行時の効率が向上します。
# 非同期タスク関数
async def baseline_task(*, item, **kwargs):
response = await client.aio.models.generate_content(
model=MODEL_NAME,
contents=prompt,
config=types.GenerateContentConfig(...)
)
return response.text
# 非同期評価関数(LLM-as-a-Judge)
async def accuracy_evaluator(*, output, expected_output, **kwargs):
response = await client.aio.models.generate_content(
model=MODEL_NAME,
contents=judge_prompt,
config=types.GenerateContentConfig(...)
)
return Evaluation(name="accuracy", value=score, comment=reason)3. 評価関数
`dataset.run_experiment()` の `evaluators` 引数には、評価関数をリストで指定します。
ここで指定する評価関数は、各データセット項目の出力を個別に評価する関数です。
今回の例では、正確性評価関数(`accuracy_evaluator`)と絵文字使用数評価関数(`emoji_count_evaluator`)を使用しています。
実験の実行
スクリプトを実行します。
# データセットをアップロード
uv run python src/upload_dataset.py
# Baseline実験を実行
uv run python src/run_experiment.py baseline
# Candidate実験を実行
uv run python src/run_experiment.py candidate実行が完了したら、Langfuse UIにアクセスして結果を確認しましょう。次のセクションでは、Baseline機能を使った比較方法を詳しく解説します。
検証編 - Baseline機能で結果を比較する
Compare Viewへのアクセス
Langfuseにログイン
Datasets メニューから `kyoto-tourism-qa` をクリック

`baseline-v1` と `candidate-v2` の両方にチェックを入れる
Actions ボタン - Compare ボタンをクリック

Baselineの設定
Compare View が開いたら、片方の実験を「Baseline」として指定します。
1. `baseline-v1` の表示横の`Set as Baseline`をクリック

これで、```candidate-v2` が Baseline との差分として表示されるようになります。
結果の見方
Delta(差分)表示
緑色 : Baseline より改善された項目
赤色 : Baseline より悪化した項目(リグレッション)
灰色 : 変化なし

今回の実験結果
筆者の実行結果では、一部の項目で `accuracy` スコアが下がっていることがわかりました。
Compare Viewでは、Datasetsの `Input`、`Expected Output`、バージョンごとの `Output` およびスコアを確認できます。
評価スコアのコメントアイコンにマウスオーバーすると、LLM-as-a-Judgeがスコアを付けた根拠を確認できます。

また、出力の右下にマウスオーバーするとトレース確認用のアイコンが表示されます。クリックするとトレース画面がオーバーレイ表示され、詳細を確認できます。



このように、評価結果のコメントやトレース詳細を確認しながら、リグレッションの有無等のプロンプト変更の影響を分析できます。
従来もCompare Viewで結果を横並びで見比べることはできましたが、Baseline機能により評価スコアやLatency、Costなどの差分が視覚的に把握しやすくなり、分析が容易になりました。
まとめ
プロンプトの改善は「感覚」ではなく「データ」で判断する時代です。Baseline機能を活用して、自信を持ってプロンプトの改善サイクルを促進しましょう。
Baseline機能のメリット
定量的な比較 : 「なんとなく良くなった」ではなく、数値で改善・悪化を判断
リグレッションの早期発見 : 特定のケースでの品質低下を見逃さない
意思決定の根拠 : プロンプト変更の採用可否を、データに基づいて判断
実務での活用ポイント
本番投入前の必須プロセス : プロンプト変更時は必ずBaseline比較を実施
継続的な品質モニタリング : 定期的に実験を実行し、品質の推移を追跡
チーム内でのコミュニケーション : Langfuse UIを共有して、品質議論の土台に


コメント