コンテンツにスキップ

第10章 エージェントの構築(Building Agents)


ここまでの9章で、エージェントを構成する要素はひととおり揃った。本章はそれをどう実装するかという問いに答える。

選択肢は大きく3つある。

第10章 図1

本章の立場を先に述べておく。まずスクラッチで一度書くべきである。 第5章で組み立てたループは50行程度であり、書けば「エージェントとは結局のところ while ループとツールディスパッチである」ということが体で分かる。この理解がないままフレームワークに乗ると、問題が起きたときに何が起きているのか分からなくなる。

そのうえで、本番システムでは適切な抽象化に乗ることを検討する。永続化、再開、可観測性、マルチエージェント — こうした機能を自前で作るのは相当な労力である。

本章の情報の鮮度について: フレームワークの世界は変化が速い。本章では2026年7月時点の状況を記す。ただし、AutoGen がメンテナンスモードに移行し、OpenAI Assistants API が2026年8月26日にサンセットするなど、大きな変化が進行中である。実装前には必ず公式ドキュメントで現況を確認してほしい。


10.2.1 何を自分で書くことになるか

Section titled “10.2.1 何を自分で書くことになるか”

第5章5.3節でループ本体は示した。ここでは、その周辺で自分で書く必要があるものを整理する。実際に本番運用しようとすると、ループ本体より周辺のほうが分量が多くなる。

要素 内容 参照
LLM APIの呼び出し 認証、リクエスト構築、レスポンスのパース 10.2.2
エージェントループ 継続・終了の判定、履歴の管理 第5章5.3節
ツールのディスパッチ 名前から実装への解決、引数の検証 第5章5.3.1節
出力のパース 構造化出力の取り出しと検証 10.2.3
エラーとレート制限 再試行、バックオフ、部分的失敗の扱い 10.2.4
コンテキスト管理 切り詰め、圧縮 第8章8.3節
予算と上限 ステップ数・コスト・時間 第5章5.4.1節
トレースとログ 何が起きたかの記録 第12章
永続化と再開 中断したところから続ける 10.2.5

第5章で見たとおり、基本形は「ツール定義を添えて呼び、tool_use が返ったら実行して tool_result を返す」の繰り返しである。ここで補足しておきたいのは、プロバイダ非依存にしたい場合の抽象化である。

複数のプロバイダに対応するなら、差異を吸収する薄い層を挟む。

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any
@dataclass
class ToolCall:
id: str
name: str
arguments: dict[str, Any]
@dataclass
class LLMResponse:
text: str
tool_calls: list[ToolCall]
stop_reason: str # "end_turn" | "tool_use" | "max_tokens" | ...
input_tokens: int
output_tokens: int
class LLMProvider(ABC):
"""プロバイダ間の差異を吸収する最小のインターフェース。"""
@abstractmethod
def complete(
self,
system: str,
messages: list[dict],
tools: list[dict],
max_tokens: int,
) -> LLMResponse: ...
@abstractmethod
def format_tool_result(self, call_id: str, content: str, is_error: bool) -> dict: ...

ただし、この抽象化を最初から作るべきかは慎重に判断してほしい。 第2章2.3節で見たとおり、プロバイダごとにパラメータ体系が異なる。たとえば Anthropic にはペナルティパラメータがなく、temperature の有効範囲も違う。無理に共通化すると、最小公倍数的な貧弱なインターフェースになるか、抽象化が漏れて結局分岐だらけになる。

単一プロバイダで始め、必要になってから抽象化するのが実務的である。

ツール呼び出しについては、各社のAPIが構造化された形(tool_use ブロックなど)で返してくれるため、パースの苦労はほとんどない。問題になるのは、ツール以外で構造化データが欲しい場合である。

素朴にやるなら「JSONで返して」とプロンプトで頼むことになるが、これは脆い。前後に説明文が付く、コードフェンスで囲まれる、末尾のカンマが混入する、といった揺れが起きる。

対策は3段階で考える。

① 構造化出力の機能を使う(最良)

各社が、スキーマへの適合を保証する機能を提供している。OpenAIでは strict: true による Structured Outputs、Anthropicではツールを1つだけ定義して tool_choice でそれを強制する方法が使える。

