智能体设计模式-CH02:路由(Routing)

英文原地址:Chapter 2: Routing

概述

虽然通过提示链进行的顺序处理是使用语言模型执行确定性、线性工作流的基础技术,但在需要自适应响应的场景中,其适用性有限。现实世界中的智能体系统往往必须根据一些不确定因素(例如环境状态、用户输入或前序操作的结果)在多种潜在行动之间进行仲裁。这种对不同专用功能、工具或子流程的控制流进行动态决策的能力,是通过一种称为路由(routing)的机制来实现的。

路由将条件逻辑引入代理的操作框架,使其从固定的执行路径转变为一种模型,即代理能够动态评估特定标准,从一组可能的后续操作中进行选择。这使系统行为更加灵活且具备上下文感知能力。

例如,一个用于客户咨询的智能体,如果配备了路由功能,首先可以对收到的查询进行分类,以确定用户意图。基于该分类,它可以将查询转交给专门用于直接问答的智能体、用于账户信息的数据库检索工具,或针对复杂问题的升级处理流程,而不是默认采用单一的预设响应路径。因此,使用路由的更复杂智能体可以:

  1. 分析用户的查询。
  2. 根据意图路由查询:
    • 如果意图是"检查订单状态",则路由到与订单数据库交互的子智能体或工具链。
    • 如果意图是"产品信息",则路由到一个负责搜索产品目录的子智能体或链路。
    • 如果意图是"技术支持",则路由到另一个可访问故障排除指南或升级至人工的链路。
    • 如果意图不明确,则路由到一个兜底的子代理或提示链。

Routing 模式的核心组件是一种执行评估并引导流程的机制。该机制可以通过多种方式实现:

  • 基于 LLM 的路由: 可以提示语言模型对输入进行分析,并输出指示下一步或目的地的特定标识符或指令。例如,一个提示可以要求 LLM "分析以下用户查询,并且只输出类别:'Order Status'、'Product Info'、'Technical Support' 或 'Other'。" 代理式系统随后读取该输出并相应地引导工作流。
  • 基于 Embedding 的路由: 将输入查询转换为向量 Embedding(参见 RAG,第 14 章)。随后将该 Embedding 与代表不同路由或能力的 Embedding 进行比较。查询会被路由到与其 Embedding 最相似的路由。这对于语义路由非常有用,因为决策基于输入的含义而不仅仅是关键词。
  • 基于规则的路由: 使用基于关键词、模式或从输入中提取的结构化数据的预定义规则或逻辑(例如 if-else 语句、switch 分支)。与基于 LLM 的路由相比,这种方式更快、更具确定性,但在处理细微差别或新颖输入方面灵活性较差。
  • 基于机器学习模型的路由: 它采用判别式模型(例如分类器),在一小部分带标签数据上专门训练以执行路由任务。尽管它在概念上与基于嵌入的方法相似,但其关键特征是监督微调过程,该过程通过调整模型参数来创建一个专用的路由函数。该技术不同于基于 LLM 的路由,因为其决策组件并非在推理时执行提示的生成式模型。相反,路由逻辑被编码在经过微调的模型的已学习权重中。虽然 LLMs 可能在预处理步骤中用于生成合成数据以扩充训练集,但它们并不参与实时的路由决策本身。

路由机制可以在智能体的操作周期中的多个节点实现。它们可以在一开始用于对主要任务进行分类,在处理链的中间阶段用于决定下一步动作,或在子程序期间从给定的工具集中选择最合适的工具。

诸如 LangChain、LangGraph 和 Google 的 Agent Developer Kit(ADK)等计算框架提供了用于定义和管理此类条件逻辑的显式构造。凭借其基于状态的图架构,LangGraph 尤其适合用于复杂的路由场景,在这些场景中,决策取决于整个系统的累积状态。同样,Google 的 ADK 提供了用于构建代理能力和交互模型的基础组件,这些组件为实现路由逻辑奠定了基础。在这些框架提供的执行环境中,开发者定义可能的操作路径,以及决定计算图中节点之间转换的函数或基于模型的评估。

路由的实现使系统能够超越确定性的顺序处理。它促进了更具适应性的执行流程的开发,从而能够对更广泛的输入和状态变化做出动态且恰当的响应。

实际应用与使用场景

路由模式是自适应智能体系统设计中的关键控制机制,使系统能够根据可变的输入和内部状态动态调整其执行路径。它通过提供必要的条件逻辑层,在多个领域展现出实用价值。

