2
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?

【Genkit】AIエージェントの司令塔|「意図分類(Intent Classification)」でコスト削減とレスポンス高速化を実現する

2
Last updated at Posted at 2026-03-19

はじめに

現在、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 等)を使ってユーザーの「意図」を分析する専用のフロー を挟んでいます。

  1. ユーザー入力: 「明日の10時に会議を入れて」
  2. 司令塔(意図分類): 軽量モデルが瞬時に分析し、intent: 'SCHEDULE_WRITE' と判定。
  3. メイン処理(ルーティング):
    • スケジュール登録用のツール(Function Calling)だけを有効化
    • 複雑な操作が必要なため、高性能なモデル(Gemini 3.1 Pro 等)に切り替え
    • 過去の記憶(RAG)は不要なので Vector Search はスキップ

このように、意図に応じて「使うツール」「使うモデル」「DB検索の有無」を動的に切り替えます。

実装例

実際に稼働しているGenkitのバックエンドコードをベースに解説します。

1. 司令塔となる「意図分類フロー」の定義

まずは、分類するカテゴリ(Intent)のスキーマを定義し、分類用のGenkit Flowを作成します。

mainAgent.ts
(抜粋)
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. メインフローでのルーティング(動的切り替え)

意図分類の結果を受け取って、実際の処理を最適化するメインフローです。

mainAgent.ts
(抜粋)
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(意図分類フロー → メインフロー)を組み合わせたオーケストレーションを非常にシンプルに、型安全に実装できる強力なフレームワークだと感じました。

2
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
2
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?