【連載】pydantic-ai徹底解説 (4) テスト・Evalsと開発運用支援

Python開発

ここまでで pydantic-ai を使ったLLMエージェント構築の基本、ツール呼び出し、依存性注入、構造化レスポンス、ストリーミングなどを学びました。
最終回となる今回は、テストやエバリュエーション(Evals)といった開発・運用面のサポート機能、そしてログ出力による可視化について紹介します。

インストール方法

pydantic-aiとその例を実行するには、以下の手順でインストールを行います:

基本的なインストール

# pipを使用する場合
!pip install 'pydantic-ai[examples]' loguru
import os
from google.colab import userdata

os.environ['GEMINI_API_KEY'] = userdata.get('GEMINI_API_KEY')
os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')
import nest_asyncio
nest_asyncio.apply()

テスト容易性

LLMを用いたアプリケーションのテストは難しいものですが、pydantic-aiは以下の機能でサポートします。

  • TestModel:LLM APIを実際には呼ばずに、ツールスキーマに基づくダミーデータを自動生成。
  • FunctionModel:任意のPython関数を使ってモックレスポンスを柔軟に定義。
  • ALLOW_MODEL_REQUESTS:Falseにすることで実APIコールを抑制し、テストを安定化。

簡易例:TestModelを使ったモック応答

import pytest
from pydantic_ai import Agent, models
from pydantic_ai.models.test import TestModel
from loguru import logger

models.ALLOW_MODEL_REQUESTS = False  # LLM API呼び出しを禁止
agent = Agent('openai:gpt-4o')

with agent.override(model=TestModel()):
    result = agent.run_sync('面白いジョークを教えてください。')
    logger.info("Mocked Result: {}", result.data)

上記ではTestModelにより、ネットワークやAPIキーへの依存を排除し、純粋なロジックテストが実現可能です。
loguruで結果をログ出力することで、テスト結果の可視性が向上します。


エバリュエーション (Evals)

Evalsはモデル応答品質を評価するプロセスです。pydantic-ai専用のEvalフレームワークはありませんが、構造化レスポンスとモックモデルでEvalフローを自動化できます。

  • 構造化レスポンスを解析し、出力の質を自動スコアリング。
  • TestModelFunctionModelで安定した再現性のある評価シナリオを作成。

Logfireとの統合

pydantic-aiはPydantic Logfireとの連携も容易で、ログやトレース、メトリクスを一元管理できます。
本番環境でのトラブルシューティングやパフォーマンス改善が容易になります。


高度な例:猫猫カンパニーエージェント

以下では、猫用商品のECサイト「猫猫カンパニー」を想定し、エージェントが在庫問い合わせや出荷情報、FAQに回答するシナリオを示します。

5.1 シナリオ概要

  • 製品情報問い合わせ
  • 注文状況確認
  • 猫関連FAQ回答

本番ではOpenAIやGeminiを利用しますが、テストではTestModelFunctionModelでレスポンスをモックします。

5.2 TestModelを用いたCI/CDテスト

from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
from pydantic_ai import models
from loguru import logger

models.ALLOW_MODEL_REQUESTS = False

inventory_agent = Agent('openai:gpt-4o', tools=[])  # 在庫チェックツールを仮定
with inventory_agent.override(model=TestModel()):
    prompt = "「キャットニップデラックス」の在庫はありますか?"
    result = inventory_agent.run_sync(prompt)
    logger.info("在庫テスト結果: {}", result.data)
    # ここではresult.dataに"十分"という文字があるかは不定
    # TestModelは自動生成したデータを返します

ここでは、LLMレスポンスをTestModelでモックし、「在庫は十分にあります」という決まった回答を返すテストです。
loguruでログ出力することで、CI上でもテキスト出力がわかりやすくなります。

5.3 FunctionModelによる高度なモックレスポンス

from pydantic_ai import Agent, models
from pydantic_ai.models.function import FunctionModel
from pydantic_ai.messages import (
    Message,
    ModelAnyResponse,
    ModelTextResponse
)
from pydantic_ai.models.function import AgentInfo
from loguru import logger

models.ALLOW_MODEL_REQUESTS = False

def conditional_response(messages: list[Message], info: AgentInfo) -> ModelAnyResponse:
    # ユーザーの最後のメッセージ内容を取得
    user_prompt = messages[-1].content if messages else ""

    if "キャットニップデラックス" in user_prompt:
        return ModelTextResponse("「キャットニップデラックス」は在庫が少なくなっています。")
    else:
        return ModelTextResponse("他の製品は在庫潤沢です。")

conditional_agent = Agent('openai:gpt-4o', tools=[])

with conditional_agent.override(model=FunctionModel(conditional_response)):
    result_deluxe = conditional_agent.run_sync("「キャットニップデラックス」在庫状況は?")
    logger.info("条件付きレスポンス(デラックス): {}", result_deluxe.data)
    assert "少なく" in result_deluxe.data

    result_other = conditional_agent.run_sync("通常版猫用おもちゃの在庫状況は?")
    logger.info("条件付きレスポンス(通常版): {}", result_other.data)
    assert "潤沢" in result_other.data

