Python Monthly Topics

PythonでLLMアプリ開発を型安全に扱う ― Pydantic AI入門

門脇@satoru_kadowakiです。2026年2月の「Python Monthly Topics」は、Python向けAIエージェントフレームワークPydantic AIについて紹介します。

はじめに

LLM(大規模言語モデル)をアプリケーションに組み込む開発が一般的になりつつあります。LLMからの出力は、基本的には「ただの文字列」ですが、LLMのAPIから構造化されたデータを取得する手段も充実してきています。主要なAIプロバイダーでは、以下のようにJSONスキーマJSON Schemaに沿った出力を保証する機能も提供されており、構造化された結果を得ることも比較的正確にできるようになってきました。

しかし、上記のように出力を構造化したとしても、出力結果の制約を全て満たすわけではなく、⁠LLMをアプリケーションに型安全に組み込める」とは限りません。実際の開発においては以下のようなケースに遭遇し、修正のためのコードを書くということもよくあります。

  • JSONスキーマの型制約は満たしているが、出力結果の制約(n文字以内、出力タグはn個まで等)に違反した値が返ってくる
  • 制約違反時に、LLMへリトライさせる仕組みを実装する必要がある
  • AIプロバイダーごとに構造化出力やツール呼び出しの仕様が異なり、切り替え時にコードの書き換えが発生する

このような問題を解決するのがPydantic AIです。Pydantic AIは、データバリデーションライブラリとして広く使われているPydanticの開発チームが手がけるPython向けAIエージェントフレームワークです。

本記事では、⁠OpenAI APIで構造化出力を扱うとどうなるか」を見た上で、Pydantic AIを体験してみます。

なお、Pydanticについては、本連載(Python Monthly Topics)でも過去に扱っていますので、ぜひ読んでみてください。

環境構築

それでは、早速進めていきましょう。Pydantic AIの動作環境は以下となっています。

  • Python 3.10以上
  • Python 3.14 / pydantic-ai 1.62.0(本記事での動作確認環境)

インストール

Pydantic AIは、以下のようにpipでインストールできます。

$ pip install pydantic-ai

使用するモデルが決まっていて、余分なパッケージのインストールを避けたい場合は、pydantic-ai-slimパッケージを使用します。たとえば、OpenAI Modelだけを使用する場合は、以下を実行します。

$ pip install "pydantic-ai-slim[openai]"

APIキーの設定

本記事ではOpenAIのgpt-5.2を使用していますが、Pydantic AIは、Anthropic、Google Gemini、など多くのAIプロバイダーのAPIに対応しています。モデル名の指定方法を変えるだけで切り替えられるため、記事のコード例は他のモデルでも動作します。

Pydantic AIで使用可能なモデルについては、以下を参照してください。

サンプルコードを実行するためには、OpenAIのAPIキーを環境変数に設定しておく必要があります[1]

export OPENAI_API_KEY="sk-..."

動作確認

まずは最小限のコードでPydantic AIが動くことを確認しましょう。以下のスクリプトをexample_1.pyとして保存して実行してみます。

Pydantic AIの動作確認例 - example_1.py
from pydantic_ai import Agent

# 利用するLLMモデルと、エージェントの振る舞い(システム指示)を設定
agent = Agent(
    "openai:gpt-5.2",
    instructions="あなたは親切なアシスタントです。",
)

# 同期実行で1回だけ問い合わせ、レスポンスオブジェクトを受け取る
res = agent.run_sync("Pydantic AIについて一言で教えてください。")

# モデルが生成した最終テキストを表示
print(res.output)

example_1.pyの実行結果は以下のようになります。

example_1.pyの実行と結果 - example_1.py
$ python example_1.py
Pydantic AIは、Pydanticの型安全性を活かしてLLMアプリを「堅牢に」作るためのPythonライブラリです。

AgentはPydantic AIの中心的なクラスです。モデル名とインストラクションを渡してインスタンスを作り、run_sync()メソッドでユーザーのプロンプトを送信します。この時点ではLLMの出力はただの文字列(str)として返されます。

OpenAIのAPIで構造化出力を試す

