[AI Agent Jump Start 基礎編#4] マルチエージェントの理解と実装
4-1. マルチエージェント設計とMCP開発(翻訳サービス連携)
この記事では以下の2点を解説します。
- マルチエージェント設計のベストプラクティス
- MCP(Model Context Protocol)を使った翻訳サービス連携
4-1. マルチエージェント設計
① エージェント間の役割分担
単一エージェントに全機能を持たせると以下の問題が発生します。
- プロンプトが肥大化しやすい
- 複雑なツール利用時に管理が困難
👉 そのため、役割ごとにエージェントを分離するのがベストプラクティスです。
設計例
-
ルーターAgent
入力を解析し、「どのエージェント(またはツール)に渡すか」を決定する。 -
プランナーAgent
複雑な質問をタスクに分解し、順序を決めて実行する。 -
ワーカーAgent
各タスクを実行(計算、検索、MCP API呼び出しなど)。
ルーターAgentの実装例
from agents import Agent
router_agent = Agent(
name="Router Agent",
instructions="""
あなたは入力を解析し、次のアクションを決めます。
- 「検索系」は Search Agent
- 「MCP操作系」は MCP Agent
- 「単純な質問」は Answer Agent
返答は「ハンドオフ」で対応。
""",
)
プランナーAgentの実装例
planner_agent = Agent(
name="Planner Agent",
instructions="""
質問をサブタスクに分割し、順序を提示してください。
可能なら、どのツールやエージェントを使うかも指定してください。
"""
)
MCP呼び出しを含めたフロー設計例
ユーザー入力
↓
ルーターAgent → プランナーAgent
↓
(サブタスクごとにハンドオフ)
↓
Search Agent or MCP Agent or Answer Agent
4-2. MCP開発(翻訳サービス連携)
MCPとは?
MCP(Model Context Protocol) は、AIモデル(LLM)と外部サービスやAPIを安全かつ一貫性のある方法でつなぐための共通ルールです。
なぜ必要?
LLMは柔軟な生成能力を持つ一方で、以下のようなリスクを引き起こす可能性があります。
-
予期せぬエンドポイントへのアクセス
社内の秘密URLや存在しない外部APIを勝手に推測し、アクセスしてしまう危険性。
→ 情報漏洩やセキュリティ事故につながる。 -
危険なリクエストの生成
「翻訳して」と入力しただけで、不要なパラメータや破壊的操作を付与する可能性。
→ APIの誤作動やデータ破壊のリスク。 -
入力パラメータの暴走
巨大テキストや不正値を送りつけ、DoS的な挙動を引き起こすリスク。
→ DoS的な障害を引き起こすリスク。
これらは「エージェント側の制御」だけでは完全に防ぎきれず、
プロトコルレベルでのガードレール が必要になります。
MCPを利用するメリット
MCPを導入することで、セキュリティ以外にも多くの利点があります。
-
スキーマ定義
translate(text: str, target_lang: str)
のように定義 → 余計なSQLやパラメータを生成できない。 -
入力・出力バリデーション
型・必須チェックで不正入力を排除。 -
実行ガードレール
定義済みツール以外は呼び出せない。未知のURLを叩けない。 -
一貫性と再利用性
MCP対応のサービスは、他のエージェント基盤でもそのまま利用可能。
→ ベンダーロックインを避け、横展開が容易。 -
開発効率の向上
プロンプトで「どう呼び出すか」を細かく書かなくても、MCPのスキーマを参照すればよい。
→ 開発者の負担が減り、実装がシンプルになる。
MCPを使う vs 使わない場合の比較
項目 | MCPあり | MCPなし |
---|---|---|
安全性 | スキーマで制御 | LLMが自由にリクエスト生成して暴走 |
拡張性 | 新しいAPIを追加しやすい | APIごとにカスタム実装が必要 |
ガードレール | 定義済みツールのみ呼び出し可能 | 未知のAPIや危険操作も実行しうる |
必要環境とライブラリ
- DeepL APIキーの準備(DeepL公式 で登録)
- Free / Proプラン選択
- 認証キー取得(Auth Key)
- Freeプランエンドポイント:
https://api-free.deepl.com
- Proプランエンドポイント:
https://api.deepl.com
注意点
- キーは公開リポジトリに絶対コミットしない
- 利用量(Freeは月あたり文字数上限あり)を把握する
- 本番は環境変数/Secret管理で注入
requirements.txt
fastapi==0.111.0
uvicorn==0.30.1
requests==2.32.3
mcp[cli]==1.12.4 # MCP公式SDK
python-dotenv==1.0.1
openai-agents==0.0.7
MCP開発(翻訳サービス連携)
コード詳細はこちら
インストール方法
pip install -r requirements.txt
.env ファイルにDeepLのAPIキーを設定:
# DeepL
DEEPL_AUTH_KEY=YOUR_DEEPL_AUTH_KEY
# Free/Proで切替(未設定なら Free を既定にします)
DEEPL_API_BASE=https://api-free.deepl.com
# Azure OpenAI
AZURE_OPENAI_API_KEY=""
AZURE_OPENAI_API_VERSION=""
AZURE_OPENAI_ENDPOINT=""
AZURE_OPENAI_DEPLOYMENT="gpt-4o"
フォルダ構成(最小)
mcp-translate/
├─ server/
│ └─ mcp_translate_server.py # FastAPI + FastMCP(/mcp に公開)
├─ agent_tools/
│ └─ translate_via_mcp.py # Agents SDK向けのMCPクライアントツール
├─ mcp_agent.py # Translate Agent(tools=[translate_via_mcp])
├─ run_mcp_agent.py # 実行エントリ(Runner.run_sync)
├─ requirements.txt
├─ .env.example # サンプル環境変数
① MCPサーバー実装(翻訳APIラッパー)
server/mcp_translate_server.py
import os
import html
import requests
from typing import Optional, Dict, Any
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
# .env ロード
load_dotenv()
mcp = FastMCP(name="Translator MCP", instructions="DeepLでテキストを翻訳します。")
# 自前でツールレジストリを持つ
tools_registry = {}
# DeepL呼び出し
def _call_deepl(
text: str, target_lang: str, source_lang: Optional[str]
) -> Dict[str, Any]:
auth_key = os.getenv("DEEPL_AUTH_KEY")
if not auth_key:
raise RuntimeError("DEEPL_AUTH_KEY が未設定です(.env を確認)")
api_base = os.getenv("DEEPL_API_BASE", "https://api-free.deepl.com")
url = f"{api_base}/v2/translate"
headers = {"Authorization": f"DeepL-Auth-Key {auth_key}"}
payload = {"text": [text], "target_lang": target_lang}
if source_lang:
payload["source_lang"] = source_lang
resp = requests.post(url, json=payload, headers=headers, timeout=15)
resp.raise_for_status()
data = resp.json()
tr = data["translations"][0]
translated = html.unescape(tr["text"])
return {
"provider": "deepl",
"translation": translated,
"detected_source_language": tr.get("detected_source_language"),
"target_lang": target_lang,
}
# MCP ツール定義
@mcp.tool()
async def translate(
text: str,
target_lang: str,
source_lang: Optional[str] = None,
) -> Dict[str, Any]:
return _call_deepl(text, target_lang, source_lang)
# ツールを自前でも登録(FastMCPが提供しない場合のため)
tools_registry["translate"] = translate
# FastAPI アプリ本体
app = FastAPI()
@app.post("/mcp")
async def call_mcp(request: Request):
payload = await request.json()
try:
tool_name = payload["tool_name"]
parameters = payload.get("parameters", {})
tool_func = tools_registry[tool_name]
result = await tool_func(**parameters)
return JSONResponse(content={"structuredContent": result})
except Exception as e:
return JSONResponse(status_code=500, content={"error": str(e)})
@app.get("/health")
def health():
return {"ok": True}
起動コマンド:
uvicorn server.mcp_translate_server:app --port 9999 --reload
② MCPクライアントツール
agents_tools/translate_via_mcp.py
# TODO translate_via_mcp client tool
import os
import httpx
import json
import uuid
from agents.tool import function_tool
MCP_URL = "http://localhost:9999/mcp"
# 二重呼び出し防止フラグと記憶用変数
_last_call_fingerprint = None
ENABLE_DUPLICATE_CALL_GUARD = (
os.getenv("ENABLE_DUPLICATE_CALL_GUARD", "true").lower() == "true"
)
def _extract_payload(res) -> dict:
# res が dict の場合(httpxで得たJSON直後)はここで処理
if isinstance(res, dict):
payload = res.get("structuredContent")
if isinstance(payload, dict):
return payload
content = res.get("content")
if isinstance(content, list):
for item in content:
if (
isinstance(item, dict)
and "json" in item
and isinstance(item["json"], dict)
):
return item["json"]
if (
isinstance(item, dict)
and "text" in item
and isinstance(item["text"], str)
):
return {"provider": "unknown", "translation": item["text"]}
raise RuntimeError(f"Unexpected MCP tool payload: {res!r}")
# res がオブジェクト形式の場合(Agent SDKやMCPクライアント経由など)
payload = getattr(res, "structuredContent", None)
if isinstance(payload, dict):
return payload
content = getattr(res, "content", None)
if isinstance(content, list):
for item in content:
if (
isinstance(item, dict)
and "json" in item
and isinstance(item["json"], dict)
):
return item["json"]
if (
isinstance(item, dict)
and "text" in item
and isinstance(item["text"], str)
):
return {"provider": "unknown", "translation": item["text"]}
raise RuntimeError(f"Unexpected MCP tool payload: {res!r}")
async def _call_mcp_translate(text, target_lang, source_lang):
payload = {
"tool_name": "translate",
"parameters": {
"text": text,
"target_lang": target_lang,
"source_lang": source_lang,
},
}
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.post(MCP_URL, json=payload)
resp.raise_for_status()
res = resp.json()
except Exception as e:
raise
payload = _extract_payload(res)
return payload
@function_tool
async def translate_via_mcp(text: str, target_lang: str, source_lang=None) -> str:
global _last_call_fingerprint
fingerprint = (text, target_lang, source_lang)
if ENABLE_DUPLICATE_CALL_GUARD:
if fingerprint == _last_call_fingerprint:
raise RuntimeError("DUPLICATE_TOOL_CALL")
_last_call_fingerprint = fingerprint
else:
print("[TOOL] Duplicate call guard is disabled (env override)")
try:
data = await _call_mcp_translate(text, target_lang, source_lang)
except Exception as e:
err = f"MCP_TRANSLATE_ERROR: {type(e).__name__}: {e}"
raise RuntimeError(err)
provider = data.get("provider") or "deepl"
translation = data.get("translation")
if not translation:
err = f"MCP_TRANSLATE_EMPTY: payload={data!r}"
raise RuntimeError(err)
return f"[{provider}] {translation}"
③ MCP Agent
mcp_agent.py
import os
from dotenv import load_dotenv
load_dotenv()
from agents import Agent
from agent_tools.translate_via_mcp import translate_via_mcp
from agents import Agent, OpenAIChatCompletionsModel, set_tracing_disabled
from openai import AsyncAzureOpenAI
client = AsyncAzureOpenAI(
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"), # 例: "2024-08-01-preview"
azure_endpoint=os.getenv(
"AZURE_OPENAI_ENDPOINT"
), # 例: "https://<your-resource>.openai.azure.com"
)
model = OpenAIChatCompletionsModel(
openai_client=client,
model=os.getenv("AZURE_OPENAI_DEPLOYMENT"), # ← デプロイ"名"(モデル名ではない)
)
set_tracing_disabled(disabled=True)
mcp_agent = Agent(
name="Translate Agent",
instructions="""
あなたは翻訳に関する質問を処理するエージェントです。
必ず translate_via_mcp ツールを利用してください。
""",
model=model,
tools=[translate_via_mcp],
)
④ 実行例
run_mcp_agent.py
from agents import Runner
from mcp_agent import mcp_agent
if __name__ == "__main__":
result = Runner.run_sync(
mcp_agent, "『私は寿司が好きです』を英語に訳してください。"
)
print("\n=== 最終回答 ===\n")
print(result.final_output)
出力例:
=== 最終回答 ===
『私は寿司が好きです』は英語で『I like sushi.』です。
✅ まとめ
MCPを使うと、翻訳APIを安全にラップし、LLMから安心して利用可能になる。
FastAPIで:9999にMCPサーバーを立て、Agents SDKから呼び出すことで翻訳エージェントが簡単に作れる。
マルチエージェントと組み合わせれば「会議メモを英訳」「外国語記事を日本語要約」など応用自在。
Discussion