LLMアプリケーションを改善したければLangfuseを使ってみてほしい~トレース確認からプロンプトの評価までの流れ

1. はじめに

LLMアプリケーションを開発・運用していると、ぶつかりがちな壁があります。「なぜこの出力になったのか分からず改善が進まない」「プロンプトやモデルを変更したいけれど影響範囲が読めない」などです。

私たちテックドクターでは、こうした課題に対して、LLMアプリケーションのオブザーバビリティ・評価プラットフォームであるLangfuseを使うことで対処しています。

この記事では、弊社のヘルスケアアプリケーション開発の実例を交えながら、トレースによる挙動の可視化、プロンプトの一元管理、データセットの構築、そして簡易的なプロンプト評価(Prompt Experiments)の実行まで、Langfuse活用の一連の流れを解説していきます。

こんな人に読んでほしい
  • LLMアプリケーションの開発・運用に携わるエンジニア
  • Langfuseの導入を検討している方



2. 導入背景

弊社ではLLMを活用したアプリケーションを開発しています。チャットボットへの相談、バイタルデータの要約、メッセージ履歴のサマリー生成などの機能を提供しており、運用中のプロンプトは結構な数になります。
その開発を進める中で、次の2つの課題が目立つようになりました。

LLMの出力の原因を追いにくい

ユーザーから「回答がおかしい」と報告を受けても、そのときの入力や会話履歴を追跡するのが困難でした。
アプリケーションログを手がかりにLLMへの入力を再構成していたのですが、手作業となり時間がかかるうえに会話のコンテキストが欠落しやすいです。原因の特定がなかなか進みませんでした。

プロンプト変更の効果を簡単に確認する手段がない

プロンプトの改善にはエンジニアだけでなく、ドメイン知識を持つ非エンジニアメンバーも関わることがあります。そのため、非エンジニアでも簡単にGUIからプロンプトの検証ができる環境がほしいと考えました。
弊社ではこうした課題への対策として、Langfuseを導入しました。


3. Langfuseの概要

Langfuseとは

Langfuse は、LLMアプリケーション向けのオープンソースのLLMエンジニアリングプラットフォームです。

LLMへの入出力やトークン消費量をトレースとして記録・可視化できるほか、プロンプトのバージョン管理やデータセットを用いた評価まで、一つのプラットフォームで完結して行うことができます。SaaS版(US / EU / HIPAAリージョン)およびセルフホスティング環境で利用可能です。

Langfuseを導入すると、主に次のようなことができるようになります。

  • オブザーバビリティ……LLMへの入出力、処理の流れ、レイテンシ、トークン数・コストをWeb UIで確認できます。ユーザーIDやセッションIDでフィルタリングして、特定ユーザーの会話を追跡することも可能です。
  • プロンプトの管理……プロンプトをコードから分離し、Langfuse上で作成・編集できます。バージョン管理やラベル付け(production / staging)にも対応しており、コードのデプロイなしにプロンプトの切り替えやロールバックが行えます。
  • 評価……データセット(入力と期待出力のペア)を用意し、プロンプトやLLMアプリケーションの出力品質を評価できます。

参考: Langfuse Overview

なお、本記事で扱う主な機能は以下の4つです。

名称 機能
Tracing LLMの入出力やレイテンシ、トークン数・コストを階層的に記録・可視化します
Prompt Management プロンプトのバージョン管理とラベル付け(production / staging)を行います
Datasets 評価用の入力データ(と期待出力)をまとめて管理します
Prompt Experiments プロンプト × データセットの組み合わせで評価を実行し、結果を比較します

このほかにも、ユーザーフィードバックをスコアとして記録する機能、LLM-as-a-Judge(LLMを評価者として出力品質を自動判定する手法)による自動評価、Annotation Queue(評価対象をキューに溜めて人手でラベリングを進める機能)などがありますが、本記事ではとくに上記4つに焦点を当てます。

導入方法

Python SDKを使う場合

(SaaS版の場合)パッケージをインストールして環境変数を設定するだけで利用を開始できます。