# Anthropic: 構造化出力を「ツールの強制呼び出し」として実現する
EXTRACT_TOOL = {
"name": "record_extraction",
"description": "問い合わせから抽出した情報を記録する。",
"input_schema": {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["billing", "technical", "sales"]},
"urgency": {"type": "integer", "minimum": 1, "maximum": 5},
"summary": {"type": "string"},
},
"required": ["category", "urgency", "summary"],
},
}
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
temperature=0, # 第2章2.3.1節
tools=[EXTRACT_TOOL],
tool_choice={"type": "tool", "name": "record_extraction"}, # 必ず呼ばせる
messages=[{"role": "user", "content": inquiry}],
)
extracted = next(b.input for b in response.content if b.type == "tool_use")

注意: 手動の extended thinking(thinking: {"type": "enabled"})を使っている場合、tool_choice の any / tool による強制は使えない。adaptive thinking なら併用できる(第3章3.3節)。

② 検証してから使う

構造化出力を使っていても、値の妥当性(業務上ありえない組み合わせなど)までは保証されない。Pydanticなどで検証する。

③ 検証に失敗したらモデルに直させる

パースや検証に失敗したら、エラー内容を添えて再試行する。第7章7.5.1節の「エラーは回復のための情報である」という原則は、ここにも適用される。

from pydantic import BaseModel, ValidationError, Field
class Extraction(BaseModel):
category: str = Field(pattern="^(billing|technical|sales)$")
urgency: int = Field(ge=1, le=5)
summary: str = Field(min_length=1, max_length=500)
def extract_with_repair(inquiry: str, max_attempts: int = 3) -> Extraction:
messages = [{"role": "user", "content": inquiry}]
for attempt in range(max_attempts):
response = call_with_tool(messages)
block = next(b for b in response.content if b.type == "tool_use")
try:
return Extraction(**block.input)
except ValidationError as e:
if attempt == max_attempts - 1:
raise
# 何がどう不正だったかを具体的に伝えて直させる。
# 検証エラーは必ず tool_result として返すこと(下の注意を参照)
messages += [
{"role": "assistant", "content": response.content},
{"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content":
f"抽出結果が検証に失敗しました:\n{e}\n"
"指摘された項目を修正して、もう一度 record_extraction を呼んでください。",
"is_error": True,
}]},
]
raise RuntimeError("到達不能")

tool_result は必ず直後に置く。 ここで「検証に失敗しました」を素のテキストのuserメッセージとして送りたくなるが、これはAPIエラーになる。仕様上、tool_result ブロックは対応する tool_use ブロックの直後のメッセージに置かなければならず、あいだに他のメッセージを挟むことはできない。違反すると tool_use ids were found without tool_result blocks immediately after というエラーが返る。

この制約のため、tool_use ブロックの id を保持しておく必要がある。b.input だけを取り出して id を捨てると、再試行の実装で行き詰まる。第5章5.3.2節でブロックそのものを保持していたのは、このためでもある。

10.2.4 エラーとレート制限の処理

Section titled “10.2.4 エラーとレート制限の処理”

第1章1.4.2節で再試行の基本形を示した。ここではエージェント特有の論点を扱う。

① レート制限は2種類ある。 リクエスト数(RPM)とトークン数(TPM)である。エージェントは1タスクで何十回も呼ぶため、どちらも枯渇しうる。特にコンテキストが膨らむとTPMのほうが先に尽きる。

② 再試行にはコストがかかる。 通常のAPIと違い、エージェントの再試行は「巨大な入力を再送する」ことを意味する。第2章2.4節で見たとおり、20ステップ目の入力は数万トークンに達しうる。再試行の回数は控えめに設定し、それより並行度を下げるほうが有効なことが多い。

③ 部分的失敗をどう扱うか。 並列にツールを実行しているとき、1つだけ失敗した場合の扱いを決めておく必要がある。原則は「失敗したものだけをエラーとして返し、成功したものはそのまま返す」である。全体を失敗にするとモデルは何が起きたか分からない。

