0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pydantic AI入門 2026 — FastAPI感覚で型安全なAIエージェントを作る完全ガイド

0
Posted at

Pydantic AI入門 2026 — FastAPI感覚で型安全なAIエージェントを作る完全ガイド

AIエージェントを作ろうとしたとき、最初に選ぶフレームワークによってその後の体験がかなり変わってくる、ということを最近しみじみ感じています。

LangChainを使ってみたけれど、なんか複雑すぎてしんどい。生のAPI呼び出しだと型安全じゃないし、構造化出力の取り扱いが面倒くさい。そういう悩みを持っている方に、今回は Pydantic AI を紹介したいと思います。

「FastAPIを知っているなら、すぐわかる。」そんなフレームワークです。


1. LangChainに感じた「もやもや」はあなただけじゃない

正直に言いましょう。LangChainは素晴らしいフレームワークです。エコシステムも大きいし、コミュニティも活発。でも実際に使い込んでいくと、いくつかの「もやもや」が蓄積していきます。

型安全じゃない。 LangChainのチェーンはランタイムまでエラーが発見できないケースが多い。IDEの補完もイマイチ効かない。

テストが重い。 エージェント全体をモックしないとユニットテストが書けない。ちょっとした動作確認のたびにAPIコールが発生してしまう。

モデル切り替えが大がかり。 OpenAIからAnthropicに変えたいと思ったとき、設定箇所が複数あって、「あれっ、どこ変えればいいんだっけ?」ってなる。

これはLangChainが悪いというより、「設計の思想の違い」なんだと思います。LangChainは汎用性と柔軟性を最優先にして設計されている。一方、Pydantic AIは 型安全とテスタビリティ を最優先にした設計になっています。

何を優先するかによって、適するフレームワークが変わる。それだけの話なんですが、2026年時点でPython開発者として型システムを活かした開発をしているなら、Pydantic AIは一度触ってみる価値があると思っています。


2. Pydantic AIって何? FastAPIを知っていれば即わかる

Pydantic AIは、Pydanticチームが開発したPythonエージェントフレームワークです。2024年末にリリースされ、2026年現在は本番運用で使えるレベルに成熟してきています。

「FastAPIのAI版」 と言うと、ピンとくる方が多いかもしれません。

FastAPIでは、エンドポイントに型付きパラメータとレスポンスモデルを宣言します。それだけでバリデーション、OpenAPI生成、IDEの補完が全部ついてくる。

Pydantic AIでは、エージェントに 型付き依存性出力型 を宣言します。それだけで構造化出力のバリデーション、モデル切り替え、テスト可能な設計が整います。

# FastAPIのアナロジーで理解する

# FastAPI: エンドポイントを宣言
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class UserResponse(BaseModel):
    name: str
    email: str

@app.get("/user/{user_id}", response_model=UserResponse)
def get_user(user_id: int) -> UserResponse:
    ...

# Pydantic AI: エージェントを宣言
from pydantic_ai import Agent
from pydantic import BaseModel

class AnalysisResult(BaseModel):
    sentiment: str
    confidence: float
    summary: str

agent = Agent(
    "openai:gpt-4o",
    output_type=AnalysisResult,
    system_prompt="テキストの感情分析を行うエージェントです。"
)

この対称性は偶然ではありません。FastAPIが「HTTPリクエストに対する型安全なレスポンス」を実現したように、Pydantic AIは「LLMへのリクエストに対する型安全なレスポンス」を実現しています。

Pydantic AIの核心概念:

  • Agent — コアオブジェクト。モデル名・出力型・システムプロンプトを持つ
  • Output Types — Pydanticモデルとして宣言した構造化出力型
  • Tools — エージェントが呼び出せる関数(型付きパラメータ)
  • Dependencies — 依存性注入でテスト可能な外部リソース(DB、APIクライアント等)

3. インストールと最初のエージェント

まずは環境を作りましょう。2026年3月時点の最新安定版は pydantic-ai >= 0.0.40 あたりです。

# pipでインストール
pip install pydantic-ai

# OpenAIを使う場合
pip install pydantic-ai[openai]

# Anthropicを使う場合
pip install pydantic-ai[anthropic]

# バージョン確認
python -c "import pydantic_ai; print(pydantic_ai.__version__)"

環境変数を設定します:

