7月30日、Shittu Olumideが「The End-to-End Agentic AI Pipeline」と題した記事を公開した。プロダクション品質のAIエージェントシステムを構成する7つのアーキテクチャコンポーネントとその役割を、単体で実行可能なコード例とともに詳しく解説している。
「AIエージェントを作ろう」系のチュートリアルの大半は、LLMをループで呼び出す40行のスクリプトで終わる。デモとしては動く。しかし、並行ユーザーが2人になった瞬間、外部APIが不安定になった瞬間、タスクが想定の6倍の手順を要した瞬間に崩壊する。
デモと本番の差は、プロンプトの工夫ではない。アーキテクチャだ。
7つのコンポーネント、1つのループ
本記事が解説するのは、プロダクショングレードのエージェントを構成する以下の7要素だ。
- Perception(知覚) — 生の入力を推論可能な形式に変換
- Memory(記憶) — 作業コンテキストと長期記憶の管理
- Reasoning / Planning(推論・計画) — 次の行動を決定
- Tool Execution(ツール実行) — 外部APIやコードの実行
- Orchestration(オーケストレーション) — ループ全体の制御
- Guardrails(ガードレール) — 全ステップへの横断的な安全制御
- Observability(可観測性) — ループ全体の監視とデバッグ
エージェントの基本ループはこうだ:Goal → Perception → Reasoning → Planning → Action → Observation → Memory Update → Reasoning(繰り返し)。最初の5要素がこのループを構成し、GuardrailsとObservabilityはループ全体を外側から包む横断的関心事として機能する。
最も見落とされるコンポーネント:Perception
チュートリアルが最もよくスキップするのがこの層だ。デモでは「ユーザーがテキストを入力するだけ」なので正規化が不要に見える。しかし実際のシステムはWebhook、ファイルアップロード、構造化APIコール、複数チャネルからの同時入力を処理しなければならない。
Perceptionの責務は、どこから来たかにかかわらず、入力をひとつの内部表現に正規化することだ。以下のコードがその本質を示している。
# perception.py
from dataclasses import dataclass, field
from typing import Any
from enum import Enum
import json
from datetime import datetime, timezone
class InputSource(Enum):
USER_TEXT = "user_text"
WEBHOOK = "webhook"
FILE_UPLOAD = "file_upload"
@dataclass
class AgentInput:
source: InputSource
content: str
metadata: dict[str, Any] = field(default_factory=dict)
received_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
def perceive_user_text(raw_text: str) -> AgentInput:
return AgentInput(source=InputSource.USER_TEXT, content=raw_text.strip(), metadata={"channel": "chat"})
def perceive_webhook(raw_payload: str) -> AgentInput:
payload = json.loads(raw_payload)
event_type = payload.get("event_type", "unknown")
description = payload.get("description", "")
return AgentInput(
source=InputSource.WEBHOOK,
content=f"Event '{event_type}' received: {description}",
metadata={"event_type": event_type, "raw_payload": payload},
)
def perceive_file_upload(filename: str, file_size_bytes: int, mime_type: str) -> AgentInput:
return AgentInput(
source=InputSource.FILE_UPLOAD,
content=f"User uploaded file '{filename}' ({mime_type}, {file_size_bytes} bytes)",
metadata={"filename": filename, "mime_type": mime_type, "size_bytes": file_size_bytes},
)
プレーンテキスト、Webhook JSON、ファイルアップロードという3種類の全く異なる入力が、同一のAgentInput構造体に収束する。推論コンポーネントは入力がどのチャネルから来たかを一切知る必要がない。
デモコードが最も誤解するコンポーネント:Memory
「メモリ=会話履歴」という実装はデモでは通用するが、本番では不十分だ。記事は記憶を以下のように分類する。
- Working Memory(作業記憶) — 現在のタスクのコンテキストウィンドウ。セッション終了と同時に消える
- Episodic Memory(エピソード記憶) — セッションをまたいで何が起きたかを保持。ベクトルストアに格納し、意味的類似性で検索
- Semantic Memory(意味記憶) — エージェントが学習・蓄積した事実や知識。「この会社の返金ポリシーは◯◯だ」といったドメイン知識がここに当たる
- Procedural Memory(手続き記憶) — タスクをこなすためのスキルやノウハウ。「このAPIを呼ぶときはまずトークンを取得する」といった手順の記憶がこれに相当する
Working MemoryとEpisodic Memoryの実装例が記事中で示されており、後者はコサイン類似度で関連エピソードを取得する仕組みになっている。エージェントに「以前似たような問い合わせを処理した、あのときはこうなった」という文脈を与えられるのは、Episodic Memoryだけだ。Semantic MemoryとProcedural Memoryは構造の説明に留まっており、実装例は省かれているが、記事ではこの2つを「エージェントが成長するための記憶層」として位置づけている。
Reasoning / Planning と Tool Execution
Reasoningは、Perceptionが渡した正規化済み入力とMemoryから取得したコンテキストをもとに、次に何をすべきかを決定するコンポーネントだ。記事ではLLMへのプロンプト構築と、その出力をアクションプランに変換するロジックがコード例とともに解説されている。重要なのは、Reasoningが「考える」だけで外部に副作用を持たない点だ。実際の外部操作はすべてTool Executionに委ねる。
Tool Executionはその名の通り、外部APIの呼び出し・コードの実行・ファイル操作などを担う。デモコードとの最大の違いはエラーハンドリングとリトライロジックの有無だ。外部サービスは必ず落ちる。Tool Executionがリトライと失敗時のフォールバックを持たない実装は、本番で必ず詰まる。記事ではツールの登録・ディスパッチ・実行結果のObservationへの変換までを一貫して示すコードが提示されている。
GuardrailsとObservabilityは「ステップ」ではない
ここが設計上の重要な分岐点だ。GuardrailsはStep 4で実行するものではなく、すべての行動提案とその実行の間に常に挟まるものとして設計する。実際のお金、実際のユーザー、実際の副作用が関わるループを生き残らせるための仕組みだ。
記事では入力バリデーション・出力フィルタリング・アクション承認の3層でGuardrailsを構成する例が示されている。構造のイメージは以下のとおりだ。
# guardrails.py(概念的な構造)
def check_input(agent_input: AgentInput) -> ValidationResult:
# 禁止ワード・インジェクション・スキーマ違反を検査
...
def check_output(proposed_action: Action) -> ValidationResult:
# 実行前に副作用の範囲・権限を検査
...
def require_approval(action: Action) -> bool:
# 高リスクアクションは人間の承認を要求
...
Observabilityも同様に、ループ全体を外側から監視する横断的関心事として扱う。個々のステップのログを取るのではなく、ループ全体の振る舞いを追跡できる設計が求められる。記事では各ステップにトレースIDを付与し、ループの開始から終了までを一本のトレースとして記録する構造が紹介されている。「どのステップで何秒かかったか」「どのツール呼び出しが失敗したか」をループ単位で可視化できなければ、本番障害の原因特定は困難になる。
各コンポーネントを分離する理由
記事が一貫して強調するのは「コンポーネントを独立して動かせるコードで示す」という方針だ。7つのサンプルコードはすべて単体で実行可能で、外部依存がない(標準ライブラリのみ)。これは設計原則でもある。自分のシステムが何を必要としているか判断するとき、コンポーネントを分離して考えられなければ、問題がどこにあるか特定できない。
どこで壊れやすいかについても、各コンポーネントごとに具体的に言及されている。Perceptionはアドホックなパースをインラインで書き始めると破綻し、MemoryはWorking Memoryだけで実装すると長期記憶を持てず、Tool Executionはエラーハンドリングとリトライを持たないと本番で必ず落ちる。
詳細はThe End-to-End Agentic AI Pipelineを参照していただきたい。