import asyncio
async def execute_all(registry, tool_uses) -> list[dict]:
"""並列実行。1つの失敗が他を巻き込まないようにする。"""
outcomes = await asyncio.gather(
*(registry.execute_async(tu.name, tu.input) for tu in tool_uses),
return_exceptions=True, # 例外もまとめて受け取る
)
results = []
for tu, out in zip(tool_uses, outcomes):
# BaseException で受けること。asyncio.CancelledError は Python 3.8 以降
# Exception ではなく BaseException の直接の子であり、Exception だけで
# 判定するとタイムアウト時にこの分岐に入らず AttributeError になる
if isinstance(out, BaseException):
results.append({
"type": "tool_result", "tool_use_id": tu.id,
"content": f"ツールの実行に失敗しました: {type(out).__name__}: {out}",
"is_error": True,
})
else:
results.append({
"type": "tool_result", "tool_use_id": tu.id,
"content": out.content, "is_error": out.is_error,
})
return results

return_exceptions=True が要点である。これがないと、1つの例外が gather 全体を中断させ、成功した結果まで捨てることになる。

④ 並行度を制御する。 複数のエージェントを同時に走らせる場合、レート制限に当たりやすい。セマフォで上限を設ける。

class RateLimitedClient:
def __init__(self, client, max_concurrent: int = 5):
self._client = client
self._sem = asyncio.Semaphore(max_concurrent)
async def create(self, **kwargs):
async with self._sem:
return await self._client.messages.create(**kwargs)

長時間動くエージェントでは、途中で落ちたときに最初からやり直さない仕組みが要る。これはスクラッチ実装で最も面倒な部分であり、フレームワークを検討する主な動機のひとつでもある。

最低限必要なのは、各ステップの終了時に次を保存することである。

  • メッセージ履歴(そのまま復元できる形で)
  • 予算の消費状況(第5章5.4.1節)
  • ツールの副作用の記録(どこまで実行済みか)

3つめが重要である。「メールを送信した」という副作用は、再開時に繰り返してはならない。第7章7.5.3節の冪等キーが効いてくる。


10.3 ネイティブなツール呼び出し

Section titled “10.3 ネイティブなツール呼び出し”

各プロバイダは、ツール呼び出しをAPIレベルで提供している。第4章・第5章で見たAnthropicの形式を基準に、他社との違いを整理する。

すでに詳しく見たので要点のみ再掲する。

tools = [{
"name": "get_weather",
"description": "...",
"input_schema": {"type": "object", "properties": {...}, "required": [...]},
}]
  • ツール定義はフラット(name / description / input_schema がトップレベル)
  • モデルの要求は tool_use ブロック、結果は tool_result ブロックで返す
  • tool_choice は auto / any / tool / none(第5章5.4.1節)
  • 複数ツールの並列呼び出しに対応

Tool Runner SDK という補助機能もある。ループを自動で回してくれるもので、スクラッチとフレームワークの中間に位置する。

from anthropic import Anthropic, beta_tool
import json
client = Anthropic()
@beta_tool
def get_weather(location: str, unit: str = "celsius") -> str:
"""指定した地点の現在の天気を取得する。
Args:
location: 都市名と国名。例: Tokyo, Japan
unit: 温度の単位。'celsius' または 'fahrenheit'
"""
return json.dumps({"temperature": "20°C", "condition": "晴れ"})
runner = client.beta.messages.tool_runner(
model="claude-opus-5",
max_tokens=1024,
tools=[get_weather],
messages=[{"role": "user", "content": "東京の天気は?"}],
)
final_message = runner.until_done()

デコレータが型ヒントとdocstringから自動でスキーマを生成する点が便利である。ただし人間の承認を挟みたい場合やカスタムのログを取りたい場合は、手動ループのほうが適するとドキュメント自身が述べている。第5章で組み立てたようなループが必要になる場面は残る。

OpenAIには2つのAPIがあり、ツール定義の形が異なる。 これが混乱の元になるので明確にしておく。

# Responses API(現行の推奨)— フラット
{
"type": "function",
"name": "get_horoscope",
"description": "...",
"parameters": {...},
"strict": True,
}
# Chat Completions API(従来)— "function" キーの下に入れ子
{
"type": "function",
"function": {
"name": "get_horoscope",
"description": "...",
"parameters": {...},
"strict": True,
},
}

第1章1.4.3節で「JSON Schemaを包む外側の構造はプロバイダごとに異なる」と述べたが、同じプロバイダの中でもAPIによって異なる。コピーしたコードが動かない原因の定番である。

Responses API の完全な往復は次のとおり。