# .envファイルに追記、またはexportで設定
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."

最初のエージェントを動かしてみましょう:

# hello_agent.py
import asyncio
from pydantic_ai import Agent

# シンプルなテキスト出力エージェント
agent = Agent(
    "openai:gpt-4o-mini",
    system_prompt="あなたは親切なアシスタントです。日本語で応答してください。"
)

async def main():
    result = await agent.run("Pythonで型安全なコードを書くメリットを3つ教えてください。")
    print(result.output)
    print(f"\n--- 使用トークン: {result.usage()} ---")

asyncio.run(main())

これだけです。agent.run() に文字列を渡すと、LLMからのレスポンスが result.output に入ります。使用トークン数も簡単に確認できます。

同期版(非同期なしで手っ取り早く試したい場合):

# 同期版: run_sync()を使う
result = agent.run_sync("質問をここに")
print(result.output)

async/await に慣れていなくても run_sync() で始められます。


4. 型安全という快感 — 構造化出力の実装

ここからが本番です。Pydantic AIの最大の魅力は「LLMの出力を型安全に取得できる」点です。

例えば、ユーザーのレビューを分析して、感情・評価スコア・要約を取得したいとします。生のAPI呼び出しだとJSONをパースしてバリデーションする処理を自分で書く必要があります。Pydantic AIでは:

from pydantic import BaseModel, Field
from pydantic_ai import Agent

class ReviewAnalysis(BaseModel):
    """レビュー分析結果"""
    sentiment: str = Field(description="positive / negative / neutral のいずれか")
    score: float = Field(ge=0.0, le=5.0, description="評価スコア(0〜5)")
    summary: str = Field(max_length=200, description="100文字以内の要約")
    key_points: list[str] = Field(description="主要なポイント(最大5つ)")

# output_typeにPydanticモデルを渡すだけ
analysis_agent = Agent(
    "openai:gpt-4o",
    output_type=ReviewAnalysis,
    system_prompt=(
        "あなたは商品レビュー分析の専門家です。"
        "与えられたレビューを分析し、構造化された形式で返してください。"
    )
)

async def analyze_review(review_text: str) -> ReviewAnalysis:
    result = await analysis_agent.run(review_text)
    return result.output  # 型: ReviewAnalysis

# 使用例
async def main():
    review = """
    このコーヒーメーカーを1ヶ月使ってみました。
    朝の一杯がとても美味しく仕上がります。
    ただ、洗い方がちょっと面倒で、分解してから洗う必要があります。
    デザインはシンプルで気に入っています。全体的には満足しています。
    """
    
    analysis = await analyze_review(review)
    print(f"感情: {analysis.sentiment}")
    print(f"スコア: {analysis.score}/5.0")
    print(f"要約: {analysis.summary}")
    print(f"ポイント: {analysis.key_points}")

result.outputReviewAnalysis 型として返ってくるので、IDEの補完が効いて、mypy/pyrightでの型チェックも通ります。score0〜5 の範囲外の値を返した場合は、Pydanticのバリデーションが走って自動的に再試行されます。

これが型安全の「快感」です。実行してから気づくエラーが、書いている時点でわかる。

バリデーションの仕組みを少し掘り下げると...

Pydantic AIは内部でLLMの出力を受け取り、宣言した output_type に対してバリデーションを実行します。バリデーション失敗時は自動的にリトライし、エラー情報をLLMにフィードバックして修正を促します。このループがデフォルトで組み込まれているので、JSONパースのトライキャッチを自分で書く必要がありません。


5. ツールとDependency Injection — テスト可能な設計

Pydantic AIのもう一つの柱が「ツール」と「依存性注入」です。

ツールとは: エージェントが呼び出せる関数のこと。LangChainの「ツール」と同じ概念ですが、型付きデコレータで宣言するだけで済みます。

依存性注入とは: エージェントが必要とする外部リソース(データベース、HTTPクライアント等)を、実行時に注入できる仕組みです。本番用とテスト用を切り替えるのが非常に簡単になります。

実際のコードで見てみましょう。「商品在庫を確認してから購入可否を判断するエージェント」を作ります:

from dataclasses import dataclass
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext

# 依存性の型定義
@dataclass
class Deps:
    db_client: "InventoryDatabase"  # 実際のDBクライアント or テスト用Mock

