はじめに
AI Agentを構築するうえで、MCP(Model Context Protocol) を使ったツール呼び出しは必須です。
今回は、MCPサーバをNode.js(Express)で構築し、最初はOpenAI(gpt-4o)を使っていたところ、Groq(llama-3.3-70b-versatile)に切り替えたらレスポンスが劇的に速くなったという体験をまとめます。
デモ動画
まずはデモ動画をご覧ください。同じ質問に対するレスポンス速度の違いが一目瞭然です。
OpenAI(gpt-4o)版
Groq(llama-3.3-70b-versatile)版
システム構成
ブラウザ (Chat UI)
│ POST /api/chat/stream(SSEストリーミング)
▼
Express サーバ (Node.js)
│ Tool Calling ループ
▼
LLM(OpenAI または Groq)
│ ツール呼び出し
▼
MCP サーバ(SSE Transport / Streamable HTTP)
│
├── get_company_sales → 売上データ取得
├── get_monthly_invoice → 月次請求金額取得
└── issue_monthly_invoice_pdf → 請求書PDF発行 (S3アップロード)
MCPサーバは @modelcontextprotocol/sdk を使って実装しています。LLMとの接続には OpenAI互換のFunction Callingを利用しているため、GroqもOpenAI SDKそのままで呼び出せます。
実装のポイント
LLMプロバイダーを環境変数で切り替える
クライアントの生成部分をプロバイダーごとに分離し、環境変数 LLM_PROVIDER で切り替えられるようにしました。
// chatService.mjs
const llmProvider = (process.env.LLM_PROVIDER ?? "groq").toLowerCase();
// OpenAI クライアント
const openaiClient = openaiApiKey
? new OpenAI({ apiKey: openaiApiKey })
: null;
const openaiModel = process.env.OPENAI_MODEL ?? "gpt-4o";
// Groq クライアント(OpenAI互換 baseURL を指定するだけ)
const groqClient = groqApiKey
? new OpenAI({
apiKey: groqApiKey,
baseURL: "https://api.groq.com/openai/v1",
})
: null;
const groqModel = process.env.GROQ_MODEL ?? "llama-3.3-70b-versatile";
GroqはOpenAI互換のAPIを提供しているため、baseURL を変えるだけで OpenAI SDKをそのまま流用できます。切り替えに必要な変更は最小限です。
ツールコールループ(Agentic Loop)
LLMがツールを呼ぶ限り繰り返し実行する、いわゆる Agentic Loop を実装しています。
const MAX_TOOL_ROUNDS = 10;
for (let i = 0; i < MAX_TOOL_ROUNDS; i++) {
const response = await client.chat.completions.create({
model,
messages,
tools: toolDefinitions,
});
const choice = response.choices[0];
// ツールコールがなければ最終応答を返す
if (choice.finish_reason !== "tool_calls" || !choice.message.tool_calls?.length) {
return choice.message.content ?? "";
}
// ツールを実行してメッセージに追加し、再度LLMへ
messages.push(choice.message);
for (const tc of choice.message.tool_calls) {
const result = await executeTool(tc.function.name, JSON.parse(tc.function.arguments));
messages.push({ role: "tool", tool_call_id: tc.id, content: result });
}
}
Llama系モデル特有の問題への対処
Groqで使用するLlamaモデルでは、ツール名に引数が埋め込まれてしまうバグが稀に発生します。
// 例: 本来は name="get_monthly_invoice", arguments='{"billing_month":"2026-01"}'
// 実際に返ってくる: name='get_monthly_invoice={"billing_month":"2026-01"}', arguments='{}'
これを吸収するために sanitizeToolCall 関数を実装しています。
function sanitizeToolCall(name, argsString) {
const eqIdx = name.indexOf("=");
if (eqIdx !== -1) {
const realName = name.slice(0, eqIdx).trim();
const embeddedArgs = name.slice(eqIdx + 1).trim();
if (embeddedArgs && (!argsString || argsString === "{}")) {
return { name: realName, arguments: embeddedArgs };
}
try {
const embedded = JSON.parse(embeddedArgs);
const existing = JSON.parse(argsString || "{}");
return { name: realName, arguments: JSON.stringify({ ...embedded, ...existing }) };
} catch {
return { name: realName, arguments: argsString || embeddedArgs };
}
}
return { name, arguments: argsString };
}
リトライ機構
Groqは高速な反面、レートリミット(429)やサービス一時不可(503)が発生することがあります。指数バックオフ付きのリトライを実装しました。
const MAX_RETRIES = 3;
const RETRY_BASE_DELAY_MS = 1000;
function isRetryableError(error) {
const msg = error instanceof Error ? error.message : String(error);
return (
msg.includes("failed_generation") ||
msg.includes("Failed to call a function") ||
error?.status === 429 ||
error?.status === 503 ||
error?.status === 502
);
}
// リトライループ
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
// ... API呼び出し ...
} catch (error) {
if (isRetryableError(error) && attempt < MAX_RETRIES - 1) {
const delay = RETRY_BASE_DELAY_MS * 2 ** attempt;
await sleep(delay);
continue;
}
throw error;
}
}
ストリーミング対応
ユーザー体験向上のため、SSEストリーミングにも対応しています。ツール実行中も { type: "tool_call", name } イベントを送出することで、フロントエンド側で「ツール呼び出し中…」といった状態表示が可能です。
// ツール実行前にフロントへ通知
yield { type: "tool_call", name: tc.function.name };
const result = await executeTool(tc.function.name, args);
messages.push({
role: "tool",
tool_call_id: tc.id,
content: result,
});
速度比較
| プロバイダー | モデル | 体感レスポンス |
|---|---|---|
| OpenAI | gpt-4o | 数秒〜十数秒(ツール呼び出しを含む場合) |
| Groq | llama-3.3-70b-versatile | 体感1〜2秒以内 |
GroqはLPU(Language Processing Unit)という独自チップで推論を行うため、トークン生成速度がGPUベースのサービスと比べて桁違いに速いです。Tool Callingが複数ラウンドにわたるAgentic Loopでは、この差がさらに大きく出ます。
まとめ
- MCPサーバ + Node.jsでAI Agentを構築し、ツール呼び出しをAgentic Loopで実現した
- GroqはOpenAI互換APIのため、
baseURLを1行変えるだけで移行できた -
llama-3.3-70b-versatileはFunction Callingに対応しており、Agentic Loopでも問題なく動作する - Groq特有の「ツール名に引数が埋め込まれるバグ」はサニタイズ関数で対処できる
- レート制限対策として指数バックオフ付きリトライを入れておくと安心
- レスポンス速度はGroqが圧倒的に速く、ユーザー体験が大きく向上した
OpenAIモデルの品質・安定性は素晴らしいですが、スピードを重視するユースケースではGroqは非常に有力な選択肢です。ぜひ試してみてください。
参考
- MakeSomethingNew、企業向けAIエージェント構築サービスを提供開始(PR TIMES)
- Model Context Protocol (MCP) 公式ドキュメント
- Groq API ドキュメント
- @modelcontextprotocol/sdk (npm)