from openai import OpenAI
import json
client = OpenAI()
tools = [{
"type": "function",
"name": "get_weather",
"description": "指定した地点の現在の天気を取得する。",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
"additionalProperties": False, # strict モードでは必須
},
"strict": True,
}]
input_list = [{"role": "user", "content": "東京の天気は?"}]
response = client.responses.create(model="gpt-5.6-terra", tools=tools, input=input_list)
input_list += response.output
for item in response.output:
if item.type == "function_call":
args = json.loads(item.arguments) # arguments は JSON 文字列
result = execute_weather(args["location"])
input_list.append({
"type": "function_call_output",
"call_id": item.call_id, # tool_use_id に相当
"output": result,
})
final = client.responses.create(model="gpt-5.6-terra", tools=tools, input=input_list)
print(final.output_text)

Anthropicとの主な差異を整理する。

Anthropic OpenAI (Responses)
スキーマのキー input_schema parameters
引数の型 dict(パース済み) JSON文字列(自分で json.loads)
呼び出しのID tool_use.id function_call.call_id
結果の返し方 tool_result ブロック function_call_output
スキーマ厳格化 — strict: True(要 additionalProperties: false と全項目 required)

strict: True は有用な機能である。スキーマへの適合が保証されるため、パースエラーが実質的になくなる。制約として、すべてのフィールドを required にする必要があり、任意項目は型に null を含める形で表現する。

10.3.3 OpenAI — Assistants API(サンセット間近)

Section titled “10.3.3 OpenAI — Assistants API(サンセット間近)”

ロードマップは Assistants API を挙げているが、2026年8月26日にサンセットする。本書執筆時点(2026年7月29日)から1か月を切っている。

新規に採用してはならない。 既存の実装は Responses API と Conversations API への移行が必要である。

あわせて、他にも移行が必要な機能がある。

機能 非推奨化 停止 移行先
Assistants API 2025-08-26 2026-08-26 Responses API + Conversations API
Agent Builder 2026-06-03 2026-11-30 ChatKit / Agents SDK / ChatGPT Workspace Agents
Evals Platform 2026-06-03 2026-11-30 Promptfoo など(第11章)
Reusable Prompts 2026-06-03 2026-11-30 プロンプトをコードに直接持つ

この表は、本書が繰り返し述べてきた「プロバイダ固有の高水準機能に深く依存すると移行コストが発生する」という論点の実例である。 Assistants API はスレッド管理・ファイル検索・コード実行を統合した便利な仕組みだったが、2年ほどで畳まれることになった。

猶予期間にも注目してほしい。Assistants API は非推奨化からサンセットまでちょうど1年が与えられたが、Agent Builder や Evals Platform は約6か月である。抽象度の高い機能ほど、その寿命と移行猶予を見積もったうえで採用する必要がある。

Gemini にも2つのAPIがある。 OpenAI と同じ構図であり、こちらのほうが混乱しやすい。現在の推奨は Interactions API で、従来の generateContent はレガシー扱いになっている。

Interactions API(現行の推奨)

  • ツール定義はフラット(type / name / description / parameters がトップレベル)。OpenAI の Responses API とよく似た形である
  • モデルの要求は function_call ステップ(name / arguments / id)。arguments はパース済みのオブジェクトであり、OpenAI のように JSON 文字列ではない
  • 結果は function_result ステップで返す(name / call_id / result)
  • 強制呼び出しは generation_config の tool_choice。値は auto / any / none / validated(プレビュー)
tools = [{
"type": "function",
"name": "get_weather",
"description": "指定した地点の現在の天気を取得する。",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
}]
# 結果の返し方
{
"type": "function_result",
"name": "get_weather",
"call_id": call_id,
"result": [{"type": "text", "text": "20°C、晴れ"}],
}

generateContent API(レガシー)

  • ツール定義は function_declarations の配列で包む
  • 結果は function_response として返す
  • 強制呼び出しは function_calling_config の mode

ネット上の Gemini のサンプルコードは、まだ大半がレガシー側の記法である。function_declarations で包んでいるコードを見たら、それは旧APIのものだと判断できる。

Anthropic OpenAI (Responses) Google (Interactions)
ツール定義 フラット フラット フラット
スキーマのキー input_schema parameters parameters
引数の型 dict JSON文字列 dict
呼び出しID tool_use.id function_call.call_id function_call.id
結果の返し方 tool_result ブロック function_call_output function_result ステップ
強制呼び出し tool_choice tool_choice generation_config.tool_choice
スキーマ厳格化 — strict: True tool_choice: "validated"(プレビュー)

