top of page

Langfuse の Observation レベル評価:「どのステップが悪いのか」をスコアで特定できるようになった

  • 執筆者の写真: 智之 黒澤
    智之 黒澤
  • 2 日前
  • 読了時間: 4分

こんにちは。ガオ株式会社の黒澤です。

Langfuse v3.153.0 で [PR #11861](https://github.com/langfuse/langfuse/pull/11861) がマージされ、LLM-as-a-Judge を Observation 単位で実行できるようになりました。本記事ではその背景と使い方をまとめます。


課題:Trace 全体への評価では「どこが悪いか」がわからない


LLM アプリの評価で、こんな状況に陥ったことはありませんか。

RAG アプリの LLM as a Judge スコアが下がった。でも、ドキュメント検索が悪いのか、回答生成が悪いのか、判断できない。

これは、従来の Langfuse の評価機能が Trace(エンドツーエンドのリクエスト全体) を評価単位としていたためです。


Before:Evaluator は Trace 全体にしか設定できなかった


Langfuse で LLM as a Judge Evaluator を作成すると、評価対象は Trace 全体の最終出力のみでした。



▲ Run on に Live Tracesを選択すると、Trace全体の評価となる


たとえば以下のような RAG パイプラインでは、`retrieve`(検索)と `llm`(生成)が別々の Observation として存在しますが、評価できるのは最終出力だけでした。


Trace: ユーザーの質問
  ├─ Span: retrieve    ← ここは評価できなかった
  └─ Generation: llm   ← ここの最終出力だけが評価対象

スコアが低くても「検索が悪いのか、生成が悪いのか」の切り分けは、ログを手で読むしかありませんでした。


After:個々の Observation を評価ターゲットに指定できるようになった


今回のアップデートで、Evaluator の設定画面に Observation をターゲットにするオプション が追加されました。


▲ 例:retrieve 用。Target で Live Observations を選択し、Where で Type=SPAN, Name=retrieve を指定


▲ 例:llm 用。Type=GENERATION, Name=llm でフィルタ。retrieve と llm で別々の Evaluator を設定できる


※ 以上は RAG パイプラインの一例。実際の Observation 名や Type はアプリの実装に合わせて設定する。


検索と生成を Langfuse へ送信する簡単なサンプルソースです。ここでは例示として、実際の検索先や LLM は呼ばず、固定文言を Langfuse へ送信します。


ソースコード

"""
Trace 構造:
  Trace: rag-pipeline
    ├─ Span: retrieve    ← Context Relevance の評価対象
    └─ Generation: llm   ← Answer Relevance の評価対象
"""
import os
try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    pass
from langfuse import get_client, propagate_attributes
def retrieve_documents(query: str) -> list[str]:
    """ドキュメント検索をシミュレート(モック)"""
    mock_docs = [
        "Python は 1991年に Guido van Rossum によって開発されました。",
        "Langfuse は LLM アプリケーションの観測・評価プラットフォームです。",
    ]
    return mock_docs[:2]

def generate_answer(query: str, context: list[str]) -> str:
    """LLM による回答生成をシミュレート(モック)"""
    context_text = "\n".join(context)
    return f"検索された文脈:{context_text[:50]}... に基づき、{query} への回答を生成します。"

def run_rag_pipeline(query: str) -> str:
    langfuse = get_client()
    with langfuse.start_as_current_observation(
        as_type="span", name="rag-pipeline", input={"query": query}
    ) as root_span:
        with propagate_attributes(tags=["observation-eval-sample"]):
            with langfuse.start_as_current_observation(
                as_type="span", name="retrieve", input={"query": query}
            ) as retrieve_span:
                documents = retrieve_documents(query)
                retrieve_span.update(output={"documents": documents, "count": len(documents)})
            with langfuse.start_as_current_observation(
                as_type="generation", name="llm",
                model="gpt-4o-mini",
                input={"query": query, "context": documents},
            ) as llm_gen:
                answer = generate_answer(query, documents)
                llm_gen.update(output={"answer": answer}, usage_details={"input_tokens": 50, "output_tokens": 30})
        root_span.update(output={"answer": answer})
    langfuse.flush()
    return answer

if __name__ == "__main__":
    answer = run_rag_pipeline("Python はいつ開発されましたか?")
    print(answer)

これにより、先ほどの RAG パイプラインに対して次のような評価設計が可能になります。


評価対象 Observation

評価軸

検出したいもの

retrieve Span

Context Relevance

検索結果がクエリに関連しているか

llm Generation

Answer Relevance

回答がユーザーの質問に答えているか


Trace 詳細画面でも変化がわかる


設定後、Trace の詳細画面を開くと、 各 Observation の行にもスコアが表示される ようになります。


▲ 各 Observation 行にスコアが付いている。一目で「どのステップが悪いか」が把握できる


一目で「どのステップのスコアが低いか」が把握できます。たとえば:


  • Context Relevance: 0.50(検索結果の精度が低い)

  • Answer Relevance: 1.00(生成自体は質問に答えている)


この場合、問題は検索ロジック側にあると判断できます。


まとめ:評価の粒度がアーキテクチャの複雑さに追いついた


Before(Trace レベル)

After(Observation レベル)

評価対象

最終出力のみ

各中間ステップも評価可能

問題の原因特定

最終出力のスコアは取れるが、複数ステップがある場合は「どのステップが悪いか」の切り分けが困難

各ステップのスコアで特定可能

向いている構成

シンプルな LLM 呼び出し

RAG・エージェント・多段階チェーン

アプリが複雑になるほど、「何かがおかしい」の検知だけでは不十分です。Observation レベルの評価は、その「どこがおかしいか」をデータとして取り出す手段です。

コメント


bottom of page