Pydantic AIを理解するために、まずはOpenAIのAPIを直接使って構造化出力を試してみましょう。題材として、本連載「Python Monthly Topics」の記事一覧ページhttps://gihyo.jp/list/group/Python-Monthly-TopicsのHTMLから、各記事のメタデータを構造化して取得してみます。記事一覧ページは、実際のHTMLの内容を抜粋したものを入力データとして、以下をpytopics.htmlとして保存し使用します。

記事一覧ページのHTML - pytopics.html
<ul>
<li><a href="/article/2026/01/monthly-python-2601">
    Pythonで始めるマルチエージェントAI ― CrewAI入門
    <span class="author">杉田雅子</span>
    <time>2026-01-27</time>
</a></li>
<li><a href="/article/2025/12/monthly-python-2512">
    Rustで書かれた高速Python型チェッカー「Pyrefly」の紹介
    <span class="author">筒井隆次</span>
    <time>2025-12-04</time>
</a></li>
<li><a href="/article/2025/11/monthly-python-2511">
    t-string:テンプレート文字列リテラルの紹介
    <span class="author">鈴木たかのり</span>
    <time>2025-11-18</time>
</a></li>
<li><a href="/article/2025/10/monthly-python-2510">
    データ検証ライブラリPydanticの紹介
    <span class="author">寺田学</span>
    <time>2025-10-28</time>
</a></li>
<li><a href="/article/2025/09/monthly-python-2509">
    Python 3.14の新機能:asyncioタスク可視化機能を使ってみよう
    <span class="author">福田隼也</span>
    <time>2025-09-30</time>
</a></li>
<li><a href="/article/2025/08/monthly-python-2508">
    PrefectではじめるPythonワークフロー・フレームワーク
    <span class="author">門脇諭</span>
    <time>2025-08-28</time>
</a></li>
</ul>

OpenAI APIでの実装

各記事のメタデータをリストとして抽出してみます。最初に、処理に使用するHTMLの読み込みと、Pydanticでパースとバリデーションを行うコードを以下のようにします。この部分は、OpenAIのAPIの例とPydantic AIの例の両方で使用する共通処理です。Pydantic自体のパースやバリデーションに関する細かい解説は割愛しますが、記事情報のリストArticleInfoのリストArticleListを表すPydanticモデルを定義しています。

HTMLの読み込みとPydanticを使用したパース/バリデーション処理の例 - article_common.py
from pathlib import Path

from pydantic import BaseModel, Field


# 解析対象のHTMLをファイルから読み込む
html_path = Path(__file__).with_name("pytopics.html")
SAMPLE_HTML = html_path.read_text(encoding="utf-8")


class ArticleInfo(BaseModel):
    """記事のメタデータ"""

    title: str = Field(description="記事のタイトル")
    author: str = Field(description="著者名")
    published_date: str = Field(description="公開日(YYYY-MM-DD形式)")
    url: str = Field(description="記事のURL(相対パス)")


class ArticleList(BaseModel):
    """記事一覧"""

    articles: list[ArticleInfo]

続いて、OpenAIのAPIを使用する例です。response_formatにJSONスキーマとしてPydanticのモデルを指定し、パースとバリデーションを行うコードは以下のようになります。

OpenAIのAPIを使用して、構造化出力を行う例 - example_2.py
from openai import OpenAI

# 共通処理で宣言したSAMPLE_HTMLとクラスをインポート
from article_common import SAMPLE_HTML, ArticleList

# OpenAI APIクライアントを初期化
client = OpenAI()


# ---- OpenAI API Structured OutputsとAPI呼び出し ----
# PydanticモデルからJSONスキーマを生成し、OpenAI Structured Outputsの要件に合わせて加工する
# Structured Outputsでは、すべてのオブジェクト型にadditionalProperties: false が必要となっている
# 参考: https://developers.openai.com/api/docs/guides/structured-outputs
# しかし、Pydanticの model_json_schema() はこの設定を自動で追加しないため、手動で追加する
def make_strict_schema(schema: dict) -> dict:
    """JSONスキーマにadditionalProperties: falseを再帰的に追加する"""
    if schema.get("type") == "object":
        schema["additionalProperties"] = False
    for value in schema.values():
        if isinstance(value, dict):
            make_strict_schema(value)
    return schema


