🤖

Mastra で作る AIエージェント(5) 軍師を気軽に交換

に公開

Mastra で作るAI エージェント というシリーズの第5回です。


前回までは、AIエージェントの構成を「三国志」になぞらえて以下のように把握しました。

  • フロントに立つリーダーの劉備=エージェント:何をやるにしても軍師に相談
  • 天才軍師・諸葛孔明=LLM:劉備に何かと助言するが、決して自分が直接前面に出ない
  • 将軍たち=ツール:劉備に呼ばれて定型作業を遂行、RAG/API/MCPなどなど

ひととおり、「チーム劉備」の組成の仕方が分かったところで、今回からしばらくは、チームの組成以外の話をまとめてご紹介します。今回はモデルルーティングです。

軍師を交換してみたい

AIエージェントの頭脳を担うのは、言わずと知れたLLMであり、我々の三国志の例では「軍師・孔明」なのですが、「軍師は孔明でなきゃいかんのか? 司馬仲達や周瑜に取り替えたいのだが?」ということがありえます。現実世界では、例えば「OpenAIのgptだけではなく、AnthropicのClaudeやGoogleのGeminiも使いたい」という感じでしょうか。もちろん、交換可能です。

各社バラバラなAPIインタフェース

ただ、問題なのは、OpenAIとAnthropicとGoogleとで会社が違うものですから、各社のAPIの使い方が微妙に違うのです。Mastra を使わずに素で各社のAPIを使おうとするとこうなります

OpenAI API を使う場合

import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

async function chatWithOpenAI(userMessage: string) {
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      { role: 'system', content: 'あなたは親切なアシスタントです。' },
      { role: 'user', content: userMessage }
    ],
    temperature: 0.7,
    max_tokens: 1000,
  });

  return response.choices[0].message.content;
}

// 使用例
const result = await chatWithOpenAI('こんにちは!');
console.log(result);

Anthropic Claude API を使う場合

import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

async function chatWithClaude(userMessage: string) {
  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 1000,
    temperature: 0.7,
    system: 'あなたは親切なアシスタントです。',
    messages: [
      { role: 'user', content: userMessage }
    ],
  });

  return response.content[0].type === 'text' 
    ? response.content[0].text 
    : '';
}

// 使用例
const result = await chatWithClaude('こんにちは!');
console.log(result);

Google Gemini API を使う場合

import { GoogleGenerativeAI } from '@google/generative-ai';

const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!);

async function chatWithGemini(userMessage: string) {
  const model = genAI.getGenerativeModel({ 
    model: 'gemini-pro',
    generationConfig: {
      temperature: 0.7,
      maxOutputTokens: 1000,
    },
  });

  const chat = model.startChat({
    history: [
      {
        role: 'user',
        parts: [{ text: 'あなたは親切なアシスタントです。' }],
      },
      {
        role: 'model',
        parts: [{ text: '承知しました。何でもお聞きください。' }],
      },
    ],
  });

  const result = await chat.sendMessage(userMessage);
  return result.response.text();
}

// 使用例
const result = await chatWithGemini('こんにちは!');
console.log(result);

これらのコードを見ると、以下の違いがあることがわかります:

  1. 初期化方法が異なる
    • OpenAI: new OpenAI({ apiKey })
    • Claude: new Anthropic({ apiKey })
    • Gemini: new GoogleGenerativeAI(apiKey) → getGenerativeModel()
  2. メッセージの構造が異なる
    • OpenAI: messages 配列に role と content
    • Claude: messages 配列だが system は別パラメータ
    • Gemini: chat.startChat() で history を設定、parts 配列
  3. レスポンスの取得方法が異なる
    • OpenAI: response.choices[0].message.content
    • Claude: response.content[0].text
    • Gemini: result.response.text()
  4. パラメータ名が異なる
    • OpenAI: max_tokens
    • Claude: max_tokens
    • Gemini: maxOutputTokens

ということで、「孔明から仲達にチェンジ!」と思うと、先ほどの20~30行を書き換える必要が出てきてしまいます。

1か所変更すればOK

Mastraを使えば、以下のように1か所変更するだけで切り替わります。これをモデルルーティングと呼びます。

export const weatherAgent = new Agent({
  id: 'weather-agent',
  name: 'Weather Agent',
  instructions: `(プロンプト)`,
  model: openai('gpt-5-mini'),    /* <=========ここだけ変更すればOK  */
  tools: { weatherTool },
});

もっとも、これは Mastra 自体が内製しているのではなく、内部で業界標準の Vercel AI SDK というライブラリを使用して実現しています。

AIエージェントフレームワークの代表的な機能の一つ

モデルルーティングは、まさにMastraを含む「AIエージェントフレームワーク」の代表的な機能の一つです。「劉備チーム」のLLMに「司馬仲達」というのもなかなかショッキングですが、よいモデルが出てきたらサッと切り替えられる手軽さも重要です。

>> 次回 : (6) AIの考え中にユーザを待たせない

Discussion