Langfuse WorkshopClickHouse Workshops

04 モニタリング

トレースされたアプリがあり、オプションのLangfuse管理プロンプトがあります。すべてのチャットターンはLangfuseにネストされたトレースとして登録されます。

ワークショップマテリアルは公開 langfuse/langfuse-workshop リポジトリで保守されています。実行可能なアプリ、チェックポイントブランチ、ローカルセットアップについてはリポジトリを使用してください。

このMarkdownファイルを表示

開始ポイント

git checkout checkpoint/04-monitoring

トレースされたアプリがあり、オプションのLangfuse管理プロンプトがあります。すべてのチャットターンはLangfuseにネストされたトレースとして登録されます。

プロンプト管理を使用したいが、モジュール3をスキップした場合は、次のコマンドを実行してプロンプトを公開します

npm run prompt:publish

AI アプリを監視する理由

本番環境では、AIアプリは多くのトレースを生成します。ほとんどは問題ありません。興味深い — 答えが漂う、エージェントが処理すべきでないリクエスト、時間とともに変わるパターン — は、知る価値のあるものです。モニタリングは、すべてのトレースを手作業で読むことなく、それらのシグナルをキャッチする方法です。

より大きな視点については、Langfuse Academy レッスンモニタリング を参照してください。

ゴール

モニタリングのゴールは、あなたの AI アプリケーションにとって知る価値のあることを見つけることです。Specs の場合、開始点として、注意する価値のある3つのイベントを選択しました:

  • ユーザー不同意 — お父さんが押し返します("いいえ、そのメニューはありません")。エージェントが間違ったステップを与えたか、アプリがその限界を示しています。
  • スコープ外リクエスト — お父さんはSpecs が構築されていないものを使用しようとします("税金を申告できますか?")。アプリが質問を拒否するかどうかを確認することと、製品の拡張アイデアをスポッティングの両方に役立ちます。
  • すべて大文字の挫折 — お父さんは "THIS STILL ISNT WORKING" のような何かを書きます。すべての大文字メッセージが怒りではありませんが、会話が追加の注意が必要である可能性が高いという安価な決定的なシグナルです。

モニタリングは、品質追跡のディメンションも持っています — 時間にわたるメトリックの平均スコア。シグナル検出を最初に推奨: 集計品質の追跡は、あなたとあなたのチームが品質があなたのコンテキストで何を意味するかについて明確な意見を持ったら最も有用です、そして最速の方法は驚くべきトレースを見ることです。

このステップではコードを変更する必要はありません。02-tracing からのトレース形状には、これらのモニターが必要なものすべてが既にあります: エージェント観察には完全な会話と最終的な答えがあり、各OpenAI世代にはシステムプロンプトと同じメッセージ配列があります。

ステップ1 — Langfuse エバリュエーターモデルを構成します

このチャプターの最初の2つのモニターはLLM-as-a-judge テンプレートを使用します。Langfuse は、プロジェクト内の LLM接続 からこれらのジャッジ呼び出しを実行するため、使用する直前に評価器モデルを今すぐ設定してください。

プロジェクトに既にデフォルトエバリュエーターモデルがある場合は、それを保持してステップ2に進みます。

  1. Langfuse で、Project Settings → LLM Connections を開きます。
  2. Add new LLM Connection をクリックします。
  3. OpenAI を選択し、接続に名前を付けて、OpenAI APIキーをシークレットフィールドに貼り付けます。
  4. 接続を保存します。
  5. デフォルト評価モデルはエバリュエーター作成中に設定されます: プロジェクトにまだモデルがない場合、Set up evaluator ウィザードは続行する前に Set up LLM connection ステップで質問します。それが表示されたら、OpenAI接続を選択し、openai / gpt-4.1 などの構造化出力対応モデルを選択してから保存します。設定されたら、Evaluators ページの上部に Default model として表示され、後で変更することもできます。

Langfuse シークレットフィールドにのみAPIキーを保持してください。ワークショップトランスクリプトまたは共有メモに貼り付けないでください。

ステップ2 — 最初の2つのジャッジベースのモニターを配線します (Langfuse UI)

Langfuse は、ユーザー不同意 と スコープ外リクエスト のための公開テンプレートを発送します。どちらもLLM-as-a-judge エバリュエーターで、観察から変数を読み取ります。2つのテンプレートは、異なるターゲットが必要です:

  • スコープ外リクエスト はシステムプロンプトが必要で、ルート dad-it-support-chat-turn エージェント観察をターゲットにします。
  • ユーザー不同意 は会話履歴が必要なため、ルート dad-it-support-chat-turn エージェント観察をターゲットにします。