在人机交互中,例如虚拟助理或由 AI 驱动的辅导系统,路由用于解读用户意图。对自然语言查询的初步分析可确定最合适的后续动作,无论是调用特定的信息检索工具、升级交由人工处理,还是依据用户表现选择课程中的下一个模块。这样,系统就能突破线性的对话流程,实现情境化响应。

在自动化的数据与文档处理流程中,路由充当分类与分发的功能。系统会基于内容、元数据或格式来分析输入数据,如电子邮件、支持工单或 API 负载。随后,系统将每个项目引导至相应的工作流程,例如销售线索的接收流程、针对 JSON 或 CSV 格式的特定数据转换函数,或紧急问题的升级路径。

在包含多个专业化工具或智能体的复杂系统中,路由充当高层调度器。一个由检索、摘要与分析等不同智能体组成的研究系统,会使用路由器根据当前目标将任务分配给最合适的智能体。类似地,AI 编码助手会通过路由识别编程语言与用户意图------调试、解释或翻译------然后再将代码片段交给正确的专业化工具。

归根结底,路由提供了逻辑判断的能力,这对构建功能多样且具备上下文感知的系统至关重要。它将智能体从一个执行预定义序列的静态执行者,转变为一个能够在变化条件下就完成任务的最有效方法做出决策的动态系统。。

实战代码示例(LangChain)

在代码中实现路由涉及定义可能的路径以及决定选择哪条路径的逻辑。像 LangChain 和 LangGraph 这样的框架为此提供了特定的组件和结构。LangGraph 的基于状态的图结构在可视化和实现路由逻辑方面尤为直观。。

这段代码展示了一个使用 LangChain 和 Google 的 Generative AI 的简易类代理系统。它设置了一个"协调器",根据请求意图(预订、信息或不明确)将用户请求路由到不同的模拟"子智能体"处理程序。系统使用语言模型对请求进行分类,然后将其委派给相应的处理函数,模拟了多智能体架构中常见的基础委派模式。

首先,确保你已经安装必要的库:

sh 复制代码
pip install langchain langgraph google-cloud-aiplatform langchain-google-genai google-adk deprecated pydantic

你还需要使用所选语言模型的 API 密钥(例如 OpenAI、Google Gemini、Anthropic)来配置你的运行环境。

python 复制代码
# Copyright (c) 2025 Marco Fago
# https://www.linkedin.com/in/marco-fago/
#
# This code is licensed under the MIT License.
# See the LICENSE file in the repository for the full license text.

from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableBranch

# --- Configuration ---
# Ensure your API key environment variable is set (e.g., GOOGLE_API_KEY)
try:
   llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0)
   print(f"Language model initialized: {llm.model}")
except Exception as e:
   print(f"Error initializing language model: {e}")
   llm = None

# --- Define Simulated Sub-Agent Handlers (equivalent to ADK sub_agents) ---

def booking_handler(request: str) -> str:
   """Simulates the Booking Agent handling a request."""
   print("\n--- DELEGATING TO BOOKING HANDLER ---")
   return f"Booking Handler processed request: '{request}'. Result: Simulated booking action."

def info_handler(request: str) -> str:
   """Simulates the Info Agent handling a request."""
   print("\n--- DELEGATING TO INFO HANDLER ---")
   return f"Info Handler processed request: '{request}'. Result: Simulated information retrieval."

def unclear_handler(request: str) -> str:
   """Handles requests that couldn't be delegated."""
   print("\n--- HANDLING UNCLEAR REQUEST ---")
   return f"Coordinator could not delegate request: '{request}'. Please clarify."

# --- Define Coordinator Router Chain (equivalent to ADK coordinator's instruction) ---
# This chain decides which handler to delegate to.
coordinator_router_prompt = ChatPromptTemplate.from_messages([
   ("system", """Analyze the user's request and determine which specialist handler should process it.
    - If the request is related to booking flights or hotels, 
      output 'booker'.
    - For all other general information questions, output 'info'.
    - If the request is unclear or doesn't fit either category, 
      output 'unclear'.
    ONLY output one word: 'booker', 'info', or 'unclear'."""),
   ("user", "{request}")
])

if llm:
   coordinator_router_chain = coordinator_router_prompt | llm | StrOutputParser()

# --- Define the Delegation Logic (equivalent to ADK's Auto-Flow based on sub_agents) ---
# Use RunnableBranch to route based on the router chain's output.

