Skip to content

智能体间路由

Supported in ADKTypeScript v1.0.0Experimental

实验性

智能体路由是实验性功能,在未来的版本中可能发生变化。我们欢迎你的反馈

当为不同任务构建智能体时,你可以定义一个路由函数,在运行时选择哪个智能体处理每次调用。RoutedAgent 提供此功能,支持出错时的智能体回退、A/B 测试、规划模式以及按输入复杂度的自动路由。如果选中的智能体在产生任何输出之前失败,路由函数将再次被调用,并附带错误上下文,以便选择回退。

RoutedAgent 不同于工作流智能体(如 SequentialAgentParallelAgent),后者以固定模式编排多个智能体;也不同于 LLM 驱动的委派,由 LLM 决定将任务转交给哪个智能体。使用 RoutedAgent,你可以编写一个显式路由函数,每次调用选择一个智能体。有关模型级路由,请参阅模型路由

路由工作原理

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 以进行初始选择。
  • 如果选中的智能体在产生任何事件之前抛出错误,将再次调用路由函数,并附带包含 failedKeyslastErrorerrorContext
  • 如果选中的智能体在产生事件之后抛出错误,错误直接传播而不重试,因为已有部分结果被发出。
  • 已经尝试过的键不能被重新选择。如果路由函数返回先前失败的键,错误将传播。
  • 如果路由函数返回 undefined,路由停止并抛出最后一个错误。

基本用法

创建多个智能体,定义一个返回键的路由函数,然后将它们包装在 RoutedAgent 中。以下示例根据可在调用间变化的外部配置值在两个智能体之间进行路由:

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,以便选择回退。故障转移仅在智能体在产生任何事件之前失败时适用(参见路由工作原理)。以下示例检查 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 配置。完整的运行器设置请参阅基本用法

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 配置。完整的运行器设置请参阅基本用法

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,
});