pip install langfuse

# .env
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_BASE_URL="<https://cloud.langfuse.com>"  # EU region
# LANGFUSE_BASE_URL="<https://us.cloud.langfuse.com>"  # US region


Langfuseクライアントは get_client() で初期化します。環境変数を設定していれば、引数なしで認証情報が自動的に読み込まれます。

from langfuse import get_client

langfuse = get_client()


Langfuse Python SDKにはトレースの方法が複数用意されています。
以下はコンテキストマネージャを使用する例です。start_as_current_observation() を使うとブロック内の処理が自動的にトレースとして記録されます。 ほかにも、デコレータ を使用したり、手動で観測値を設定 したりすることもできます。

from langfuse import get_client

langfuse = get_client()

# コンテキストマネージャを使用してスパンを作成する
with langfuse.start_as_current_observation(
    as_type="span",
    name="process-request"
) as span:
    span.update(output="Processing complete")

    # LLM呼び出しにおいて、ネストされた生成を作成する. LLM呼び出しを記録する場合は as_type="generation" を指定
    with langfuse.start_as_current_observation(
        as_type="generation",
        name="llm-call",
        model="gpt-4o"
    ) as generation:
        # ここにLLMを呼び出すためのロジックを記述します
        generation.update(output="Generated response")

# すべてのスパンは、それぞれのコンテキストブロックを抜ける際に自動的にクローズされます。

参考: Python SDK - Getting Started / Instrumentation


フレームワークインテグレーションを使う場合

別のトレース方法として、フレームワークインテグレーションを使う方法も紹介します。

Langfuse SDKはOpenTelemetryベースで構築されているため、Pydantic AIやGoogle ADKなどのOTel対応フレームワークを使っている場合は、@observe() デコレータやコンテキストマネージャを使わなくてもトレースを自動記録できます。Langfuseはインテグレーションが充実しており、さまざまなLLMアプリケーションフレームワークと手軽に統合できます。

例えば、以下はPydantic AIを使用する例です。

from langfuse import get_client
from pydantic_ai import Agent

# Langfuseクライアントを初期化
langfuse = get_client()

# Pydantic AIのすべてのエージェントでインストルメンテーションを有効化
Agent.instrument_all()

# エージェントを作成して実行するだけでトレースが自動記録される
agent = Agent("openai:gpt-4o", instrument=True)
result = agent.run_sync("こんにちは")

参考: Pydantic AI Integration

以上がLangfuseの概要です。次のセクションからは、LLMアプリケーション改善の流れを追う形で、Langfuseの各機能について詳しくご紹介していきたいと思います。
トレースの確認、プロンプトの管理、プロンプト評価の実行の順で見ていきましょう。


4. トレースでLLMの挙動を確認する(Tracingについて)

なにか問題が起きたとき、トレース機能によってLLMへの入出力や処理の流れを追跡できます。

トレースの階層構造

Langfuseのトレースは、LLMアプリケーションの1回のリクエスト処理(ユーザー入力から応答生成まで)の実行を階層構造で記録します。 トレースの中にはobservationと呼ばれる個々の処理ステップがネストされており、それぞれのobservationには処理の種類を表すtypeが割り当てられます。 代表的なobservation typeは以下の3つです。

  • Span: 任意の処理区間(前処理、後処理など)
  • Generation: LLM呼び出し。フレームワークインテグレーション経由であれば入力プロンプト、出力テキスト、モデル名、トークン数、コストが記録される。
  • Tool: 天気予報のAPI呼び出しのような、ツール呼び出しを表す

このほかにも用途に応じた複数のobservation typeが用意されており、フレームワークインテグレーション経由では、自動的に適切なtypeが設定されます。(コンテキストマネージャ経由の場合は自分でtypeを指定することになります)

ここからは、記録されたトレースを管理画面上でどのように確認・活用するかを見ていきましょう。

一覧表示からトレースを確認する

管理画面のトレース一覧を開くと、記録されたトレースが表示されます。

