/>
S
Shineos Tech Blog

目次

「動かない」を防ぐ!LangSmithを活用したLLMエージェントの挙動デバッグ術

「動かない」を防ぐ!LangSmithを活用したLLMエージェントの挙動デバッグ術

| Shineos Dev Team
Share:

はじめに

この記事の要点

  • LangSmithはLangChainが提供するLLMアプリのデバッグ・モニタリング基盤で、複雑なエージェントの挙動を可視化できる
  • トレーシング機能により、各ツールの実行順序や入出力を詳細に追跡し、ハルシネーションやエラーの特定を高速化する
  • プロンプトハブによるバージョン管理とA/Bテストにより、実データに基づいた最適なプロンプト選定が可能
  • 評価データセットを用いた回帰テストにより、プロンプト変更による不具合を未然に防ぎ、品質を定量的に担保できる

LLMエージェントを本番環境で運用していると、「開発環境では動いていたのに本番で失敗する」「なぜこのプロンプトが選ばれたのか分からない」といった課題に直面します。従来のログでは、LLMの内部状態やエージェントの意思決定プロセスを追跡することが困難でした。

本記事では、LangSmithを使ったLLMエージェントのデバッグ手法を、実装コード例とともに解説します。エージェントの挙動を可視化し、問題を素早く特定するための実践的なテクニックを紹介します。

LangSmithとはどのようなツールですか?

LangSmithは、LangChainが提供するLLMアプリケーションの開発・デバッグ・モニタリングプラットフォームです。LLMの入出力、エージェントの実行トレース、コスト分析などを一元管理できます。

主な機能として、トレーシング(実行履歴の記録)、デバッグ(問題箇所の特定)、評価(出力品質の測定)、モニタリング(本番環境の監視)を提供します。特にマルチエージェントシステムでは、各エージェントの実行フローを可視化できるため、複雑な挙動の理解に役立ちます。

プロフェッショナルなデバッグを実現するポイントとは?

  • LangSmithのトレーシング機能で、LLMエージェントの実行フローを完全に可視化できる
  • プロンプトのバージョン管理とA/Bテストで、最適なプロンプトを科学的に選定可能
  • カスタムメタデータとフィルタリングで、本番障害の原因を素早く特定できる
  • コスト分析とレイテンシ監視で、運用コスト and パフォーマンスを最適化できる
  • 評価データセットを活用し、プロンプト改善の効果を定量的に測定できる

LangSmithを導入する基本的な手順は?

まず、LangSmithを使い始めるための基本的なセットアップを行います。

インストールと環境設定

# 必要なパッケージのインストール
pip install langsmith langchain-openai langchain

# 環境変数の設定
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "your-langsmith-api-key"
os.environ["LANGCHAIN_PROJECT"] = "my-agent-project"
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"

プロジェクト名を設定することで、複数のプロジェクトを並行して管理できます。開発環境と本番環境で異なるプロジェクト名を使い分けることで、トレースを分離して管理できます。

基本的なトレーシングの実装

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.tools import tool
from langsmith import traceable

# カスタムツールの定義
@tool
def get_weather(location: str) -> str:
    """指定された場所の天気情報を取得します"""
    # 実際にはAPIを呼び出す
    return f"{location}の天気は晴れです"

@tool
def calculate(expression: str) -> str:
    """数式を計算します"""
    try:
        result = eval(expression)
        return f"計算結果: {result}"
    except Exception as e:
        return f"エラー: {str(e)}"

# エージェントのセットアップ
llm = ChatOpenAI(model="gpt-4", temperature=0)
tools = [get_weather, calculate]

