---
title: "Pydantic AIとは？v2の使い方とツール・依存性注入・テストを実行済みコードで解説"
url: "https://www.issoh.co.jp/tech/details/6743/"
published: 2025-05-14
updated: 2026-10-30
categories: ["AIエージェント・MCP"]
publisher: "株式会社一創"
---

# Pydantic AIとは？v2の使い方とツール・依存性注入・テストを実行済みコードで解説

Pydantic AI（パッケージ名 `pydantic-ai`）は、データ検証ライブラリPydanticの開発チームが作っているPython製のAIエージェントフレームワークです。LLMの出力やツールの引数をPydanticの型で検証し、検証に失敗したらモデルに直させる。この仕組みを、FastAPIと同じ「型ヒント＋依存性注入」の書き方で組めます。2026年6月23日に安定版のv2.0.0が出て、10月28日時点の最新は2.63.0です。動かすにはPython 3.11以上が必要です。掲載したコードの主要な処理は、2.63.0でLLMを `TestModel`・`FunctionModel` に、HTTP通信をモックに置き換えて動作を確認しています（実際のプロバイダAPIへの接続と回答の質は確認の範囲外です）。v1や2024年頃の解説から書き方が変わった箇所も示します。

## まとめ：Pydantic AIの要点

- **正体**：Pydanticチーム製の型安全なエージェントフレームワーク。現行はv2系で、2.55.0（2026年10月9日）からPython 3.11以上が必須です。
- **インストール**：`pip install pydantic-ai`。v2の既定に入るのはOpenAI・Anthropic・Googleなどで、Groq・Bedrock・Mistralはextrasで追加します。
- **最小構成**：`Agent("openai:gpt-6.1-sol", instructions=...)` を作り、`run_sync` で実行して `result.output` を読みます。v2の `openai:` はResponses APIを使います。
- **ツールと依存**：`@agent.tool`（RunContextを受け取る）と `@agent.tool_plain` で登録し、`deps_type` と `ctx.deps` で外部リソースを渡します。
- **再試行の上限**：ツール内で `ModelRetry` を投げるとモデルがやり直します。既定の上限は1回で、超えると `UnexpectedModelBehavior` になります。
- **テスト**：`TestModel`・`FunctionModel` と `agent.override()` を使えば、LLMを呼ばずにツールの呼ばれ方をCIで確かめられます。
- **向かない場面**：Python以外のチーム、グラフ状態の保存・可視化をフレームワークに任せたい案件、頻繁な更新に依存の固定で付き合えない運用です。

## Pydantic AIの位置づけ：開発元・v2・対応Python