画面キャプチャ

一覧画面では、トレース名・実行日時・レイテンシ、実際の入出力、使用トークン数とコスト、付与されたタグやメタデータ、ユーザーID・セッションIDなどを確認できます。

フィルター機能を使えば、ユーザーID・セッションID・タグ・日時範囲などの条件でトレースを絞り込めます。

また、入出力を含む各カラムに対してテキスト検索も可能なので、目当てのログ情報を容易に探し当てることができます。

一覧から任意のトレースを選択すると詳細画面が開き、内容がツリー形式で表示されます。 各ノードを選択すると、その処理ステップの入力・出力・実行時間・トークン数などが表示されます。

このように詳細画面で関連するトレース情報を一覧できることで、いわゆるAgentic Workflowと呼ばれるような、LLMを多段階的に呼び出すアプリケーションを構築している場合は、デバッグがとても捗ります。

セッション単位の会話追跡

弊社では活用していませんが、トレースにセッションIDを付与しておくと、管理画面から同一セッションに属する複数のトレースをまとめて閲覧できます。

会話の流れの中で「どの時点から出力がおかしくなったのか」を時系列で追えるので、こちらもデバッグに役立ちそうです。

ユーザーフィードバックの記録と活用

Langfuseでは、トレースに対して スコア(Score) を紐づけて記録できます。SDK経由でプログラムから記録したり、管理画面のUI上で人手で記録することも可能です。

弊社のアプリではユーザーがLLMの回答に対してアプリ内でGood/Badの評価を行える仕組みを実装しており、その結果をLangfuse上でトレースに紐づくスコアとして記録しています。

from langfuse import get_client

langfuse = get_client()

langfuse.create_score(
    trace_id="trace-id",
    name="user-feedback",
    value=1,       # Good: 1, Bad: 0
    data_type="NUMERIC",
    comment="ユーザーからのフィードバック",
)


記録されたスコアはトレース詳細画面に表示されます。トレース一覧画面ではスコアによるフィルタリングも可能で、たとえば「Bad評価が付いたトレースだけを抽出して、共通するパターンを分析する」といった使い方もできます。
参考: Tracing / Scores

5. プロンプトを管理する(Prompt Managementについて)

トレースで問題のあるプロンプトを特定できたら、改善を行います。そのとき役に立つのがプロンプト管理の機能です。

Prompt Managementの概要

Langfuseの Prompt Management 機能を使うと、プロンプトをコードから分離してLangfuse上でバージョン管理できます。 各バージョンには productionstaging といったラベルを付けられ、コードのデプロイなしにプロンプトの切り替えやロールバックが可能です。
プロンプトの形式にはtext(単一文字列)とchat(メッセージ配列)の2種類があります。チャットボットのようなアプリケーションでは chat 形式を使い、各メッセージの rolecontent を定義する必要があります。

バージョン管理とラベル

プロンプトを保存するたびにバージョン番号が自動でインクリメントされます。

各バージョンにはラベルを付与でき、アプリケーションからは「production ラベルの付いた最新バージョン」を取得する、という運用が可能です。

たとえば、新しいプロンプトをまず staging ラベルで作成し、評価(後述するPrompt Experiments)を経て問題がなければ production ラベルに昇格させる、というワークフローが実現できます。

variablesとmessage placeholder

プロンプト内に動的な値を埋め込むことで、プロンプトテンプレートとして使用できます。埋め込む値としてはvariables、prompt references、message placeholders がサポートされています。本記事では、主に variables と message placeholder を扱います。

  • variables: {{変数名}} の形式でプロンプト本文に記述し、実行時に文字列へ置換します。
あなたはヘルスケアアシスタントです。
ユーザーのタイムゾーンは {{TIMEZONE}} です。
現在時刻は {{CURRENT_TIME}} です。
{{LANGUAGE}} で回答してください。

  • message placeholder: chat形式のプロンプトで使う仕組みです。会話履歴のような可変長のメッセージ配列を動的に展開するためのもので、 placeholder タイプのメッセージを追加することで設定できます。