3社に共通する構造は次のとおりである。名前・説明・JSON Schemaでツールを定義し、モデルが「呼びたい」という構造化データを返し、アプリケーションが実行して結果を返す。 第4章4.4.2節で述べた「実行の主導権はアプリケーション側にある」という原則は、どのプロバイダでも変わらない。

差異はキー名と細部の作法にとどまる。したがって、第4〜7章で学んだ設計の考え方はプロバイダを問わず通用する。

一方で、同一プロバイダ内で新旧2つのAPIが併存している点には注意が必要である。OpenAI(Responses / Chat Completions)も Google(Interactions / generateContent)もそうなっている。ネット上のサンプルや生成されたコードが「どちらのAPIのものか」を見分けられないと、動かない原因が分からなくなる。


10.4.1 フレームワークが提供するもの

Section titled “10.4.1 フレームワークが提供するもの”

自分で書くと大変で、フレームワークなら手に入るものを列挙する。

機能 自前実装の労力
永続化と再開(durable execution) 大
チェックポイントと巻き戻し 大
マルチエージェントの協調・委譲 大
Human-in-the-Loop の中断・再開 中〜大
ストリーミングの統一的な扱い 中
トレースと可観測性 中
プロバイダ抽象化 中
多数のツール・データソース連携 中

特に永続化と再開は、自前で作ると相当に厄介である。「20ステップ目で落ちたので18ステップ目から再開する」を正しく実装するには、状態のスナップショットと副作用の追跡が必要になる。ここがフレームワークを採用する最大の動機になることが多い。

10.4.2 主要フレームワークの現況(2026年7月)

Section titled “10.4.2 主要フレームワークの現況(2026年7月)”

LangGraph / LangChain

グラフベースのオーケストレーション基盤。ノードが処理、エッジが遷移を表す状態機械として書く。永続化・再開・ストリーミング・Human-in-the-Loop が中核機能であり、この分野では最も成熟している。

現在の整理では、LangChain がエージェントフレームワーク、LangGraph がその下で動くオーケストレーション・ランタイムという関係になっている。単純なエージェントは create_agent で作れる。

from langchain.agents import create_agent
from langchain.tools import tool
@tool
def search(query: str) -> str:
"""情報を検索する。"""
return f"検索結果: {query}"
agent = create_agent(model, tools=[search])
result = agent.invoke({"messages": [{"role": "user", "content": "..."}]})

より細かい制御が要るなら StateGraph を直接組む。

  • 強み: 明示的な制御、耐久実行、監査しやすさ。規制の厳しい環境や複雑な分岐に向く
  • 弱み: 定型コードが多く、学習コストが高い。単純なタスクには過剰
  • 向く場面: 複雑な多段処理、失敗からの復旧が必要なもの

CrewAI

役割ベースのマルチエージェント協調。Crew(協調して働くエージェントのチーム)と Flow(状態管理とイベント駆動の制御フロー)という2階層で構成される。推奨される使い方は「Flow をアプリケーションの骨格にし、複雑な部分で Crew を呼ぶ」形である。

  • 強み: 立ち上がりが速い。役割分担の記述が直感的
  • 弱み: 単純な単一エージェントには過剰。トークンのオーバーヘッドが大きい(単純なワークフローで LangGraph の最大3倍という独立ベンチマークの報告がある)
  • 向く場面: 専門役割を持つ複数エージェントの協調、プロトタイプ

OpenAI Agents SDK

エージェント・ハンドオフ・ガードレール・セッションという軽量なプリミティブを提供する。抽象化が薄く、トレーシングが組み込まれている。名前に反して100以上のモデルに対応しており、OpenAI専用ではない。

  • 強み: 抽象化のオーバーヘッドが小さい。トレーシング内蔵
  • 弱み: まだ 0.x 系でありAPIが安定していない
  • 向く場面: OpenAIエコシステム中心の構成、軽量なマルチエージェント

Pydantic AI