class InventoryDatabase:
    """本番用データベースクライアント(例)"""
    async def check_stock(self, product_id: str) -> int:
        # 実際のDB照会
        return 42  # 在庫数を返す
    
    async def get_product_info(self, product_id: str) -> dict:
        return {"name": "コーヒーメーカー", "price": 12800}

# エージェントの宣言
class PurchaseDecision(BaseModel):
    can_purchase: bool
    reason: str
    stock_count: int
    recommendation: str

purchase_agent = Agent(
    "anthropic:claude-sonnet-4-20250514",
    deps_type=Deps,
    output_type=PurchaseDecision,
    system_prompt="在庫情報を元に購入可否を判断してください。"
)

# ツールの定義
@purchase_agent.tool
async def check_inventory(ctx: RunContext[Deps], product_id: str) -> dict:
    """指定商品の在庫数を確認する"""
    stock = await ctx.deps.db_client.check_stock(product_id)
    info = await ctx.deps.db_client.get_product_info(product_id)
    return {
        "stock_count": stock,
        "product_name": info["name"],
        "price": info["price"]
    }

# 実行
async def main():
    deps = Deps(db_client=InventoryDatabase())
    result = await purchase_agent.run(
        "商品ID: COFFEE-MAKER-001 は今すぐ購入できますか?",
        deps=deps
    )
    decision = result.output
    print(f"購入可否: {'可能' if decision.can_purchase else '不可'}")
    print(f"理由: {decision.reason}")
    print(f"在庫: {decision.stock_count}")

@purchase_agent.tool デコレータをつけるだけで、エージェントが自動的にツールとして認識します。ctx.deps を通じて依存性にアクセスできます。型は全部 Deps クラスから推論されるので、IDEが補完してくれます。

なぜ依存性注入がテストで嬉しいか:

本番では InventoryDatabase() を注入する。テストでは Mock を注入する。エージェントのコード自体は一切変えない。これだけで、APIコールなしのユニットテストが書けます。


6. TestModelで高速テスト — APIコール0のCI/CDを実現

Pydantic AIには TestModel という組み込みのテスト用モデルがあります。LLMのAPIを一切呼ばずに、エージェントの振る舞いをテストできます。

CI/CDを回すたびに実際のLLM APIを呼ぶのは、コストがかかるし、テストが非決定論的になるし、速度も遅い。TestModel はこの問題を解決します。

# test_purchase_agent.py
import pytest
from pydantic_ai.models.test import TestModel
from pydantic_ai import Agent

# テスト用のMock DB
@dataclass
class MockInventoryDatabase:
    stock_data: dict[str, int]
    
    async def check_stock(self, product_id: str) -> int:
        return self.stock_data.get(product_id, 0)
    
    async def get_product_info(self, product_id: str) -> dict:
        return {"name": "テスト商品", "price": 9999}

@pytest.mark.asyncio
async def test_purchase_agent_with_stock():
    """在庫あり時のテスト"""
    mock_db = MockInventoryDatabase(stock_data={"TEST-001": 10})
    mock_deps = Deps(db_client=mock_db)
    
    # TestModelを使うことでAPIコールなし
    with purchase_agent.override(model=TestModel()):
        result = await purchase_agent.run(
            "TEST-001 は購入できますか?",
            deps=mock_deps
        )
    
    # ツールが呼ばれたかを検証
    assert result.output is not None
    # TestModelはデフォルトで全ツールを1回呼び出す

@pytest.mark.asyncio
async def test_purchase_agent_no_stock():
    """在庫なし時のテスト"""
    mock_db = MockInventoryDatabase(stock_data={"TEST-002": 0})
    mock_deps = Deps(db_client=mock_db)
    
    with purchase_agent.override(model=TestModel()):
        result = await purchase_agent.run(
            "TEST-002 は購入できますか?",
            deps=mock_deps
        )
    
    # ツールの呼び出し履歴を検証
    for call in result.all_messages():
        print(call)  # どんな会話が発生したかトレース可能

purchase_agent.override(model=TestModel()) のコンテキストマネージャー内では、実際のLLM呼び出しが発生しません。ツールの呼び出しパターンや会話履歴をローコストで検証できます。

pytest-asyncioと組み合わせるとCI/CDがこう変わります:

