Skip to content
Published on

AIエージェント開発完全ガイド2025:Tool Calling、ReAct、Multi-Agent、MCPまで

Authors

はじめに:AIエージェントの時代(じだい)が来(き)た

2024年(ねん)末(まつ)から2025年(ねん)現在(げんざい)まで、AI業界(ぎょうかい)最大(さいだい)のトピックは間違(まちが)いなくAIエージェントです。OpenAIは「2025年(ねん)はエージェントの年(とし)」と宣言(せんげん)し、AnthropicのClaudeはコンピュータを直接(ちょくせつ)操作(そうさ)するComputer Use機能(きのう)を公開(こうかい)し、GoogleはProject Marinerでブラウザ自動化(じどうか)エージェントを披露(ひろう)しました。

単(たん)に質問(しつもん)に答(こた)えるチャットボットを超(こ)えて、自律的(じりつてき)に計画(けいかく)を立(た)て、ツールを使(つか)い、フィードバックを反映(はんえい)して複雑(ふくざつ)なタスクを完遂(かんすい)するAIエージェントがプロダクション環境(かんきょう)にデプロイされ始(はじ)めています。このガイドでは、AIエージェント開発(かいはつ)のコアコンセプトからプロダクションデプロイまで全(すべ)てをカバーします。


1. AIエージェントとは何(なに)か?

1.1 定義(ていぎ)

AIエージェントは、LLM(大規模(だいきぼ)言語(げんご)モデル)を頭脳(ずのう)として使用(しよう)し、外部(がいぶ)ツールを活用(かつよう)し、メモリを維持(いじ)し、自律的(じりつてき)に計画(けいかく)を立(た)てて目標(もくひょう)を達成(たっせい)するシステムです。

┌─────────────────────────────────────────┐
AI Agent│                                          │
│  ┌──────────┐  ┌──────────┐  ┌────────┐ │
│  │   LLM    │  │  Tools   │  │ Memory │ │
 (Brain)(Actions)(State) │ │
│  └────┬─────┘  └────┬─────┘  └───┬────┘ │
│       │              │            │      │
│       └──────┬───────┴────────────┘      │
│              │                           │
│       ┌──────▼──────┐                    │
│       │  Planning   │                    │
  (Strategy) │                    │
│       └─────────────┘                    │
└─────────────────────────────────────────┘

4つのコア構成(こうせい)要素(ようそ):

構成要素役割(やくわり)例(れい)
LLM(頭脳)自然言語理解、推論、意思決定GPT-4o、Claude 3.5 Sonnet、Gemini 2.0
Tools(ツール)外部世界との相互作用API呼び出し、DBクエリ、ファイルI/O、Web検索
Memory(記憶)会話履歴、長期知識の保存会話バッファ、ベクターDB、要約メモリ
Planning(計画)タスク分解、戦略策定ReAct、Plan-and-Solve、Tree of Thought

1.2 Agent vs Chatbot vs RAG Pipeline

レベル0: Chatbot
  ユーザー → LLM → 応答
  (単純な会話、外部ツールなし)

レベル1: RAG Pipeline
  ユーザー → 検索 → LLM + 文書 → 応答
  (検索増強生成、固定パイプライン)

レベル2: Tool-Using Agent
  ユーザー → LLM[ツール選択 → 実行 → 結果観察] → 応答
  (自律的ツール選択、単一ループ)

レベル3: Multi-Step Agent
  ユーザー → LLM[計画 → ツール1 → 観察 → ツール2... → 最終] → 応答
  (多段階計画および実行)

レベル4: Multi-Agent System
  ユーザー → オーケストレータ → [Agent1, Agent2, Agent3] → 統合応答
  (複数の専門エージェント協業)

1.3 エージェント能力(のうりょく)ピラミッド

          ┌─────────────┐
Multi-Agent │  ← 複雑な協業タスク
Systems         ┌┴─────────────┴┐
Multi-Step   │  ← 多段階計画 + 実行
Planning        ┌┴───────────────┴┐
Tool Calling  │  ← 外部ツール使用
+ Actions       ┌┴─────────────────┴┐
Reasoning (CoT)  │  ← 思考チェーン
       │                   │
      ┌┴───────────────────┴┐
Text Generation  │  ← 基本テキスト生成
      └─────────────────────┘

2. Tool Calling / Function Calling 深掘(ふかぼ)り

2.1 動作(どうさ)原理(げんり)

Tool Callingは、LLMが自然言語(しぜんげんご)入力(にゅうりょく)を構造化(こうぞうか)された関数(かんすう)呼(よ)び出(だ)しに変換(へんかん)する能力(のうりょく)です。全体(ぜんたい)のフローは以下(いか)の通(とお)りです:

ユーザー: 「ソウルの天気を教えて」
┌─────────────────┐
LLM 推論      │  「天気の質問なのでget_weatherを呼ぶべきだ」
└────────┬────────┘
┌─────────────────┐
Tool Call 生成  │  get_weather(location="Seoul")
└────────┬────────┘
┌─────────────────┐
│  関数実行        │  → Weather API 呼び出し → 結果返却
└────────┬────────┘
┌─────────────────┐
LLM 応答生成   │  「ソウルの現在の気温は15度で晴れです」
└─────────────────┘