schema = make_strict_schema(ArticleList.model_json_schema())

# Responses APIでHTMLから記事情報を構造化抽出する
# openai.types.responses.response.Responseオブジェクトが返される
res = client.responses.create(
    model="gpt-5.2",
    instructions="与えられたHTMLから記事の一覧情報を抽出してください。",
    input=SAMPLE_HTML,
    text={  # JSONスキーマに従って構造化したデータを返すように指示
        "format": {
            "type": "json_schema",
            "name": "ArticleList",
            "schema": schema,
        }
    },
)

# 返却されたJSON文字列をPydanticで検証しながらパース
article_list = ArticleList.model_validate_json(res.output_text)

# 抽出した記事情報を出力
for article in article_list.articles:
    print(article)

OpenAIのStructured Outputsは、スキーマ内のすべてのオブジェクト型にadditionalProperties: falseが設定されていることを要求しますが、Pydanticのmodel_json_schema()はこれを出力しません。そのため、スキーマを後処理するヘルパー関数が必要になります。これはOpenAI固有の制約で、他のプロバイダーではまた別の調整が必要になる場合があります。additionalPropertiesについては前述のStructured model outputsに必須項目の解説がありますので、参照してみてください。

example_2.pyを実行すると、結果は以下となります。

$ python example_2.py
title='Pythonで始めるマルチエージェントAI ― CrewAI入門' author='杉田雅子' published_date='2026-01-27' url='/article/2026/01/monthly-python-2601'
(省略)
title='PrefectではじめるPythonワークフロー・フレームワーク' author='門脇諭' published_date='2025-08-28' url='/article/2025/08/monthly-python-2508'

上記の結果から、構造化出力データをJSON化することが可能であることがわかります。しかし、前述の説明にもあるように、Pydanticモデルからスキーマを生成してOpenAIに渡すだけでもひと手間かかっています。

また、仮に以下のような制約を設けたい場合、JSONスキーマだけでは対応しきれません。

  • published_dateが本当にYYYY-MM-DD形式になっているか
  • url/article/で始まる妥当なパスであるか

これらは、Pydanticのfield_validatorで検証することはできますが、バリデーション失敗時にLLMへ再生成を促すリトライの仕組みは自分で書く必要があります。上記以外にも、ツール呼び出しの管理やモデルの切り替えについても同様で、AIプロバイダーが提供するAPIだけでは、開発者が多くのコードを書くことになります。

Pydantic AIで構造化出力を実装する

Pydantic AIでは、どのようにしてLLMから適切に構造化したデータを受け取ることができるのか、まずはOpenAIのAPIで実装したコードと同じ処理をPydantic AIで書き換えてみましょう。

Pydantic AIで書き換える

サンプルHTMLの読み込みや、ArticleInfoArticleListモデルの定義はarticle_common.pyをそのまま使用します。OpenAIのAPIで実装していた部分を、Pydantic AIによる実装に書き換えます。以下が書き換え後のコードです。

Pydantic AIのAgentを使った構造化出力の例 - example_3.py
from pydantic_ai import Agent

# 共通処理で宣言したSAMPLE_HTMLとクラスをインポート
from article_common import SAMPLE_HTML, ArticleList


# Agentのoutput_typeにArticleListを指定
agent = Agent(
    "openai:gpt-5.2",
    output_type=ArticleList,  # スキーマとしてPydanticのモデルを指定
    instructions="与えられたHTMLから記事の一覧情報を抽出してください。",
)

# SAMPLE_HTMLを入力としてAgentを実行
res = agent.run_sync(SAMPLE_HTML)

# res.outputはArticleList型のオブジェクトとして返される
for article in res.output.articles:
    print(article)

主な変更点としては、前述の動作確認で使用したAgentクラスの引数にoutput_type=ArticleListを指定するだけです。これだけで、Pydantic AIが以下のような部分を吸収してくれています。

  • Pydanticのモデル定義からスキーマの生成
  • LLM呼び出し
  • レスポンスのパース、バリデーション