型安全を前面に出したフレームワーク。入出力がPydanticで検証され、OpenTelemetryによる計装が組み込まれている。

  • 強み: 検証と可観測性。構造化されたテスト可能なコードになる
  • 弱み: Python専用
  • 向く場面: 型と検証を重視するPythonチーム

Microsoft Agent Framework

AutoGen と Semantic Kernel を統合したもので、2026年4月に v1.0 に到達した。グラフによるオーケストレーション、可観測性、ガバナンス機能(タスク遵守の監視、PII検出、プロンプトインジェクション対策)を備える。

  • 向く場面: .NET / Azure 中心の企業システム

LlamaIndex

RAGとデータ連携に強みを持つ。多数のデータソースコネクタとインデックス構造を提供する。エージェント機能も持つが、中心的な価値はRAG基盤にある。

  • 向く場面: 検索・知識ベースが中心のエージェント(第3章・第8章)

Haystack

パイプライン指向のNLPフレームワーク。検索・QA・RAGのコンポーネントを組み合わせて構成する。エージェント機能も追加されている。

  • 向く場面: 検索パイプラインの構築、既存のNLPワークフローとの統合

smolagents

Hugging Face による極小のフレームワーク(エージェントのロジックがおよそ1,000行に収まる)。2種類のエージェントを提供する。

  • CodeAgent(既定): ReActと同様に動くが、行動をPythonコードとして書いて実行する点が特徴的である

  • ToolCallingAgent: 従来型の、組み込みのツール呼び出しを使う方式

  • 強み: 徹底して単純。読めば全部わかるので学習教材として優れている

  • 弱み: 複雑なワークフローのための機能が乏しい。リリース頻度が低い

  • 向く場面: 軽い自動化、学習目的

コードを生成して実行する CodeAgent は、第7章7.6.2節で扱ったコード実行のリスクをそのまま抱える。サンドボックス実行は必須である。

ロードマップの「Smol Depot」について: これは Hugging Face の smolagents を指していると思われる。同名の別プロジェクトは確認できない。

AutoGen — メンテナンスモードに移行

ロードマップは AutoGen を挙げているが、現在は新機能の開発が行われていない。公式リポジトリは「AutoGen はメンテナンスモードに入った。新機能や機能強化は行われず、今後はコミュニティ管理となる」と明記している。後継は上述の Microsoft Agent Framework である。

つまり保守の担い手が Microsoft からコミュニティへ移っている。既存のワークロードに破壊的変更は予定されていないとされるが、新規採用は避けるべきである。

フレームワーク 中核の抽象 主な強み 注意点
LangGraph / LangChain 状態グラフ 耐久実行、制御の明示性、成熟度 定型コードと学習コスト
CrewAI 役割とクルー 立ち上がりの速さ トークンのオーバーヘッド
OpenAI Agents SDK エージェントとハンドオフ 軽量、トレーシング内蔵 0.x 系でAPIが不安定
Pydantic AI 型安全なエージェント 検証、OpenTelemetry Python専用
Microsoft Agent Framework グラフとワークフロー 企業向けガバナンス .NET / Azure 寄り
LlamaIndex インデックスとクエリ RAG基盤 エージェント機能は副次的
Haystack パイプライン 検索・QA エージェントは後付け
smolagents コード生成型と従来型の2種 極小・可読 機能が限定的
AutoGen 会話するエージェント群 — メンテナンスモード・コミュニティ管理。新規採用非推奨

10.4.4 フレームワーク選択が性能に効く

Section titled “10.4.4 フレームワーク選択が性能に効く”

見落とされがちな事実として、オーケストレーションの設計は、モデル選択に匹敵するほど性能に影響する。同一モデルでも、足回り(scaffold)が違えばベンチマークのスコアが大きく変わるという報告がある。

これは第7章7.1節で見た「ツール設計への投資がプロンプト調整より効果的だった」という観察と同じ方向を指している。モデルの周辺をどう作るかが、モデルそのものと同じくらい重要である。


第10章 図2

10.5.2 フレームワークを「選ばない」判断

Section titled “10.5.2 フレームワークを「選ばない」判断”

フレームワークには代償がある。

① 抽象化の漏れ。 問題が起きたとき、フレームワークの内部を読む羽目になる。第5章のループを理解していれば読めるが、していなければ手詰まりになる。