2.2 OpenAI Function Calling 形式(けいしき)

import openai

client = openai.OpenAI()

# ツール定義
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a given location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City name (e.g., Seoul, Tokyo)"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit"
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# 最初の呼び出し:LLMがツール呼び出しを決定
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "ソウルの天気は?"}
    ],
    tools=tools,
    tool_choice="auto"  # auto / none / required / specific
)

# ツール呼び出しの確認と実行
message = response.choices[0].message
if message.tool_calls:
    tool_call = message.tool_calls[0]
    # 実際の関数実行
    result = get_weather(
        location=json.loads(tool_call.function.arguments)["location"]
    )

    # 結果をLLMに再度渡す
    final_response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "user", "content": "ソウルの天気は?"},
            message,  # assistantのtool_callメッセージ
            {
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result)
            }
        ],
        tools=tools
    )

2.3 Anthropic Tool Use 形式(けいしき)

import anthropic

client = anthropic.Anthropic()

# ツール定義
tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a given location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name (e.g., Seoul, Tokyo)"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit"
                }
            },
            "required": ["location"]
        }
    }
]

# 最初の呼び出し
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "ソウルの天気を教えて"}
    ]
)

# stop_reasonが"tool_use"の場合の処理
if response.stop_reason == "tool_use":
    tool_block = next(
        block for block in response.content
        if block.type == "tool_use"
    )

    # ツール実行
    result = get_weather(**tool_block.input)

    # 結果を再度渡す
    final_response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        tools=tools,
        messages=[
            {"role": "user", "content": "ソウルの天気を教えて"},
            {"role": "assistant", "content": response.content},
            {
                "role": "user",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_block.id,
                        "content": json.dumps(result)
                    }
                ]
            }
        ]
    )

2.4 Google Gemini Function Calling

import google.generativeai as genai

# 関数宣言
get_weather_func = genai.protos.FunctionDeclaration(
    name="get_weather",
    description="Get current weather for a location",
    parameters=genai.protos.Schema(
        type=genai.protos.Type.OBJECT,
        properties={
            "location": genai.protos.Schema(
                type=genai.protos.Type.STRING,
                description="City name"
            ),
        },
        required=["location"]
    )
)

tool = genai.protos.Tool(
    function_declarations=[get_weather_func]
)

model = genai.GenerativeModel(
    model_name="gemini-2.0-flash",
    tools=[tool]
)

chat = model.start_chat()
response = chat.send_message("ソウルの天気は?")

# function_call処理
if response.candidates[0].content.parts[0].function_call:
    fc = response.candidates[0].content.parts[0].function_call
    result = get_weather(location=fc.args["location"])

    response = chat.send_message(
        genai.protos.Content(
            parts=[genai.protos.Part(
                function_response=genai.protos.FunctionResponse(
                    name="get_weather",
                    response={"result": result}
                )
            )]
        )
    )

2.5 Tool Schema定義(ていぎ)(JSON Schema)

効果的(こうかてき)なTool Schema記述法(きじゅつほう):

{
  "name": "search_products",
  "description": "Search for products in the e-commerce catalog. Returns a list of matching products with price, rating, and availability. Use when the user wants to find or browse products.",
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Search query text. Can be product name, category, or keywords."
      },
      "category": {
        "type": "string",
        "enum": ["electronics", "clothing", "home", "books", "toys"],
        "description": "Product category filter. Optional."
      },
      "min_price": {
        "type": "number",
        "description": "Minimum price in USD. Optional."
      },
      "max_price": {
        "type": "number",
        "description": "Maximum price in USD. Optional."
      },
      "sort_by": {
        "type": "string",
        "enum": ["relevance", "price_low", "price_high", "rating", "newest"],
        "description": "Sort order for results. Defaults to relevance."
      },
      "limit": {
        "type": "integer",
        "description": "Max number of results to return (1-50). Defaults to 10."
      }
    },
    "required": ["query"]
  }
}

2.6 並列(へいれつ)ツール呼(よ)び出(だ)し

# OpenAI - 並列呼び出しをネイティブサポート
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "ソウルと東京の天気を比較して"}
    ],
    tools=tools,
    parallel_tool_calls=True  # デフォルト True
)

# レスポンスに複数のtool_callsが含まれる
for tool_call in message.tool_calls:
    pass

# 並列実行して全結果を渡す
import asyncio

async def execute_parallel_tools(tool_calls):
    tasks = []
    for tc in tool_calls:
        args = json.loads(tc.function.arguments)
        tasks.append(execute_tool(tc.function.name, args))
    return await asyncio.gather(*tasks)

2.7 強制(きょうせい)ツール使用(しよう) vs Auto

# Auto: LLMがツール使用有無を自律的に判断
tool_choice = "auto"

# Required: 必ず1つ以上のツールを使用
tool_choice = "required"

# None: ツール使用禁止(テキスト応答のみ)
tool_choice = "none"

# 特定ツールの強制使用
tool_choice = {"type": "function", "function": {"name": "get_weather"}}

