はじめに
現在、Firebase Genkitをバックエンドに採用し、スケジュール管理や目標設定をサポートするパーソナルAIエージェントアプリを開発しています。
AIエージェント開発において、機能(カレンダー操作、タスク管理、過去の記憶の検索など)が増えれば増えるほど、「すべてのリクエストを高コストのLLMモデル+全ツール+RAG(Vector Search)で処理すると、コストが跳ね上がり、レスポンスも極端に遅くなる」 という深刻な課題に直面します。
たとえば、ユーザーが「おはよう」と挨拶しただけなのに、AIが裏側で一生懸命データベースを検索したり、カレンダーツールを起動しようか迷ったりするのは明らかに非効率です。
この記事では、Firebase Genkitを用いて 「ユーザーの入力を安価で高速なモデルで事前分類(意図分類)し、最適な処理ルートへ振り分ける司令塔アーキテクチャ」 を実装し、コスト削減と爆速レスポンスを両立した方法を紹介します。
対象読者
- AIエージェントやLLMアプリのAPIコスト・レイテンシ(遅延)に悩んでいる方
- Firebase Genkitを使った実践的なルーティング(処理の分岐)の設計を知りたい方
- LLMの「Function Calling(ツール呼び出し)」の精度を上げたい方
アーキテクチャの概要:意図分類(Intent Classification)とは?
本アプリでは、メインの処理に入る前に、高速・安価な軽量モデル(Gemini 3 Flash / 3.1 Flash Lite 等)を使ってユーザーの「意図」を分析する専用のフロー を挟んでいます。
- ユーザー入力: 「明日の10時に会議を入れて」
-
司令塔(意図分類): 軽量モデルが瞬時に分析し、
intent: 'SCHEDULE_WRITE'と判定。 -
メイン処理(ルーティング):
- スケジュール登録用のツール(Function Calling)だけを有効化
- 複雑な操作が必要なため、高性能なモデル(Gemini 3.1 Pro 等)に切り替え
- 過去の記憶(RAG)は不要なので Vector Search はスキップ
このように、意図に応じて「使うツール」「使うモデル」「DB検索の有無」を動的に切り替えます。
実装例
実際に稼働しているGenkitのバックエンドコードをベースに解説します。
1. 司令塔となる「意図分類フロー」の定義
まずは、分類するカテゴリ(Intent)のスキーマを定義し、分類用のGenkit Flowを作成します。
(抜粋)
import { z } from 'genkit';
import { ai } from '../core/bootstrap';
import { vertexAI } from '@genkit-ai/google-genai';
// 1. ユーザーの意図(Intent)の定義
const UserIntentSchema = z.enum([
'SCHEDULE_READ', // 予定の確認
'SCHEDULE_WRITE', // 予定の登録・変更
'TASK_OP', // タスク(やること)の操作
'MEMORY_RECALL', // 過去の記憶の検索
'GENERAL_KNOWLEDGE', // 一般的な質問
'CHIT_CHAT' // 挨拶や雑談
]);
// 2. 分類結果のスキーマ
const IntentClassificationResultSchema = z.object({
intent: UserIntentSchema,
hasContextReference: z.boolean().describe("「それ」「あれ」など過去の文脈を指しているか"),
requiresWebSearch: z.boolean().describe("最新のWeb検索が必要か"),
});
// 3. 意図分類フロー(司令塔)
const intentClassifierFlow = ai.defineFlow({
name: 'intentClassifierFlow',
inputSchema: z.object({
query: z.string(),
lastAiMessage: z.string().optional(),
}),
outputSchema: IntentClassificationResultSchema,
}, async ({ query, lastAiMessage }) => {
const prompt = `ユーザーの入力を分析し、指定のJSON形式で出力してください。
[分類ルール]
- SCHEDULE_WRITE: 時刻指定のある予定の操作(例:「明日の15時に会議」)
- TASK_OP: 時刻指定のないタスク操作(例:「牛乳を買う」)
- CHIT_CHAT: 挨拶や感情表現(例:「おはよう」「疲れた」)
...(その他のルール定義)
[直近のAIの発言]
"${lastAiMessage || 'なし'}"
[入力]
User: "${query}"`;
// ★ポイント:ここは推論速度とコストを重視し、安価な軽量モデルを使用する
const response = await ai.generate({
prompt,
model: vertexAI.model('gemini-3.1-flash-lite-preview'),
output: { schema: IntentClassificationResultSchema },
});
return response.output || { intent: 'GENERAL_KNOWLEDGE', hasContextReference: false, requiresWebSearch: false };
});
2. メインフローでのルーティング(動的切り替え)
意図分類の結果を受け取って、実際の処理を最適化するメインフローです。
(抜粋)
export const mainAgentFlow = ai.defineFlow({
name: 'mainAgentFlow',
// ... (入出力スキーマ定義)
}, async ({ query, userId }) => {
// 1. 意図分類の実行
const { intent, hasContextReference, requiresWebSearch } = await intentClassifierFlow({ query });
console.log(`[Intent] ${intent} (ContextRef: ${hasContextReference})`);
// ==========================================
// 【爆速ルート】純粋な雑談の場合 (CHIT_CHAT)
// ==========================================
if (intent === 'CHIT_CHAT' && !hasContextReference && !requiresWebSearch) {
// ツールもRAGも不要。最速・最安で応答を返す
const fastResponse = await ai.generate({
prompt: `ユーザーと親しみやすく雑談してください。\nUser: ${query}`,
model: vertexAI.model('gemini-3.1-flash-lite-preview'),
});
return { response: fastResponse.text };
}
// ==========================================
// 【通常/高負荷ルート】ツール実行や記憶検索が必要な場合
// ==========================================
// RAG(Vector Search)の要否を判定
let longTermMemory = "長期記憶は参照しませんでした。";
if (['MEMORY_RECALL', 'GOAL_PLANNING'].includes(intent)) {
// 記憶が必要なインテントの時だけ、重いVector Searchを実行する
const documents = await ai.retrieve({ /* ... Retrieverの呼び出し ... */ });
longTermMemory = JSON.stringify(documents);
}
// ツールの確実な実行のために、インテントに応じてモデルの賢さ(グレード)を切り替える
const requiresToolModel = ['SCHEDULE_WRITE', 'TASK_OP'].includes(intent);
const selectedModel = requiresToolModel
? vertexAI.model('gemini-3-flash-preview') // ツール呼び出しをミスしない高性能モデル
: vertexAI.model('gemini-3.1-flash-lite-preview'); // それ以外は軽量モデルで節約
// LLMの実行
const finalResponse = await ai.generate({
prompt: `あなたは優秀なエージェントです...(プロンプト構築)`,
model: selectedModel,
tools: [ /* 必要なツール群 */ ],
});
return { response: finalResponse.text };
});
得られた3つの効果
この司令塔アーキテクチャを導入したことで、エージェントの実用性が劇的に向上しました。
1. コストの大幅削減
すべてのリクエストに高性能モデル(Flash / Proなど)を使う必要がなくなりました。
「挨拶」や「簡単な質問」はすべて軽量モデル(Flash Lite等)で処理されるため、APIコールあたりのコストが最小限に抑えられます。
2. レスポンスタイム(レイテンシ)の爆速化
適用前はすべてのリクエストで「Vector Search(RAG)の検索待ち時間(数秒)」が発生していましたが、意図分類によって 「DB検索が不要なインテント」は即座にLLMの回答生成に移る ことができるようになり、雑談時のレスポンスが速くなりました。
3. Function Calling(ツール呼び出し)の精度向上
LLMに一度に20個近いツール(カレンダー、タスク、天気、検索など)を渡し、「よしなにやって」と指示すると、LLMが混乱して間違ったツールを呼び出す(ハルシネーション)確率が上がります。
意図を事前に分類することで、「今回はスケジュール操作だから、カレンダーツール以外は使わないように」といったプロンプトの出し分けが可能になり、ツール実行の安定性が飛躍的に向上しました。
おわりに
LLMを使ったアプリケーション開発では、「いかにプロンプトを工夫するか」に目が行きがちですが、実運用においては 「いかにLLMの無駄遣いを減らし、適材適所でモデルやツールをルーティングするか」 というアーキテクチャ設計が非常に重要になります。
Firebase Genkitは、こうした複数のFlow(意図分類フロー → メインフロー)を組み合わせたオーケストレーションを非常にシンプルに、型安全に実装できる強力なフレームワークだと感じました。