② コンテキストの見えにくさ。 フレームワークが組み立てたプロンプトが何なのか分かりにくいことがある。第2章のコスト構造や第8章のコンテキスト管理を制御したいとき、これは障害になる。CrewAI のトークンオーバーヘッドが大きいという報告は、この種の不透明さの現れである。

③ 変化の速さ。 本章で見たとおり、AutoGen はメンテナンスモードに入り、Assistants API はサンセットする。依存先の寿命はリスクである。

④ 過剰性。 多くのエージェントは、第5章のループ + 第7章のツール + 第8章の context editing で十分に動く。永続化も再開もマルチエージェントも要らないなら、フレームワークの価値は限定的である。

実務的な指針を示す。

  • まずスクラッチで動くものを作る。 何が必要かは、作ってみないと分からない
  • 具体的に困った機能が出てきてからフレームワークを検討する。「あったほうが良さそう」で選ばない
  • 採用するなら、フレームワークが生成しているプロンプトとコンテキストを一度は自分の目で確認する
  • 移行可能性を残す。 ツールの実装本体はフレームワークに依存しない純粋な関数として書き、フレームワークは薄い接続層に留める

最後の点は重要である。第7章で設計したツールが、フレームワークのデコレータと密結合していなければ、乗り換えは接続層の書き換えで済む。

# ✅ 移行しやすい: 実装は純粋な関数
def search_internal_docs(query: str, doc_type: str | None = None) -> str:
"""(第7章で設計した実装。フレームワークに依存しない)"""
...
# フレームワークへの接続は薄い層で行う
langchain_tool = tool(search_internal_docs)
anthropic_tool = beta_tool(search_internal_docs)

  • 実装の選択肢は3つ: スクラッチ・SDK付属のループ・フレームワーク
  • まずスクラッチで一度書く。 エージェントが while ループとツールディスパッチであることを理解していないと、フレームワークで問題が起きたときに手が出ない
  • スクラッチで面倒なのはループ本体ではなく、出力のパース・エラーとレート制限・永続化と再開である
  • 構造化出力はプロバイダの機能を使うのが最良。失敗したらエラーを添えて直させる
  • エージェントの再試行は巨大な入力の再送を意味する。回数を増やすより並行度を下げるほうが有効なことが多い
  • 並列ツール実行では return_exceptions=True を使い、1つの失敗が成功した結果を巻き込まないようにする
  • 3社のツール呼び出しは構造が同じで、キー名と作法が違うだけである。第4〜7章の設計の考え方はプロバイダを問わず通用する
  • 同一プロバイダ内で新旧2つのAPIが併存している。 OpenAI は Responses(フラット)と Chat Completions(入れ子)、Google は Interactions(現行)と generateContent(レガシー)。サンプルコードがどちらのものか見分けられないと詰まる
  • 検証エラーをモデルに返すときも、必ず tool_result として tool_use の直後に置く。 素のテキストで返すとAPIエラーになる
  • OpenAI Assistants API は2026年8月26日にサンセットする。 新規採用してはならない。猶予は非推奨化から1年、より新しい機能では約6か月しかない
  • AutoGen はメンテナンスモードに移行し、コミュニティ管理になった。 後継は Microsoft Agent Framework(2026年4月に v1.0)
  • フレームワークの最大の価値は永続化と再開にある。ここが不要なら採用の動機は弱い
  • オーケストレーションの設計は、モデル選択に匹敵するほど性能に効く
  • フレームワークの代償は、抽象化の漏れ・コンテキストの不透明さ・依存先の寿命・過剰性である
  • ツールの実装は純粋な関数として書き、フレームワークは薄い接続層に留める。 移行可能性を残す

問1 第5章5.3節で示したスクラッチのループを本番運用するために、本章10.2.1節の表のうち「まだ実装していないもの」を挙げよ。そのうち、次の3つの状況それぞれで最優先で実装すべきものを1つずつ選び、理由を述べよ。

(a) 社内向けの調査アシスタント。1タスク5〜10ステップ、失敗したらユーザーがやり直せばよい (b) 夜間バッチで1万件の文書を処理する。1件あたり3ステップ (c) 数時間かかるコード移行作業を自律実行する

問2 次のコードには、エージェント特有の問題が2つある。指摘し、修正せよ。