# Anthropicの場合
tool_choice = {"type": "auto"}       # 自動
tool_choice = {"type": "any"}        # 必ず1つ
tool_choice = {"type": "tool", "name": "get_weather"}  # 特定ツール強制

2.8 ストリーミングでのTool Calling

# OpenAI Streaming Tool Calls
stream = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    stream=True
)

tool_calls_buffer = {}
for chunk in stream:
    delta = chunk.choices[0].delta

    if delta.tool_calls:
        for tc in delta.tool_calls:
            idx = tc.index
            if idx not in tool_calls_buffer:
                tool_calls_buffer[idx] = {
                    "id": tc.id,
                    "function": {"name": "", "arguments": ""}
                }
            if tc.function.name:
                tool_calls_buffer[idx]["function"]["name"] += tc.function.name
            if tc.function.arguments:
                tool_calls_buffer[idx]["function"]["arguments"] += tc.function.arguments

# Anthropic Streaming Tool Use
with client.messages.stream(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=messages,
) as stream:
    for event in stream:
        if event.type == "content_block_start":
            if event.content_block.type == "tool_use":
                print(f"Tool: {event.content_block.name}")
        elif event.type == "content_block_delta":
            if event.delta.type == "input_json_delta":
                print(event.delta.partial_json)

3. Tool Calling パフォーマンス最適化(さいてきか)(重要(じゅうよう)セクション)

3.1 Tool Description エンジニアリング

Tool Descriptionは、LLMがツールを正(ただ)しく選択(せんたく)・使用(しよう)する上(うえ)で**最(もっと)も重要(じゅうよう)な要素(ようそ)**です。

# BAD: 曖昧な説明
{
    "name": "search",
    "description": "Search for things"  # 曖昧すぎ
}

# GOOD: 明確で具体的な説明
{
    "name": "search_knowledge_base",
    "description": "Search the internal knowledge base for technical documentation, API references, and troubleshooting guides. Use this when the user asks about product features, API usage, error codes, or configuration options. Do NOT use for general web search or current events.",
}

# BEST: 例と使用条件を含む
{
    "name": "search_knowledge_base",
    "description": """Search the internal knowledge base for technical documentation.

USE WHEN:
- User asks about product features or capabilities
- User needs API reference or code examples
- User encounters error codes or needs troubleshooting

DO NOT USE WHEN:
- User asks about pricing (use get_pricing instead)
- User asks about their account (use get_account_info instead)

EXAMPLES:
- "How do I authenticate?" -> query="API authentication"
- "What does error 429 mean?" -> query="error 429 rate limit"
""",
}

3.2 パラメータ記述(きじゅつ)の品質(ひんしつ)

# BAD
"location": {
    "type": "string",
    "description": "location"
}

# GOOD
"location": {
    "type": "string",
    "description": "City name in English. Use the most common name. Examples: 'Seoul', 'New York', 'Tokyo'. Use full names (e.g., 'Los Angeles' not 'LA')."
}

3.3 ツール数(すう)の削減(さくげん)

# BAD: 細分化されすぎたツール(LLMが混乱)
tools = [
    "get_user_name",
    "get_user_email",
    "get_user_phone",
    "get_user_address",
    # ... 20個以上のツール
]

# GOOD: 関連ツールを1つに統合
tools = [
    {
        "name": "get_user_info",
        "description": "Get user information. Specify which fields you need.",
        "parameters": {
            "type": "object",
            "properties": {
                "user_id": {"type": "string"},
                "fields": {
                    "type": "array",
                    "items": {
                        "type": "string",
                        "enum": ["name", "email", "phone", "address", "preferences"]
                    },
                    "description": "List of fields to retrieve."
                }
            }
        }
    }
]

3.4 Few-shot例(れい)をシステムプロンプトに含(ふく)める

system_prompt = """You are a helpful assistant with access to tools.

Example 1:
User: "What's the weather in Seoul?"
Action: Call get_weather with location="Seoul", unit="celsius"

Example 2:
User: "Find me a good Italian restaurant"
Action: Call search_restaurants with cuisine="italian", sort_by="rating"

Example 3:
User: "What's 2+2?"
Action: Do NOT use any tools. Respond directly: "2+2 = 4"
"""

3.5 スキーマの簡素化(かんそか)

# BAD: 深くネストされたスキーマ
{
    "type": "object",
    "properties": {
        "filter": {
            "type": "object",
            "properties": {
                "conditions": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "field": {"type": "string"},
                            "operator": {"type": "string"},
                            "value": {"type": "object"}  # 3段階ネスト
                        }
                    }
                }
            }
        }
    }
}

# GOOD: フラットなスキーマに変換
{
    "type": "object",
    "properties": {
        "filter_field": {"type": "string", "description": "Field to filter on"},
        "filter_operator": {"type": "string", "enum": ["eq", "gt", "lt", "contains"]},
        "filter_value": {"type": "string", "description": "Filter value as string"}
    }
}

3.6 エラーハンドリングとリトライロジック

import tenacity