# Define the branches for the RunnableBranch
branches = {
   "booker": RunnablePassthrough.assign(output=lambda x: booking_handler(x['request']['request'])),
   "info": RunnablePassthrough.assign(output=lambda x: info_handler(x['request']['request'])),
   "unclear": RunnablePassthrough.assign(output=lambda x: unclear_handler(x['request']['request'])),
}

# Create the RunnableBranch. It takes the output of the router chain
# and routes the original input ('request') to the corresponding handler.
delegation_branch = RunnableBranch(
   (lambda x: x['decision'].strip() == 'booker', branches["booker"]), # Added .strip()
   (lambda x: x['decision'].strip() == 'info', branches["info"]),     # Added .strip()
   branches["unclear"] # Default branch for 'unclear' or any other output
)

# Combine the router chain and the delegation branch into a single runnable
# The router chain's output ('decision') is passed along with the original input ('request')
# to the delegation_branch.
coordinator_agent = {
   "decision": coordinator_router_chain,
   "request": RunnablePassthrough()
} | delegation_branch | (lambda x: x['output']) # Extract the final output

# --- Example Usage ---
def main():
   if not llm:
       print("\nSkipping execution due to LLM initialization failure.")
       return

   print("--- Running with a booking request ---")
   request_a = "Book me a flight to London."
   result_a = coordinator_agent.invoke({"request": request_a})
   print(f"Final Result A: {result_a}")

   print("\n--- Running with an info request ---")
   request_b = "What is the capital of Italy?"
   result_b = coordinator_agent.invoke({"request": request_b})
   print(f"Final Result B: {result_b}")

   print("\n--- Running with an unclear request ---")
   request_c = "Tell me about quantum physics."
   result_c = coordinator_agent.invoke({"request": request_c})
   print(f"Final Result C: {result_c}")

if __name__ == "__main__":
   main()

如前所述,这段 Python 代码使用 LangChain 库和 Google 的生成式 AI 模型(具体为 gemini-2.5-flash)构建了一个简单的类代理系统。具体来说,它定义了三个模拟的子代理处理器:booking_handler、info_handler 和 unclear_handler,分别用于处理特定类型的请求。

核心组件是 coordinator_router_chain,它使用 ChatPromptTemplate 指示语言模型将传入的用户请求分类为三类之一:'booker'、'info' 或 'unclear'。该路由链的输出随后由 RunnableBranch 使用,将原始请求委派给相应的处理函数。RunnableBranch 会检查语言模型的决策,并将请求数据导向 booking_handler、info_handler 或 unclear_handler。coordinator_agent 将这些组件组合起来,先对请求进行路由决策,然后将请求传递给选定的处理器。最终输出则从处理器的响应中提取。

主函数通过三个示例请求演示了系统的用法,展示了不同输入如何被路由并由模拟代理处理。包含了针对语言模型初始化的错误处理以确保健壮性。代码结构模仿了一个基本的多智能体框架,其中中央协调器根据意图将任务分配给专业代理。

实战代码示例(Google ADK)

Agent Development Kit(ADK)是一个用于构建代理系统的框架,为定义智能体的能力与行为提供了结构化环境。与基于显式计算图的架构不同,ADK 范式中的路由通常通过定义一组离散的"工具"来实现,这些工具代表代理的功能。针对用户查询选择合适的工具由框架的内部逻辑管理,该逻辑利用底层模型将用户意图匹配到正确的功能处理程序。

这段 Python 代码展示了一个使用 Google 的 ADK 库构建的 Agent Development Kit(ADK)应用示例。它设置了一个"Coordinator"(协调者)智能体,根据预设指令将用户请求路由到专业子智能体("Booker"负责预订,"Info"负责一般信息)。子智能体随后使用特定工具来模拟处理请求,展示了智能体系统中的基础委派模式。

python 复制代码
# Copyright (c) 2025 Marco Fago
#
# This code is licensed under the MIT License.
# See the LICENSE file in the repository for the full license text.

import uuid
from typing import Dict, Any, Optional

from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
from google.adk.tools import FunctionTool
from google.genai import types
from google.adk.events import Event

# --- Define Tool Functions ---
# These functions simulate the actions of the specialist agents.

def booking_handler(request: str) -> str:
   """
   Handles booking requests for flights and hotels.
   Args:
       request: The user's request for a booking.
   Returns:
       A confirmation message that the booking was handled.
   """
   print("-------------------------- Booking Handler Called ----------------------------")
   return f"Booking action for '{request}' has been simulated."

