Pydantic AIではじめるAIエージェント超入門(1)
Pydantic AIとは
Pydantic AI は、AIエージェント開発を効率的に行えるPython製のフレームワークです。
その名のとおり、Pydantic のデータ型バリデーションの恩恵を受けて、より安定したAIエージェントの開発ができます。
また、Pydantic を開発している公式チームによって提供されているため、信頼性が高く安心して利用できる点も大きな利点です。
型安全:
設計が完全に型安全であるため、IDEの強力なサポート(オートコンプリート、型チェック)を受けられ、多くのエラーを実行まえに検知できます。
マルチプロバイダー:
OpenAI、Gemini、Anthropic、Mistralなど、主要なLLMとプロバイダをサポートしており、特定のモデルに縛られず開発を進められます。
構造化された出力:
出力を即座にバリデーションでき、効率的なデータ処理が行えます。
可観測性:
Pydantic Logfire(またはOpenTelemetry対応プラットフォーム)と緊密に統合され、リアルタイムのデバッグ、コスト追跡、トレーシング(処理の追跡)が容易に行えます。
評価:
構築したエージェントシステムの性能と精度を体系的にテスト・評価し、そのパフォーマンスを継続的に監視できます。
標準規格への対応:
Model Context Protocol(MCP)、Agent2Agent、AG-UIの規格を統合し、拡張性と相互運用性を確保できます。
3分で作るAIエージェント
まずはごく簡単なエージェントを作成します。
本記事では gemini-2.0-flash-lite を利用するため、環境変数 GOOGLE_API_KEY にAPIキーを設定しています。
次のように記述するだけで、指定したLLMモデル(次の例では gemini-2.0-flash-lite )から回答を得られます。
from pydantic_ai import Agent
agent = Agent(
model="google-gla:gemini-2.0-flash-lite",
instructions="簡潔に1文で回答してください",
)
result = agent.run_sync("こんにちは")
print(result.output)
Agent:
エージェントの核となるコンポーネントで、ユーザーの入力(プロンプト)を受け取り、指定されたモデルやツールを使ってタスクを実行し、構造化された出力を返す役割を担います。
model:
エージェントがタスクを実行するために利用するLLMのモデルを指定します。
instructions:
エージェントの挙動やタスクに対するアプローチを定義します。エージェントに「どのように振る舞うべきか」「回答の形式」などを指示するために使用されます。
Agent
Agent は、LLMとやり取りするための主要なインターフェースであり、開発者がAIアプリケーションを構築するための主要なコンポーネントです。
簡単に言えば、AgentはLLMに「何を」「どのように」処理させるかをコントロールする司令塔のような役割を果たします。
Agentは次の要素で構成されます。
| 要素 | 説明 |
|---|---|
| Instructions(指示) | 開発者が書くLLM用の指示セット |
| Function tool(s)とtoolsets | LLMが応答生成中に呼び出せる関数 |
| Structured output type | 実行終了時にLLMが返す必要がある構造化データ型 |
| Dependency type constraint | 動的な指示、ツール、出力関数で使用される依存性 |
| LLMモデル | エージェントに関連付けられたデフォルトのLLMモデル(オプション) |
| Model Settings | リクエストを微調整するためのモデル設定(オプション) |
Agentには次のような特徴があります。
複雑なタスクの実行:
単なる質問応答だけでなく、複数のステップや外部連携が必要な複雑なタスクも自動的に計画・実行できます。
構造化された出力の保証:
Pydanticの強力なバリデーション機能により、LLMの回答を常に指定した型(Schema)に沿った構造化データとして受け取ることができ、後続のプログラム処理を容易にします。
再利用性:
一度設定したAgentは、アプリケーション全体やコンポーネントの一部として再利用できます。
Instructions(指示)
instructions は、エージェント(LLM)が「個々のタスクをどのように実行するか」を開発者が定義するための設定です。
回答の形式、トーン、制約条件など、そのタスク単位の具体的な行動指針をモデルに伝える役割を持ちます。
言い換えると、「エージェントに渡す作業マニュアル」です。
agent = Agent(
instructions="質問には日本語で、できるだけ簡潔に答えてください。"
)
このように instructions を設定しておくと、同じモデルを使っていても、
「英語で丁寧に答える」・「日本語で短く答える」などのタスクレベルの違いを柔軟に表現できます。
system_prompt との違い
よく混同されるのが system_prompt との関係です。
system_prompt はエージェント全体の 人格・立場・背景設定 を定義するのに対し、
instructions は 実行時にどう振る舞うか という「下位レイヤーの指示」を定義します。
例えば次のように使い分けられます。
agent = Agent(
system_prompt="あなたはフレンドリーなアシスタントAIです。",
instructions="ユーザーの質問には日本語で簡潔に答えてください。"
)
-
system_prompt→ 「あなたはどんなAIなのか?」(人格・役割) -
instructions→ 「どう答えるのか?」(行動ルール)
このように system_prompt はエージェントの基盤的性格を、 instructions はタスクごとの実行方針を制御します。
両者を分離することで、人格を保ったまま指示内容だけを切り替えるといった設計が可能になります。
次の例では、同じタスクをエージェントによって振る舞いを変えています。
from pydantic_ai import Agent
friendly = Agent(
model="google-gla:gemini-2.0-flash-lite",
system_prompt="あなたはフレンドリーなアシスタントAIです。",
instructions="大阪の店員として、100文字程度で答えてください",
)
butler = Agent(
model="google-gla:gemini-2.0-flash-lite",
system_prompt="あなたは執事のように礼儀正しく話すAIです。",
instructions="東京のホテルマンとして、100文字程度で答えてください",
)
question = "おすすめのお土産は?"
print("=== フレンドリーAI ===")
print(friendly.run_sync(question).output)
print("\n=== 執事AI ===")
print(butler.run_sync(question).output)
出力例:
=== フレンドリーAI ===
おおきに!大阪土産やったら、やっぱり「551蓬莱」の豚まんは外せへんね! あとは、くいだおれ太郎プリンとか、面白いもんもええで! 喜んでもらえる土産、一緒に探そか!
=== 執事AI ===
かしこまりました。お客様。東京のお土産でございますね。
銀座の老舗和菓子や、東京駅限定のスイーツなど、数多ございます。お客様のお好みに合わせ、おすすめを提案いたしますので、お気軽にお申し付けくださいませ。
まとめると次のようになります。
| 項目 | system_prompt | instructions |
|---|---|---|
| 意味 | AIの人格・世界観・立場を定義 | タスクごとの行動方針を定義 |
| 適用範囲 | エージェント全体(継続) | 各 .run() 実行単位(都度) |
| 主な用途 | 役割設定・口調統一 | 回答形式・制約指定 |
| 例 | 「あなたは医療アシスタントです」 | 「症状を箇条書きでまとめてください」 |
pydantic-ai ではこのように、人格(system_prompt)と行動(instructions)を分けて設計することで、
再利用性の高い・柔軟なエージェント構成にできます。
Structured output type(構造化された出力)
「構造化された出力の保証」の例として、ここでは「顧客からの質問」を分析し、「意図」「重要度」「推奨されるアクション」の3つの要素を持つ構造化されたデータとして出力するエージェントを作成します。
from enum import Enum
from pydantic import BaseModel, Field
from pydantic_ai import Agent
# エージェントが出力する構造化データ(Schema)を定義
class Priority(str, Enum):
"""質問の重要度レベル"""
LOW = "低"
MEDIUM = "中"
HIGH = "高"
class CustomerQueryAnalysis(BaseModel):
"""顧客からの問い合わせを分析した結果"""
intent: str = Field(
description="顧客がこの問い合わせで求めている中心的な意図(例:料金プランの確認、バグ報告、機能要望)"
)
priority: Priority = Field(description="問い合わせの緊急度または重要度")
suggested_action: str = Field(
description="この問い合わせに対する推奨される次のステップ(例:開発チームへのエスカレーション、FAQへの誘導、担当者からの電話連絡)"
)
# エージェントを初期化し、InstructionとSchemaを設定
agent = Agent(
model="google-gla:gemini-2.0-flash-lite",
instructions="あなたはプロフェッショナルなカスタマーサポートのエキスパートです。与えられた顧客の問い合わせを詳細に分析し、指定されたスキーマに従って日本語で回答を生成してください。",
output_type=CustomerQueryAnalysis,
)
# エージェントを実行し、構造化された出力を取得
user_query = "昨日からアカウントにログインできなくなりました。パスワードリセットも機能しません。早急に助けてください!"
print(f"--- ユーザーの問い合わせ ---\n{user_query}\n")
# run_syncで実行すると、result.output は定義した Pydantic クラスのインスタンスになる
result = agent.run_sync(user_query)
# 結果は Pydantic インスタンスとしてアクセス可能
print("--- エージェントの分析結果(構造化データ)---")
print(f"意図: {result.output.intent}")
print(f"重要度: {result.output.priority.value}") # Enumの値を取得
print(f"推奨されるアクション: {result.output.suggested_action}")
print("-" * 40)
print(f"result.output は {type(result.output)} 型のオブジェクトです。")
次の実行結果では、 CustomerQueryAnalysis で定義したクラスの構造( intent , priority , suggested_action )と型( str, Enum )を満たすことが保証されていることがわかります。
--- ユーザーの問い合わせ ---
昨日からアカウントにログインできなくなりました。パスワードリセットも機能しません。早急に助けてください!
--- エージェントの分析結果(構造化データ)---
意図: アカウントへのログインに関する問題
重要度: 高
推奨されるアクション: 技術チームへのエスカレーション
----------------------------------------
result.output は <class '__main__.CustomerQueryAnalysis'> 型のオブジェクトです。
output_type
output_type は、エージェント(LLM)からの出力を、開発者が意図した「厳密なデータ構造」に強制しています。
これは、LLMが出力するテキストを単なる文字列として扱うのではなく、プログラムで簡単に扱える構造化データ(JSON、Pythonオブジェクトなど)に変換するために使用されます。
| 項目 | 説明 |
|---|---|
| 厳密な構造の保証 | エージェントに、標準のPydantic BaseModel で定義されたスキーマ(型、フィールド名、説明)に従って回答を生成するよう指示する。これにより、LLMが自由にテキストを生成するのではなく、指定された形式に沿った回答を返すことが保証される。 |
| プログラムとの連携 | LLMの出力をそのまま文字列としてパース(解析)する手間がなくなり、自動的に定義したPydanticクラスのインスタンスとして受け取れる。これにより、後続のPythonコードでのデータ処理(例:データベースへの保存、次の関数への受け渡し)が容易で安全になる。 |
| バリデーション(検証) | Pydanticの機能により、LLMの出力がスキーマの制約(型、値の範囲、必須フィールドなど)を満たしているかが自動的に検証される。もし不適合な出力があった場合、エラーとして捕捉できる。 |
output_typeにPydanticモデルを設定すると、Agentは以下の手順を実行します。
- プロンプトの拡張: Pydanticモデルの定義(フィールド名、型、説明)を基に、LLMが理解できる形式の指示(メタデータ)を自動的に元のプロンプトに追加します。
- JSON生成の強制: LLMは、その指示に従い、指定された構造を持つJSON形式のテキストを生成しようとします。
- インスタンス化と検証: Pydantic AIは、LLMが生成したJSONを自動的にパースし、指定されたPydanticクラス(
output_type)のインスタンスとして返します。
Tools(ツール)
これまで紹介してきた instructions や system_prompt は、エージェント(LLM)の振る舞いそのものを定義する静的な仕組みでした。
しかし、実際のアプリケーション開発では、単に「文章を生成する」だけでなく、外部の関数やAPIを呼び出して処理を実行したい場面が多くあります。
Pydantic AI では、これを実現する仕組みとして Tools(ツール) が用意されています。
Tools は、LLMが自ら関数を呼び出して外部処理を行うための仕組みです。
ChatGPT の「関数呼び出し(Function calling)」や「ツールの利用」と同じ概念で、エージェントが生成した回答の中で「必要に応じて関数を実行する」ができます。
- 最新の天気情報を取得する
- データベースから顧客情報を取得する
- 計算や整形など、LLMが不得意な処理をPythonで補完する
といったケースで @agent.tool デコレータを使うことで、LLM が自然言語を解析して自動的にその関数を呼び出せるようになります。
Pydantic AI の Tools には以下の特徴があります。
| 特徴 | 説明 |
|---|---|
| 型安全 | Pydanticモデルの型ヒントに基づいて、LLMが関数引数を正しく構築 |
| 自動呼び出し | LLMが回答の中でツールが必要と判断した場合、自動的に関数を呼び出す |
| Dependencies対応 | 後述する deps 機構を通して、ツールに外部値(APIキー・設定情報など)を動的に注入できる |
| シンプルな構文 | Python関数に @agent.tool を付けるだけで登録できる |
次のコードでは @agent.tool を使った関数を呼び出してプロンプトに入力された数列がフィボナッチ数列かどうかを判別しています。
from pydantic_ai import Agent, RunContext
agent = Agent(model="google-gla:gemini-2.0-flash-lite")
# @agent.tool デコレータを使ってツール関数を登録
# この関数は LLM から呼び出される可能性がある
@agent.tool
def is_fibonacci(ctx: RunContext[None], seq: list[int]) -> bool:
"""
与えられた整数列がフィボナッチ数列なら True
"""
print("is_fibonacciが呼び出されました")
return all(seq[i] + seq[i + 1] == seq[i + 2] for i in range(len(seq) - 2))
# LLM は自然言語を理解して必要に応じて is_fibonacci() を呼び出す
result = agent.run_sync("[8, 13, 21]がフィボナッチ数列か教えてください")
print(result.output)
出力例:
is_fibonacciが呼び出されました
はい、[8, 13, 21]はフィボナッチ数列です。
RunContext
RunContext は、ツール関数(@agent.tool)が実行されるときに「エージェントの実行環境情報を受け取るためのコンテキストオブジェクト」です。
Pydantic AI では、@agent.tool で定義する関数の最初の引数に RunContext[...] 型を指定します。
RunContext は「エージェントの実行状態を表す箱」のようなもので、ツールが呼び出された際に次のような情報を参照できます。
| 属性 | 説明 |
|---|---|
ctx.deps |
agent.run_sync() / agent.run() 実行時に渡された依存関係(deps)を参照できる |
ctx.model |
現在実行中の LLM モデル情報を取得する |
ctx.tools |
同じエージェントに登録されている他のツールを参照できる |
ctx.output_type |
出力型(Structured Output)を指定している場合、そのスキーマを参照できる |
@agent.tool で使う場合は、ツール関数の第1引数に ctx: RunContext[None] を追加します。
[None] の部分は依存関係の型を指定するもので、依存関係を使わない場合は None とします。
Dependencies(依存関係)
Dependencies は、エージェント(Agent)に「外部から動的な値を注入」するための仕組みです。
これを使うことで、実行時の状況やユーザー情報に応じてエージェントの振る舞いを変えることができます。
Pydantic AI における deps は、単なる「変数渡し」ではなく、エージェントの動作全体を動的に制御する仕組み として設計されています。
次のコードでは deps の依存注入を使って顧客IDに応じた挨拶をしています。
from typing import TypedDict
from pydantic_ai import Agent, RunContext
# 顧客データベースのような外部情報
CUSTOMERS = {1: "佐藤", 2: "鈴木", 3: "高橋", 4: "田中"}
# 依存関係の型定義
class CustomerDeps(TypedDict):
customer_id: int
name: str | None
agent = Agent(model="google-gla:gemini-2.0-flash-lite")
@agent.tool
def greet_customer(ctx: RunContext[CustomerDeps]) -> str:
"""
顧客IDに応じて挨拶内容を切り替えるツール。
既存顧客には感謝の言葉を、新規顧客には初回挨拶を返す。
"""
cid = ctx.deps["customer_id"]
name = ctx.deps.get("name", CUSTOMERS.get(cid, "お客様"))
if cid in CUSTOMERS:
return f"いつもお世話になっております{name}様。"
else:
return f"はじめまして{name}様。よろしくお願いいたします。"
# 実行時に customer_id を動的に注入
existing_customer = agent.run_sync("顧客に挨拶", deps={"customer_id": 4})
new_customer = agent.run_sync("顧客に挨拶", deps={"customer_id": 5, "name": "加藤"})
print(existing_customer.output)
print(new_customer.output)
出力例:
いつもお世話になっております田中様。
加藤様、はじめまして。よろしくお願いいたします。
この仕組みを応用することで、次のようなことが実現できます。
- ctx.deps にユーザー属性・設定値などを渡すことで、実行時の挙動を柔軟に変更する
- エージェント実行結果を次のエージェントに依存値として渡すことで、複数のエージェントを連携させる構成を実現できる
- LLMがタスク内容を理解して、自分でどの関数(ツール)を呼ぶか判断する
次のコードは事業ドメインごとにエージェントが関数を自動的に割り振る例です。
from pydantic_ai import Agent, RunContext
agent = Agent(model="google-gla:gemini-2.0-flash-lite")
# 営業ドメインのツール
@agent.tool
def sales_report(ctx: RunContext[None], month: str) -> str:
"""営業レポートを生成"""
print("[TOOL] sales_report() が呼ばれました")
return f"{month}の営業実績は前年比+12%でした。"
# 経理ドメインのツール
@agent.tool
def accounting_summary(ctx: RunContext[None], month: str) -> str:
"""経理部門の決算サマリ"""
print("[TOOL] accounting_summary() が呼ばれました")
return f"{month}の経理報告:支出総額は前月比95%に減少しました。"
# カスタマーサポートドメインのツール
@agent.tool
def support_feedback(ctx: RunContext[None], month: str) -> str:
"""カスタマーサポートの顧客満足度レポート"""
print("[TOOL] support_feedback() が呼ばれました")
return f"{month}の顧客満足度は 4.6 / 5 でした。"
# プロンプトの内容に応じて LLM が自動で適切な関数を選択
res1 = agent.run_sync("営業部の4月の実績を教えて")
res2 = agent.run_sync("経理の4月の支出サマリを出して")
res3 = agent.run_sync("サポート部の4月の顧客満足度を確認したい")
print(res1.output)
print(res2.output)
print(res3.output)
出力例:
[TOOL] sales_report() が呼ばれました
[TOOL] accounting_summary() が呼ばれました
[TOOL] support_feedback() が呼ばれました
4月の営業実績は前年比+12%でした。
4月の経理報告によると、支出総額は前月比95%に減少しました。
4月の顧客満足度は 4.6 / 5 でした。
まとめ
本記事では、Pydantic AI を使って次の内容を紹介しました。
- エージェントの基本構造
- Pydantic のデータ型による構造化された入出力
- Tools の基本的な使い方
- 依存関係(Dependencies)による動的な値の注入方法
AI エージェントの開発者にとって、LLM がどのようなデータを返すかには、これまで不安定な要素がありました。
Pydantic AI では、データ型を安全に管理することで、こうした不確定要素を軽減できる可能性があります。
複数エージェントの連携とデータ構造 に続きます
Discussion