class ToolExecutor:
    def __init__(self, client, tools, model="gpt-4o"):
        self.client = client
        self.tools = tools
        self.model = model

    @tenacity.retry(
        stop=tenacity.stop_after_attempt(3),
        wait=tenacity.wait_exponential(min=1, max=10),
        retry=tenacity.retry_if_exception_type(ToolExecutionError)
    )
    async def execute_tool(self, name, arguments):
        try:
            result = await self.tool_registry[name](**arguments)
            return {"status": "success", "result": result}
        except ValidationError as e:
            return {
                "status": "error",
                "error": f"Invalid parameters: {str(e)}",
                "hint": "Please check the parameter types and try again"
            }
        except RateLimitError:
            raise ToolExecutionError("Rate limited, will retry")
        except Exception as e:
            return {
                "status": "error",
                "error": f"Tool execution failed: {str(e)}"
            }

    async def run_agent_loop(self, messages, max_iterations=10):
        for i in range(max_iterations):
            response = await self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                tools=self.tools
            )

            if response.choices[0].finish_reason == "stop":
                return response.choices[0].message.content

            for tool_call in response.choices[0].message.tool_calls:
                result = await self.execute_tool(
                    tool_call.function.name,
                    json.loads(tool_call.function.arguments)
                )
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(result)
                })

        return "Max iterations reached"

3.7 ツール結果(けっか)のキャッシング

import hashlib

class CachedToolExecutor:
    def __init__(self):
        self.cache = {}
        self.cache_ttl = 300  # 5分

    def _cache_key(self, tool_name, arguments):
        arg_str = json.dumps(arguments, sort_keys=True)
        return hashlib.md5(
            f"{tool_name}:{arg_str}".encode()
        ).hexdigest()

    async def execute_with_cache(self, tool_name, arguments):
        CACHEABLE_TOOLS = {"get_weather", "search_products", "get_stock_price"}

        if tool_name not in CACHEABLE_TOOLS:
            return await self.execute_tool(tool_name, arguments)

        key = self._cache_key(tool_name, arguments)
        if key in self.cache:
            entry = self.cache[key]
            if time.time() - entry["timestamp"] < self.cache_ttl:
                return entry["result"]

        result = await self.execute_tool(tool_name, arguments)
        self.cache[key] = {
            "result": result,
            "timestamp": time.time()
        }
        return result

3.8 レイテンシ最適化(さいてきか)

import asyncio

async def optimized_tool_execution(tool_calls):
    """並列実行 + タイムアウト管理"""

    independent_calls = group_independent_calls(tool_calls)

    results = {}
    for group in independent_calls:
        group_tasks = [
            asyncio.wait_for(
                execute_tool(tc.name, tc.args),
                timeout=30.0  # 30秒タイムアウト
            )
            for tc in group
        ]
        group_results = await asyncio.gather(
            *group_tasks, return_exceptions=True
        )
        for tc, result in zip(group, group_results):
            if isinstance(result, Exception):
                results[tc.id] = {"error": str(result)}
            else:
                results[tc.id] = result

    return results

3.9 Tool Callingのファインチューニング

# UnslothによるTool Callingファインチューニング
from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Llama-3.1-8B-Instruct",
    max_seq_length=4096,
    load_in_4bit=True,
)

model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                     "gate_proj", "up_proj", "down_proj"],
    lora_alpha=16,
    lora_dropout=0,
    bias="none",
)

# Tool Calling学習データ形式
training_data = [
    {
        "messages": [
            {"role": "system", "content": "You are an assistant with tools..."},
            {"role": "user", "content": "What's the weather in Seoul?"},
            {"role": "assistant", "content": None, "tool_calls": [
                {
                    "type": "function",
                    "function": {
                        "name": "get_weather",
                        "arguments": '{"location": "Seoul", "unit": "celsius"}'
                    }
                }
            ]},
            {"role": "tool", "content": '{"temp": 15, "condition": "sunny"}'},
            {"role": "assistant", "content": "ソウルの現在の天気は15度で晴れです。"}
        ]
    }
]

4. ReActパターン実装(じっそう)

4.1 Thought-Action-Observationループ

ReAct(Reasoning + Acting)は、LLMが**思考(しこう)(Thought) → 行動(こうどう)(Action) → 観察(かんさつ)(Observation)**ループを繰(く)り返(かえ)して複雑(ふくざつ)な問題(もんだい)を解決(かいけつ)するパターンです。

質問:2025年のソウルの人口は東京の何%か?」

Thought 1: ソウルと東京の2025年の人口をそれぞれ調べる必要がある。
Action 1: search("2025年 ソウル 人口")
Observation 1: ソウルの2025年の人口は約940万人です。

Thought 2: 次に東京の人口を調べなければならない。
Action 2: search("2025年 東京 人口")
Observation 2: 東京の2025年の人口は約1,396万人です。

Thought 3: 比率を計算できる。940/1396 * 100
Action 3: calculator("940 / 1396 * 100")
Observation 3: 67.34

Thought 4: 計算完了。答えをまとめよう。
Final Answer: 2025年のソウルの人口(約940万)は東京(約1,396万)の約67.3%です。

4.2 スクラッチから実装(じっそう)(Python)

import re
import json
from openai import OpenAI