def info_handler(request: str) -> str:
   """
   Handles general information requests.
   Args:
       request: The user's question.
   Returns:
       A message indicating the information request was handled.
   """
   print("-------------------------- Info Handler Called ----------------------------")
   return f"Information request for '{request}'. Result: Simulated information retrieval."

def unclear_handler(request: str) -> str:
   """Handles requests that couldn't be delegated."""
   return f"Coordinator could not delegate request: '{request}'. Please clarify."

# --- Create Tools from Functions ---
booking_tool = FunctionTool(booking_handler)
info_tool = FunctionTool(info_handler)

# Define specialized sub-agents equipped with their respective tools
booking_agent = Agent(
   name="Booker",
   model="gemini-2.0-flash",
   description="A specialized agent that handles all flight 
           and hotel booking requests by calling the booking tool.",
   tools=[booking_tool]
)

info_agent = Agent(
   name="Info",
   model="gemini-2.0-flash",
   description="A specialized agent that provides general information
      and answers user questions by calling the info tool.",
   tools=[info_tool]
)

# Define the parent agent with explicit delegation instructions
coordinator = Agent(
   name="Coordinator",
   model="gemini-2.0-flash",
   instruction=(
       "You are the main coordinator. Your only task is to analyze
        incoming user requests "
       "and delegate them to the appropriate specialist agent. 
        Do not try to answer the user directly.\n"
       "- For any requests related to booking flights or hotels,
         delegate to the 'Booker' agent.\n"
       "- For all other general information questions, delegate to the 'Info' agent."
   ),
   description="A coordinator that routes user requests to the
     correct specialist agent.",
   # The presence of sub_agents enables LLM-driven delegation (Auto-Flow) by default.
   sub_agents=[booking_agent, info_agent]
)

# --- Execution Logic ---
async def run_coordinator(runner: InMemoryRunner, request: str):
   """Runs the coordinator agent with a given request and delegates."""
   print(f"\n--- Running Coordinator with request: '{request}' ---")
   final_result = ""
   try:
       user_id = "user_123"
       session_id = str(uuid.uuid4())
       await runner.session_service.create_session(
           app_name=runner.app_name, user_id=user_id, session_id=session_id
       )

       for event in runner.run(
           user_id=user_id,
           session_id=session_id,
           new_message=types.Content(
               role='user',
               parts=[types.Part(text=request)]
           ),
       ):
           if event.is_final_response() and event.content:
               # Try to get text directly from event.content 
               # to avoid iterating parts
               if hasattr(event.content, 'text') and event.content.text:
                    final_result = event.content.text
               elif event.content.parts:
                   # Fallback: Iterate through parts and extract text (might trigger warning)
                   text_parts = [part.text for part in event.content.parts if part.text]
                   final_result = "".join(text_parts)
               # Assuming the loop should break after the final response
               break

       print(f"Coordinator Final Response: {final_result}")
       return final_result
   except Exception as e:
       print(f"An error occurred while processing your request: {e}")
       return f"An error occurred while processing your request: {e}"

async def main():
   """Main function to run the ADK example."""
   print("--- Google ADK Routing Example (ADK Auto-Flow Style) ---")
   print("Note: This requires Google ADK installed and authenticated.")

   runner = InMemoryRunner(coordinator)
   # Example Usage
   result_a = await run_coordinator(runner, "Book me a hotel in Paris.")
   print(f"Final Output A: {result_a}")
   result_b = await run_coordinator(runner, "What is the highest mountain in the world?")
   print(f"Final Output B: {result_b}")
   result_c = await run_coordinator(runner, "Tell me a random fact.") # Should go to Info
   print(f"Final Output C: {result_c}")
   result_d = await run_coordinator(runner, "Find flights to Tokyo next month.") # Should go to Booker
   print(f"Final Output D: {result_d}")

if __name__ == "__main__":
   import nest_asyncio
   nest_asyncio.apply()
   await main()

该脚本由一个主协调者(Coordinator)智能体和两个专业子智能体组成:Booker 和 Info。每个专业智能体都配备了一个 FunctionTool,它封装了一个用于模拟动作的 Python 函数。booking_handler 函数用于模拟处理航班和酒店预订,而 info_handler 函数用于模拟检索一般信息。unclear_handler 被作为无法由协调者委派的请求的后备处理器,尽管当前协调者逻辑在主 run_coordinator 函数中并未明确在委派失败时使用它。

