PydanticAI: Type-safeで堅牢なAIエージェントを構築する秘訣

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

LLMが「気まぐれ」で思い通りではないデータを返すとき

AI開発に携わっている方なら、GPTやClaudeからの結果をパースするのに苦労した経験があるはずです。実際、最も頭を悩ませるのはAIの知能の低さではありません。むしろ、データの返し方における「創造性」が高すぎることなのです。余計な説明を付け加えたり、括弧が抜けていたり、最悪の場合、勝手にuser_idcustomer_idに変更して後続のコードを壊したりします。

以前、カスタマーサポートのチケットを自動処理するプロジェクトに参加したことがありました。そこでは、ボットが統一性のない日付形式を返したために、システムが時々500エラーを吐いていました。パースエラーの発生率が15〜20%に達することもあり、データベースに保存する前にその混乱を片付けるためだけに、大量の正規表現や複雑なtry-exceptロジックを書く羽目になりました。

根本的な原因は、2つの世界のギャップにあります。LLMは確率に基づいて動作しますが、ソフトウェアは厳格なロジックに基づいて動作します。中間に保護層がなければ、AIエージェントシステムは常に「運任せ」の状態になってしまいます。

従来のやり方:手動パースか、それとも「重量級」フレームワークか?

PydanticAIを知る前、私や多くの開発者は次の2つの選択肢の間で頭を抱えていました:

  • 手動パース: json.loads() を使って各キーをチェックする方法です。これは非常に手間がかかり、コードが長く煩雑になるだけでなく、スキーマが大きくなるとメンテナンスが極めて困難になります。
  • 重量級フレームワークの使用: LangChainやLlamaIndexには OutputParser が備わっていますが、正直なところ、これらは時に肥大化しすぎています。抽象化レイヤーが多すぎるため、特にデータフローに深く介入する必要がある場合、デバッグが苦行になります。

私の苦い経験から言えるのは、プロンプトだけでAIに正しい形式を返させようと「教育」してはいけないということです。フレームワークの階層で、データ構造を厳格に遵守させる必要があります。

PydanticAI – Type-safeな課題への解決策

PydanticAIは、Python開発者の間で「国民的」データバリデーションライブラリであるPydanticの開発チームによって生み出されました。その哲学は非常に実用的で、LLMという不安定な世界にType-hintingの厳格さを持ち込むことです。

最大のメリットは、PydanticAIが結果のバリデーションをエージェントの中心に据えていることです。AIが誤ったスキーマを返した場合、フレームワークが自動的にエラーを指摘し、AIに再試行(retry)を要求します。これにより、システムは常に100%クリーンなデータを受け取ることができ、本番環境でのバグのリスクを最小限に抑えることができます。

実践:5分で作成するチケット分析エージェント

まずは、pipでライブラリを素早くインストールしましょう:

pip install pydantic-ai

以下は、コンテキストを理解させつつ、一分一厘違わぬ正確なデータを返させるためのエージェントの構成方法です:

from pydantic import BaseModel, Field
from pydantic_ai import Agent
from typing import List

# 1. 期待されるデータ構造の定義
class TicketAnalysis(BaseModel):
    priority: str = Field(description="優先度: Low, Medium, High")
    category: str = Field(description="カテゴリ: 技術、支払い、アカウント")
    tags: List[str] = Field(default_factory=list, description="関連するキーワード")
    summary_vi: str = Field(description="内容の要約(日本語)")

# 2. モデルと結果スキーマを使用してエージェントを初期化
agent = Agent(
    'openai:gpt-4o', 
    result_type=TicketAnalysis,
    system_prompt='あなたはチケット分析の専門家です。分析を行い、標準化されたデータを返してください。',
)

# 3. エージェントの実行
def analyze_customer_issue():
    user_input = "パスワードを変更したのにログインできません。アプリに500エラーが表示されます。"
    result = agent.run_sync(user_input)
    
    # 返されるデータはすでにオブジェクトであり、コード補完(IntelliSense)が利用可能
    data = result.data
    print(f"[{data.priority.upper()}] カテゴリ: {data.category}")
    print(f"要約: {data.summary_vi}")

analyze_customer_issue()

Dependency Injection:非常に価値のある機能

PydanticAIで特に気に入っている点は、Dependencies(依存関係)の処理方法です。エージェントがデータベースにアクセスしたり、外部APIを呼び出したりする必要がある場合、以前のようにトリッキーなグローバル変数を渡す必要はありません。

Deps クラスを定義して、それを直接エージェントに注入(inject)できます。この方法は、ユニットテストを書く際や、開発(Dev)環境と本番(Prod)環境でデータベース設定を柔軟に切り替える必要がある場合に非常に便利です。

from dataclasses import dataclass

@dataclass
class MyDeps:
    db_session: str  # 実際のDBセッション
    api_key: str

agent_with_deps = Agent('openai:gpt-4o', deps_type=MyDeps)

@agent_with_deps.tool
def get_user_info(ctx, user_id: int) -> str:
    # ctx.deps経由でスマートにdepsにアクセス
    return f"データベース {ctx.deps.db_session} のユーザー {user_id} はVIPです"

なぜ今、これが最良の選択肢なのか?

数ヶ月間、あらゆる種類のフレームワークを使い倒した結果、PydanticAIがこれほどまでに有用である3つの理由を導き出しました:

  1. 高い信頼性: バリデーション失敗時の自動再試行メカニズムにより、データ形式に関連する実行時エラーの90%を排除できます。
  2. 快適なコーディング: Pydanticを使用しているため、VS CodeやPyCharmのコード補完が非常に正確です。フィールドが int なのか str なのか、あるいは正確な名前は何だったかを記憶しておく必要はありません。
  3. 制御の容易さ: このフレームワークは、謎めいた抽象化レイヤーの背後にロジックを隠しません。純粋なPythonであるため、既存のCI/CDフローに簡単に統合できます。

本格的なAIエージェントプロジェクトを開始しようとしているなら、不安定な文字列操作は卒業して、このType-safeなアプローチを今すぐ試してみてください。信じてください、本番環境にデプロイした後の夜の眠りがずっと深くなるはずですから。

Share: