🌊

Mastra で作る AIエージェント(2) 会話履歴とネット検索

に公開

Mastra で作るAI エージェント(2) 会話履歴とネット検索

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


エージェントにどんどんツールを追加したい

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

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

現在のところ、ツールとしての将軍は関羽ひとりしかいません。三国志の世界では劉備に「五虎大将軍」がいたように、我々のエージェントにも様々なツールを付属していきましょう

ネット検索するツール

LLMの苦手なことのひとつに「モデルが作られたあとのこと(学習していないこと)は分からない=最新のニュースは知らない」という点がありました。劉備が「ネット検索ツール」という将軍を擁していると、孔明も心強いですよね。

web-search-agent.ts というWeb検索エージェントです。

// web-search-agent.ts
import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';

export const webSearchAgent = new Agent({
  id: 'web-search-agent',
  name: 'Web Search Agent',
  instructions: `
あなたはWeb検索を行うアシスタントです。

## できること
- インターネット上の最新情報を検索して回答
- ニュース、技術情報、一般的な質問への回答
- 複数の情報源を参照した総合的な回答

## 回答時のルール
- 検索結果に基づいて正確な情報を提供してください
- 情報の出典や日付が分かる場合は明記してください
- 複数の情報源がある場合は、それらを総合して回答してください
- 最新の情報かどうかを考慮し、古い情報の場合はその旨を伝えてください
- 検索で見つからなかった場合は、その旨を正直に伝えてください

## 注意事項
- 検索結果を鵜呑みにせず、信頼性を考慮してください
- 主観的な意見と事実を区別して回答してください
  `,
  model: openai('gpt-5-mini'),
  tools: {
    webSearch: openai.tools.webSearch(),
  },
});

今回のポイントは、末尾のこの部分でしょう。

  tools: {
    webSearch: openai.tools.webSearch(),
  },

まず、「tools 」配下のJSONの中に、 webSearch というプロパティがあります。これは名前は何でもよくて、自分が分かるのであれば searchEngine でも myTool でも好きな名前に変更できます。

その後ろの、 openai.tools.webSearch()ですが、これは Vercel AI SDK の OpenAI プロバイダーが提供する Web 検索ツール@ai-sdk/openai パッケージに含まれています。OpenAIのモデルに Web 検索機能を追加するラッパーツールという位置づけです。

いずれにしても、1行書いただけでWeb検索ツールを追加できるのだからラクチンですよね。Web検索ツールは、他にもいろいろな選択肢があり、用途や予算に応じて最適なものを選ぶとよいと思います。

MCPを使って簡単にツールを追加

MCP(Model Context Protocol)は、LLMと外部ツール、つまり我々の例では劉備と関羽を接続するための標準規格(プロトコル)です

世の中にはいろいろな「AIエージェント(劉備たち)」がいて、一方でいろいろな「ツール(関羽や張飛といった将軍たち)」がいて、将軍とエージェントとの間では様々なコミュニケーションが繰り広げられています。「拙者、メール送信サービスでござる、拙者を使う場合のパラメータ定義はうんぬん」「あ、そ。じゃあこの宛先にこんなメールを送っておいて」「承知つかまつった、現在のステータスは・・・・」

俯瞰でみれば、会話の内容なんてだいたい同じようなものなのですが、ツールごとに、あるいはエージェントごとに「プロトコル」が違っていたんですね。履歴書の様式が微妙に違うというか。電源コンセントの形状が国ごとに違うというか。

で、それを標準化しましょう、そうすれば同じ履歴書を使いまわせる! ということでその規格をAnthropic社が2024年11月に発表しまして、多くのAIエージェントおよびツールがそれに乗っかった、というものです。2025年の前半は、AIエージェント周辺ではMCPの話題がかなり賑わっていました。

さて、Mastra エージェントもMCPをサポートします。これにより、既存の有用なツールを流用することが可能になります。今回は先ほどのエージェントに無理やり「GitHub MCP」をツールとして追加してみました。

import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { MCPClient } from '@mastra/mcp';

const githubMcp = new MCPClient({
  id: 'github-mcp',
  servers: {
    github: {
      command: 'npx',
      args: ['-y', '@modelcontextprotocol/server-github'],
      env: process.env.GITHUB_TOKEN
        ? { GITHUB_TOKEN: process.env.GITHUB_TOKEN }
        : {},
    },
  },
});