スコープ外リクエスト の場合:

  1. Langfuse で、Evaluators → Set up evaluator を開きます(リストが空の間、ボタンは Create Evaluator と読みます) Use existing リストから Out-of-Scope Request を選ぶ (Langfuse managed evaluators)。Create from scratch タイルから LLM as a judge evaluator を開始しないでください — そこは空白の Create new evaluator フォームを開き、テンプレートではありません。それに陥った場合は、ダイアログを閉じて、代わりにリストから管理されたエバリュエーターを選びます。

  2. 最終的なOpenAI世代をターゲットにします:

    • 観察タイプ: generation
    • Tool Call count = 0 (ツール決定を除外するため)
  3. テンプレートの変数を世代の Input からマップします:

    テンプレート変数オブジェクトフィールドJsonPath
    {{system_prompt}}Input$.messages[0].content
    {{last_user_message}}Input$.messages[-1:].content

    [-1:] スライスは世代入力の最終メッセージを読み取り、会話の成長に応じてマッピングが機能し続けるようにします。トレースに異なるメッセージ形状がある場合は、世代入力を検査してJsonPathを調整します。

  4. ステップ1で設定したデフォルトジャッジモデルを使用するか、別の構造化出力対応ジャッジモデルを選択して保存します。

  5. エバリュエーターを有効にします。

変数マッピング

ユーザー不同意 の場合:

  1. Langfuse で、Evaluators → Set up evaluator を開き、Use existing リストから User Disagreement を選択します。

  2. ルートエージェント観察をターゲットにします:

    • 観察タイプ: agent
    • 観察名: dad-it-support-chat-turn
  3. テンプレートの変数をエージェント観察の Input からマップします:

    テンプレート変数オブジェクトフィールドJsonPath
    {{conversation_history}}Input$.messages
    {{last_user_message}}Input$.messages[-1:].content

    エージェント入力はブラウザからのチャットリクエストなので、最後のメッセージはそのターンのお父さんの最新メッセージです。

  4. ステップ1で設定したデフォルトジャッジモデルを使用するか、別の構造化出力対応ジャッジモデルを選択して保存します。

  5. エバリュエーターを有効にします。

ユーザー不同意エバリュエーターの変数マッピング。

💡 カスタムエバリュエーター。 発送されたテンプレートは高速なオンランプですが、使用する必要はありません。Evaluators → Set up evaluator → Create from scratch → LLM as a judge evaluator を使用すると、独自のプロンプトを記述し、独自の変数を定義できます。同じマッピングフロー — 各変数を正しい観察の正しいJsonPathに指していて、完了です。

ステップ3 — すべて大文字の挫折のためのコードエバリュエーターを追加します

上記の2つのモニターはセマンティック判断が必要なためLLM-as-a-judge を使用します。このモニターはそうではありません。大文字の長い実行を含むユーザーメッセージの安価な決定的なチェックが必要なだけです。

コードエバリュエーターはそのパターンに適しています: モデル呼び出しなし、プロンプト設計なし、ライブ観察で実行される簡単なルール。

  1. Langfuse で、Evaluators → Set up evaluator を開き、Create from scratch で Code evaluator を選択します。
  2. Python を選択します。
  3. エバリュエーターを user_all_caps_signal と命名します。
  4. 次のコードを貼り付けます:
from dataclasses import dataclass
from typing import Any


@dataclass
class ObservationContext:
    input: Any = None
    output: Any = None
    metadata: Any = None


@dataclass
class ExperimentContext:
    item_expected_output: Any = None
    item_metadata: Any = None


@dataclass
class EvaluationContext:
    observation: ObservationContext
    experiment: ExperimentContext | None = None


@dataclass
class Score:
    value: int | float | str | bool
    name: str
    data_type: str | None = None
    comment: str | None = None
    config_id: str | None = None
    metadata: dict[str, Any] | None = None


@dataclass
class EvaluationResult:
    scores: list[Score]


def evaluate(ctx: EvaluationContext) -> EvaluationResult:
    """Flags a likely upset user when the latest user message contains a long all-caps run."""
    input = ctx.observation.input
    text = ""

    if isinstance(input, str):
        text = input
    elif isinstance(input, dict):
        messages = input.get("messages")
        if isinstance(messages, list):
            for message in reversed(messages):
                if (
                    isinstance(message, dict)
                    and message.get("role") == "user"
                    and isinstance(message.get("content"), str)
                ):
                    text = message["content"]
                    break

    longest_run = 0
    current_run = 0

    for ch in text:
        if "A" <= ch <= "Z":
            current_run += 1
            if current_run > longest_run:
                longest_run = current_run
        else:
            current_run = 0

    has_all_caps_signal = longest_run >= 6

    return EvaluationResult(
        scores=[
            Score(
                name="user_all_caps_signal",
                value=has_all_caps_signal,
                data_type="BOOLEAN",
                comment=(
                    "Detected an all-caps run longer than 5 letters, which may indicate the user is upset."
                    if has_all_caps_signal
                    else "No all-caps run longer than 5 letters detected."
                ),
                metadata={
                    "text": text,
                    "longest_run": longest_run,
                },
            )
        ]
    )
  1. 不同意モニターと同じルートエージェント観察をターゲットにします:
    • ターゲット: Live Observations
    • 観察タイプ: agent
    • 観察名: dad-it-support-chat-turn
  2. エバリュエーターを保存して有効にします。

なぜこのターゲット? ルートエージェント観察入力はブラウザからのチャットリクエストなので、エバリュエーターはツール呼び出しやフォローアップ世代が形状を複雑にする前に、お父さんの最新ユーザーメッセージを検査できます。