Pydantic AIは、LLMを呼ぶ処理を「型の付いたPython関数の集まり」として組むためのフレームワークです。モデルとの往復、ツール呼び出しの実行ループ、出力の検証と再試行を受け持ち、開発者は関数と型を書きます。公式のリポジトリは [GitHub の pydantic/pydantic-ai](https://github.com/pydantic/pydantic-ai) で、ライセンスはMITです。

### Pydantic本体との関係

Pydantic本体（`pydantic`）がデータ構造の定義と検証を担い、Pydantic AIはその上に載る別パッケージです。ツールの引数スキーマも出力の型も `BaseModel` や型ヒントから作られるため、既存のPydanticモデルをそのまま出力型に使えます。BaseModelやバリデーションの基礎は[Pydanticの読み方・BaseModelの使い方とV1との違い](/tech/details/4838/)で解説しています。手元の実測環境では、`pydantic-ai-slim` 2.63.0と一緒に `pydantic` 2.14.2が入りました。

### v2で変わった点とv1の扱い

[公式のバージョンポリシー](https://github.com/pydantic/pydantic-ai/blob/v2.63.0/docs/version-policy.md)によれば、v1は2025年9月、v2.0は2026年6月23日に出ました。マイナー版で意図的な破壊的変更はしない方針で、v1にはv2安定版のリリースから少なくとも6か月はセキュリティ修正を出すとしています。v1系の更新は今も続いていて、10月21日の[1.107.9](https://github.com/pydantic/pydantic-ai/releases/tag/v1.107.9)では、`web_fetch` ツールのHTML変換に関する深刻度Highの脆弱性（GHSA-4whf-38hw-pxxh）が修正されました。v2系では2.59.0で修正済みです。PyPIの記録では、v2系は2.54.0（10月3日）から2.63.0（10月28日）までの25日間に10回リリースされています。

v1や古い解説のコードを動かすときにつまずきやすいのは、次の変更です。いずれも[公式のUpgrade Guide](https://github.com/pydantic/pydantic-ai/blob/v2.63.0/docs/changelog.md)に載っていて、表の「2.63.0での挙動」は手元で実行して確かめた結果です。

| v1・旧解説の書き方                  | v2の書き方                               | 2.63.0での挙動           |
| --------------------------- | ------------------------------------ | -------------------- |
| `Agent("gpt-5")`            | `Agent("openai:gpt-5")`              | UserError            |
| `openai:` ＝Chat Completions | 同じ接頭辞がResponses API                  | OpenAIResponsesModel |
| Chat Completionsを使い続ける      | `openai-chat:`                       | OpenAIChatModel      |
| `history_processors=`       | `capabilities=[ProcessHistory(...)]` | TypeError            |
| `result_type=`              | `output_type=`                       | TypeError            |
| `mcp_servers=`              | `toolsets=`                          | 引数なし                 |
| `agent.to_a2a()`            | 上流の `fasta2a`                        | メソッドなし               |
| `grok:` 接頭辞                 | `xai:`                               | 改称                   |

挙動が黙って変わる点もあります。ひとつは `end_strategy` の既定で、`'early'` から `'graceful'` になりました。モデルが出力ツールと同じ応答で別のツールを呼んだとき、v1では後者を飛ばしていましたが、v2では実行します。メール送信のような副作用を持つツールがあるなら、移行時に確認してください。もうひとつ、2.60.0（2026年10月23日）では再試行メッセージの形が変わりました。ツールへのやり直し指示は `RetryPromptPart` から、`outcome='retried'` の付いた `ToolReturnPart` に変わりました。会話履歴を `isinstance` で調べているコードは、そのままでは一致しなくなります。

## インストール：pydantic-ai と pydantic-ai-slim の使い分け

配布は全部入りの `pydantic-ai` と、必要な部分だけをextrasで足す `pydantic-ai-slim` の2つです。v2では全部入りの中身が絞られ、`pip install pydantic-ai` で入るのは `pydantic-ai-slim[openai,anthropic,google,cli,mcp,evals,web,retries,logfire]` になりました。Groq・Bedrock・Mistral・Cohereなどは既定から外れたので、使うものを明示して入れます。

```
pip install pydantic-ai                       # 既定のextras一式
pip install "pydantic-ai-slim[openai]"        # OpenAIだけ使う最小構成
pip install "pydantic-ai[groq,bedrock]"       # 既定から外れたプロバイダを足す
python -c "import pydantic_ai; print(pydantic_ai.__version__)"
```

Python 3.10の環境では、エラーにならずに古い版が入る点に注意してください。[2.55.0のリリースノート](https://github.com/pydantic/pydantic-ai/releases/tag/v2.55.0)には「Python 3.10でインストールすると2.54.0以前に解決される」と書かれています。この記事のコードが動かない、ドキュメントにある引数が無いというときは、最初に `__version__` を確かめます。Python 3.10自体も[2026年10月にサポート終了](https://devguide.python.org/versions/)を迎えるので、3.11以上の仮想環境を作り直すのが確実です。

## 最小構成のエージェント：作成と実行

エージェントは `Agent` を1つ作り、同期の `run_sync`、非同期の `run`、ストリーミングの `run_stream` のどれかで実行します。モデルは「プロバイダ:モデル名」の文字列で指定し、結果は `result.output` で受け取ります。APIキーは `OPENAI_API_KEY` などの環境変数から読まれます。

```
from pydantic_ai import Agent

agent = Agent(
    "openai:gpt-6.1-sol",              # 「プロバイダ:モデル名」。v2の openai: は Responses API
    instructions="1文で簡潔に答えて。",
)
result = agent.run_sync("Pydantic AIとは何ですか？")
print(result.output)                   # 文字列の回答（.data は存在しない）
```

`OpenAIAgent` や `LocalAgent` のような、プロバイダごとのクラスはありません。どのプロバイダでも同じ `Agent` に文字列かモデルオブジェクトを渡します。プロバイダ名を省いた `Agent("gpt-5")` は、2.63.0では `UserError: Unknown model: gpt-5. Did you mean 'openai-chat:gpt-5'?` で止まりました。なお2.63.0は、観測の設定が無いまま実行すると標準エラーにLogfireの案内バナーを出します。環境変数 `PYDANTIC_AI_NO_BANNER=1` を設定すると表示されません。

## ツールの定義とツール呼び出し

ツールは、LLMが必要に応じて呼ぶPython関数です。Pydantic AIは関数のシグネチャとdocstringからJSONスキーマを作ってモデルに渡し、モデルが返した引数を型で検証してから関数を実行します。モデル側の仕組みは[Function Callingの仕組みとOpenAI・Claude・Geminiの実装](/tech/details/4631/)と同じで、Pydantic AIはその往復と検証を受け持ちます。

### @agent.tool と @agent.tool\_plain の違い

違いは、第1引数で実行時の文脈（`RunContext`）を受け取るかどうかだけです。依存や会話の情報を使うなら `@agent.tool`、入力だけで完結する関数なら `@agent.tool_plain` を使います。素の `@tool` デコレーターはありません。

```
import random
from pydantic_ai import Agent, ModelRetry, RunContext

agent = Agent("openai:gpt-6.1-sol", deps_type=str)

@agent.tool_plain                      # 文脈を受け取らない関数
def roll_dice(sides: int = 6) -> str:
    """サイコロを1回振る。

    Args:
        sides: 面の数
    """
    if sides < 1:                      # 型は合っても値が不正なら、モデルに直させる
        raise ModelRetry("面の数は1以上にしてください。")
    return str(random.randint(1, sides))

@agent.tool                            # 第1引数で RunContext を受け取る
def get_player_name(ctx: RunContext[str]) -> str:
    """プレイヤー名を返す。"""
    return ctx.deps                    # 実行時に渡した deps

result = agent.run_sync("サイコロを振って、出た目とプレイヤー名を教えて", deps="Anne")
print(result.output)
```

### docstringから生成されるツール・引数の説明

ツールの説明文には、モデルが呼び出しを判断できるよう、用途や使用条件を記述します。上の `roll_dice` を2.63.0で登録し、モデルに渡るスキーマを取り出すと次のようになりました。docstringの1行目がツールの説明に、`Args:` 節の各行が引数の `description` に入り、既定値もスキーマに反映されます。

```
roll_dice: サイコロを1回振る。
{'additionalProperties': False,
 'properties': {'sides': {'default': 6, 'description': '面の数', 'type': 'integer'}},
 'type': 'object'}
```

docstringを書かないと、引数の説明が空のままモデルに渡ります。「どの単位で」「どの形式で」渡すべきかが分かれる引数には、`Args:` 節を必ず書いてください。

### tools= 引数によるツールの一括登録

デコレーターを使わず、関数のリストを `Agent` の `tools=` に渡しても登録できます。第1引数が `RunContext` かどうかは自動で判別されるので、上の2関数を混ぜて渡して構いません。ツールを別モジュールに置いて複数のエージェントで共有したいときに向きます。

```
agent = Agent("openai:gpt-6.1-sol", deps_type=str, tools=[roll_dice, get_player_name])
```

## 依存性注入：deps\_type と RunContext でリソースを渡す

DB接続、APIキー、HTTPクライアントのように、ツールが使うリソースは依存（deps）として実行時に渡します。グローバル変数に置かないので、テストでモックに差し替えやすくなります。手順は3つです。`Agent(..., deps_type=MyDeps)` で型を宣言し、ツールの第1引数を `RunContext[MyDeps]` にし、実行時に `deps=` で実体を渡します。

```
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext

@dataclass
class MyDeps:                          # ツールが使うリソースを型で束ねる
    api_key: str
    http_client: httpx.AsyncClient

agent = Agent("openai:gpt-6.1-sol", deps_type=MyDeps)

@agent.tool
async def fetch_profile(ctx: RunContext[MyDeps], user_id: str) -> str:
    """ユーザーのプロフィールをJSONで返す。"""
    r = await ctx.deps.http_client.get(
        "https://api.example.com/users",
        params={"id": user_id},
        headers={"Authorization": f"Bearer {ctx.deps.api_key}"},
    )
    r.raise_for_status()
    return r.text

async def main():
    async with httpx.AsyncClient() as client:
        deps = MyDeps(api_key="sk-...", http_client=client)
        result = await agent.run("ユーザー42を要約して", deps=deps)
        print(result.output)

if __name__ == "__main__":             # 接続先URLとAPIキーは実環境の値に置き換える
    import asyncio
    asyncio.run(main())
```

引数に型を書くだけで一致するインスタンスが自動で注入される「Contextクラス」は、Pydantic AIにはありません。その解決に失敗すると出るという `NoMatchingContextError` も存在しない例外です。依存は必ず `deps_type` と `deps=` と `ctx.deps` の3点で受け渡します。`RunContext` からは依存のほか、使用量（`ctx.usage`）や再試行の回数（`ctx.retry`）も読めます。

## 構造化出力とModelRetryによる再試行

回答を文字列で受け取って後段でパースすると、形式の崩れで処理が止まりがちです。`output_type` にPydanticモデルを渡すと、検証を通った型付きのオブジェクトが `result.output` に入ります。検証に失敗したときはエラー内容がモデルに返され、作り直しを求めます。

```
from pydantic import BaseModel
from pydantic_ai import Agent

class CityLocation(BaseModel):
    city: str
    country: str

agent = Agent("openai:gpt-6.1-sol", output_type=CityLocation)
result = agent.run_sync("2012年のオリンピック開催地は？")
print(result.output.city)              # 型付きの属性として読める
```

業務ロジックのエラーをモデルへの指示に変えたいときは、ツールの中で `ModelRetry` を投げます。存在しない注文IDを渡されたら、例外で止めずに「IDを確認して」と返してやり直させる使い方です。

```
from dataclasses import dataclass
from pydantic_ai import Agent, ModelRetry, RunContext

@dataclass
class Deps:
    db: dict[int, str]                 # 実際はDBハンドル

agent = Agent("openai:gpt-6.1-sol", deps_type=Deps)

@agent.tool(retries=2)                 # このツールだけ再試行を2回まで許す
def lookup_order(ctx: RunContext[Deps], order_id: int) -> str:
    """注文IDから注文内容を返す。"""
    row = ctx.deps.db.get(order_id)
    if row is None:
        raise ModelRetry(f"注文 {order_id} は存在しません。IDを確認してください。")
    return row
```

上限の扱いは実行して確かめました。モデル役の `FunctionModel` に、1回目は存在しないID（999）、2回目は正しいID（1）を渡させると、エラー文がツールの戻り値としてモデルに届き、2回目で正しい注文内容が返りました。一方、`retries` を指定せずに失敗し続けさせると、2回目の失敗で `UnexpectedModelBehavior: Tool 'lookup_order' exceeded max retries count of 1` が出て止まりました。既定の上限は1回なので、モデルが一度で直せない可能性がある検索系のツールには `retries` を明示します。エージェント全体の既定は `Agent(..., retries=...)` で変えられます。

## モデルの切り替えとPydantic AI Gateway

ツールや依存のコードはモデルに縛られないので、モデル文字列を差し替えるだけで別のプロバイダで動きます。開発中は安いモデル、本番は精度の高いモデルという使い分けも、コードの他の部分を変えずにできます。

### モデル文字列の書き方と対応プロバイダ

```
from pydantic_ai import Agent

agent = Agent("openai:gpt-6.1-sol")            # OpenAI（Responses API）
agent = Agent("openai-chat:gpt-6.1-sol")       # OpenAI（Chat Completions API）
agent = Agent("anthropic:claude-sonnet-5-5")   # Anthropic（要 anthropic extras）
agent = Agent("google:gemini-3.6-flash")       # Google（3.5 Flashの移行先）
```

2.63.0の補完用の型 `KnownModelName` には780個のモデル名があり、接頭辞はOpenAI・Anthropic・Google・Bedrock・Groq・Mistral・Cohere・xAI・DeepSeekなど28種類（`gateway/` 付きを含む）でした。この一覧はマイナー版でも増減する、とバージョンポリシーに明記されています。モデルIDは各社の更新が速いので、記事のIDは書いた時点の例として読み、実装時は各プロバイダの最新一覧で確認してください。文章を生成せず判断だけを返すモデルにも対応していて、その一例は[TypeSafe Jevの料金・API実装・採用条件](/tech/details/18238/)で扱っています。

### Pydantic AI GatewayによるAPIキーの統合

Pydantic AI Gatewayは、フレームワークとは別に提供されるモデルのゲートウェイです。[公式ドキュメント](https://github.com/pydantic/pydantic-ai/blob/v2.63.0/docs/gateway.md)によれば、1本のキーでOpenAI・Anthropic・Google Vertex・Groq・AWS Bedrockにアクセスでき、利用上限をプロジェクト・ユーザー・キーの単位で日次・週次・月次に設定できます。プロバイダの自前キーを持ち込む方式（BYOK）と、推論料金をGatewayに払う方式を選べます。[Pydanticの料金ページ](https://pydantic.dev/pricing)では、BYOKの上乗せは全プランで0%、Gatewayが用意するプロバイダを使う場合の上乗せはPersonal・Teamプランで5%、Growth以上で3%です。PersonalとTeamで登録できるBYOKのキーは3件までです。リクエストは共通形式に変換されず、各プロバイダの形式のまま通ります。

```
export PYDANTIC_AI_GATEWAY_API_KEY="pylf_v1_us_..."   # Logfireで発行したキー
```

```
from pydantic_ai import Agent

agent = Agent("gateway/openai:gpt-6.1-sol")   # 頭に gateway/ を付けるだけ
print(agent.run_sync("こんにちは").output)
```

アカウント作成時にUSかEUのリージョンを選びます。キーに `us` を含むダミー値で接続先を確かめると、`https://gateway-us.pydantic.dev/proxy/openai/` になりました。複数プロバイダの費用と障害時の切り替えを1か所で管理したいチーム向けで、1社のモデルだけを使う段階ならプロバイダに直接つなぐ構成で足ります。

## 会話履歴・ストリーミング・観測

会話を続けるには、前回の結果のメッセージを `message_history` に渡します。履歴はアプリ側で保持して渡すもので、Pydantic AIが暗黙に覚えておくわけではありません。履歴が長くなったら、v2では `capabilities` に `ProcessHistory` を入れてモデルへ送る分を絞ります（v1の `history_processors=` は2.63.0では `TypeError` になりました）。

```
from pydantic_ai import Agent
from pydantic_ai.capabilities import ProcessHistory
from pydantic_ai.messages import ModelMessage

def keep_recent(messages: list[ModelMessage]) -> list[ModelMessage]:
    return messages[-3:]               # モデルへ送るのは直近3件だけ

agent = Agent("openai:gpt-6.1-sol", capabilities=[ProcessHistory(keep_recent)])
r1 = agent.run_sync("私の名前はアリスです。")
r2 = agent.run_sync("私の名前は？", message_history=r1.all_messages())
print(r2.output)
```

5往復させて、モデルに届いたメッセージ数を `FunctionModel` で数えました。`ProcessHistory` なしでは1・3・5・7・9件と増え続け、ありでは1・3・3・3・3件で止まりました。`all_messages()` で受け取る履歴も、なしの10件に対してありは4件で、処理後の内容に置き換わっています。元の履歴を残しておきたいなら、アプリ側で別に保存してください。切り方にも注意が要ります。プロバイダはツール呼び出しと戻り値の組が崩れた履歴を拒否するため、[公式ドキュメント](https://github.com/pydantic/pydantic-ai/blob/v2.63.0/docs/message-history.md)によれば、Pydantic AIは送信前に履歴を自動で修復します。呼び出しの無い戻り値は削除されるので、戻り値だけが残る位置で切ると、その結果はモデルに届きません。

チャット画面のように逐次表示したいときは `run_stream` を使います。`stream_text(delta=True)` にすると差分だけが流れてきます。

次のコードはasync関数内、またはトップレベルのawaitに対応したNotebookで実行します。

```
async with agent.run_stream("長い物語を書いて") as response:
    async for text in response.stream_text(delta=True):
        print(text, end="")
```

観測は同じ会社のPydantic Logfireと統合されていて、2行の計装でエージェントの実行・モデルへの要求・ツール呼び出しがOpenTelemetryのトレースになります。v2の既定の計装形式はバージョン5で、エージェント実行のスパンはトークン数を `gen_ai.aggregated_usage.*` に記録します。Logfire以外のOpenTelemetry対応バックエンドにも送れます。出力の質を継続的に測るには、同じリポジトリの `pydantic-evals`（2.63.0）を使います。[評価のドキュメント](https://github.com/pydantic/pydantic-ai/blob/v2.63.0/docs/evals.md)によれば、データセットと評価器をPythonのコードで定義し、最終出力やツール呼び出しの経路を、用意したデータか本番のトラフィックに対して評価できます。

```
import logfire

logfire.configure()
logfire.instrument_pydantic_ai()       # 全エージェントの実行・モデル要求・ツール呼び出しを計装
```

## TestModelとFunctionModelによるLLM不要のテスト

エージェントのテストで本物のLLMを呼ぶと、料金がかかり、結果も毎回変わります。Pydantic AIには、モデルの代わりに決まった応答を返す `TestModel` と `FunctionModel` があり、[公式のテストガイド](https://github.com/pydantic/pydantic-ai/blob/v2.63.0/docs/testing.md)は次の3点の組み合わせを勧めています。

- `models.ALLOW_MODEL_REQUESTS = False` で、本物のモデルへの送信をテスト全体で禁止する
- `agent.override(model=...)` で、アプリのコードを変えずにモデルだけ差し替える
- `TestModel` で全ツールを呼ばせるか、`FunctionModel` で応答を台本どおりに返す

テスト対象のモジュールを `myapp/agents.py` として用意します。前の章のサイコロと注文検索のエージェントを1ファイルにまとめたものです。

```
import random
from dataclasses import dataclass
from pydantic_ai import Agent, ModelRetry, RunContext

# defer_model_check=True：APIキーの確認を実行時まで遅らせ、キーの無いCIでもimportできるようにする
agent = Agent("openai:gpt-6.1-sol", deps_type=str, defer_model_check=True)

@agent.tool_plain
def roll_dice(sides: int = 6) -> str:
    """サイコロを1回振る。

    Args:
        sides: 面の数
    """
    if sides < 1:
        raise ModelRetry("面の数は1以上にしてください。")
    return str(random.randint(1, sides))

@agent.tool
def get_player_name(ctx: RunContext[str]) -> str:
    """プレイヤー名を返す。"""
    return ctx.deps

@dataclass
class Deps:
    db: dict[int, str]

order_agent = Agent("openai:gpt-6.1-sol", deps_type=Deps, defer_model_check=True)

@order_agent.tool(retries=2)
def lookup_order(ctx: RunContext[Deps], order_id: int) -> str:
    """注文IDから注文内容を返す。"""
    row = ctx.deps.db.get(order_id)
    if row is None:
        raise ModelRetry(f"注文 {order_id} は存在しません。IDを確認してください。")
    return row
```

テストは次のとおりです。2件目は、1回目のツール結果が再試行（`outcome='retried'`）で、2回目が正しい注文内容であることまで確かめます。

```
from pydantic_ai import models
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart, ToolCallPart, ToolReturnPart
from pydantic_ai.models.function import AgentInfo, FunctionModel
from pydantic_ai.models.test import TestModel

from myapp.agents import Deps, agent, order_agent

models.ALLOW_MODEL_REQUESTS = False    # 本物のLLMへ送ろうとすると RuntimeError

def test_tools_are_called():
    with agent.override(model=TestModel()):         # 全ツールを呼び、戻り値をJSONで返す
        result = agent.run_sync("サイコロを振って", deps="Anne")
    assert '"get_player_name":"Anne"' in result.output

def test_unknown_order_is_retried():
    def script(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
        n = len(messages)                  # 1回目=依頼のみ、以降は往復ごとに2件ずつ増える
        if n == 1:
            return ModelResponse(parts=[ToolCallPart("lookup_order", {"order_id": 999})])
        if n == 3:
            return ModelResponse(parts=[ToolCallPart("lookup_order", {"order_id": 1})])
        return ModelResponse(parts=[TextPart("完了")])
    with order_agent.override(model=FunctionModel(script)):
        result = order_agent.run_sync("注文を調べて", deps=Deps(db={1: "商品A"}))
    returns = [
        part
        for message in result.all_messages()
        for part in message.parts
        if isinstance(part, ToolReturnPart) and part.tool_name == "lookup_order"
    ]
    assert result.output == "完了"
    assert [r.outcome for r in returns] == ["retried", "success"]
    assert "注文 999 は存在しません" in str(returns[0].content)
    assert returns[1].content == "商品A"
```

2.63.0で実行すると、`TestModel` の出力は `{"roll_dice":"4","get_player_name":"Anne"}` のように各ツールの戻り値を並べたJSONになりました（サイコロの目は毎回変わります）。`ALLOW_MODEL_REQUESTS = False` のまま `openai:` のエージェントを実行すると、`RuntimeError: Model requests are not allowed, since ALLOW_MODEL_REQUESTS is False` で止まります。上のテスト2件は、`OPENAI_API_KEY` を設定しない環境のpytestで2件とも通りました。

CIで1つ注意が要ります。`Agent("openai:...")` は作った時点でプロバイダを組み立てるため、`OPENAI_API_KEY` が無い環境ではエージェントを定義したモジュールのimportで `UserError`（環境変数 `OPENAI_API_KEY` の設定を求めるメッセージ）が出て、テストの収集段階で止まりました。上の `myapp/agents.py` が `defer_model_check=True` を付けているのはこのためで、確認が実行時まで遅れます。もう1つの方法は、CIにだけダミーの値を設定することです。`defer_model_check=True` にしたエージェントは、キーが無くても `override` の中では動き、外で実行したときに初めて同じ `UserError` になりました。

## 本番運用で使う機能：capabilities・使用量の上限・耐障害実行・MCP

v2で中心になった概念が `capabilities` です。ツール・フック・指示・モデル設定をひとまとめにした部品で、Agentに `capabilities=[...]` で渡します。上の `ProcessHistory` もその1つで、Web検索・Webフェッチ・プロンプトキャッシュ・思考モードの設定なども同じ形で足します。v1でAgentの引数だった `instrument=`・`prepare_tools=`・`history_processors=` は、すべてここへ移りました。

費用の暴走を止めるのは `UsageLimits` です。何も指定しなくても、モデルへの要求は1回の実行あたり50回が上限になっています。ツールを呼び続けるモデルを `FunctionModel` で再現すると、51回目の手前で `UsageLimitExceeded: The next request would exceed the request_limit of 50` が出て止まりました。業務では、要求回数のほかにツール呼び出し回数・トークン数・費用（`cost_limit`）でも上限を設定できます。

```
from pydantic_ai import UsageLimits

result = agent.run_sync(
    "今月の注文を集計して",
    deps=deps,
    usage_limits=UsageLimits(request_limit=10, tool_calls_limit=20),
)
```

処理が途中で落ちても再開させたい長時間の実行には、[耐障害実行（durable execution）](https://github.com/pydantic/pydantic-ai/blob/v2.63.0/docs/durable%5Fexecution/overview.md)の統合を使います。2.63.0のドキュメントにはTemporal・DBOS・Prefect・Restate・AWS Lambda・Absurd・pgtask・Apache Airflowの8エンジンが並び、1つのエージェントに使えるエンジンは1つです。外部ツールの接続はMCPに対応していて、v2ではMCPサーバーを `toolsets=` に渡します。サーバー側の作り方は[MCPサーバーの作り方とAIエージェントへの接続手順](/tech/details/15365/)を参照してください。A2Aの組み込みは上流の `fasta2a` パッケージへ移り、AG-UIは `AGUIAdapter` で扱います。

## 採用判断：Pydantic AIが向く場面と向かない場面

Pydantic AIを選ぶ決め手は、LLMの出力を後段のPythonコードがそのまま型付きで使えることです。問い合わせの分類結果をDBに書く、請求書から抜き出した項目を基幹システムへ渡す、といった「LLMの答えを次の処理の入力にする」業務では、`output_type` と `ModelRetry` が検証と再試行を受け持つので、パース失敗の処理を自前で書かずに済みます。FastAPIとPydanticで作ったバックエンドがあるチームなら、既存のモデルがそのまま出力型になります。

次の条件に当たるなら、別の選択肢を先に検討してください。

- **アプリの主言語がTypeScript**：Pydantic AIはPython専用です。バックエンドをPythonに寄せる予定が無いなら候補から外れます。
- **グラフの状態をフレームワークに保存・可視化させたい**：v2では `pydantic_graph.persistence` と単体のMermaid生成が削除されました。図示には `Graph.render()`、エージェントの実行状態の保存・再開には別パッケージPydantic AI Harnessの `StepPersistence` が用意されていますが、グラフの状態そのものは保存されません。グラフ状態をチェックポイントとして管理したい場合は、LangGraphを先に検討してください。違いは[LangChainとLangGraphの違いと使い分け](/tech/details/4059/)で比べています。
- **依存のバージョンを固定できない運用**：v2系は25日で10回のマイナー版が出ています。破壊的変更はしない方針でも、既定値や警告は増えます。`pydantic-ai-slim==2.63.0` のように版を固定し、更新時にテストを回す体制が前提です。
- **役割分担型のマルチエージェントを設定中心で組みたい**：Pydantic AIはツールから別のエージェントを呼ぶ「委譲」をコードで書く設計です。役割とタスクの定義を中心に組む方式なら[CrewAIの仕組みとLangGraphとの違い](/tech/details/13598/)も比べてください。

社内データとつないだエージェントの設計から実装・運用まで外部に任せたい場合は、一創の[AIエンジン開発](/service/ai/recommend/)でも相談を受けています。

## よくあるエラーと原因・対処

2.63.0で実際に出した例外と、ソースのdocstringに書かれた発生条件をまとめました。例外の多くは `AgentRunError` の子クラスで、プロバイダのAPIで起きた失敗は `ModelAPIError` でまとめて捕まえられます。

| 状況             | 例外                       | 対処                        |
| -------------- | ------------------------ | ------------------------- |
| プロバイダ名を省いたモデル名 | UserError                | `openai:` などを付ける          |
| v1の引数を渡した      | TypeError                | Upgrade Guideで新しい名前へ      |
| ツールの再試行が上限を超えた | UnexpectedModelBehavior  | `retries` を上げる・説明を直す      |
| 要求回数が上限50を超えた  | UsageLimitExceeded       | ループの原因を探す・上限を調整           |
| 429（レート制限）     | ModelRateLimitError      | `retry_after` を見て待つ       |
| 401・403（認証）    | ModelAuthenticationError | APIキーとモデルの権限を確認           |
| その他の4xx・5xx    | ModelHTTPError           | ステータスと本文を確認               |
| テストで本物のモデルを呼んだ | RuntimeError             | `override` の範囲を見直す        |
| APIキーの環境変数が無い  | UserError                | キーを設定・`defer_model_check` |

429は `ModelRateLimitError` であると同時に `ModelHTTPError` のインスタンスでもあるので、`except ModelHTTPError` と書いた既存のコードでも捕まります。プロバイダから明示的に「残高や割り当てが尽きた」と返った場合は、待っても解消しないため別の `ModelQuotaExceededError` になります。

## Pydantic AIに関するよくある質問

### Pydantic AIのインストールは pip でどう書きますか？

`pip install pydantic-ai` です。OpenAIだけ使うなら `pip install "pydantic-ai-slim[openai]"` のように、必要なプロバイダをextrasで足します。Python 3.11以上が必要で、3.10では2.54.0以前が入ります。

### @tool デコレーターは使えますか？

素の `@tool` はありません。`@agent.tool`（第1引数で `RunContext` を受け取る）か `@agent.tool_plain` を使うか、`Agent(..., tools=[...])` に関数のリストを渡します。

### pydantic ai の context（依存）はどう渡しますか？

`Agent(..., deps_type=MyDeps)` で型を宣言し、ツールの第1引数を `RunContext[MyDeps]` にして、実行時に `deps=` で実体を渡します。ツール内では `ctx.deps` で参照します。型を書くだけで自動注入される仕組みはありません。

### Pydantic AIとLangChainはどちらを選べばよいですか？

LLMの出力を型付きで次の処理に渡すことが中心で、Pythonの関数と型ヒントだけで書きたいならPydantic AIです。部品の数と既存の連携先の多さを優先するならLangChain、状態の保存と分岐の多いフローならLangGraphが向きます。

### Pydantic AI Gatewayは必須ですか？

必須ではありません。複数プロバイダのキー・費用上限・障害時の切り替えを1か所で管理したいときに使う別製品で、モデル文字列の頭に `gateway/` を付けると経由します。1社のモデルだけならプロバイダへ直接つなぐ構成で足ります。

## 関連記事

- [Pydanticとは？読み方・BaseModelの使い方とV1との違い【2.13対応】](/tech/details/4838/)
- [Function Calling（ファンクションコーリング）とは｜仕組みとOpenAI・Claude・Geminiの実装【2026年10月】](/tech/details/4631/)
- [LangChainとLangGraphの違いと使い分け｜v1.0以降の関係をコードで確認【2026年10月】](/tech/details/4059/)
- [LangGraphのTool Calling実装｜ToolNode・create\_agent・エラー処理とループ上限【2026年10月】](/tech/details/5428/)
- [MCPサーバーの作り方とAIエージェントへの接続手順｜Python SDK 2.3でツール定義・認可を実装](/tech/details/15365/)

---

出典: [Pydantic AIとは？v2の使い方とツール・依存性注入・テストを実行済みコードで解説](<https://www.issoh.co.jp/tech/details/6743/>)（株式会社一創）