class ReActAgent:
    def __init__(self, tools: dict, model: str = "gpt-4o"):
        self.client = OpenAI()
        self.tools = tools
        self.model = model
        self.max_steps = 10

    def _build_system_prompt(self):
        tool_descriptions = "\n".join([
            f"- {name}: {func.__doc__}"
            for name, func in self.tools.items()
        ])

        return f"""You are an AI assistant that uses a Thought-Action-Observation loop.

Available tools:
{tool_descriptions}

For each step, output EXACTLY ONE of:
1. Thought: [your reasoning]
2. Action: tool_name(arg1="value1", arg2="value2")
3. Final Answer: [your complete answer]

Rules:
- Always start with a Thought
- After each Action, wait for an Observation
- When you have enough information, give a Final Answer
"""

    def run(self, question: str) -> str:
        messages = [
            {"role": "system", "content": self._build_system_prompt()},
            {"role": "user", "content": question}
        ]

        for step in range(self.max_steps):
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                temperature=0
            )
            output = response.choices[0].message.content
            messages.append({"role": "assistant", "content": output})

            if "Final Answer:" in output:
                return output.split("Final Answer:")[-1].strip()

            action_match = re.search(
                r'Action:\s*(\w+)\((.*?)\)', output, re.DOTALL
            )
            if action_match:
                tool_name = action_match.group(1)
                args_str = action_match.group(2)
                args = self._parse_args(args_str)

                if tool_name in self.tools:
                    try:
                        observation = self.tools[tool_name](**args)
                    except Exception as e:
                        observation = f"Error: {str(e)}"
                else:
                    observation = f"Error: Unknown tool '{tool_name}'"

                messages.append({
                    "role": "user",
                    "content": f"Observation: {observation}"
                })

        return "Max steps reached without final answer"

    def _parse_args(self, args_str: str) -> dict:
        args = {}
        for match in re.finditer(r'(\w+)="([^"]*)"', args_str):
            args[match.group(1)] = match.group(2)
        return args

4.3 ReAct vs 単純(たんじゅん)なTool Calling

比較項目単純なTool CallingReActパターン
思考過程暗黙的(LLM内部)明示的(Thought出力)
デバッグ困難(ブラックボックス)容易(推論過程が可視化)
多段階タスク制限的強力(チェーン可能)
レイテンシ高速(1-2回呼出)低速(複数回呼出)
コスト低い高い(トークン消費増加)
適合ケース単純API呼出複雑な調査・分析

5. Agentフレームワーク比較(ひかく)

5.1 主要(しゅよう)フレームワーク概要(がいよう)

LangChain / LangGraph

# LangGraph - グラフベースエージェント
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

@tool
def search_web(query: str) -> str:
    """Search the web for information."""
    return web_search(query)

class AgentState(TypedDict):
    messages: list
    next_step: str

def should_continue(state):
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tools"
    return "end"

workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.add_node("tools", execute_tools)
workflow.add_conditional_edges("agent", should_continue, {
    "tools": "tools",
    "end": END,
})
workflow.add_edge("tools", "agent")
workflow.set_entry_point("agent")

app = workflow.compile()

CrewAI(役割(やくわり)ベースマルチエージェント)

from crewai import Agent, Task, Crew, Process

researcher = Agent(
    role='Senior Research Analyst',
    goal='Find the latest trends in AI agent development',
    backstory='Expert researcher with deep knowledge of AI/ML',
    tools=[search_tool, web_scraper],
    llm='gpt-4o',
    verbose=True
)

writer = Agent(
    role='Tech Content Writer',
    goal='Create engaging technical blog posts',
    backstory='Experienced tech writer specializing in AI topics',
    tools=[write_tool],
    llm='gpt-4o',
    verbose=True
)

editor = Agent(
    role='Editor',
    goal='Ensure content quality and accuracy',
    backstory='Senior editor with technical background',
    llm='gpt-4o',
    verbose=True
)

research_task = Task(
    description='Research the latest AI agent frameworks',
    expected_output='Comprehensive research report',
    agent=researcher
)

writing_task = Task(
    description='Write a blog post based on findings',
    expected_output='Well-structured blog post',
    agent=writer,
    context=[research_task]
)

editing_task = Task(
    description='Review and improve the blog post',
    expected_output='Publication-ready blog post',
    agent=editor,
    context=[writing_task]
)

crew = Crew(
    agents=[researcher, writer, editor],
    tasks=[research_task, writing_task, editing_task],
    process=Process.sequential,
    verbose=True
)

result = crew.kickoff()

AutoGen(Microsoft)

from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager

assistant = AssistantAgent(
    name="AI_Assistant",
    llm_config={"model": "gpt-4o"},
    system_message="You are a helpful AI assistant."
)

coder = AssistantAgent(
    name="Coder",
    llm_config={"model": "gpt-4o"},
    system_message="You are an expert Python programmer."
)

user_proxy = UserProxyAgent(
    name="User",
    human_input_mode="NEVER",
    code_execution_config={"work_dir": "workspace"},
)

group_chat = GroupChat(
    agents=[user_proxy, assistant, coder],
    messages=[],
    max_round=10
)

manager = GroupChatManager(
    groupchat=group_chat,
    llm_config={"model": "gpt-4o"}
)