このエバリュエーターはステップ1 からLangfuse エバリュエーターモデルを必要としません。これは、LLM ジャッジではなく、Langfuse内で実行されている純粋なPythonです。

確認

npm run dev

各モニターをライト アップする必要がある4つのターンを送信します:

  1. スコープ内 — "Bluetooth をオンにするにはどうすればいいですか?" (両方のモニターでクリーンにスコア付けされるべき)
  2. スコープ外 — "税金を申告できますか?"
  3. 不同意 — 通常の質問を尋ねてから、"いいえ、そのメニューはありません" で返信してください
  4. すべて大文字 — "THIS STILL ISNT WORKING"

Langfuse で、エバリュエーターが実行されるのを待ち(数秒後に更新)、次にエバリュエーターのスコアでトレースをソートします。スコープ外、不同意、すべて大文字トレースがトップに浮かぶはずです。

トレース上で発火しているスコープ外エバリュエーター — 世代はスコープ外とフラグされ、左側のパネルはリクエストがiPhone ヘルプスコープ外にあることのエージェントの推論を示します。

ユーザーが同意しません例

スコープ外モニターが発火すると、チャットボットが既にリクエストを適切に拒否したことを確認できます — 正確に私たちが要求したもの。しかし、これらのトレースも最も興味深い読むエンドツーエンドです: スコープ外ヒットの定期的なストリームは、追加スコープ が処理する価値があるという最初のシグナルです。 "写真を新しいiPadに移動するのに役立つ?" は馬鹿みたいですが、本物の機能リクエストは、モニター出力に隠れている可能性があります。

ユーザー不同意は、はるかに高いシグナルイベントです。ユーザーがエージェントが与えたばかりの答えに押し戻ったとき、何かほぼ確実に進みました — 間違ったツール結果、不足しているコンテキスト、彼らがいるiPhoneと一致しない指示。これらはあなたが最初に読みたいトレースであり、05-dataset に変えるための主要な候補です。

すべて大文字シグナルは意図的にざらっとしています。ユーザーが確実に怒っているという主張ではなく、会話が横向きになっている可能性があるという安価な決定的な手掛かりです。これにより、レビュー用の優れた「これらを最初に読む」モニターが作られます、特に豊かな不同意やスコープ外ジャッジと組み合わせられたときは特にそうです。

本番トラフィックをシード化してモニターが発火するのを見る

4つの手作業でタイプされたターン通行が配線が機能することを証明します。しかし、モニタリングはボリュームで生計を立てます — だから現実的な本番データのバッチをシード化して、何が起こるかを見てみましょう。

npm run langfuse:seed:otel:no-scores

これは実際の "Dad IT support" トラフィックのスナップショット — プラス綜合エッジケースのひとにぎり(スコープ外の質問、ALL-CAPSメッセージ、および "いいえ、そのメニューはありません" の不同意) — をあなたのLangfuseプロジェクトの production 環境に再生します。既に .env にあるLangfuse キーを再利用し、すべてのタイムスタンプを移動してから、最新のトレースが "now" に着陸するようにします。

:no-scores バリアントは ベイクされたスコアなしで トレースをシード化します。それが全体のポイントです: エバリュエーターは既にライブなので、表示されるスコアは あなたの モニターがこの新しいトラフィックに対して実行から来ています — データにベイクされた数値ではなく。

⚠️ シードは べき等ではありません。OpenTelemetry は毎回新しいトレースIDをミントするため、再実行するとデータが2倍になります。一度実行します。クリーンなスレートが必要な場合は、再度シードする前に、Langfuse の前のシードトレースを削除します。

Tracing を開き、production 環境にフィルタリングして、数秒後に更新します。シード化されたバッチ全体に着陸するスコアを見て、エバリュエーターがそれを通す — スコープ外、すべて大文字、不同意エッジケースが、手作業でタイプされたターンとまったく同じように浮かぶのを見てください。それが本番トラフィックに対してモニターがどのように見えるか、そして正確にフラグされたトレースの山です。

ラップアップ

良いモニターは、シグナルをノイズから分離する方法です。本番環境はたくさんのトレースを意味し、最も重要な質問はどのトレースを見るべき? — モニターが答えます。

信号リクエストモニターが配置されたら、時間をかけた次のステップは 平均メトリック追跡 — 品質メトリックを選択して漂流を見る。正しい方法はエラー分析: 見ている驚くべきトレースのサンプルを見て、失敗モードでグループ化し、失敗モードをエバリュエーターに変える。Academy のモニタリングレッスン はこれについてもっと深く行きます。

これらのモニターがキャッチするトレースは、05-dataset のための最良のソースでもあります — 実際のロック イン、または修正したい動作の例です。

最終状態

これは 05-dataset の開始ポイントです。

このページの内容

Track your progress?

Optional. We email a link to confirm your address; progress records once you open it.

Please use your work email address, not a personal one.

Progress tracking also requires accepting the current Terms of Service in Privacy settings.

JA