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 で、ライセンスはMITです。
Pydantic本体との関係
Pydantic本体(pydantic)がデータ構造の定義と検証を担い、Pydantic AIはその上に載る別パッケージです。ツールの引数スキーマも出力の型も BaseModel や型ヒントから作られるため、既存のPydanticモデルをそのまま出力型に使えます。BaseModelやバリデーションの基礎はPydanticの読み方・BaseModelの使い方とV1との違いで解説しています。手元の実測環境では、pydantic-ai-slim 2.63.0と一緒に pydantic 2.14.2が入りました。
v2で変わった点とv1の扱い
公式のバージョンポリシーによれば、v1は2025年9月、v2.0は2026年6月23日に出ました。マイナー版で意図的な破壊的変更はしない方針で、v1にはv2安定版のリリースから少なくとも6か月はセキュリティ修正を出すとしています。v1系の更新は今も続いていて、10月21日の1.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に載っていて、表の「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のリリースノートには「Python 3.10でインストールすると2.54.0以前に解決される」と書かれています。この記事のコードが動かない、ドキュメントにある引数が無いというときは、最初に __version__ を確かめます。Python 3.10自体も2026年10月にサポート終了を迎えるので、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の実装と同じで、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実装・採用条件で扱っています。
Pydantic AI GatewayによるAPIキーの統合
Pydantic AI Gatewayは、フレームワークとは別に提供されるモデルのゲートウェイです。公式ドキュメントによれば、1本のキーでOpenAI・Anthropic・Google Vertex・Groq・AWS Bedrockにアクセスでき、利用上限をプロジェクト・ユーザー・キーの単位で日次・週次・月次に設定できます。プロバイダの自前キーを持ち込む方式(BYOK)と、推論料金をGatewayに払う方式を選べます。Pydanticの料金ページでは、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件で、処理後の内容に置き換わっています。元の履歴を残しておきたいなら、アプリ側で別に保存してください。切り方にも注意が要ります。プロバイダはツール呼び出しと戻り値の組が崩れた履歴を拒否するため、公式ドキュメントによれば、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)を使います。評価のドキュメントによれば、データセットと評価器をPythonのコードで定義し、最終出力やツール呼び出しの経路を、用意したデータか本番のトラフィックに対して評価できます。
import logfire
logfire.configure()
logfire.instrument_pydantic_ai() # 全エージェントの実行・モデル要求・ツール呼び出しを計装
TestModelとFunctionModelによるLLM不要のテスト
エージェントのテストで本物のLLMを呼ぶと、料金がかかり、結果も毎回変わります。Pydantic AIには、モデルの代わりに決まった応答を返す TestModel と FunctionModel があり、公式のテストガイドは次の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)の統合を使います。2.63.0のドキュメントにはTemporal・DBOS・Prefect・Restate・AWS Lambda・Absurd・pgtask・Apache Airflowの8エンジンが並び、1つのエージェントに使えるエンジンは1つです。外部ツールの接続はMCPに対応していて、v2ではMCPサーバーを toolsets= に渡します。サーバー側の作り方はMCPサーバーの作り方とAIエージェントへの接続手順を参照してください。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の違いと使い分けで比べています。 - 依存のバージョンを固定できない運用:v2系は25日で10回のマイナー版が出ています。破壊的変更はしない方針でも、既定値や警告は増えます。
pydantic-ai-slim==2.63.0のように版を固定し、更新時にテストを回す体制が前提です。 - 役割分担型のマルチエージェントを設定中心で組みたい:Pydantic AIはツールから別のエージェントを呼ぶ「委譲」をコードで書く設計です。役割とタスクの定義を中心に組む方式ならCrewAIの仕組みとLangGraphとの違いも比べてください。
社内データとつないだエージェントの設計から実装・運用まで外部に任せたい場合は、一創のAIエンジン開発でも相談を受けています。
よくあるエラーと原因・対処
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社のモデルだけならプロバイダへ直接つなぐ構成で足ります。