pydantic-ai-testing について
PydanticAIエージェントのテストを書く開発者向けのスキルです。決定的なテストや記録済みのモデル対話の検証に使う、モデルの代替、依存関係の上書き、スナップショット、カセット更新手順を扱います。
主な機能
- 01TestModelによる決定的なテスト
- 02FunctionModelによる応答のカスタマイズ
- 03VCRによるAPI対話の記録
- 04inline_snapshotによる出力検証
- 05ツールと依存関係のテストパターン
- 06対象を限定したカセットとスナップショットの更新
活用シーン
- 01APIを呼び出さないエージェントのテスト
- 02モデル応答とツール呼び出しの制御
- 03記録済みLLM対話の再生
- 04出力構造と取得メッセージの検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ existential-birds/ beagle --skill pydantic-ai-testingインストール後、Claude Code で/pydantic-ai-testingを入力して読み込まれたか確認します。
詳細
PydanticAI エージェントのテスト
TestModel(決定論的なテスト)
API を呼び出さずにテストするには、TestModel を使用します。
import pytest
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
def test_agent_basic():
agent = Agent('openai:gpt-4o')
# Override with TestModel for testing
result = agent.run_sync('Hello', model=TestModel())
# TestModel generates deterministic output based on output_type
assert isinstance(result.output, str)
TestModel の設定
from pydantic_ai.models.test import TestModel
# Custom text output
model = TestModel(custom_output_text='Custom response')
result = agent.run_sync('Hello', model=model)
assert result.output == 'Custom response'
# Custom structured output (for output_type agents)
from pydantic import BaseModel
class Response(BaseModel):
message: str
score: int
agent = Agent('openai:gpt-4o', output_type=Response)
model = TestModel(custom_output_args={'message': 'Test', 'score': 42})
result = agent.run_sync('Hello', model=model)
assert result.output.message == 'Test'
# Seed for reproducible random output
model = TestModel(seed=42)
# Force tool calls
model = TestModel(call_tools=['my_tool', 'another_tool'])
オーバーライド用コンテキストマネージャー
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
agent = Agent('openai:gpt-4o', deps_type=MyDeps)
def test_with_override():
mock_deps = MyDeps(db=MockDB())
with agent.override(model=TestModel(), deps=mock_deps):
# All runs use TestModel and mock_deps
result = agent.run_sync('Hello')
assert result.output
FunctionModel(カスタムロジック)
モデルの応答を完全に制御するには、次のようにします。
from pydantic_ai import Agent, ModelMessage, ModelResponse, TextPart
from pydantic_ai.models.function import AgentInfo, FunctionModel
def custom_model(
messages: list[ModelMessage],
info: AgentInfo
) -> ModelResponse:
"""Custom model that inspects messages and returns response."""
# Access the last user message
last_msg = messages[-1]
# Return custom response
return ModelResponse(parts=[TextPart('Custom response')])
agent = Agent(FunctionModel(custom_model))
result = agent.run_sync('Hello')
ツール呼び出しを伴う FunctionModel
from pydantic_ai import ToolCallPart, ModelResponse
from pydantic_ai.models.function import AgentInfo, FunctionModel
def model_with_tools(
messages: list[ModelMessage],
info: AgentInfo
) -> ModelResponse:
# First request: call a tool
if len(messages) == 1:
return ModelResponse(parts=[
ToolCallPart(
tool_name='get_data',
args='{"id": 123}'
)
])
# After tool response: return final result
return ModelResponse(parts=[TextPart('Done with tool result')])
agent = Agent(FunctionModel(model_with_tools))
@agent.tool_plain
def get_data(id: int) -> str:
return f"Data for {id}"
result = agent.run_sync('Get data')
VCR カセット(記録された API 呼び出し)
実際の LLM API とのやり取りを記録して再生します。
import pytest
@pytest.mark.vcr
def test_with_recorded_response():
"""Uses recorded cassette from tests/cassettes/"""
agent = Agent('openai:gpt-4o')
result = agent.run_sync('Hello')
assert 'hello' in result.output.lower()
# To record/update cassettes:
# uv run pytest --record-mode=rewrite tests/test_file.py
カセットファイルは YAML 形式で tests/cassettes/ に保存されます。
インラインスナップショット
自動更新されるスナップショットを使って、期待される出力を検証します。
from inline_snapshot import snapshot
def test_agent_output():
result = agent.run_sync('Hello', model=TestModel())
# First run: creates snapshot
# Subsequent runs: asserts against it
assert result.output == snapshot('expected output here')
# Update snapshots:
# uv run pytest --inline-snapshot=fix
ゲート:VCR カセットとインラインスナップショット
記録または修正を行うと、ディスク上のファイルが書き換えられます。この手順に従い、ステップを省略しないでください。
- 再生パス(記録/修正フラグなし): 対象のパスに対して
uv run pytestを実行します。すべて成功すること(または、失敗の原因が把握されており、更新対象の成果物とは無関係であること)。 - 対象範囲を確定: 更新する
tests/cassettes/内のカセット、またはsnapshot(...)アサーションを特定し、変更されるファイルがそれらだけであることを確認します。 - 記録または修正: 対象のパスに対して、範囲を限定したコマンドを1つだけ実行します。
uv run pytest --record-mode=rewrite …またはuv run pytest --inline-snapshot=fix …を実行します。 - 事後条件: 記録/修正フラグを付けずに同じテストをもう一度実行し、すべて成功することを確認します。
git diffを確認し、予期された.yaml/スナップショットの変更だけであることを確かめます。
ステップ 4 が失敗した場合は、意図しない差分を元に戻し、再記録する前にテストまたはモデルを修正してください。
ツールのテスト
from pydantic_ai import Agent, RunContext
from pydantic_ai.models.test import TestModel
def test_tool_is_called():
agent = Agent('openai:gpt-4o')
tool_called = False
@agent.tool_plain
def my_tool(x: int) -> str:
nonlocal tool_called
tool_called = True
return f"Result: {x}"
# Force TestModel to call the tool
result = agent.run_sync(
'Use my_tool',
model=TestModel(call_tools=['my_tool'])
)
assert tool_called
依存関係を使用したテスト
from dataclasses import dataclass
from unittest.mock import AsyncMock
@dataclass
class Deps:
api: ApiClient
def test_tool_with_deps():
# Create mock dependency
mock_api = AsyncMock()
mock_api.fetch.return_value = {'data': 'test'}
agent = Agent('openai:gpt-4o', deps_type=Deps)
@agent.tool
async def fetch_data(ctx: RunContext[Deps]) -> dict:
return await ctx.deps.api.fetch()
with agent.override(
model=TestModel(call_tools=['fetch_data']),
deps=Deps(api=mock_api)
):
result = agent.run_sync('Fetch data')
mock_api.fetch.assert_called_once()
メッセージの取得
実行時のすべてのメッセージを確認します。
from pydantic_ai import Agent, capture_run_messages
agent = Agent('openai:gpt-4o')
with capture_run_messages() as messages:
result = agent.run_sync('Hello', model=TestModel())
# Inspect captured messages
for msg in messages:
print(msg)
テストパターンのまとめ
| シナリオ | 方法 |
|---|---|
| API を使わないユニットテスト | TestModel() |
| カスタムモデルロジック | FunctionModel(func) |
| 記録された実際の応答 | @pytest.mark.vcr |
| 出力構造の検証 | inline_snapshot |
| ツールが呼び出されることのテスト | TestModel(call_tools=[...]) |
| 依存関係のモック | agent.override(deps=...) |
pytest の設定
一般的な pyproject.toml:
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto" # For async tests
テストを実行します。
uv run pytest tests/test_agent.py -v
uv run pytest --inline-snapshot=fix # Update snapshots
コメント
Markdown 対応