FunctionModelにより入力プロンプト内容に応じた動的なモックレスポンスを実装し、より複雑なテストが可能になります。

5.4 Evalsシナリオ:プロンプト改善の効果測定

from pydantic_ai import Agent, models
from pydantic_ai.models.function import FunctionModel
from pydantic_ai.messages import Message, ModelAnyResponse, ModelTextResponse
from pydantic_ai.models.function import AgentInfo
from loguru import logger

baseline_prompt = "「キャットニップデラックス」について教えてください。"
improved_prompt = "「キャットニップデラックス」について、出荷予定日も含めて教えてください。"

def eval_function(messages: list[Message], info: AgentInfo) -> ModelAnyResponse:
    # メッセージリストからユーザーの最後のプロンプトを取得
    user_prompt = messages[-1].content if messages else ""
    if "出荷予定" in user_prompt:
        return ModelTextResponse("「キャットニップデラックス」は人気商品です。出荷予定日は2日後です。")
    else:
        return ModelTextResponse("「キャットニップデラックス」は人気商品です。")

eval_agent = Agent('openai:gpt-4o')
models.ALLOW_MODEL_REQUESTS = False

with eval_agent.override(model=FunctionModel(eval_function)):
    baseline_result = eval_agent.run_sync(baseline_prompt)
    improved_result = eval_agent.run_sync(improved_prompt)

    baseline_score = 0
    improved_score = 0
    if "出荷予定日" in baseline_result.data:
        baseline_score += 1
    if "出荷予定日" in improved_result.data:
        improved_score += 1

    logger.info("ベースラインスコア: {}", baseline_score)
    logger.info("改善後スコア: {}", improved_score)
    # 改善後プロンプトでより詳細情報(出荷予定日)が加わったことを確認

loguruでログに出力することで、Evals結果を明確に可視化し、どのプロンプトがより良い応答を引き出したかを簡単に確認できます。

5.5 loguruを用いたログ出力例

loguruは、シンプルかつ強力なロギングツールで、色分け・フォーマット・フィルタリングなどを簡潔に行えます。
上記例のようにlogger.infologger.debugを使うことで、テストやEvalsのプロセスを人間が読みやすい形で残せます。

from loguru import logger

logger.add("debug_log.log", format="{time} {level} {message}", level="DEBUG")

# テストコード中
logger.debug("Evals用ベースラインプロンプト: {}", baseline_prompt)
logger.debug("Evals用改善後プロンプト: {}", improved_prompt)
logger.info("Evals結果 - ベースライン: {}, 改善後: {}", baseline_result.data, improved_result.data)

これで、実行時にはコンソール出力とdebug_log.logファイル両方でプロセスを追跡できます。

5.6 Logfireでの観測とデバッグ

本番では、Logfireを導入することで運用状況をより深く観測できます。
loguruでローカルなテキストログを扱いつつ、Logfireでは構造化ログやトレースを管理し、問題発生時の根本原因分析が容易になります。

import os
from google.colab import userdata

os.environ['LOGFIRE_TOKEN'] = userdata.get('LOGFIRE_TOKEN')
import logfire

logfire.configure()

production_agent = Agent('openai:gpt-4o', tools=[])
# 実運用中に Logfire へ送信されるログをもとに、問題発生時のデバッグが可能。

まとめ

  • TestModelFunctionModelなどのモックモデルでLLMレスポンスを安定化し、CI/CDでのテストを容易化。
  • ALLOW_MODEL_REQUESTSで実APIコールを遮断し、外部依存性のないテストを構築。
  • Evalsでプロンプト改善効果を定量化し、開発サイクルを改善。
  • loguruでログ出力フォーマットを改善し、テスト結果・Evals結果を可読性高く可視化。
  • Logfireなどの観測ツールと組み合わせることで、本番環境での運用・デバッグが強化。

この一連の例を通して、pydantic-aiによるLLMエージェント開発が、より型安全・テスト可能・拡張性に優れ、開発から運用、改善に至るまでのプロセスを包括的にサポートできることが示されました。

📒ノートブック

Google Colab

参考サイト

GitHub - pydantic/pydantic-ai: Agent Framework / shim to use Pydantic with LLMs
Agent Framework / shim to use Pydantic with LLMs. Contribute to pydantic/pydantic-ai development by creating an account on GitHub.
Hello World Example Not Working in Jupyter Notebook · Issue #144 · pydantic/pydantic-ai
The provided 'Hello World' example in the documentation is still not working. version: 0.0.9 import dotenv dotenv.load_dotenv() agent = Agent( 'openai:gpt-4o', ...

コメント

タイトルとURLをコピーしました