ここで定義したmessage placeholderの使用方法は、次のDatasetのセクションで説明します。

[
  { "role": "system", "content": "あなたはヘルスケアアシスタントです。" },
  { "type": "placeholder", "name": "MESSAGE_HISTORY" },
  { "role": "user", "content": "{{USER_PROMPT}}" }
]


UIでのプロンプト作成・編集

管理画面のプロンプト一覧から新規作成ボタンをクリックすると、プロンプトの作成画面が開きます。

ここでプロンプトの識別名、形式(text / chat)、プロンプト本文を入力して保存することで、プロンプトの追加が行えます。必要に応じて、モデル名や temperature といったパラメータも設定できます。

既存のプロンプトを編集する場合は、プロンプト一覧から対象を選択し、本文を修正して保存します。保存するたびに自動的に新しいバージョンとして追加され、過去のバージョンもすべて保持されるため、いつでも差分の確認やロールバックが可能です。

SDKでのプロンプト作成

プロンプトはUIだけでなく、Python SDKからもプログラムで作成・更新できます。

from langfuse import get_client

langfuse = get_client()

# text形式のプロンプトを作成
langfuse.create_prompt(
    name="health-assistant",
    type="text",
    prompt="あなたはヘルスケアアシスタントです。{{LANGUAGE}} で回答してください。",
    commit_message="create initial version",
    tags=["base"],
)

# chat形式のプロンプトを作成
langfuse.create_prompt(
    name="health-assistant-chat",
    type="chat",
    prompt=[
        {"role": "system", "content": "あなたはヘルスケアアシスタントです。"},
        {"type": "placeholder", "name": "MESSAGE_HISTORY"},
        {"role": "user", "content": "{{USER_PROMPT}}"},
    ],
    commit_message="create initial version",
    tags=["eval"],
)

弊社では、アプリケーションが実際に使用するプロンプトはGoogleのCloud Spannerで管理しており、Langfuse上には評価中のプロンプトを保存しています。両者のプロンプトの不整合を防ぐために、Spanner側のプロンプトをマスタデータとして、これをLangfuse側に反映するスクリプトを用意しています。
参考: Prompt Management Overview / Variables / Message Placeholders

6. データセットを用意する(Datasetについて)

ここまで、Langfuseのトレースおよびプロンプトの管理機能について説明してきました。これから説明するDataset、Prompt Experimentsはプロンプトの評価に関する機能です。

データセットの概要

データセット(Dataset)は、プロンプト評価に使用する入力データの集合です。
Langfuseのデータセットは、おもに以下の要素で構成されています。

  • Dataset: DatasetItemの集合。
  • DatasetItem: 個別の入出力ペア。input(入力値)、expected_output(期待する出力、任意)、metadata(カスタム属性、任意)を持つ
  • DatasetRun: データセットに対して評価を実行した結果。

主なデータセットの構築方法としては、トレースから取り込む方法、1から手動で作成する方法、CSVファイルからインポートする方法などがあります。

トレースからデータを追加する

本番やステージング環境で記録されたトレースをもとに、評価用データを作成できます。

具体的には以下の手順でDatasetを作成できます。

  1. トレース詳細画面で、データセットに追加したいobservationを選択する
  2. データセットへの追加ボタンをクリックする
  3. 次項「データセットアイテムの構造」に記載の形式に合わせて整形し、保存する

実際のユーザー入力をそのまま評価用データとして再利用できるため、現実的なテストケースを手早く蓄積できるのが利点です。

ただし、トレースに記録されたデータはプロンプトのvariablesやplaceholderの構造とは形式が異なることが多いため、データセットに追加する段階でプロンプトの変数構造に合わせた整形が必要になります。

手動でデータを作成する

プロダクトの要件を満たす典型的な入力パターンや、特定のエッジケースをあらかじめ用意しておく方法です。まだトレースが十分に蓄積されていない初期段階のデータセット構築に向いています。

