コンテンツにスキップ

エージェント間でルーティングする

ADKでサポートTypeScript v1.0.0試験運用

試験運用

エージェントルーティングは試験運用の機能であり、今後のリリースで変更される可能性があります。 フィードバックを お待ちしています。

異なるタスク向けのエージェントを構築する場合、各呼び出しをどのエージェントが 処理するかを実行時に選択するルーティング関数を定義できます。RoutedAgent は この機能を提供し、エラー時のエージェントフォールバック、A/B テスト、計画モード、 入力の複雑さに基づく自動ルーティングを可能にします。選択されたエージェントが 出力を生成する前に失敗した場合、ルーティング関数はエラーコンテキスト付きで再度 呼び出され、フォールバックを選択できます。

RoutedAgent は、SequentialAgentParallelAgent のような ワークフローエージェントとは異なります。ワークフロー エージェントは複数のエージェントを固定パターンでオーケストレーションします。また、 LLM がどのエージェントへ引き継ぐかを決める LLM 主導の委譲とも 異なります。RoutedAgent では、開発者が明示的なルーティング関数を書き、呼び出し ごとに 1 つの エージェントを選択します。モデルレベルのルーティングについては モデルルーティングを参照してください。

ルーティングの仕組み

RoutedAgentRoutedLlm はどちらも、選択と フェイルオーバーを処理する共通のルーティングユーティリティを基盤にしています。

ルーター関数は、利用可能なエージェントのマップと現在のコンテキストを受け取り、 実行するエージェントのキーを返します。同期関数でも非同期関数でもかまいません。

type AgentRouter = (
  agents: Readonly<Record<string, BaseAgent>>,
  context: InvocationContext,
  errorContext?: { failedKeys: ReadonlySet<string>; lastError: unknown },
) => Promise<string | undefined> | string | undefined;

agents パラメータには、明示的なキーを持つ Record<string, BaseAgent>、または エージェント配列を渡せます。配列を渡した場合は、各エージェントの name プロパティが キーとして使用されます。

フェイルオーバーの動作:

  • ルーターは最初の選択のために、まず errorContext なしで呼び出されます。
  • 選択されたエージェントが イベントを 1 つも yield する前に エラーを投げた 場合、ルーターは failedKeyslastError を含む errorContext 付きで再度 呼び出されます。
  • 選択されたエージェントが イベントを yield した後に エラーを投げた場合、部分的な 結果がすでに出力されているため、エラーは再試行されず直接伝播します。
  • すでに試行されたキーは再選択できません。ルーターが以前に失敗したキーを返した場合、 エラーが伝播します。
  • ルーターが undefined を返した場合、ルーティングは停止し、最後のエラーが投げられます。

基本的な使用方法

複数のエージェントを作成し、キーを返すルーター関数を定義して、それらを RoutedAgent でラップします。次の例では、呼び出し間で変化する外部設定値に基づいて 2 つのエージェント間でルーティングします。

import { LlmAgent, RoutedAgent, InMemoryRunner } from '@google/adk';

const agentA = new LlmAgent({
  name: 'agent_a',
  model: 'gemini-flash-latest',
  instruction: 'You are Agent A. Always identify yourself as Agent A.',
});

const agentB = new LlmAgent({
  name: 'agent_b',
  model: 'gemini-flash-latest',
  instruction: 'You are Agent B. Always identify yourself as Agent B.',
});

// External configuration that can change at runtime
const config = { selectedAgent: 'agent_a' };

const routedAgent = new RoutedAgent({
  name: 'my_routed_agent',
  agents: { agent_a: agentA, agent_b: agentB },
  router: () => config.selectedAgent,
});

const runner = new InMemoryRunner({
  agent: routedAgent,
  appName: 'my_app',
});

const session = await runner.sessionService.createSession({
  appName: 'my_app',
  userId: 'user_1',
});

const run = runner.runAsync({
  userId: 'user_1',
  sessionId: session.id,
  newMessage: { role: 'user', parts: [{ text: 'Who are you?' }] },
});

for await (const event of run) {
  if (event.content?.parts?.[0]?.text) {
    console.log(event.content.parts[0].text);
  }
}

次の呼び出しの前に config.selectedAgent'agent_b' に変更すると、別の エージェントへルーティングされます。

エラー時のフォールバック

エージェントが失敗すると、ルーターは errorContext 付きで再度呼び出され、 フォールバックを選択できます。フェイルオーバーは、エージェントがイベントを yield する 前に失敗した場合にのみ適用されます(ルーティングの仕組みを 参照)。次の例では、失敗したエージェントを再選択しないように errorContext.failedKeys を確認します。

import {
  BaseAgent,
  InvocationContext,
  LlmAgent,
  RoutedAgent,
} from '@google/adk';

