Lesson CH08-L02
Hand-off
制御を完全に別Agentに引き渡すハンドオフパターン。
- 読了目安
- 10 min
- Colab目安
- 15 min
- 合計
- 25 min
- 前提
- Delegation
関連: 公式ドキュメント
一行サマリ
Hand-off は アプリケーションコード側で「次にどの Agent を呼ぶか」を決定 し、前 Agent の出力を次 Agent の入力として 完全に制御を移譲 するパターン。Delegation と違って前 Agent には戻らないため、役割が完全に分離した工程パイプライン や、入力に応じたルーティング に向く。
ヒーロー: 制御を「次の Agent」に渡し切る
Delegation (Ch8-L01) は親が子を呼んで戻ってくる コール・リターン の関係。Hand-off は前の Agent が終わったら 役目を終え、後続 Agent が独自の deps / model / instructions で続きを担当します。前後 Agent はお互いを 知らなくてよい ため、結合度が下がります。
概念: 2 つの典型形
| 形 | 構造 | 例 |
|---|---|---|
| 直列パイプライン | A → B → C を順番に流す | 翻訳 → 校正 → 要約 / 分類 → 抽出 → 整形 |
| ルーティング | 最初の Agent が「どの専門 Agent に渡すか」を決定 | カスタマーサポート → (請求 / 技術 / 解約) ヘ振り分け |
直列は Python の単純な順次呼び出し、ルーティングは 最初の Agent の output_type に Enum / Literal を入れて分岐するパターンが定番です。
コード: 3 つのパターン
パターン 1: 直列パイプライン (要約 → 翻訳)
import asyncio
from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
model = GoogleModel('gemini-3-flash-preview')
summarizer = Agent(
model,
instructions='与えられた英語の文章を 3 文以内の英語で要約してください。',
)
translator = Agent(
model,
instructions='与えられた英文を自然な日本語に翻訳してください。',
)
async def pipeline(en_text: str) -> str:
# Hand-off 1: 要約
summary_en = (await summarizer.run(en_text)).output
# Hand-off 2: 翻訳 — summarizer の出力を translator の入力に
summary_ja = (await translator.run(summary_en)).output
return summary_ja
ja = asyncio.run(pipeline(
'Pydantic AI is a Python agent framework that focuses on type safety and developer experience...'
))
print(ja)ポイント:
- アプリケーションコード (Python の async 関数) が指揮者
- 各 Agent は前後を知らない (
summarizerはtranslatorの存在を知らない) - usage は 各 result.usage() を別々に取得 して必要なら合算する
パターン 2: ルーティング (分類 → 専門 Agent)
from typing import Literal
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
model = GoogleModel('gemini-3-flash-preview')
class RouteDecision(BaseModel):
category: Literal['billing', 'technical', 'cancel', 'other']
reason_ja: str = Field(description='判断理由 (30 字以内)')
router = Agent(
model,
output_type=RouteDecision,
instructions='ユーザー問い合わせを billing / technical / cancel / other に分類してください。',
)
billing_agent = Agent(model, instructions='請求関連の問い合わせに丁寧に答える担当です。')
tech_agent = Agent(model, instructions='技術トラブルの一次対応をする担当です。')
cancel_agent = Agent(model, instructions='解約手続きを案内する担当です。')
fallback_agent = Agent(model, instructions='その他の問い合わせを丁寧に受け止める担当です。')
async def handle(user_msg: str) -> str:
decision = (await router.run(user_msg)).output
target = {
'billing': billing_agent,
'technical': tech_agent,
'cancel': cancel_agent,
}.get(decision.category, fallback_agent)
final = (await target.run(user_msg)).output
return f'[{decision.category}] {final}'router は 「振り分けだけ」を仕事にする極小 Agent。本実装では具体応答を生成しないので トークン消費が小さい のがメリット。
パターン 3: Hand-off + 構造化引き継ぎ
工程間で 構造化データを丸ごと引き継ぎ たいとき。前 Agent の output_type=BaseModel を、次 Agent への入力テキストに整形して渡します。
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
model = GoogleModel('gemini-3-flash-preview')
class ExtractedInvoice(BaseModel):
vendor: str = Field(description='請求元')
total_yen: int = Field(description='合計金額 (円)')
items: list[str] = Field(description='品目リスト')
extractor = Agent(
model,
output_type=ExtractedInvoice,
instructions='請求書テキストから vendor / total_yen / items を抽出してください。',
)
approver = Agent(
model,
instructions='構造化された請求書情報を読み、承認可否と理由を 2 文で答えてください。',
)
async def review(invoice_text: str) -> str:
inv = (await extractor.run(invoice_text)).output # ExtractedInvoice
prompt = (
f'次の請求書を承認するかどうか判定してください:\\n'
f'- 請求元: {inv.vendor}\\n'
f'- 金額: {inv.total_yen} 円\\n'
f'- 品目: {", ".join(inv.items)}'
)
return (await approver.run(prompt)).output各 Agent は 専門の input/output 型 に集中でき、テストも容易になります。
観察: usage は集約されない
Hand-off では各 agent.run(...) の usage は 独立 です。Delegation のように usage=ctx.usage で集約されないため、必要なら自前で合算します。
r1 = await router.run(msg)
r2 = await target.run(msg)
total_in = r1.usage().input_tokens + r2.usage().input_tokens
total_out = r1.usage().output_tokens + r2.usage().output_tokensLogfire (Ch9) を使うと trace ID で繋がった可視化 ができ、自前合算なしで全体像が見えます。
まとめ
- アプリケーションコード が次の Agent を選ぶ Programmatic ハンドオフ
- 直列パイプライン + ルーティング が 2 大典型形
- 前後 Agent はお互いを知らない → 結合度が低い / テスト容易
- usage は集約されないので 自前合算 or Logfire
- 直列 4 段以上は Pydantic Graph (次レッスン) を検討
次レッスンでは Pydantic Graph — 状態と遷移を持つグラフでマルチステップ処理を組むパターンを扱います。
Colab で実際に動かす
本レッスンの内容を Google Colab 上で実行できるノートブックを用意しています。下のボタンから自分のColab環境に開けます (要 Google アカウント / GOOGLE_API_KEY)。
notebooks/ch08/02-handoff.ipynb