user_proxy.initiate_chat(
    manager,
    message="Create a Python script that analyzes CSV data"
)

5.2 フレームワーク比較(ひかく)テーブル

基準LangChain/LangGraphCrewAIAutoGenSmolagentsClaude Agent SDK
アプローチグラフベース役割ベース会話ベースコードベースツールベース
学習曲線高い中程度中程度低い低い
マルチエージェントLangGraph経由ネイティブネイティブ制限的制限的
カスタマイズ非常に高い高い高い中程度中程度
本番対応高い中程度中程度初期段階高い
LLMサポート多様多様多様多様Claudeのみ
ツール生態系広大中程度中程度HuggingFaceMCPベース
デバッグLangSmithログログログ内蔵
非同期サポート完全部分的完全部分的完全
推奨ケース複雑なワークフロー役割協業コード生成クイックプロトClaude生態系

6. マルチエージェントアーキテクチャ

6.1 Supervisorパターン

1つの調整者(ちょうせいしゃ)(Supervisor)が複数(ふくすう)の専門(せんもん)エージェントにタスクを配分(はいぶん)します。

                ┌──────────────┐
SupervisorAgent                └──────┬───────┘
          ┌────────────┼────────────┐
          │            │            │
    ┌─────▼─────┐ ┌───▼────┐ ┌────▼────┐
Research  │ │ Writer │ │ AnalystAgent    │ │ Agent  │ │  Agent    └───────────┘ └────────┘ └─────────┘

6.2 Peer-to-Peerパターン

    ┌──────────┐     ┌──────────┐
Agent A  │◄───►│ Agent B    └────┬─────┘     └─────┬────┘
         │                  │
         └──────┬───────────┘
         ┌──────▼─────┐
Agent C         └────────────┘

6.3 階層(かいそう)パターン

              ┌────────────┐
CEOAgent              └─────┬──────┘
         ┌──────────┼──────────┐
         │                     │
   ┌─────▼──────┐       ┌─────▼──────┐
Tech Lead  │       │  PM LeadAgent     │       │   Agent   └─────┬──────┘       └─────┬──────┘
         │                     │
    ┌────┼────┐          ┌─────┼─────┐
    │         │          │           │
┌───▼──┐ ┌───▼──┐  ┌────▼──┐  ┌────▼──┐
│Dev 1 │ │Dev 2 │  │Design │  │  QA│Agent │ │Agent │  │Agent  │  │Agent  │
└──────┘ └──────┘  └───────┘  └───────┘

7. MCP(Model Context Protocol)

7.1 MCPが必要(ひつよう)な理由(りゆう)

従来(じゅうらい)のTool Callingの問題点(もんだいてん):

Before MCP:
┌─────────────┐     ┌──────────────────────────┐
Agent App  │────►│ カスタムAPIラッパー (GitHub)│             │────►│ カスタムAPIラッパー (Slack)│             │────►│ カスタムAPIラッパー (DB)│             │────►│ カスタムAPIラッパー (ファイル)└─────────────┘     └──────────────────────────┘
  → ツールごとに異なるインターフェース、重複コード

After MCP:
┌─────────────┐     ┌──────────────┐     ┌──────────┐
Agent App  │────►│  MCP Client  │────►│ GitHub  (MCP Host) │     │              │────►│ Slack│             │     │  標準         │────►│ Database│             │     │  プロトコル    │────►│ Files└─────────────┘     └──────────────┘     └──────────┘
  → 統一インターフェース、再利用可能、拡張容易

7.2 MCPアーキテクチャ

┌────────────────────────────────────────────┐
MCP Host  (Claude Desktop, IDE, Agent Application)│                                             │
│  ┌─────────────┐  ┌─────────────┐          │
│  │ MCP Client  │  │ MCP Client... (GitHub) (Database)  │          │
│  └──────┬──────┘  └──────┬──────┘          │
└─────────┼────────────────┼─────────────────┘
          │  stdio/SSE     │  stdio/SSE
┌─────────▼──────┐  ┌─────▼────────┐
MCP Server    │  │  MCP Server  (GitHub)  (Database)Resources     │  │  ResourcesTools         │  │  ToolsPrompts       │  │  Prompts└────────────────┘  └──────────────┘

7.3 MCPサーバー構築(こうちく)(Python)

from mcp.server import Server
from mcp.types import Tool, TextContent
import json

server = Server("todo-server")
todos = []