example_2.pyで使用したmake_strict_schema()のようなヘルパー関数や、JSONスキーマの変換も不要なため、シンプルでわかりやすいコードになっています。また、res.outputArticleList型のオブジェクトとして返されるため、res.output.articles[0].titleのように名前付きの属性としてアクセスできます。

ここでは実行結果を割愛しますが、example_3.pyを実行するとexample_2.pyと同様の結果が得られます。

より安全に⁠バリデーションを強化する

Pydantic AIを使用することは、コードがシンプルになるだけではありません。前述のとおり、Structured Outputsで型レベルの制約は保証されていても、⁠日付が妥当か」などの制約をLLMが守ってくれるとは限りません。Pydantic AIでは、Pydanticモデルに以下のようなバリデーションルールを書くことで、LLMからのデータをバリデーションして、構造化されたデータを型安全に扱うことができます。

これまでのコードと同様に、バリデーションルールをarticle_common_strict.pyとしてバリデーションルールを記述します。

強化したバリデーションルールの例 - article_common_strict.py
from datetime import date
from pathlib import Path

from pydantic import BaseModel, Field, field_validator


# 解析対象のHTMLをファイルから読み込む
html_path = Path(__file__).with_name("pytopics.html")
SAMPLE_HTML = html_path.read_text(encoding="utf-8")


class ArticleInfo(BaseModel):
    """記事のメタデータ"""

    title: str = Field(
        min_length=1, description="記事のタイトル"
    )  # min_length=1で空文字を禁止
    author: str = Field(min_length=1, description="著者名")
    published_date: date = Field(description="公開日")  # date型で日付形式を強制
    url: str = Field(description="記事のURL(相対パス)")

    @field_validator("url")
    @classmethod
    def validate_url_format(cls, v: str) -> str:
        """URLが`/article/`で始まる相対パスかを検証し、不正なら例外を送出する"""
        if not v.startswith("/article/"):
            raise ValueError(
                f"URLは'/article/'で始まる相対パスである必要があります(実際の値: {v})"
            )
        return v


class ArticleList(BaseModel):
    """記事一覧"""

    articles: list[ArticleInfo] = Field(min_length=1)

