Lesson CH02-L01
DI入門
deps_typeとRunContextで依存値を注入する基本パターン。
- 読了目安
- 12 min
- Colab目安
- 18 min
- 合計
- 30 min
- 前提
- 静的型チェック
関連: 公式ドキュメント
一行サマリ
deps_type=YourDeps で Agent に 依存値の型 を宣言し、agent.run(..., deps=YourDeps(...)) で実行時に値を渡す — Agent 内のあらゆる関数は RunContext[YourDeps] 経由で ctx.deps として参照できる。これが PydanticAI の DI。
ヒーロー: Agent の "外" にあるべきもの
Agent 自身がやるべきは「LLM に投げる・結果を返す」だけです。HTTP クライアント・DB 接続・APIキー・設定値などは Agent の外で生成し、deps として注入 するのが正解です。
概念: なぜ Agent に DI が必要か
「依存性注入 (DI)」と呼ぶと大げさですが、目的はシンプルに次の 3 つです。
- テスト容易性 —
agent.run(..., deps=FakeDeps(...))で本物の HTTP / DB の代わりにモックを差し込める (Ch2-L02 で扱う) - 明示性 — Agent が "何に依存しているか" が引数として表に出る。グローバル変数で隠れない
- 再利用性 — 同じ Agent インスタンスをユーザー別 / 環境別の deps と組み合わせて使い回せる
PydanticAI の DI はフレームワーク特有の "コンテナ" を持たず、「deps_type という型 + RunContext 経由のアクセス」だけ のミニマル設計です。覚えるべきことは少ないです。
コード: 4 つのパターン
パターン 1: もっとも単純な Deps (in-memory store)
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
@dataclass
class CityDb:
cities: dict[str, str] # 国名 -> 首都名 の辞書
agent = Agent(
'google-gla:gemini-3-flash-preview',
deps_type=CityDb,
instructions='ユーザーの国名から首都を答えてください。',
)
@agent.instructions
def known_cities(ctx: RunContext[CityDb]) -> str:
countries = ', '.join(ctx.deps.cities.keys())
return f'参照可能な国は: {countries}'
deps = CityDb(cities={'日本': '東京', 'フランス': 'パリ', 'ブラジル': 'ブラジリア'})
print(agent.run_sync('フランスの首都は?', deps=deps).output)ポイント:
- Deps は
@dataclassで定義 するのが最もシンプル (mutable / コンストラクタ自動生成 / 型で見通しよし) deps_type=CityDbを Agent に宣言 →RunContext[CityDb]で受けられる- 同じ Agent を
agent.run(..., deps=CityDb({'日本': '東京'}))のように 呼び出しごとに別 deps で動かせる
パターン 2: 複数のリソースをまとめる
実プロジェクトでは依存が 1 つで済むことは稀です。1 つの dataclass に全部まとめる のが定石です。
import os
from dataclasses import dataclass, field
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class AppConfig:
api_base_url: str
user_lang: str
@dataclass
class AppDeps:
config: AppConfig
http: httpx.AsyncClient
cache: dict[str, str] = field(default_factory=dict)
agent = Agent(
'google-gla:gemini-3-flash-preview',
deps_type=AppDeps,
instructions='ユーザーの問いに答えてください。',
)
@agent.instructions
def lang(ctx: RunContext[AppDeps]) -> str:
return f'回答は {ctx.deps.config.user_lang} で。'
# 起動時に 1 度だけ重い初期化
async def make_deps() -> AppDeps:
return AppDeps(
config=AppConfig(api_base_url=os.environ['API_BASE'], user_lang='ja'),
http=httpx.AsyncClient(timeout=10.0),
)
# 利用側
deps = await make_deps()
print((await agent.run('Tokyo の現在気温は?', deps=deps)).output)ポイント:
AppConfigのような 不変設定 とhttpx.AsyncClientのような 長寿命オブジェクト を 1 つの dataclass に集約field(default_factory=dict)で 可変なフィールド にデフォルトを与える- 重い初期化は
make_deps()のような factory 関数 に分離して、テストではmake_test_deps()を用意
パターン 3: Tool / Validator から deps を参照
@agent.instructions だけでなく、Tool (Ch3) や output_validator からも同じ RunContext[Deps] で deps にアクセスできます。
from pydantic_ai import Agent, RunContext, ModelRetry
@dataclass
class Deps:
blocked_words: set[str]
agent = Agent(
'google-gla:gemini-3-flash-preview',
deps_type=Deps,
instructions='短い日本語で答えてください。',
)
@agent.output_validator
def no_blocked(ctx: RunContext[Deps], output: str) -> str:
for word in ctx.deps.blocked_words:
if word in output:
raise ModelRetry(f'「{word}」を含めずに言い換えてください。')
return output
deps = Deps(blocked_words={'秘密', 'パスワード'})
print(agent.run_sync('社内システムのログイン手順を 1 行で', deps=deps).output)deps は 1 つの run の中で instructions も output_validator も tool も同じインスタンスを共有 します。「ユーザーごとに違う設定」のような run スコープのデータを置くのに最適です。
パターン 4: ライフサイクル — 重いリソースの開閉
httpx.AsyncClient や asyncpg.Pool のようなリソースは 作成・破棄が高コスト です。async with で アプリ起動時に作って終了時に閉じる のが定石です。
import asyncio
async def main():
async with httpx.AsyncClient() as http:
deps = AppDeps(config=AppConfig('https://api.example.com', 'ja'), http=http)
# 何度でも run を回せる
for q in ['Tokyo', 'Paris', 'Brasilia']:
r = await agent.run(f'{q} の現在気温は?', deps=deps)
print(r.output)
# async with を抜けたら http が自動で close される
asyncio.run(main())設計のコツ
| コツ | なぜ |
|---|---|
| Deps は 1 つの dataclass にまとめる | Agent の依存関係が「型 1 個読めば全部わかる」ようになる |
| 不変値は frozen dataclass / Pydantic BaseModel | run 中に書き換えられない保証で事故防止 |
可変キャッシュは dict フィールド | run スコープのキャッシュをそのまま置ける |
| deps_type = run の "リクエストスコープ" | リクエスト/タスクが終わったら捨てる前提で設計 |
| factory 関数を別に作る | プロダクション用と TestModel 用の 2 種類の make_deps() を作りやすい (Ch2-L02) |
いつ Tool / Instructions / Deps のどれを使うか
PydanticAI には情報を Agent に流す手段が複数あります。混乱しがちなので役割を整理します。
| 手段 | 評価タイミング | 用途 |
|---|---|---|
| Static instructions | 起動時 (固定文字列) | 役割・口調・形式 |
Dynamic instructions (@agent.instructions) | run のたび | 軽い文脈 (時刻・ユーザー名・環境) |
Deps (deps_type + RunContext.deps) | run のたび | リクエストスコープのオブジェクト全部 (HTTP / DB / 設定) |
Tool (@agent.tool, Ch3) | LLM が判断したとき | LLM 自身が "取りに行く" 必要がある重い情報 |
Deps は「外から渡す軽〜中量のオブジェクト全般」と覚えればだいたい正しいです。
まとめ
deps_type=YourDeps+agent.run(..., deps=YourDeps(...))で外部リソースを安全に注入- 受け取り口は
RunContext[YourDeps]。instructions/output_validator/toolの全部から同じ deps を参照できる - Deps は 1 つの dataclass にまとめる。重い初期化は factory 関数 +
async withで - リクエストスコープの "コンテナ" として設計し、本物 / テストの差し替えで
Ch2-L02の TestModel と組み合わせる
次レッスンでは、ここで作った DI 構造を TestModel と組み合わせてユニットテスト する方法を扱います。LLM を呼ばずに Agent ロジックを完全テストするのが目標です。
Colab で実際に動かす
本レッスンの内容を Google Colab 上で実行できるノートブックを用意しています。下のボタンから自分のColab環境に開けます (要 Google アカウント / GOOGLE_API_KEY)。
notebooks/ch02/01-di-basics.ipynb