本番環境で動くAIエージェント設計の完全ガイド
AIエージェントは、LLMが自律的にツールを使い、複数ステップのタスクを実行するシステムです。デモレベルのエージェントを作るのは比較的容易ですが、本番環境で信頼性高く動かすには、独自の設計課題があります。
1. AIエージェントの本番課題
なぜ本番環境は難しいのか
PoC(概念実証)で動いたエージェントが、本番で問題を起こすケースには共通のパターンがあります。
非決定性の問題
LLMの出力は確率的であり、同じ入力でも毎回同じ結果にはなりません。ある条件下では完璧に動作したワークフローが、微妙なプロンプトの差異・モデルバージョンの変更・文脈の違いによって壊れることがあります。
長時間タスクの中断リスク
数十ステップに及ぶ長時間タスクでは、途中で失敗した場合の「回復」設計がないと、最初からやり直しになります。これはコストと時間の無駄だけでなく、副作用(送信済みメール・実行済みAPIコールなど)があった場合の問題にもつながります。
スコープクリープ
曖昧な指示を受けたエージェントが「良かれと思って」過剰なアクションをとる問題です。意図しないファイルの削除・不要なAPIコールの大量送信などが発生することがあります。
ツール呼び出しの失敗カスケード
あるツールの失敗が後続ステップに連鎖し、システム全体が停止または誤った方向に進む問題です。
2. オーケストレーション設計パターン
パターン1: オーケストレーター+サブエージェント構成
最も汎用性が高く、多くの本番システムで採用されているパターンです。
[オーケストレーター]
│
├─ タスク分解・サブエージェントへの指示
├─ 結果の統合・品質チェック
└─ 次ステップの判断
│
├─ [サブエージェントA] リサーチ担当
├─ [サブエージェントB] コード生成担当
└─ [サブエージェントC] 検証・テスト担当
オーケストレーターはタスクの全体像を把握し、各サブエージェントには限定的なスコープのタスクを割り当てます。この分離により:
- 各エージェントの責務が明確になる
- 失敗したサブタスクのみを再試行できる
- 並列実行が可能になりスループットが向上する
パターン2: チェックポイント付きパイプライン
長時間タスクを複数のフェーズに分割し、各フェーズ完了時に状態を永続化するパターンです。
class AgentPipeline:
def __init__(self, state_store):
self.state_store = state_store
self.phases = [
self.phase_research,
self.phase_analyze,
self.phase_draft,
self.phase_review,
self.phase_finalize,
]
async def run(self, task_id: str, input_data: dict):
# 既存のチェックポイントを読み込む
checkpoint = await self.state_store.get(task_id)
start_phase = 0
state = {"input": input_data, "results": {}}
if checkpoint:
start_phase = checkpoint["last_completed_phase"] + 1
state = checkpoint["state"]
print(f"Resuming from phase {start_phase}")
for i, phase_fn in enumerate(self.phases[start_phase:], start=start_phase):
try:
result = await phase_fn(state)
state["results"][phase_fn.__name__] = result
# チェックポイントを保存
await self.state_store.set(task_id, {
"last_completed_phase": i,
"state": state,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
print(f"Phase {phase_fn.__name__} failed: {e}")
raise
return state["results"]
パターン3: ヒューマン・イン・ザ・ループ(HITL)
リスクの高いアクション(メール送信・外部API呼び出し・データ削除など)の前に人間の確認を挟むパターンです。
class HITLAgent:
def __init__(self, approval_service):
self.approval_service = approval_service
async def execute_with_approval(
self,
action: dict,
risk_level: str # "low", "medium", "high"
):
if risk_level == "high":
# 人間の承認を待つ
approval = await self.approval_service.request_approval(
action=action,
context=self.get_current_context(),
timeout_seconds=3600 # 1時間タイムアウト
)
if not approval.approved:
return {"status": "rejected", "reason": approval.reason}
elif risk_level == "medium":
# 非同期通知(確認不要だが記録)
await self.approval_service.notify(action)
# アクションを実行
return await self.execute_action(action)
3. エラーハンドリングと回復戦略
指数バックオフとリトライ
ツール呼び出しの一時的な失敗に対して、指数バックオフを使ったリトライを実装します。
import asyncio
import random
async def call_tool_with_retry(
tool_fn,
max_retries: int = 3,
base_delay: float = 1.0,
max_delay: float = 60.0
):
last_error = None
for attempt in range(max_retries + 1):
try:
return await tool_fn()
except (TransientError, RateLimitError) as e:
last_error = e
if attempt == max_retries:
break
# 指数バックオフ + ジッター
delay = min(base_delay * (2 ** attempt), max_delay)
delay += random.uniform(0, delay * 0.1) # 10%ジッター
print(f"Attempt {attempt + 1} failed. Retrying in {delay:.1f}s...")
await asyncio.sleep(delay)
raise last_error
フォールバック戦略
プライマリツールが失敗した場合のフォールバックを設計します。
async def search_with_fallback(query: str):
"""複数の検索手段を試みる"""
strategies = [
lambda: search_primary_api(query),
lambda: search_secondary_api(query),
lambda: search_cached_knowledge(query),
lambda: ask_human_for_input(query),
]
for strategy in strategies:
try:
result = await strategy()
if result and result.get("confidence", 0) > 0.5:
return result
except Exception as e:
print(f"Strategy failed: {e}, trying next...")
continue
return {"error": "All strategies exhausted", "query": query}
エラーの分類と対応
class ErrorClassifier:
"""エラーを分類して適切な対応を決定"""
@staticmethod
def classify(error: Exception) -> str:
error_str = str(error).lower()
if "rate limit" in error_str or "429" in error_str:
return "rate_limit" # リトライ(バックオフ)
elif "timeout" in error_str or "504" in error_str:
return "timeout" # リトライ(即座)
elif "not found" in error_str or "404" in error_str:
return "not_found" # フォールバック
elif "permission" in error_str or "403" in error_str:
return "permission" # 人間に通知・停止
elif "invalid" in error_str or "400" in error_str:
return "invalid_input" # プロンプト修正・再試行
else:
return "unknown" # ログ・人間に通知
@staticmethod
def should_retry(error_type: str) -> bool:
return error_type in ["rate_limit", "timeout"]
@staticmethod
def should_escalate(error_type: str) -> bool:
return error_type in ["permission", "unknown"]
4. スコープ制限とガードレール
最小権限の原則
エージェントに与えるツールと権限は、タスク完了に必要な最小限に留めます。
# 悪い例: 全機能を持つスーパーエージェント
all_tools = [
read_file, write_file, delete_file,
send_email, access_database,
call_external_api, execute_code,
access_filesystem, manage_users
]
# 良い例: タスク別の最小権限セット
research_agent_tools = [read_file, search_web, access_knowledge_base]
writing_agent_tools = [read_file, write_file, check_grammar]
notification_agent_tools = [send_email, send_slack_message]
出力の検証
エージェントの出力を実行前に検証するレイヤーを設けます。
class OutputValidator:
def __init__(self, rules: list):
self.rules = rules
def validate(self, output: dict) -> tuple[bool, list[str]]:
violations = []
for rule in self.rules:
if not rule.check(output):
violations.append(rule.description)
return len(violations) == 0, violations
# ルール例
class NoDeletionRule:
description = "削除操作は禁止"
def check(self, output: dict) -> bool:
actions = output.get("actions", [])
return not any(a.get("type") == "delete" for a in actions)
class MaxActionsRule:
description = "1回の実行で最大10アクション"
def check(self, output: dict) -> bool:
return len(output.get("actions", [])) <= 10
5. 状態管理と観測性
エージェント状態の設計
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime
from typing import Any
class AgentStatus(Enum):
PENDING = "pending"
RUNNING = "running"
WAITING_APPROVAL = "waiting_approval"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
@dataclass
class AgentState:
task_id: str
status: AgentStatus
current_step: int
total_steps: int
context: dict[str, Any]
tool_calls: list[dict] = field(default_factory=list)
errors: list[dict] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
def record_tool_call(self, tool_name: str, input_data: dict, output: Any):
self.tool_calls.append({
"tool": tool_name,
"input": input_data,
"output": output,
"timestamp": datetime.now().isoformat(),
"step": self.current_step
})
self.updated_at = datetime.now()
def record_error(self, error: Exception, step: int):
self.errors.append({
"message": str(error),
"type": type(error).__name__,
"step": step,
"timestamp": datetime.now().isoformat()
})
ログと観測性
本番エージェントには、実行の全ステップを追跡できる観測性が不可欠です。
import logging
import json
from functools import wraps
class AgentLogger:
def __init__(self, task_id: str):
self.task_id = task_id
self.logger = logging.getLogger(f"agent.{task_id}")
def log_tool_call(self, tool_name: str, input_data: dict):
self.logger.info(json.dumps({
"event": "tool_call",
"task_id": self.task_id,
"tool": tool_name,
"input_keys": list(input_data.keys()),
"timestamp": datetime.now().isoformat()
}))
def log_decision(self, reasoning: str, chosen_action: str):
self.logger.info(json.dumps({
"event": "decision",
"task_id": self.task_id,
"reasoning_length": len(reasoning),
"action": chosen_action,
"timestamp": datetime.now().isoformat()
}))
def log_completion(self, result: dict, duration_seconds: float):
self.logger.info(json.dumps({
"event": "completion",
"task_id": self.task_id,
"success": True,
"duration_seconds": duration_seconds,
"timestamp": datetime.now().isoformat()
}))
6. 実装のチェックリスト
本番エージェントをリリースする前に確認すべき項目です。
設計フェーズ
- タスクを最小サブタスクに分解できているか
- 各エージェントの権限が最小限に絞られているか
- ヒューマン・イン・ザ・ループが必要な箇所が定義されているか
- 副作用を持つアクションが洗い出されているか
実装フェーズ
- エラー分類と対応策が実装されているか
- チェックポイントによる再開機能があるか
- 出力検証レイヤーが存在するか
- 全ステップのログが記録されているか
テストフェーズ
- ツール失敗時の動作がテストされているか
- 長時間タスクの途中中断・再開がテストされているか
- スコープ外のアクションを試みた場合の動作が確認されているか
- 本番と同等の環境でエンドツーエンドテストをしたか
ここまでの要点
AIエージェントの本番運用は、単純な「動くこと」から「信頼性高く・安全に動き続けること」への転換が求められます。
オーケストレーター+サブエージェントの構成・チェックポイント管理・ヒューマン・イン・ザ・ループ・最小権限の原則——これらは全て、「エージェントが意図通りに動き、予期せぬ状況でも適切に振る舞う」ための基盤です。
まずは小さなパイプラインから実装を始め、徐々に信頼性設計を重ねていくことをお勧めします。
個人開発者の視点から(実体験メモ)
メンバーシップのご案内
Antigravity Lab では、このようなAIエージェントの実践的な設計・実装ガイドを継続して発信しています。
メンバーシップに登録いただくと、以下にアクセスできます。
- エージェント設計パターンの深掘り実装例(本記事のような詳細ガイド)
- MCPサーバー・ツール統合の実践事例
- AI×アプリ開発の最新動向と実装レシピ
- エディタ・IDE統合を活用した開発ワークフロー
毎週新しいプレミアムコンテンツを追加しています。AIエージェント開発を本格的に学びたい方のご参加を、心よりお待ちしています。