はじめに
現代の Web アプリケーションでは、フロントエンドからバックエンド API を呼び出す構成が一般的です。
ユーザーは UI を操作して API リクエストを発行し、必要な情報を取得したり操作を行ったりします。
しかし、この方法にはいくつかの課題があります:
- ユーザーは UI の使い方を覚える必要がある
- 複雑な操作は手順が多く、ユーザーの負担となる
- 特定のタスクを自動化することが困難
これらの課題を解決する一つの方法として、Mastra のようなチャットエージェントを活用することが考えられます。
この方法では、ユーザーは自然言語で指示を出すだけで、エージェントが適切な API を呼び出して操作を代行します。
本記事では、認証が必要なバックエンド API を Mastra Agent が Tool を使って呼び出せるようにする実装方法について解説します。
Mastra とは
Mastra は、TypeScript ベースのオープンソースエージェントフレームワークです。
AI アプリケーションや機能を素早く構築するための基本的な要素を提供しています。
主な要素として以下が挙げられます:
- Agent: メモリを持ち、関数を実行できる AI エージェント
- Tools: エージェントが利用できる機能(関数)
- Workflow: 決定的な LLM 呼び出しのチェーン
- RAG: アプリケーション固有の知識をエージェントに供給する仕組み
- Playground: ローカルでエージェントと対話しながら開発できる環境
今回は Agent と Tools を扱います。
Agent
Mastra における Agent とは、LLM がタスクを実行するために必要な一連のアクションを自律的に決定できるシステムです。
Agent は Tool や Workflow などにアクセスでき、複雑なタスクを実行したり外部システムと連携したりすることができます。
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
export const myAgent = new Agent({
name: "My Agent",
instructions: "You are a helpful assistant.",
model: openai("gpt-4o-mini"),
});
Tool は Agent や Workflow によって実行できる型付き関数です。
実際にタスクを実行したり外部システムと連携したりする処理を行います。
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const weatherTool = createTool({
id: "getWeather",
description: "Get the current weather for a location",
inputSchema: z.object({
location: z.string().describe("The location to get weather for"),
}),
execute: async ({ context }) => {
const response = await fetch(`${your_api}/weather?location=${context.location}`);
return response.json();
},
});
今回扱うアーキテクチャは既存のフロントエンドとバックエンドの間に Mastra Server を配置する方式です。
従来の構成:フロントエンド > バックエンド API
新しい構成:フロントエンド > Mastra Server > バックエンド API
この構成の利点は以下の通りです:
- 既存のバックエンド API をそのまま活用できる
- フロントエンドの変更を最小限に抑えられる
- 実装コストが比較的低い
- 自然言語インターフェースを通じた操作が可能になる
ユーザーは従来の UI を操作する代わりにチャットインターフェースを通じて「〇〇の情報を取得して」「××を更新して」などと自然言語で指示するだけで、エージェントが適切な API を呼び出して操作を実行します。
認証の課題
このアーキテクチャを実装する際の主な課題は、バックエンド API の認証情報をどのように Agent/Tool から渡すかということです。
ここでは Authorization ヘッダーに JWT などのアクセストークンを含める認証方式を考えます。
多くの場合ユーザーがログインするとフロントエンドがアクセストークンを取得し、それをリクエストヘッダーに含めてバックエンド API を呼び出します。
Mastra Server を中継点として導入する場合、このアクセストークンを Mastra Server 越しにバックエンド API へのリクエストに含めて渡す必要があります。
解決方法: RuntimeContext を利用したトークンの受け渡し
Mastra には「RuntimeContext」という依存性注入の仕組みがあり、これを使うことで解決できます。
RuntimeContext は Agent や Tool に実行時の設定を提供するためのシステムで、以下のような特徴があります:
- 型安全な runtimeContext を通じてエージェントに実行時の設定変数を渡せる
- ツールの実行コンテキスト内でこれらの変数にアクセスできる
- 基盤となるコードを変更せずにエージェントの動作を変更できる
- 同じエージェント内の複数のツール間で設定を共有できる
先ほどの課題は以下のようにして解決できます。
- Mastra Server のミドルウェアでリクエストヘッダーからアクセストークンを取り出し、RuntimeContext に保存
- Mastra Agent の Tool で RuntimeContext からアクセストークンを取得し、バックエンド API を呼び出す
Mastra Server に ミドルウェア を設定し、クライアントからのリクエストヘッダーからアクセストークンを抽出します。抽出したトークンは RuntimeContext に保存します。
type MyRuntimeContext = {
authToken: string;
};
export const mastra = new Mastra({
server: {
middleware: [
{
handler: async (c, next) => {
const runtimeContext =
c.get<MyRuntimeContext>("runtimeContext");
const authHeader = c.req.header("Authorization");
if (!authHeader) {
return new Response("Unauthorized", { status: 401 });
} else {
const authToken = authHeader.split(" ")[1];
runtimeContext.set("authToken", authToken);
}
await next();
},
path: "/api/agents/yourAgent/*",
},
],
},
});
このコードではクライアントからのリクエストから Authorization ヘッダーを取得し、"Bearer " の後ろの部分をアクセストークンとして RuntimeContext に保存しています。
次に、Mastra Agent の Tool を実装し、RuntimeContext からアクセストークンを取得してバックエンド API を呼び出します。
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const getUserInfo = createTool({
id: "getUserInfo",
description: "ユーザー情報を取得するツール",
inputSchema: z.object({
userId: z.string().describe("情報を取得するユーザーID"),
}),
execute: async ({ context, runtimeContext }) => {
var authToken = runtimeContext.get("authToken") as string | undefined;
if (!authToken) {
throw new Error("認証情報がありません");
}
const response = await fetch(`${YOUR_API_ENDPOINT}/users/${context.userId}`, {
method: "GET",
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`API エラー: ${response.status}`);
}
const data = await response.json();
return data;
},
});
このツールでは RuntimeContext から取得したアクセストークンを Authorization ヘッダーに含めてバックエンド API を呼び出します。
エージェントの設定と複数ツールの連携
最後に作成したツールをエージェントに登録します。複数の API エンドポイントに対応するツールを連携させることで、ユーザーは自然言語で様々な操作を依頼できるようになります。
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
import { getUserInfo, updateUserInfo, getOrderHistory } from "../tools/userTools";
export const userManagementAgent = new Agent({
name: "ユーザー管理アシスタント",
instructions: `あなたはユーザーがアカウント情報を管理するのを支援するアシスタントです。
ユーザー情報の取得、プロフィールの更新、注文履歴の確認などを手伝うことができます。`,
model: openai("gpt-4o-mini"),
tools: {
getUserInfo,
updateUserInfo,
getOrderHistory,
},
});
この設定により、ユーザーは「私のアカウント情報を教えて」「メールアドレスを変更して」「最近の注文履歴を見せて」などと自然に問いかけるだけで、エージェントが適切なツールを選択して API を呼び出し、対応することができます。各ツールは RuntimeContext から同じ認証トークンを利用するため、ユーザーセッション内で一貫した認証状態を維持できます。
開発時の課題と解決策
上述した実装の場合、Mastra Playground をローカルで実行してプロンプトを調整する際、Playground からの呼び出しにはアクセストークンが含まれないため バックエンド API を呼び出せないという課題があります。
これに対しては以下のような解決策が考えられます:
- 開発環境では認証をバイパスする
- 開発環境用のモックアクセストークンを使用する
- 開発環境専用のモックAPIを用意する
開発環境での認証バイパスの例
export const authMiddleware = async (c, next) => {
const runtimeContext = c.get<MyRuntimeContext>("runtimeContext");
const authHeader = c.req.header("Authorization");
if (!authHeader) {
if (process.env.NODE_ENV === "development") {
console.log("開発環境: 認証をバイパスします");
const devToken = await getDevAuthToken();
runtimeContext.set("authToken", devToken);
} else {
return new Response("Unauthorized", { status: 401 });
}
} else {
const authToken = authHeader.split(" ")[1];
runtimeContext.set("authToken", authToken);
}
await next();
};
async function getDevAuthToken() {
return process.env.DEV_AUTH_TOKEN || "dev-token-for-testing";
}
この方法では、開発環境では Authorization ヘッダーが無い場合でも開発用のトークンを自動的に設定するため、Playground からのテストが容易になります。
モック API を活用する方法
もう一つの方法として、開発環境ではモック API を使用することもできます。
export const getUserInfo = createTool({
execute: async ({ context, runtimeContext }) => {
var authToken = runtimeContext.get("authToken") as string | undefined;
if (!authToken) {
throw new Error("認証情報がありません");
}
if (process.env.NODE_ENV === "development") {
return getMockUserInfo(context.userId);
}
const response = await fetch(`https://api.example.com/users/${context.userId}`, {
});
},
});
function getMockUserInfo(userId: string) {
return {
id: userId,
name: "テストユーザー",
email: "test@example.com",
preference: {
theme: "light",
language: "ja",
},
};
}
この方法では、開発環境でモックデータを返すことで、実際の API がなくてもエージェントの動作をテストできます。
まとめ
Mastra の RuntimeContext を活用することで、認証が必要なバックエンド API を Agent/Tool 越しに安全に呼び出すことができます。この方法は、以下のような利点があります:
- セキュリティを確保しながら API アクセスを実現
- ユーザーエクスペリエンスの向上(自然言語インターフェース)
- 既存のバックエンドシステムとの連携が容易
- 複雑な操作の自動化が可能
このアプローチにより、ユーザーは複雑な UI の操作方法を覚える必要がなく自然言語で指示を出すだけで様々な操作が行えるようになります。また認証情報はフロントエンドで取得したものを Middleware を通じて Agent に渡す方式なので、セキュリティリスクも最小限に抑えられます。
Mastra は気軽にエージェントの開発や検証が出来て楽しいので、ぜひ触ってみてください。特に、既存のシステムに自然言語インターフェースを追加したい場合は今回紹介した RuntimeContext を活用したアプローチが参考になるでしょう。