@server.list_tools()
async def list_tools():
    return [
        Tool(
            name="add_todo",
            description="Add a new todo item",
            inputSchema={
                "type": "object",
                "properties": {
                    "title": {"type": "string", "description": "Todo title"},
                    "priority": {
                        "type": "string",
                        "enum": ["low", "medium", "high"]
                    }
                },
                "required": ["title"]
            }
        ),
        Tool(
            name="list_todos",
            description="List all todo items",
            inputSchema={"type": "object", "properties": {}}
        ),
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "add_todo":
        todo = {
            "title": arguments["title"],
            "priority": arguments.get("priority", "medium"),
            "completed": False
        }
        todos.append(todo)
        return [TextContent(
            type="text",
            text=f"Added: {todo['title']} ({todo['priority']})"
        )]
    elif name == "list_todos":
        if not todos:
            return [TextContent(type="text", text="No todos yet!")]
        result = "\n".join([
            f"{'[x]' if t['completed'] else '[ ]'} {i}. {t['title']}"
            for i, t in enumerate(todos)
        ])
        return [TextContent(type="text", text=result)]

if __name__ == "__main__":
    import asyncio
    from mcp.server.stdio import stdio_server

    async def main():
        async with stdio_server() as (read_stream, write_stream):
            await server.run(read_stream, write_stream)

    asyncio.run(main())

7.4 主要(しゅよう)MCPサーバー一覧(いちらん)

MCPサーバー機能活用事例
filesystemファイル読み書き・検索コード分析、ドキュメント管理
githubIssue、PR、コード検索開発ワークフロー自動化
databaseSQLクエリ実行データ分析、レポート生成
slackメッセージ送信・検索チームコミュニケーション自動化
web-searchWeb検索情報収集、リサーチ
memory長期メモリ保存ユーザーコンテキスト維持
puppeteerブラウザ制御Web自動化、スクレイピング
postgresPostgreSQLクエリDB管理および分析

8. メモリシステム

8.1 短期(たんき)メモリ

class ConversationMemory:
    def __init__(self, max_messages: int = 20):
        self.messages = []
        self.max_messages = max_messages

    def add(self, role: str, content: str):
        self.messages.append({"role": role, "content": content})
        if len(self.messages) > self.max_messages:
            system = [m for m in self.messages if m["role"] == "system"]
            recent = self.messages[-self.max_messages:]
            self.messages = system + recent

8.2 長期(ちょうき)メモリ(ベクターDB)

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

class LongTermMemory:
    def __init__(self):
        self.embeddings = OpenAIEmbeddings()
        self.vectorstore = Chroma(
            collection_name="agent_memory",
            embedding_function=self.embeddings,
            persist_directory="./memory_db"
        )

    def store(self, text: str, metadata: dict = None):
        self.vectorstore.add_texts(
            texts=[text], metadatas=[metadata or {}]
        )

    def recall(self, query: str, k: int = 5) -> list:
        results = self.vectorstore.similarity_search(query, k=k)
        return [doc.page_content for doc in results]

8.3 エピソードメモリ

class EpisodicMemory:
    """過去のインタラクションの成功/失敗パターンを記憶"""

    def __init__(self):
        self.episodes = []

    def record_episode(self, task, actions, outcome, success):
        self.episodes.append({
            "task": task,
            "actions": actions,
            "outcome": outcome,
            "success": success,
            "timestamp": datetime.now().isoformat()
        })

    def recall_similar(self, current_task, k=3):
        similar = find_similar_episodes(current_task, self.episodes, k)
        return similar

8.4 ワーキングメモリ(スクラッチパッド)

class WorkingMemory:
    """エージェントの現在のタスク状態を管理"""

    def __init__(self):
        self.scratchpad = {}
        self.current_plan = []
        self.completed_steps = []

    def update_plan(self, plan: list):
        self.current_plan = plan

    def mark_step_done(self, step_index: int, result: str):
        self.completed_steps.append({
            "step": self.current_plan[step_index],
            "result": result
        })

    def get_context(self) -> str:
        return f"""Current Plan: {json.dumps(self.current_plan)}
Completed Steps: {json.dumps(self.completed_steps)}
Scratchpad: {json.dumps(self.scratchpad)}"""

9. プロダクションデプロイ

9.1 安全性(あんぜんせい)

class SafeToolExecutor:
    ALLOWED_TOOLS = {
        "search_web", "get_weather", "search_products",
        "get_user_info", "create_ticket"
    }

    DANGEROUS_TOOLS = {
        "delete_user", "execute_sql", "send_email",
        "modify_config", "deploy_service"
    }

    def __init__(self):
        self.rate_limiter = RateLimiter(
            max_calls=100,
            time_window=60  # 毎分100回
        )

    async def execute(self, tool_name, args, user_context):
        # 1. 許可ツール確認
        if tool_name not in self.ALLOWED_TOOLS:
            if tool_name in self.DANGEROUS_TOOLS:
                return await self.request_human_approval(
                    tool_name, args, user_context
                )
            raise ToolNotAllowedError(f"'{tool_name}' is not whitelisted")

        # 2. レートリミット確認
        if not self.rate_limiter.allow(user_context.user_id):
            raise RateLimitExceededError()

        # 3. 入力検証
        self.validate_args(tool_name, args)

        # 4. 実行
        return await self.tool_registry[tool_name](**args)

9.2 可観測性(かかんそくせい)(Observability)

# LangSmith統合
from langsmith import traceable

@traceable(name="agent_run")
async def run_agent(query: str, session_id: str):
    with trace_context(session_id=session_id):
        result = await agent.invoke(query)
    return result

# Langfuse統合
from langfuse import Langfuse

langfuse = Langfuse()

def traced_tool_call(tool_name, args):
    generation = langfuse.generation(
        name=f"tool_call_{tool_name}",
        input=args,
        model="gpt-4o"
    )
    try:
        result = execute_tool(tool_name, args)
        generation.end(output=result, status="success")
        return result
    except Exception as e:
        generation.end(output=str(e), status="error")
        raise

9.3 コスト管理(かんり)

class CostTracker:
    # 価格テーブル(USD/1Mトークン、2025年基準)
    PRICING = {
        "gpt-4o": {"input": 2.50, "output": 10.00},
        "gpt-4o-mini": {"input": 0.15, "output": 0.60},
        "claude-3-5-sonnet": {"input": 3.00, "output": 15.00},
        "claude-3-5-haiku": {"input": 0.80, "output": 4.00},
    }

    def __init__(self, budget_limit: float = 10.0):
        self.total_cost = 0.0
        self.budget_limit = budget_limit

    def track(self, model, input_tokens, output_tokens):
        pricing = self.PRICING.get(model, {"input": 0, "output": 0})
        cost = (
            input_tokens / 1_000_000 * pricing["input"] +
            output_tokens / 1_000_000 * pricing["output"]
        )
        self.total_cost += cost

        if self.total_cost > self.budget_limit:
            raise BudgetExceededError(
                f"Budget exceeded: ${self.total_cost:.4f}"
            )

9.4 エラー回復(かいふく)パターン

class ResilientAgent:
    async def run_with_fallback(self, query, max_retries=3):
        for attempt in range(max_retries):
            try:
                return await self.primary_agent.run(query)
            except ToolExecutionError as e:
                logger.warning(f"Tool error (attempt {attempt+1}): {e}")
                if attempt < max_retries - 1:
                    await self.reset_tools()
                    continue
            except LLMError as e:
                logger.warning(f"LLM error: {e}, fallback使用")
                return await self.fallback_agent.run(query)
            except BudgetExceededError:
                return "予算超過。より簡単なクエリをお試しください。"

        return "複数回の試行後もリクエストを完了できませんでした。"

10. クイズ

Q1: AIエージェントの4つのコア構成要素は何ですか?

正解: LLM(頭脳)、Tools(ツール)、Memory(記憶)、Planning(計画)

LLMは自然言語理解と推論を担当し、Toolsは外部世界と相互作用し、Memoryは状態を維持し、Planningはタスクを分解して戦略を策定します。

Q2: OpenAIのtool_choiceオプションで「required」と「auto」の違いは?

正解: 「auto」はLLMがツール使用の有無を自律的に判断し、不要と判断すればテキストで応答します。「required」はLLMに少なくとも1つのツールを呼び出すことを強制します。単純な挨拶でもツールを呼び出してしまうため、ツール応答が必須の場面でのみ使用すべきです。

Q3: ReActパターンの3つのステージは何で、単純なTool Callingに対する利点は?

正解: Thought(思考) - Action(行動) - Observation(観察)。利点は、推論過程が明示的に出力されデバッグが容易なこと、多段階推論が必要な複雑なタスクに強力なこと、エラー発生時にどこで問題が起きたか把握しやすいことです。デメリットはトークン消費が多く、レイテンシが長くなることです。

Q4: MCP(Model Context Protocol)のアーキテクチャでHost、Client、Serverの役割は?

正解: HostはMCPを実行するアプリケーション(Claude Desktop、IDEなど)、ClientはHost内で特定のMCP Serverと通信するコネクタ、Serverは実際のツール/リソースを提供する外部プロセス(GitHubサーバー、DBサーバーなど)です。ClientとServerはstdioまたはSSEプロトコルで通信します。

Q5: プロダクションAIエージェントデプロイ時の安全性のための3つの主要戦略は?

正解: 1) Tool Whitelisting - 許可されたツールのみ実行可能に制限、2) Rate Limiting - 時間あたりのツール呼び出し回数を制限して乱用を防止、3) Human-in-the-Loop - 高リスク操作(削除、送信、デプロイなど)は人間の承認後に実行。追加で入力検証、監査ログ、コスト上限設定も重要です。


11. 参考(さんこう)資料(しりょう)

  1. OpenAI Function Calling Documentation - OpenAI API公式ドキュメント
  2. Anthropic Tool Use Guide - Claude ツール使用ガイド
  3. Google Gemini Function Calling - Google AI関数呼び出しドキュメント
  4. Model Context Protocol Specification - Anthropic MCP公式スペック
  5. LangChain Documentation - LangChainフレームワークドキュメント
  6. LangGraph Multi-Agent Guide - LangGraphマルチエージェントガイド
  7. CrewAI Documentation - CrewAI公式ドキュメント
  8. AutoGen Documentation - Microsoft AutoGenドキュメント
  9. ReAct: Synergizing Reasoning and Acting in LLMs - Yao et al., 2023
  10. Toolformer: Language Models Can Teach Themselves to Use Tools - Schick et al., 2023
  11. Gorilla: Large Language Model Connected with APIs - Patil et al., 2023
  12. LangSmith Documentation - LangSmith可観測性プラットフォームドキュメント
  13. Langfuse Open Source LLM Observability - Langfuseドキュメント
  14. HuggingFace Smolagents - HuggingFace軽量エージェントフレームワーク