具体的にどの部分のバリデーションが強化されているか、内容を以下にまとめます。

  • titleやauthorは、min_length=1で空値を防ぐ
    • articlesリストにもmin_length=1を指定し、記事が1件も抽出されないケースを防いでいる
  • published_dateをstrではなくdate型を指定
    • LLMが「2025年12月」のような曖昧な形式で返した場合、Pydanticの日付検証でバリデーションエラーが発生する
  • field_validatorで記事URLが/article/で始まるパスであることを検証
    • LLMがフルURL(https://gihyo.jp/article/...)や不正なパスを返した場合、エラーメッセージが返される

Agentクラスについては前述のコードとほぼ同じですが、バリデーション失敗時に自動リトライが行われるようにretries引数を指定します。

自動リトライ設定を追加した例 - example_4.py
from pydantic_ai import Agent

# 共通処理で宣言したSAMPLE_HTMLと厳密な記事一覧モデルをインポート
from article_common_strict import SAMPLE_HTML, ArticleList


# retriesオプションを追加
agent = Agent(
    "openai:gpt-5.2",
    output_type=ArticleList,
    instructions="与えられたHTMLから記事の一覧情報を抽出してください。",
    retries=3,  # バリデーション失敗時に最大3回リトライ
)

res = agent.run_sync(SAMPLE_HTML)

# res.outputはArticleList型のオブジェクトとして返される
for article in res.output.articles:
    print(article)

Agentクラスにretries=3を指定するだけで、バリデーションエラー → LLMへのフィードバック → 再生成のループが最大3回まで自動実行されます。また、エラー発生時のエラーメッセージは、Pydanticが生成する具体的な内容であるため、LLMへのリトライも明確になり、より正確なリトライへ繋げることができます。

このように、Pydantic AIではPydanticモデルの定義がLLM出力の検証ゲートになり、プロンプトの工夫に頼るだけでない、コードレベルの品質を保証することができます。

Pydantic AIのさらに便利な機能

ここまでは「HTMLを読んでメタデータを返す」というタスクをLLMに与えていました。しかし、実際のアプリケーション開発では、データベースへの問い合わせや外部APIの呼び出しなど、LLMだけでは完結しない処理が多くあります。

Pydantic AIでは、こうした外部処理をエージェントに組み込む仕組みとしてツール(Tools)が用意されています。ツールは、Python関数にデコレーターを付けるだけで登録でき、LLMが必要に応じて呼び出します。

ツール(Tools)を定義する

ここでは例として、各記事にカテゴリやタグを付与してみます。カテゴリの判定には記事本文を使用します。記事本文の処理をツールとして切り出すことで、⁠データ処理部分は確実な処理として組み込み、判定のみLLMに任せる」という役割分担を実現します。

なお、記事の取得処理部分もHTTPXなどのHTTPクライアントを使用して取得するのが一般的ですが、今回はその処理部分は割愛して以下のように記事が取得済みである想定とします。

取得済み記事として辞書を定義 - article_body.py
ARTICLE_BODIES: dict[str, str] = {
    "/article/2026/01/monthly-python-2601": "CrewAIは複数のAIエージェントを協調させるフレームワークです...",
    "/article/2025/12/monthly-python-2512": "PyreflyはMeta社が開発したRust製のPython型チェッカーです...",
    "/article/2025/11/monthly-python-2511": "t-stringはPython 3.14で導入されたテンプレート文字列リテラルです...",
    "/article/2025/10/monthly-python-2510": "Pydanticはデータバリデーションと設定管理のためのライブラリです...",
    "/article/2025/09/monthly-python-2509": "Python 3.14ではasyncioにタスク可視化機能が追加されました...",
    "/article/2025/08/monthly-python-2508": "PrefectはPythonベースのワークフロー管理フレームワークです...",
}

続いて、Pydanticの出力モデルにcategorytagsを追加します。

カテゴリとタグを出力モデルに追加した例 - snippet_1.py
from datetime import date
from typing import Literal

from pydantic import BaseModel, Field


class ArticleInfo(BaseModel):
    """記事のメタデータ(カテゴリ・タグ付き)"""

    title: str = Field(min_length=1, description="記事のタイトル")
    author: str = Field(min_length=1, description="著者名")
    published_date: date = Field(description="公開日")
    url: str = Field(description="記事のURL(相対パス)")
    # カテゴリは以下の選択肢のいずれかとする
    category: Literal[
        "Web開発",
        "型・ツール",
        "AI・機械学習",
        "パッケージ管理",
        "Python新機能",
        "その他",
    ] = Field(description="記事のカテゴリ")
    # タグは1~5個のリストとする
    tags: list[str] = Field(
        min_length=1,
        max_length=5,
        description="技術タグのリスト",
    )
    # field_validatorは前節と同じため省略

最後にエージェントとツールを以下のように定義します。

カテゴリとタグを判定されるAgentの例 - snippet_2.py
# Agentのinstructionsで、ツールを使って記事本文を参照するように指示
agent = Agent(
    "openai:gpt-5.2",
    output_type=ArticleList,
    instructions=(
        "与えられたHTMLから記事の一覧情報を抽出してください。"
        "各記事のカテゴリとタグを判定するために、"
        "fetch_article_bodyツールで記事本文を参照してください。"
    ),
    retries=3,
)


# fetch_article_bodyツールを定義
@agent.tool_plain
def fetch_article_body(url: str) -> str:
    """記事のURLを受け取り、記事本文の冒頭部分を返す。

    Args:
        url: 記事の相対パス(例: /article/2026/01/monthly-python-2601)
    """
    body = ARTICLE_BODIES.get(url)
    if body:
        return body
    return f"{url}: 本文が見つかりません"

Agentでは、instructions引数にカテゴリとタグ判別を追加しています。その際、fetch_article_bodyツールを使用するように指示することで、LLMは各記事に対してツールを呼び出し、本文を参照してカテゴリとタグが判定されます。

ツールは、@agent.tool_plainデコレーターを使用して関数をツールとして定義しています。ツールの特徴として、Pydantic AIが関数のdocstringをツールの説明文として自動的にLLMに渡すという機能があります。引数の説明もdocstring内のArgsセクションから抽出されるため、Pythonの標準的なドキュメンテーション慣習に従うだけでLLMへのツール説明が整うことになります。

ここまでのコードを合わせた完全版をexample_5.pyとして保存します。

実行結果は以下のようになり、カテゴリとタグがLLMによって判定されていることが確認できます。

カテゴリとタグをLLMに判定させた出力結果の例
$ python example_5.py
title='Pythonで始めるマルチエージェントAI ―CrewAI入門' author='杉田雅子'
published_date=datetime.date(2026, 1, 27) url='/article/2026/01/monthly-python-2601'
category='AI・機械学習' tags=['Python', 'マルチエージェント', 'AI', 'CrewAI']

title='Rustで書かれた高速Python型チェッカー「Pyrefly」の紹介' author='筒井隆次'
published_date=datetime.date(2025, 12, 4) url='/article/2025/12/monthly-python-2512'
category='型・ツール' tags=['Python', '型チェック', 'Pyrefly', 'Rust']

title='t-string:テンプレート文字列リテラルの紹介' author='鈴木たかのり'
published_date=datetime.date(2025, 11, 18) url='/article/2025/11/monthly-python-2511'
category='Python新機能' tags=['Python', 'Python 3.14', 't-string', '文字列']

title='データ検証ライブラリPydanticの紹介' author='寺田学'
published_date=datetime.date(2025, 10, 28) url='/article/2025/10/monthly-python-2510'
category='Web開発' tags=['Python', 'Pydantic', 'データ検証', 'スキーマ']

title='Python 3.14の新機能:asyncioタスク可視化機能を使ってみよう' author='福田隼也'
published_date=datetime.date(2025, 9, 30) url='/article/2025/09/monthly-python-2509'
category='Python新機能' tags=['Python', 'Python 3.14', 'asyncio', '可視化']

title='PrefectではじめるPythonワークフロー・フレームワーク' author='門脇諭'
published_date=datetime.date(2025, 8, 28) url='/article/2025/08/monthly-python-2508'
category='その他' tags=['Python', 'Prefect', 'ワークフロー', 'ETL']

依存性注入(Dependency Injection)

最後に依存性注入(Dependency Injection)について解説します。

前述のツール定義では、記事本文データARTICLE_BODIESをグローバル変数として定義していました。しかし実際のアプリケーションでは、⁠本番ではデータベースから取得し、テストではモックデータを使いたい」ということがあります。このような場面で役立つのが依存性注入(Dependency Injection)です。依存性注入とは、関数やクラスが必要とするリソース(依存)を外部から渡す方法のことです。

ここでは、前節のグローバル変数 ARTICLE_BODIES を依存性注入に置き換える例を示します。依存リソースはdataclassやTypedDictなど、Pythonの標準的な方法で定義できます。以下のように、後の処理でツールからアクセスできるように、記事本文の辞書をarticle_bodiesフィールドとしてクラスを定義します。

記事本文を依存データとしてdataclassを定義する例 - deps_snippet_1.py
from dataclasses import dataclass


@dataclass
class ArticleDeps:
    # エージェントが必要とする依存リソースをまとめたdataclass
    article_bodies: dict[str, str]

続いてAgentdeps_type引数にArticleDepsを渡します。これにより、Pydantic AIがツールやinstructionsの中でリソースへアクセスできるようになります。

依存リソースをAgentクラスに定義する例 - deps_snippet_2.py
from pydantic_ai import Agent

agent = Agent(
    "openai:gpt-5.2",
    output_type=ArticleList,
    deps_type=ArticleDeps,  # ← 依存リソースの型を宣言
    instructions=(
        "与えられたHTMLから記事の一覧情報を抽出してください。"
        "各記事のカテゴリとタグを判定するために、"
        "fetch_article_bodyツールで記事本文を参照してください。"
    ),
    retries=3,
)

ツールの宣言では、RunContextからリソースを受け取るようにします。以下のように、前述の@agent.tool_plainの代わりに@agent.toolデコレーターを使い、第1引数にRunContext[ArticleDeps]を指定して受け取ります。これにより、前述のdeps_typeパラメータで依存リソースの型を宣言し、RunContextを通じてツールやインストラクションの中からそのリソースにアクセスすることができます。

fetch_article_body関数内では、ctx.deps 属性を使用してctx.deps.article_bodiesのようにリソースにアクセスできるため、グローバル変数を参照する必要がなくなります。

記事本文を受け取るツールの例 - deps_snippet_3.py
from pydantic_ai import RunContext


@agent.tool
def fetch_article_body(ctx: RunContext[ArticleDeps], url: str) -> str:
    """記事のURLを受け取り、記事本文の冒頭部分を返す。

    Args:
        ctx: 実行コンテキスト(依存リソースへのアクセス手段)
        url: 記事の相対パス(例: /article/2026/01/monthly-python-2601)
    """
    body = ctx.deps.article_bodies.get(url)
    if body:
        return body
    return f"{url}: 本文が見つかりません"

最後にrun_sync()メソッドのdeps引数に依存リソースを渡します。エージェントはこの依存リソースを実行中に保持し、ツールが呼び出されるたび依存関係としてLLMに提供します。

run_syncメソッドに依存関係を渡す例 - deps_snippet_4.py
# 本番用データ(実際にはDBやAPIから取得)
prod_deps = ArticleDeps(
    article_bodies={
        "/article/2026/01/monthly-python-2601": "CrewAIは複数のAIエージェントを協調させるフレームワークです...",
        "/article/2025/12/monthly-python-2512": "PyreflyはMeta社が開発したRust製のPython型チェッカーです...",
        # ...
    }
)
# run_syncの第1引数は、モデルに解析させる対象データ(SAMPLE_HTML)
# deps引数は、ツール内で参照する外部依存データ(prod_deps)
res = agent.run_sync(SAMPLE_HTML, deps=prod_deps)
for article in res.output.articles:
    print(article)

ここまでのコードを合わせた完全版をexample_6.pyとして保存します。

依存性注入の最大のメリットは、テスト時に依存リソースを容易に差し替えられる点です。deps引数に渡すオブジェクトを切り替えるだけでテストと本番環境での実行が行えます。

依存性注入とグローバル変数の違いを簡単にまとめると以下のようになります。

比較項目 グローバル変数 依存性注入
テスト モック化が難しい deps を差し替えるだけ
本番・開発環境の切り替え コードの書き換えが必要 インスタンス生成時に分岐するだけ
可読性 依存関係が暗黙的 何に依存しているか型で明示される
並行実行 状態競合のリスクあり 実行ごとにdepsを渡すため安全

Pydantic AIの依存性注入は、deps_typeRunContextdeps の3つのキーワードを押さえるだけです。グローバル変数への依存をなくし、テスタブルで保守しやすいエージェントコードを書くために、ぜひ活用してみてください。

まとめ

本記事では、Pythonで型安全なLLMアプリを作成する「Pydantic AI」を紹介しました。

OpenAI APIを直接使う場合と比較すると、output_type にPydanticモデルを指定するだけでスキーマ生成・バリデーション・リトライが一貫して行われ、プロバイダーの差異もPydantic AIが吸収してくれるのは開発者にとって大きなメリットです。バリデーションの強化field_validator、型制約⁠⁠、ツールによる外部処理の組み込み、依存性注入によるテスタビリティの確保と、実際のアプリケーション開発で求められる要素が一通りカバーされています。

また、本記事では紹介しきれませんでしたが、Pydantic AIはMCP(Model Context Protocol)にも対応しています。本記事のような用途ではツールを直接定義する方がシンプルですが、⁠社内で共通ツールを管理したい」⁠Claude Desktopと同じツールをエージェントでも使いたい」といった場面では、MCPとの連携が有効な選択肢になります。興味のある方は、Pydantic AIのMCPについても試してみてください。

「LLMの出力をPydanticモデルで安全に受け止める」というシンプルな思想のもと、Pythonらしい書き方でエージェントを構築できるのがPydantic AIの魅力です。LLMをアプリに組み込む際の選択肢のひとつとして、ぜひ試してみてください。

おすすめ記事

記事・ニュース一覧