なお、CSVファイルからデータセットを一括インポートすることも可能です。大量のテストケースをスプレッドシート等で管理している場合に便利です。

データセットアイテムの構造

データセットアイテムの inputexpected_output には値を格納できます。UIのPrompt Experimentsで利用する場合は、input はJSON objectである必要があり、実行時にそのJSONキーを見て、プロンプトテンプレートの同名のvariablesやmessage placeholderに値が自動で埋め込まれます。そのため、input のキー名はプロンプト側の変数名と一致させておく必要があります。

たとえば、セクション5で紹介したchat形式のプロンプトは次のような構造でした。

[
  { "role": "system", "content": "あなたはヘルスケアアシスタントです。" },
  { "type": "placeholder", "name": "MESSAGE_HISTORY" },
  { "role": "user", "content": "{{USER_PROMPT}}" }
]


このプロンプトに対してPrompt Experimentsを実行するには、データセットアイテムの inputMESSAGE_HISTORYUSER_PROMPT というキーが含まれている必要があります。弊社ではこれに加え、プロンプト内で使用している TIMEZONECURRENT_TIMELANGUAGE も共通で含めるようにしています。

フィールド 説明
USER_PROMPT ユーザーの入力テキスト
MESSAGE_HISTORY 会話履歴のメッセージ配列(会話履歴を使うプロンプトの場合)
TIMEZONE ユーザーのタイムゾーン(例: Asia/Tokyo)
CURRENT_TIME エージェント実行時の日時
LANGUAGE ユーザーの言語(例: 日本語)

実際の input は以下のようになります。

{
  "MESSAGE_HISTORY": [
    { "role": "user", "content": "寝つきが悪くて困っています" },
    { "role": "assistant", "content": "最近の生活習慣はいかがですか?" }
  ],
  "TIMEZONE": "Asia/Tokyo",
  "USER_PROMPT": "何か改善できることはありますか?",
  "CURRENT_TIME": "2026-03",
  "LANGUAGE": "日本語"
}

参考: Datasets


7. プロンプトを評価する(Prompt Experimentsについて)

UIから評価を実行する

プロンプトとデータセットの準備ができたら、Prompt Experimentsで評価を実行します。

  1. 管理画面のデータセット一覧から対象のデータセットを開く
  2. Experiment実行ボタンをクリックし、Prompt Experimentを選択する
  3. 名称、評価対象プロンプト、LLMのAPI接続設定、対象データセットなどを入力する。(LLM-as-a-Judgeによる自動評価を行いたい場合はEvaluatorもあわせて設定する)
  4. 実行を開始する

結果の確認と比較

実行が完了したら、Dataset Runs画面で結果を確認します。以下はテスト的に評価を実行した際の結果画面です。

画面キャプチャ

結果画面では、前述した inputexpected_output に加え、LLMからのレスポンス、そのコストとレイテンシなどを横並びで確認できます。

期待どおりの結果が得られなければ、Prompt Managementでプロンプトを修正し、再度Experimentを実行します。このサイクルを繰り返すことで、プロンプトの品質を段階的に高めていくことができます。
参考: Prompt Experiments (UI) / Datasets


8. まとめ

本記事では、Langfuseを使ったLLMアプリケーション改善の流れを、トレースの確認からプロンプト評価の実行まで一通り紹介しました。

トレースによって、問題が起きたときにLLMへの入出力や処理の流れを管理画面上で追跡できるようになります。

そのうえで、Prompt Managementでプロンプトをバージョン管理し、データセットを用意してPrompt Experimentsで修正前後の出力を並べて比較することで、プロンプトの改善サイクルを効率的に回すことができます。

今回は目視での確認・比較を中心に紹介しましたが、Langfuseにはこのほかにも、LLMを評価者として出力品質を自動スコアリングするLLM-as-a-Judgeや、Python SDKによるLLMアプリケーションの評価といった機能も用意されています。更に評価を自動化したい場合はこれらの機能の使用を検討すると良いかもしれません。




書いた人:大瀧