根据其指令所定义,Coordinator 代理的主要职责是分析传入的用户消息,并将其委派给 Booker 或 Info 代理。由于 Coordinator 定义了 sub_agents,这种委派由 ADK 的 Auto-Flow 机制自动处理。run_coordinator 函数会设置一个 InMemoryRunner,创建用户和会话 ID,然后使用该 runner 通过 coordinator 代理处理用户请求。runner.run 方法处理请求并产出事件,代码从 event.content 中提取最终的响应文本。

主函数通过使用不同的请求运行 coordinator 来演示系统的用法,展示它如何将预订请求委派给 Booker,将信息请求委派给 Info 代理。

回顾

是什么(What)

Agentic 系统往往需要应对种类繁多的输入和情境,单一的线性流程无法处理。简单的顺序式工作流缺乏基于上下文进行决策的能力。若没有机制为特定任务选择正确的工具或子流程,系统将保持僵化且缺乏适应性。此类限制使构建能够处理现实世界用户请求之复杂性与多变性的复杂应用变得困难。

为什么(Why)

路由模式通过将条件逻辑引入智能体的运行框架,提供了一种标准化的解决方案。它使系统能够先分析传入查询,以判断其意图或性质。基于该分析,智能体会动态将控制流导向最合适的专用工具、函数或子智能体。该决策可通过多种方法驱动,包括提示 LLMs、应用预定义规则,或使用基于嵌入的语义相似度。最终,路由将静态、预设的执行路径转变为灵活、具备上下文感知能力的工作流,能够选择最佳行动。

经验法则(Rule of Thumb)

当智能体必须根据用户输入或当前状态在多个不同的工作流、工具或子智能体之间进行选择时,使用路由(Routing)模式。对于需要分诊或分类传入请求以处理不同类型任务的应用至关重要,例如客服机器人区分销售咨询、技术支持和账户管理问题。

路由模式,使用 LLM 作为路由器

关键点

  • 路由使智能体能够根据条件对工作流中的下一步进行动态决策。
  • 它使智能体能够处理多样化的输入并调整其行为,超越线性执行。
  • 路由逻辑可以通过 LLMs、基于规则的系统或嵌入相似度来实现。
  • 像 LangGraph 和 Google ADK 这样的框架提供了在智能体工作流中定义和管理路由的结构化方法,尽管它们采用了不同的架构方式。

总结

路由模式是在构建真正动态且响应迅速的智能体系统时的关键步骤。通过实现路由,我们超越了简单的线性执行流程,使我们的智能体能够就如何处理信息、响应用户输入以及使用可用工具或子智能体做出智能决策。

我们已经看到路由如何应用于各个领域,从客户服务聊天机器人到复杂的数据处理流水线。分析输入并有条件地引导工作流的能力,是创建能够处理现实世界任务内在多样性的智能体的基础。

使用 LangChain 和 Google ADK 的代码示例展示了两种不同但同样有效的路由实现方法。LangGraph 的基于图的结构提供了一种可视化且显式的方式来定义状态和转换,非常适合具有复杂路由逻辑的多步骤工作流。另一方面,Google ADK 往往专注于定义各自独立的能力(Tools),并依赖框架将用户请求路由到相应的工具处理器,对于具有明确定义的离散动作集合的代理来说,这可能更为简单。

掌握路由模式对于构建能够智能应对不同场景、并根据上下文提供定制化响应或动作的代理至关重要。这是创建多才多能且健壮的代理式应用的关键组成部分。

参考资料

  1. LangGraph Documentation: www.langchain.com/
  2. Google Agent Developer Kit Documentation: google.github.io/adk-docs/
相关推荐
杯莫停丶13 小时前
设计模式之:简单工厂模式
java·设计模式·简单工厂模式
kyle~14 小时前
设计模式---观察者模式
服务器·观察者模式·设计模式
Query*19 小时前
Java 设计模式——适配器模式进阶:原理深挖、框架应用与实战扩展
java·设计模式·适配器模式
Meteors.19 小时前
23种设计模式——中介者模式 (Mediator Pattern)详解
java·设计模式·中介者模式
Query*21 小时前
Java 设计模式——适配器模式:从原理到3种实战的完整指南
java·设计模式·适配器模式
Meteors.21 小时前
23种设计模式——状态模式(State Pattern)
java·设计模式·状态模式
星星点点洲1 天前
PostgreSQL 15二进制文件
开发语言·设计模式·golang
让我上个超影吧1 天前
设计模式【工厂模式和策略模式】
java·设计模式·策略模式
Query*1 天前
Java 设计模式——建造者模式:从原理到实战的极简指南
java·设计模式·建造者模式
Tiny_React1 天前
智能体设计模式-CH05:工具使用(Tool Use)
设计模式