ここまでで 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フローを自動化できます。
- 構造化レスポンスを解析し、出力の質を自動スコアリング。
TestModel
やFunctionModel
で安定した再現性のある評価シナリオを作成。
Logfireとの統合
pydantic-aiはPydantic Logfireとの連携も容易で、ログやトレース、メトリクスを一元管理できます。
本番環境でのトラブルシューティングやパフォーマンス改善が容易になります。
高度な例:猫猫カンパニーエージェント
以下では、猫用商品のECサイト「猫猫カンパニー」を想定し、エージェントが在庫問い合わせや出荷情報、FAQに回答するシナリオを示します。
5.1 シナリオ概要
- 製品情報問い合わせ
- 注文状況確認
- 猫関連FAQ回答
本番ではOpenAIやGeminiを利用しますが、テストではTestModel
やFunctionModel
でレスポンスをモックします。
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.info
やlogger.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では構造化ログやトレースを管理し、問題発生時の根本原因分析が容易になります。
AI エージェントフレームワーク「pydantic-ai」を触ってみる⑩
《テスト・Evals編》
logfire で記録を保存してみた!
これはエージェントの処理内容を追うのに使えそう! https://t.co/Vw7cORdab5 pic.twitter.com/orUQstZHTO— Maki@Sunwood AI Labs. (@hAru_mAki_ch) December 8, 2024
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 へ送信されるログをもとに、問題発生時のデバッグが可能。
まとめ
TestModel
・FunctionModel
などのモックモデルでLLMレスポンスを安定化し、CI/CDでのテストを容易化。ALLOW_MODEL_REQUESTS
で実APIコールを遮断し、外部依存性のないテストを構築。- Evalsでプロンプト改善効果を定量化し、開発サイクルを改善。
loguru
でログ出力フォーマットを改善し、テスト結果・Evals結果を可読性高く可視化。- Logfireなどの観測ツールと組み合わせることで、本番環境での運用・デバッグが強化。
この一連の例を通して、pydantic-aiによるLLMエージェント開発が、より型安全・テスト可能・拡張性に優れ、開発から運用、改善に至るまでのプロセスを包括的にサポートできることが示されました。
コメント