async def run_tools(registry, tool_uses):
outcomes = await asyncio.gather(
*(registry.execute_async(tu.name, tu.input) for tu in tool_uses)
)
return [{"type": "tool_result", "tool_use_id": tu.id, "content": o.content}
for tu, o in zip(tool_uses, outcomes)]

問3 Anthropic向けに書かれた次のツール定義を、OpenAI の Responses API 向けに書き直せ。strict: True を有効にすること。変更点を箇条書きで説明すること。

なお status と limit は任意項目のつもりで書かれているが、strict モードではすべての項目を required にする必要がある。この2つをどう表現するか、10.3.2節の記述を踏まえて設計し、その判断も説明に含めよ。その際、default のようなキーワードが strict モードで使えるとは限らない点にも触れること。

{
"name": "search_orders",
"description": "顧客の注文履歴を検索する。",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {"type": "string"},
"status": {"type": "string", "enum": ["pending", "shipped"]},
"limit": {"type": "integer", "default": 10},
},
"required": ["customer_id"],
},
}

問4 あるチームが、AutoGen で構築したマルチエージェントシステムを本番運用している。10.4.2節の状況を踏まえ、次に答えよ。

(a) ただちに移行すべきか。判断の材料となる要素を3つ挙げよ (b) 移行するとした場合、10.4.3節の表からどの選択肢が考えられるか。チームがPython中心である場合とAzure中心である場合に分けて述べよ (c) 10.5.2節の最後で述べた「移行可能性を残す」設計が事前にできていた場合、移行コストはどう変わるか

問5 次の3つのプロジェクトについて、10.5.1節のフローチャートに従って実装方法を選び、理由を述べよ。フローチャートだけでは決まらない場合、追加で確認すべきことを挙げよ。

(a) 問い合わせメールを分類して担当部署にタグ付けする。1日5,000件 (b) 顧客ごとに月次レポートを生成する。データ収集から作図まで20ステップ程度、途中で失敗することがある (c) 社内の技術文書について質問に答えるチャットボット。RAGが中心

問6 10.4.4節で「オーケストレーションの設計はモデル選択に匹敵するほど性能に効く」と述べた。本書のこれまでの章から、この主張を支持する具体例を3つ挙げ、それぞれどの章の内容かを示せ。


出典 内容 参照日
Anthropic — Tool Runner @beta_tool デコレータ、tool_runner、until_done()、手動ループを使うべき場面 2026-07-29
OpenAI — Function Calling Responses API と Chat Completions のツール定義の差、strict モード、function_call_output 2026-07-29
Anthropic — Handle Tool Calls tool_result を tool_use の直後に置く制約 2026-07-29
Google — Gemini Function Calling Interactions API(現行)のツール定義・function_result・tool_choice、generateContent がレガシーであること 2026-07-29
smolagents (GitHub) CodeAgent と ToolCallingAgent、約1,000行という規模 2026-07-29
microsoft/autogen (GitHub) メンテナンスモードとコミュニティ管理への移行 2026-07-29
OpenAI — Deprecations Assistants API のサンセット日(2026-08-26)、Agent Builder / Evals Platform / Reusable Prompts の停止予定 2026-07-29
LangChain — Agents create_agent の現行API 2026-07-29
LangGraph — Overview LangChain と LangGraph の役割分担、StateGraph、耐久実行 2026-07-29
CrewAI — Introduction Crews と Flows の役割分担 2026-07-29
Langfuse — Comparing Open-Source AI Agent Frameworks 各フレームワークの中核抽象・強み・弱み 2026-07-29
Uvik — Agentic AI Frameworks 2026 本番採用の状況、CrewAI のトークンオーバーヘッド、足回りによる性能差 2026-07-29
VentureBeat — Microsoft retires AutoGen and debuts Agent Framework AutoGen と Semantic Kernel のメンテナンスモード移行、Microsoft Agent Framework 2026-07-29
roadmap.sh — AI Agents Roadmap 章構成の基準 2026-07-29

次章予告: 第11章では評価とテストを扱う。本書はここまで何度も「評価セットで測る」と述べてきたが、その中身にあたる章である。追跡すべき指標、ツールの単体テスト、フローの統合テスト、Human-in-the-Loop 評価、そして LLM-as-a-Judge の実際を見ていく。

Built with Astro ・ Deployed on Cloudflare Pages

© 2026 watakumi — made with 💜 & ☕ ・watakumi.page