# CI上でのテスト実行
# APIキーなしで実行可能
PYTHONPATH=. pytest tests/ -v

# 実行時間: 数秒 (APIコールなし)
# 費用: $0
# 再現性: 100%

これはかなり実務的に嬉しい変化です。「テストを書くたびにAPIを消費する」ストレスから解放されます。


7. モデルを1行で切り替える & 本番運用のヒント

Pydantic AIのモデル指定は文字列で行うため、切り替えが非常に簡単です。

# 開発時: 安いモデルで動作確認
agent = Agent("openai:gpt-4o-mini", output_type=MyOutput)

# 本番: 性能重視
agent = Agent("openai:gpt-4o", output_type=MyOutput)

# コスト最適化したい
agent = Agent("anthropic:claude-haiku-4-20250514", output_type=MyOutput)

# ローカルモデル (Ollama)
agent = Agent("ollama:llama3.3", output_type=MyOutput)

# フォールバック設定
from pydantic_ai.models.fallback import FallbackModel

fallback_model = FallbackModel(
    "openai:gpt-4o",          # プライマリ
    "anthropic:claude-sonnet-4-20250514"  # フォールバック
)
agent = Agent(fallback_model, output_type=MyOutput)

環境変数でモデルを切り替えるパターン:

import os
from pydantic_ai import Agent

MODEL = os.getenv("AI_MODEL", "openai:gpt-4o-mini")
agent = Agent(MODEL, output_type=MyOutput)

本番とステージングでモデルを変えたいとき、コードを一切変えずに環境変数だけで制御できます。

本番運用のヒント:

① Pydantic Logfireでトレーシング

import logfire

logfire.configure()
logfire.instrument_pydantic_ai()

# 以降、エージェントの全実行が自動的にトレース記録される
agent = Agent("openai:gpt-4o", output_type=MyOutput)

どのプロンプトがどれだけのトークンを使ったか、ツールの実行時間、エラー率などをダッシュボードで確認できます。

② リトライ設定のカスタマイズ

from pydantic_ai import Agent
from pydantic_ai.settings import ModelSettings

agent = Agent(
    "openai:gpt-4o",
    output_type=MyOutput,
    model_settings=ModelSettings(
        max_tokens=2000,
        temperature=0.7,
    ),
    retries=3  # バリデーション失敗時のリトライ回数
)

③ 非同期処理の最適化

import asyncio
from pydantic_ai import Agent

agent = Agent("openai:gpt-4o-mini", output_type=MyOutput)

# 複数のリクエストを並列処理
async def batch_process(queries: list[str]) -> list[MyOutput]:
    tasks = [agent.run(q) for q in queries]
    results = await asyncio.gather(*tasks)
    return [r.output for r in results]

# 10件のリクエストを並列実行(順番にやる10倍速い)
outputs = await batch_process(queries)

asyncio.gather() との組み合わせで、複数のエージェント実行を並列化できます。API呼び出しが I/O バウンドなので、非同期による恩恵が大きい。


8. まとめ: 型安全なAIエージェント開発の新しい標準へ

Pydantic AIを使ってみると、「なんでこれ最初からなかったんやろ」という感覚になります。

型安全。テスタブル。モデル非依存。依存性注入。Observable。これ全部、普通のサーバーサイド開発ではとっくに当たり前になっていたことです。AIエージェントの世界でも同じことが当たり前になる時代が来た、ということかもしれません。

LangChainと比べてPydantic AIが明らかに優れているのは 型安全性とテスタビリティ の部分です。ただ、LangChainの方がエコシステムが大きく、実装済みのインテグレーションは豊富です。どちらを選ぶかは目的次第。

  • 型安全さとテスタビリティを重視するなら → Pydantic AI
  • 既存の豊富なコネクタ・インテグレーションが必要なら → LangChain/LangGraph
  • グラフベースの複雑なマルチエージェントなら → LangGraph

今後のPydantic AIのロードマップには、マルチエージェント協調とより高度なグラフベースのワークフローが含まれています。FastAPIがWebフレームワークのデファクトになったように、Pydantic AIがAIエージェントフレームワークのデファクトになる日が来るかもしれません。

まずは pip install pydantic-ai して、このガイドのコードを動かしてみてください。型安全なエージェント開発の感覚を、手で触れて理解してみることをおすすめします。


参考資料

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?