export const webSearchAgent = new Agent({
  id: 'web-search-agent',
  name: 'Web Search Agent',
  instructions: `
あなたはWeb検索を行うアシスタントです。

## できること
- インターネット上の最新情報を検索して回答
- ニュース、技術情報、一般的な質問への回答
- 複数の情報源を参照した総合的な回答
- GitHub MCPを使って、リポジトリ検索・Issue/PR・コード/READMEの参照が可能

## 回答時のルール
- 検索結果に基づいて正確な情報を提供してください
- 情報の出典や日付が分かる場合は明記してください
- 複数の情報源がある場合は、それらを総合して回答してください
- 最新の情報かどうかを考慮し、古い情報の場合はその旨を伝えてください
- 検索で見つからなかった場合は、その旨を正直に伝えてください
- GitHub MCPを使った場合は、リポジトリやIssue/PR番号など参照先を明記してください

## 注意事項
- 検索結果を鵜呑みにせず、信頼性を考慮してください
- 主観的な意見と事実を区別して回答してください
  `,
  model: openai('gpt-5.1'),
  tools: async () => {
    let githubTools = {};

    try {
      githubTools = await githubMcp.listTools();
    } catch (error) {
      console.warn(
        '[web-search-agent] GitHub MCP tools are unavailable. ' +
        'Set GITHUB_TOKEN in .env if you want GitHub tools enabled.',
        error,
      );
    }

    return {
      webSearch: openai.tools.webSearch(),
      ...githubTools,
    };
  },
});

これによりエージェントは、必要に応じてWeb検索だけでなく、GitHubに接続してIssuesとかPullRequestの情報も使って回答することができるようになります。

会話履歴を使って回答する

あと、地味だけどすごく重要なのが、会話履歴です。ChatGPTでも、何度も会話のラリーが続きますよね。いちいち「え・・・と、何でしたっけ?」って言われたらイラっとしますよね。

あれは僕らの体感としては、あたかも人間としゃべっているかのように、AIと自然に会話をしているつもりになっているのですが、実は LLM孔明は話した内容をどんどん忘れます。僕らが会話を持ち掛けるたびに毎回、「エージェント・劉備」と「会話履歴担当の文官」がしこしこ「これまでの会話の内容」をかき集めまして、「ちなみに、過去の会話履歴はこのとおりじゃ」と孔明に届けているのです。「どうする、孔明?」

先ほどのエージェントに、さらに会話履歴を追加したものがこちらです。

import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
import { MCPClient } from '@mastra/mcp';

// 中略

export const webSearchAgent = new Agent({
  id: 'web-search-agent',
  name: 'Web Search Agent',
  instructions: `  (中略)  `,
  model: openai('gpt-5.1'),
  tools: async () => {
    /*(中略)*/
    return {
      webSearch: openai.tools.webSearch(),
      ...githubTools,
    };
  },
  memory: new Memory({
    storage: new LibSQLStore({
      id: 'web-search-storage',
      url: 'file:../mastra.db', // path is relative to the .mastra/output directory
    }),
  }),
});

new Agent() の中で、これまでの将軍は tools というプロパティに定義していましたが、会話履歴は memoryというプロパティを使います。 tools は外部アクション用、 memoryは履歴や状態の保持・検索用です。

memoryの使い道は、会話履歴の他にもいろいろあるのですが(スレッド管理、セマンティックリコール、ワーキングメモリなど)、おいおい解説していきます。

memory の具体的な格納先はstorage としてLibSQLが選択されています。ローカルでの簡易的な用途に向いています。本番運用でしっかり永続+検索拡張するにはPostgreSQL / pgvector などが向くかもしれません。

AIエージェントとツールの関係

AIエージェントを作って使ってしてみると、やはり「賢さ」の源泉は「LLM」が担っているのだとつくづく感じます。蜀軍の頭脳は孔明なのです。

しかし、孔明の賢さも将軍たちがいてこそ輝くように、LLMに「どんなツールを持たせるか」次第で、単なるテキスト生成を超えて API、データベース、その他のシステムとやり取りできるように能力が拡張されることになります。

ひとつ面白いのは、エージェント・劉備は軍師・孔明LLMに「将軍=ツールのリスト」を渡すだけで、それらの将軍=ツールを使うかどうか、いつ使うか、これは孔明LLMが決める、という点です。リストに載っているのに使われなかったり、劉備の意図しないタイミングで将軍が使われたりすることもあります。

もちろん、システムプロンプトの形でエージェントからLLMに対してツールを使う順番やタイミングを示唆することはできますが、最終決定はあくまでLLMが握っています。このあたりが「AIエージェントとは自律的に動くAIです」と言われる「自律」の一端なのだと理解しています。

>> 次回 : (3) RAGで社内情報を活用~チャンク編

Discussion