PydanticAI ビジュアルガイド

Lesson CH04-L03

Union型

複数候補の出力型をUnionで定義し、分岐を表現する。

読了目安
10 min
Colab目安
14 min
合計
24 min
前提
Streamed Output

関連: 公式ドキュメント

一行サマリ

output_type=Success | Error のような Union 型 を Agent に渡すと、LLM は 「どちらを返すか」を判断 して選んだ型のオブジェクトを返す。受け側は isinstance で分岐、または discriminated union で型を一発判別できる。

ヒーロー: 1 つの Agent が複数の "応答形" を持つ

「クエリの結果」と「クエリ失敗の理由」のように、応答が 質的に違う複数の形 を取りうる場面で Union が効きます。LLM 自身が「今の状況ならこっちの型」と選んでくれるので、呼び出し側は型で分岐するだけ。

図を読み込み中…
図1. 単一 output_type vs Union output_type

概念: Union を使うべき 2 つのシーン

シーン
成功 / 失敗の分岐在庫照会の結果 vs 在庫切れエラー
アクション選択"search" / "create" / "delete" の 3 種類のリクエスト型

Union[A, B] または Python 3.10+ の A | B の構文どちらでも書けます。

コード: 4 つのパターン

パターン 1: 成功 / 失敗 の Union

from pydantic import BaseModel
from pydantic_ai import Agent
 
class StockOK(BaseModel):
    product: str
    quantity: int
 
class StockError(BaseModel):
    product: str
    reason: str  # '不存在' / '在庫切れ' など
 
agent = Agent(
    'google-gla:gemini-3-flash-preview',
    output_type=StockOK | StockError,
    instructions='在庫システム照会の結果を返してください。在庫があれば StockOK、なければ StockError。',
)
 
# 利用側は isinstance で分岐
result = agent.run_sync('商品 X-1 の在庫は?')
if isinstance(result.output, StockOK):
    print(f'在庫あり: {result.output.product} × {result.output.quantity}')
else:
    print(f'❌ {result.output.product}: {result.output.reason}')

ポイント:

  • output_type=A | Bどちらの型でも返せる ことを LLM に通知
  • 利用側は isinstance で分岐 (Pyright も narrow してくれる)
  • LLM が「成功できそう」「無理そう」を判断して型を選ぶ

パターン 2: 3 種類以上のアクション (discriminated union)

Literal フィールドを 判別子 として使うと、LLM が型をきっちり選んでくれます。

from typing import Literal
from pydantic import BaseModel, Field
from pydantic_ai import Agent
 
class SearchAction(BaseModel):
    type: Literal['search'] = 'search'
    query: str
 
class CreateAction(BaseModel):
    type: Literal['create'] = 'create'
    title: str
    body: str
 
class DeleteAction(BaseModel):
    type: Literal['delete'] = 'delete'
    id: int
 
Action = SearchAction | CreateAction | DeleteAction
 
agent_a = Agent(
    'google-gla:gemini-3-flash-preview',
    output_type=Action,
    instructions=(
        'ユーザーの自然文を、3 種類のアクションのいずれかに変換してください。\\n'
        '- search: 検索したい場合\\n'
        '- create: 何かを作成したい場合\\n'
        '- delete: 削除したい場合'
    ),
)
 
result = agent_a.run_sync('「PydanticAI入門」というタイトルで記事を書きたい')
match result.output:
    case SearchAction(query=q):
        print(f'検索: {q}')
    case CreateAction(title=t, body=b):
        print(f'作成: {t} / {b[:40]}...')
    case DeleteAction(id=i):
        print(f'削除: id={i}')

ポイント:

  • 各型に type: Literal['xxx'] を共通フィールドとして持たせる
  • LLM はこの type フィールドで「どの型を組むか」を明示する
  • Python 3.10+ の match で型分岐を綺麗に書ける

パターン 3: Union[T, str] でフォールバック

「構造化できれば Booking、無理ならフリーテキスト」のように、フォールバック として str を混ぜることも可能です。

from datetime import date
from pydantic import BaseModel
from pydantic_ai import Agent
 
class Booking(BaseModel):
    customer: str
    booking_date: date
    party_size: int
 
agent_b = Agent(
    'google-gla:gemini-3-flash-preview',
    output_type=Booking | str,
    instructions=(
        '予約情報が抽出できれば Booking、'
        '不足や曖昧で抽出できなければ追加質問の文字列を返してください。'
    ),
)
 
# 不完全な入力で曖昧
result = agent_b.run_sync('明日予約お願いします')
if isinstance(result.output, Booking):
    print('予約完了:', result.output)
else:
    print('追加質問:', result.output)  # str

これで 「フォーマット強制 + 自然言語フォールバック」 の混在が綺麗に書けます。

パターン 4: List of Union (複雑データ)

list[A | B] も書けます。複数アクションの一括解析などに使えます。

agent_multi = Agent(
    'google-gla:gemini-3-flash-preview',
    output_type=list[SearchAction | CreateAction | DeleteAction],
    instructions='ユーザーの文章から、すべてのアクションを順番に抽出してください。',
)
 
result = agent_multi.run_sync('まず「Python」で検索して、次に「Note 1」を新規作成、最後に id=42 を削除して')
for action in result.output:
    match action:
        case SearchAction(query=q):
            print(f'1. search: {q}')
        case CreateAction(title=t):
            print(f'2. create: {t}')
        case DeleteAction(id=i):
            print(f'3. delete: id={i}')
図を読み込み中…
図2. discriminated union — type フィールドで分岐

いつ Union を使うか

  • ✅ 応答パターンが 質的に違う (成功 / 失敗、複数アクション)
  • isinstance または match で分岐 したいロジック
  • ✅ LLM が「どの形を返すか」を 状況から判断 する余地がある
  • ❌ 同じ形の中で 1 フィールドだけ違う → Annotated + Field で十分
  • ❌ 5 種類以上の Union → 複数 Agent (Multi-Agent / Ch8) に分割を検討

まとめ

  • output_type=A | B複数の応答型 を表現、LLM が状況に応じて選ぶ
  • 各型に type: Literal['x'] を持たせると discriminated union として確実に判別
  • 利用側は isinstance または match 文で分岐
  • Booking | str構造化 + フォールバック自然文 の併用も可能

これで Ch4「Structured Output」全 3 レッスンが完了です。次の Ch5「Capabilities」 からは、Gemini 固有の機能 (Web Search / Thinking / Safety Settings) を扱います。

Colab で実際に動かす

本レッスンの内容を Google Colab 上で実行できるノートブックを用意しています。下のボタンから自分のColab環境に開けます (要 Google アカウント / GOOGLE_API_KEY)。

Open in Colab

notebooks/ch04/03-union-output.ipynb