const primaryAgent = new LlmAgent({
  name: 'primary',
  model: 'gemini-flash-latest',
  instruction: 'You are the primary agent.',
});

const fallbackAgent = new LlmAgent({
  name: 'fallback',
  model: 'gemini-pro-latest',
  instruction: 'You are the fallback agent.',
});

const router = (
  agents: Readonly<Record<string, BaseAgent>>,
  context: InvocationContext,
  // errorContext is provided when a previously selected agent fails
  errorContext?: { failedKeys: ReadonlySet<string>; lastError: unknown },
) => {
  if (!errorContext) {
    return 'primary'; // Try primary first
  }
  if (errorContext.failedKeys.has('primary')) {
    return 'fallback'; // Fall back if primary failed
  }
  return undefined; // No more options, propagate the error
};

const routedAgent = new RoutedAgent({
  name: 'my_routed_agent',
  agents: { primary: primaryAgent, fallback: fallbackAgent },
  router,
});

計画モード

ルーターは任意の外部状態を読み取り、異なる指示、モデル、ツールを持つエージェントの 中から選択できます。これにより、エージェントが動作を動的に切り替える計画モードを 実装できます。たとえば、基本エージェントには読み書きツールを持たせ、計画エージェントは 読み取り専用アクセスに制限し、分析のためにより強力なモデルを使わせることができます。

次の例では、別の RoutedAgent 構成を示します。runner の完全な設定については 基本的な使用方法を参照してください。

import {
  FunctionTool,
  LlmAgent,
  RoutedAgent,
} from '@google/adk';
import { z } from 'zod';

const readFileTool = new FunctionTool({
  name: 'read_file',
  description: 'Reads content from a file.',
  parameters: z.object({ filePath: z.string() }),
  execute: (args) => ({ content: `Contents of ${args.filePath}` }),
});

const writeFileTool = new FunctionTool({
  name: 'write_file',
  description: 'Writes content to a file.',
  parameters: z.object({ filePath: z.string(), content: z.string() }),
  execute: (args) => ({ result: `Wrote to ${args.filePath}` }),
});

const basicAgent = new LlmAgent({
  name: 'basic',
  model: 'gemini-flash-latest',
  instruction: 'You are a basic assistant. Use tools to help the user.',
  tools: [readFileTool, writeFileTool],
});

const planningAgent = new LlmAgent({
  name: 'planning',
  model: 'gemini-flash-latest',
  instruction: 'You are a planning expert. Analyze carefully. You can only read files.',
  tools: [readFileTool],
});

// Toggle this to switch between basic and planning agents
let planningMode = false;

const routedAgent = new RoutedAgent({
  name: 'my_routed_agent',
  agents: { basic: basicAgent, planning: planningAgent },
  router: () => (planningMode ? 'planning' : 'basic'),
});

呼び出し前に planningMode = true を設定すると、制限されたツールセットと異なる 指示を持つ計画エージェントへルーティングされます。

複雑さに基づく自動ルーティング

ルーター関数は軽量な分類モデルを呼び出して入力を分類し、その結果に応じて異なる エージェントへルーティングできます。ルーターは非同期にできるため、エージェントを 選択する前に内部で LLM 呼び出しを実行できます。

次の例では、別の RoutedAgent 構成を示します。runner の完全な設定については 基本的な使用方法を参照してください。

import {
  BaseAgent,
  Gemini,
  InvocationContext,
  LlmAgent,
  RoutedAgent,
} from '@google/adk';

const simpleAgent = new LlmAgent({
  name: 'simple',
  model: 'gemini-flash-latest',
  instruction: 'You are a simple assistant for basic questions.',
});

const complexAgent = new LlmAgent({
  name: 'complex',
  model: 'gemini-pro-latest',
  instruction: 'You are an expert assistant for complex analysis.',
});

// Lightweight model to classify input complexity
const classifierModel = new Gemini({ model: 'gemini-flash-latest' });

const router = async (
  agents: Readonly<Record<string, BaseAgent>>,
  context: InvocationContext,
) => {
  // Extract the user's input text
  const text = context.userContent?.parts?.[0]?.text || '';
  if (!text) return 'simple';

  const prompt =
    `Classify this request as 'simple' or 'complex'. ` +
    `Reply with ONLY that word.\nRequest: "${text}"`;

  const generator = classifierModel.generateContentAsync({
    contents: [{ role: 'user', parts: [{ text: prompt }] }],
    toolsDict: {},
    liveConnectConfig: {},
  });

  let classification = '';
  for await (const resp of generator) {
    if (resp.content?.parts?.[0]?.text) {
      classification += resp.content.parts[0].text;
    }
  }

  return classification.toLowerCase().includes('complex')
    ? 'complex'
    : 'simple';
};

const routedAgent = new RoutedAgent({
  name: 'my_routed_agent',
  agents: { simple: simpleAgent, complex: complexAgent },
  router,
});