prompt = ChatPromptTemplate.from_messages([
    ("system", "あなたは親切なアシスタントです。ツールを使って質問に答えてください。"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# エージェントの実行(自動的にトレースされる)
result = agent_executor.invoke({"input": "東京の天気を教えて、その後10+20を計算して"})
print(result["output"])

このコードを実行すると、LangSmithのダッシュボードに実行トレースが自動的に記録されます。エージェントがどのツールを選択し、どのような順序で実行したかが可視化されます。

LangSmithでのデバッグフロー

トレースの詳細分析とデバッグ

LangSmithのトレース機能を使うと、エージェントの実行過程を詳細に分析できます。

メタデータの追加とフィルタリング

from langsmith import Client
from langchain_core.runnables import RunnableConfig

client = Client()

# カスタムメタデータを追加してトレース
config = RunnableConfig(
    tags=["production", "customer-support"],
    metadata={
        "user_id": "user_123",
        "session_id": "session_456",
        "environment": "production",
        "version": "v2.1"
    }
)

result = agent_executor.invoke(
    {"input": "在庫管理システムの現在の在庫数を確認して"},
    config=config
)

メタデータを活用することで、特定のユーザー、セッション、環境でのトレースを素早く検索できます。本番環境で問題が発生した際、該当するユーザーIDやセッションIDでフィルタリングすることで、問題の再現性を確認できます。

エラートレースの分析

実際に試してみた

失敗談と教訓: 以前、マルチエージェントシステムの開発中、「たまに計算結果が間違う」というバグに悩まされました。 従来の print デバッグでは、各エージェントが内部でどう判断したかが分からず、一向に原因が特定できませんでした。 LangSmithを導入してトレースを見たところ、ツール呼び出し前の「思考(Thought)」段階で、数値のパースに失敗していることが一目瞭然となり、わずか15分で修正できました。 「ログは見えないものを映さないが、トレースは思考を映す」と実感した瞬間です。

from langsmith.run_helpers import traceable

@traceable(name="custom_processing", run_type="chain")
def process_user_query(query: str, user_context: dict) -> dict:
    """ユーザークエリを処理する関数"""
    try:
        # クエリの前処理
        processed_query = preprocess(query, user_context)
        
        # エージェントの実行
        result = agent_executor.invoke({"input": processed_query})
        
        # 後処理
        final_result = postprocess(result, user_context)
        
        return {
            "success": True,
            "result": final_result,
            "tokens_used": result.get("token_usage", {})
        }
    except Exception as e:
        # エラー情報をトレースに記録
        return {
            "success": False,
            "error": str(e),
            "error_type": type(e).__name__,
            "query": query
        }

def preprocess(query: str, context: dict) -> str:
    """クエリの前処理(簡略化)"""
    return f"{context.get('user_name', 'ユーザー')}さんの質問: {query}"

def postprocess(result: dict, context: dict) -> str:
    """結果の後処理(簡略化)"""
    return result.get("output", "")

# 使用例
user_context = {"user_name": "田中", "user_id": "123"}
result = process_user_query("今日の売上を確認して", user_context)

@traceableデコレータを使うことで、カスタム関数の実行も追跡できます。エラーが発生した場合、その時点でのすべての変数の状態がトレースに記録されるため、問題の原因を特定しやすくなります。

プロンプトのバージョン管理とA/Bテスト

LangSmithを使うと、プロンプトのバージョン管理とA/Bテストを効率的に行えます。

プロンプトハブの活用

from langchain import hub

# プロンプトをLangSmith Hubから取得
prompt = hub.pull("shineos/customer-support-agent")

# プロンプトのバージョンを指定
prompt_v2 = hub.pull("shineos/customer-support-agent:v2")

# エージェントの作成
agent_v2 = create_openai_functions_agent(llm, tools, prompt_v2)
agent_executor_v2 = AgentExecutor(agent=agent_v2, tools=tools)

# A/Bテストのための実行
import random

def run_with_ab_test(user_input: str):
    # ランダムにバージョンを選択
    version = random.choice(["v1", "v2"])
    
    if version == "v1":
        prompt = hub.pull("shineos/customer-support-agent:v1")
    else:
        prompt = hub.pull("shineos/customer-support-agent:v2")
    
    agent = create_openai_functions_agent(llm, tools, prompt)
    executor = AgentExecutor(agent=agent, tools=tools)
    
    config = RunnableConfig(
        tags=[f"prompt-version-{version}"],
        metadata={"ab_test_version": version}
    )
    
    result = executor.invoke({"input": user_input}, config=config)
    return result, version

# テスト実行
result, version = run_with_ab_test("返品手続きについて教えて")
print(f"使用バージョン: {version}")
print(f"結果: {result['output']}")

プロンプトをLangSmith Hubで管理することで、チーム全体でプロンプトを共有し、バージョン管理できます。A/Bテストの結果はLangSmithのダッシュボードで比較分析できます。

評価データセットの活用

from langsmith import Client

client = Client()

# 評価用データセットの作成
dataset_name = "customer-support-qa"

# データセットが存在しない場合は作成
try:
    dataset = client.read_dataset(dataset_name=dataset_name)
except:
    dataset = client.create_dataset(
        dataset_name=dataset_name,
        description="カスタマーサポート用のQ&Aデータセット"
    )

# テストケースの追加
test_cases = [
    {
        "input": {"input": "注文をキャンセルしたい"},
        "expected_output": "注文キャンセルの手順",
        "metadata": {"category": "order_management"}
    },
    {
        "input": {"input": "配送状況を確認したい"},
        "expected_output": "配送追跡情報",
        "metadata": {"category": "shipping"}
    },
    {
        "input": {"input": "返金はいつされますか"},
        "expected_output": "返金処理のタイムライン",
        "metadata": {"category": "refund"}
    }
]

for case in test_cases:
    client.create_example(
        inputs=case["input"],
        outputs={"expected": case["expected_output"]},
        dataset_id=dataset.id,
        metadata=case["metadata"]
    )

print(f"データセット '{dataset_name}' に {len(test_cases)} 件のテストケースを追加しました")

評価データセットを使うことで、プロンプトの変更が回答品質に与える影響を定量的に測定できます。継続的にテストを実行し、品質の劣化を防げます。

検証データ

評価の重要性(実体験): あるチャットボット案件で、親切さを上げるためにプロンプトを「フレンドリーにして」と変更した際、回答精度自体が10%低下してしまったことがありました。 この時、LangSmithの評価セットで回帰テストを回していなければ、「愛想はいいが嘘をつくボット」をリリースして大惨事になるところでした。 「変更したら必ずテスト」は、ソフトウェア開発だけでなくプロンプトエンジニアリングでも鉄則です。

本番環境でのモニタリング戦略

LangSmithを使った本番環境のモニタリング手法を紹介します。

コストとレイテンシの追跡

from datetime import datetime, timedelta
from langsmith import Client

client = Client()

def analyze_production_metrics(days: int = 7):
    """過去N日間の本番メトリクスを分析"""
    
    # 期間の設定
    end_time = datetime.now()
    start_time = end_time - timedelta(days=days)
    
    # トレースの取得
    runs = client.list_runs(
        project_name="my-agent-project",
        start_time=start_time,
        end_time=end_time,
        filter='eq(tags, "production")'
    )
    
    # メトリクスの集計
    total_cost = 0
    total_latency = 0
    error_count = 0
    success_count = 0
    latency_list = []
    
    for run in runs:
        # コストの集計
        if run.total_cost:
            total_cost += run.total_cost
        
        # レイテンシの集計
        if run.latency:
            latency_ms = run.latency
            total_latency += latency_ms
            latency_list.append(latency_ms)
        
        # 成功/失敗のカウント
        if run.error:
            error_count += 1
        else:
            success_count += 1
    
    total_requests = success_count + error_count
    
    # 統計の計算
    avg_latency = total_latency / total_requests if total_requests > 0 else 0
    error_rate = (error_count / total_requests * 100) if total_requests > 0 else 0
    
    # P95レイテンシの計算
    if latency_list:
        latency_list.sort()
        p95_index = int(len(latency_list) * 0.95)
        p95_latency = latency_list[p95_index] if p95_index < len(latency_list) else latency_list[-1]
    else:
        p95_latency = 0
    
    print(f"=== 過去{days}日間の本番メトリクス ===")
    print(f"総リクエスト数: {total_requests}")
    print(f"成功率: {(success_count/total_requests*100):.2f}%")
    print(f"エラー率: {error_rate:.2f}%")
    print(f"総コスト: ${total_cost:.4f}")
    print(f"平均レイテンシ: {avg_latency:.0f}ms")
    print(f"P95レイテンシ: {p95_latency:.0f}ms")
    print(f"1リクエストあたり平均コスト: ${(total_cost/total_requests):.6f}")
    
    return {
        "total_requests": total_requests,
        "error_rate": error_rate,
        "total_cost": total_cost,
        "avg_latency_ms": avg_latency,
        "p95_latency_ms": p95_latency
    }

# メトリクスの分析実行
metrics = analyze_production_metrics(days=7)

定期的にメトリクスを分析することで、コストの急増やレイテンシの劣化を早期に検知できます。アラートを設定し、閾値を超えた場合に通知を受け取る仕組みも構築できます。

エラーパターンの自動検出

from collections import Counter

def detect_error_patterns(days: int = 1):
    """エラーパターンを自動検出"""
    
    end_time = datetime.now()
    start_time = end_time - timedelta(days=days)
    
    # エラーが発生したトレースのみ取得
    error_runs = client.list_runs(
        project_name="my-agent-project",
        start_time=start_time,
        end_time=end_time,
        filter='and(eq(tags, "production"), exists(error))'
    )
    
    error_types = []
    error_messages = []
    failed_tools = []
    
    for run in error_runs:
        if run.error:
            error_types.append(type(run.error).__name__ if hasattr(run.error, '__name__') else 'Unknown')
            error_messages.append(str(run.error)[:100])
        
        # 失敗したツールを特定
        if run.run_type == "tool":
            failed_tools.append(run.name)
    
    # パターンの集計
    error_type_counts = Counter(error_types)
    failed_tool_counts = Counter(failed_tools)
    
    print("=== エラーパターン分析 ===")
    print("\n【エラータイプ別】")
    for error_type, count in error_type_counts.most_common(5):
        print(f"  {error_type}: {count}件")
    
    print("\n【失敗したツール】")
    for tool_name, count in failed_tool_counts.most_common(5):
        print(f"  {tool_name}: {count}件")
    
    return {
        "error_types": dict(error_type_counts),
        "failed_tools": dict(failed_tool_counts)
    }

# エラーパターンの検出
patterns = detect_error_patterns(days=1)

エラーパターンを自動的に分析することで、頻発する問題を優先的に対処できます。特定のツールやAPIで問題が集中している場合、そのツールの実装を重点的に改善できます。

よくある質問

LangSmithは無料で使えますか?

LangSmithには無料プランが用意されており、月間5,000トレースまで無料で利用できます。個人開発や小規模なプロジェクトであれば、無料プランで十分に活用できます。

既存のLangChainコードにLangSmithを導入するのは難しいですか?

環境変数を設定するだけで、既存のLangChainコードに対してトレーシングが自動的に有効になります。コードの変更は最小限で済みます。

トレースデータはどこに保存されますか?

トレースデータはLangSmithのクラウドサーバーに保存されます。セキュリティが懸念される場合は、機密情報を含むデータをトレースから除外する設定が可能です。

LangChain以外のLLMフレームワークでも使えますか?

LangSmithはLangChain専用ですが、LangChain経由で他のLLMプロバイダー(OpenAI、Anthropic、Google、Azure等)を利用できます。また、REST APIを使えば、任意のフレームワークからトレースを送信することも可能です。

本番環境でトレーシングを有効にするとパフォーマンスに影響しますか?

LangSmithのトレーシングは非同期で動作するため、アプリケーションのレスポンスタイムへの影響は最小限です。ただし、大量のトレースを送信する場合は、ネットワーク帯域幅を考慮する必要があります。

エージェントが期待通りに動作しない場合、どこから調査を始めればよいですか?

まず、LangSmithのトレース画面でエージェントの実行フローを確認し、どのステップで問題が発生しているかを特定します。プロンプトの内容、ツールの選択、各ステップの入出力を順番にチェックすることで、問題箇所を絞り込めます。

筆者の視点:LangSmithは「必須」か?

導入の動機

当初は無料枠のあるLangSmithを「とりあえず」入れていただけでしたが、エージェントが複雑化するにつれ、これがなければデバッグ不可能な状態になりました。

実践での苦労

正直なところ、LangChain以外のフレームワーク(LlamaIndexなど)と混在している環境では、トレースの手動計装(Manual Instrumentation)が少し手間でした。デコレータを適切に配置しないと、綺麗なツリー構造で表示されない苦労がありました。

発見と本音

意外だったのは、「非エンジニア(PM)への説明ツール」 として優秀だという点です。 「AIがなぜその回答をしたのか」をトレース画面で見せることで、PMが納得してくれるようになり、仕様調整がスムーズになりました。

推奨する人・しない人

  • おすすめ: 本番環境でLangChainを使用しているチーム、エージェントの挙動をブラックボックスにしたくない人。
  • 非推奨: 単発の単純なLLM呼び出ししかしないアプリ(コストと導入の手間が見合わない)。

おわりに

私見(Shineos Dev Team)

個人的には、「LLMアプリ開発は、コードを書いている時間より、ログを見ている時間の方が長い」 と感じています。 その「ログを見ている時間」をどれだけ短縮し、洞察を得られる時間に変えられるかが、開発スピードの鍵を握ります。 LangSmithは、そのための最強の相棒になってくれるはずです。

LangSmithを活用することで、LLMエージェントの複雑な挙動を可視化し、デバッグやモニタリングを効率化できます。トレーシング機能により実行フローを詳細に追跡でき、プロンプトのバージョン管理とA/Bテストで最適なプロンプトを科学的に選定できます。

本番環境でのコスト分析やエラーパターンの自動検出により、運用品質を継続的に改善できます。評価データセットを活用した品質測定で、プロンプトの変更が与える影響を定量的に把握できます。

私たちShineosでは、LLMエージェントを活用した業務自動化システムの開発支援を行っています。LangSmithを使った本番運用のベストプラクティスや、トラブルシューティングのノウハウをご提供できます。LLMエージェントの開発・運用でお困りの際は、ぜひお気軽にご相談ください。

参考リンク

関連記事

LangChainとLangGraphで実現するマルチエージェントシステム

複数のAIエージェントを協調させて複雑なタスクを実行する方法を、LangChainとLangGraphを使った実装例とともに実践的に解説します。

LangChain AIエージェント LLM

AIエージェントの実践的導入ガイド - 業務自動化の第一歩

LLMを単なるチャットボットではなく、自律的にタスクを遂行する「エージェント」として活用するための技術ガイドです。アーキテクチャ、実装例、そして課題解決への道筋を解説します。

LLM

Google Antigravity 実践ガイド (Deep Dive) - 複雑なコンテキスト管理とメモリの最適化

Zennで公開中の実践ガイドの続編。Google Antigravityの「エージェントメモリ」と「コンテキスト管理」を深掘りし、大規模プロジェクトでの実践的な設定テクニックを解説します。

AIエージェント

AIアプリ開発における「コスト爆発」を防ぐためのトークン最適化とキャッシュ戦略

LLM APIのコストが予算を圧迫していませんか?Shineosが実践する、トークン使用量を60%削減し、レスポンス速度を70%向上させた実践的な最適化手法を詳しく解説します

LLM

AI導入で直面する「回答の不確実性」をどう制御するか?Shineos流のガードレール実装

LLMの幻覚や不適切な回答を防ぐ「ガードレール」の実装パターンを解説。NVIDIA NeMo GuardrailsやShineos独自のバリデーションロジックを用いた、本番運用に耐えうるAIアプリケーション構築ガイド。

LLM