1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCPサーバを使ったAI Agentで、モデルをOpenAIからGroqに替えたらレスポンスが爆速になった話

1
Last updated at Posted at 2026-04-26

はじめに

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)

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?