智能体设计模式

第2章:路由

通过条件逻辑和基于意图的路由在AI智能体中实现动态决策

第2章:路由

路由模式概述

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

路由将条件逻辑引入智能体的操作框架,使从固定执行路径转向智能体动态评估特定标准以从一组可能的后续行动中选择的模型成为可能。这允许更灵活和上下文感知的系统行为。

例如,一个为客户查询设计的智能体,当配备路由功能时,可以首先对传入查询进行分类以确定用户的意图。基于这种分类,它可以将查询引导到专门用于直接问答的智能体、用于账户信息的数据库检索工具,或用于复杂问题的升级程序,而不是默认为单一、预定的响应路径。因此,使用路由的更复杂智能体可以:

  1. 分析用户的查询。
  2. 路由基于其意图的查询:
    • 如果意图是"检查订单状态",路由到与订单数据库交互的子智能体或工具链。
    • 如果意图是"产品信息",路由到搜索产品目录的子智能体或链。
    • 如果意图是"技术支持",路由到访问故障排除指南或升级到人工的不同链。
    • 如果意图不清楚,路由到澄清子智能体或提示链。

路由模式的核心组件是执行评估并引导流程的机制。这种机制可以通过几种方式实现:

  • 基于LLM的路由: 语言模型本身可以被提示分析输入并输出指示下一步或目标的特定标识符或指令。例如,提示可能要求LLM"分析以下用户查询并仅输出类别:'订单状态'、'产品信息'、'技术支持'或'其他'。"智能体系统然后读取此输出并相应地引导工作流。
  • 基于嵌入的路由: 输入查询可以转换为向量嵌入(见RAG,第14章)。然后将此嵌入与表示不同路由或能力的嵌入进行比较。查询被路由到其嵌入最相似的路由。这对于语义路由很有用,其中决策基于输入的含义而不是仅仅关键词。
  • 基于规则的路由: 这涉及使用基于从输入中提取的关键词、模式或结构化数据的预定义规则或逻辑(如if-else语句、switch cases)。这可能比基于LLM的路由更快、更确定性,但对于处理细微或新颖输入不太灵活。
  • 基于机器学习模型的路由: 它采用判别模型,如分类器,该模型已在标记数据的小语料库上专门训练以执行路由任务。虽然它与基于嵌入的方法在概念上相似,但其关键特征是有监督的微调过程,该过程调整模型的参数以创建专门的路由函数。这种技术与基于LLM的路由不同,因为决策组件不是推理时执行提示的生成模型。相反,路由逻辑编码在微调模型的学习权重中。虽然LLM可以在预处理步骤中用于生成合成数据以增强训练集,但它们不参与实时路由决策本身。

路由机制可以在智能体操作周期内的多个节点实现。它们可以在开始时应用以分类主要任务,在处理链中的中间点确定后续行动,或在子例程中选择给定集合中最合适的工具。

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

路由的实现使系统能够超越确定性顺序处理。它促进了更自适应执行流的开发,这些流可以动态和适当地响应更广泛的输入和状态变化。

实际应用和用例

路由模式是设计自适应智能体系统的关键控制机制,使它们能够根据可变输入和内部状态动态改变其执行路径。其效用通过提供必要的条件逻辑层跨越多个领域。

在人机交互中,如虚拟助手或AI驱动的导师,路由用于解释用户意图。对自然语言查询的初始分析确定最合适的后续行动,无论是调用特定的信息检索工具、升级到人工操作员,还是根据用户表现选择课程中的下一个模块。这允许系统超越线性对话流并上下文响应。

在自动化的数据和文档处理管道中,路由充当分类和分发功能。传入数据,如电子邮件、支持票据或API负载,根据内容、元数据或格式进行分析。然后系统将每个项目引导到相应的工作流,如销售线索摄取过程、JSON或CSV格式的特定数据转换函数,或紧急问题升级路径。

在涉及多个专门工具或智能体的复杂系统中,路由充当高级调度器。由用于搜索、总结和分析信息的不同智能体组成的研究系统将使用路由器根据当前目标将任务分配给最合适的智能体。同样,AI编码助手使用路由在将代码片段传递给正确的专门工具之前识别编程语言和用户意图——调试、解释或翻译。

最终,路由提供了创建功能多样化和上下文感知系统所必需的逻辑仲裁能力。它将智能体从预定义序列的静态执行器转变为动态系统,该系统可以决定在变化条件下完成任务的最有效方法。

实践代码示例(LangChain)

在代码中实现路由涉及定义可能的路径和决定采取哪条路径的逻辑。LangChain和LangGraph等框架为此提供特定组件和结构。LangGraph的基于状态的图结构对于可视化和实现路由逻辑特别直观。

此代码演示了使用LangChain和Google的生成AI的简单类智能体系统。它设置了一个"协调器",根据请求的意图(预订、信息或不清楚)将用户请求路由到不同的模拟"子智能体"处理器。系统使用语言模型对请求进行分类,然后将其委托给适当的处理器函数,模拟多智能体架构中常见的基本委托模式。

首先,确保安装了必要的库:

pip install langchain langgraph google-cloud-aiplatform langchain-google-genai google-adk deprecated pydantic

您还需要设置环境,使用您选择的语言模型的API密钥(如OpenAI、Google Gemini、Anthropic)。

# Copyright (c) 2025 Marco Fago
# https://www.linkedin.com/in/marco-fago/
#
# 此代码在MIT许可证下许可。
# 有关完整许可证文本,请参阅存储库中的LICENSE文件。

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

# --- 配置 ---
# 确保设置了API密钥环境变量(如GOOGLE_API_KEY)
try:
    llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0)
    print(f"语言模型已初始化: {llm.model}")
except Exception as e:
    print(f"初始化语言模型时出错: {e}")
    llm = None

# --- 定义模拟子智能体处理器(相当于ADK sub_agents) ---
def booking_handler(request: str) -> str:
    """模拟预订智能体处理请求。"""
    print("\n--- 委托给预订处理器 ---")
    return f"预订处理器处理请求: '{request}'。结果: 模拟预订操作。"

def info_handler(request: str) -> str:
    """模拟信息智能体处理请求。"""
    print("\n--- 委托给信息处理器 ---")
    return f"信息处理器处理请求: '{request}'。结果: 模拟信息检索。"

def unclear_handler(request: str) -> str:
    """处理无法委托的请求。"""
    print("\n--- 处理不清楚的请求 ---")
    return f"协调器无法委托请求: '{request}'。请澄清。"

# --- 定义协调器路由器链(相当于ADK协调器的指令) ---
# 此链决定委托给哪个处理器。
coordinator_router_prompt = ChatPromptTemplate.from_messages([
    ("system", """分析用户的请求并确定应该处理它的专业处理器。
    - 如果请求与预订航班或酒店相关,
      输出'booker'。
    - 对于所有其他一般信息问题,输出'info'。
    - 如果请求不清楚或不符合任一类别,
      输出'unclear'。
    仅输出一个词: 'booker'、'info'或'unclear'。"""),
    ("user", "{request}")
])

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

# --- 定义委托逻辑(相当于ADK基于sub_agents的自动流程) ---
# 使用RunnableBranch根据路由器链的输出进行路由。
# 为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'])),
}

# 创建RunnableBranch。它获取路由器链的输出
# 并将原始输入('request')路由到相应的处理器。
delegation_branch = RunnableBranch(
    (lambda x: x['decision'].strip() == 'booker', branches["booker"]), # 添加了.strip()
    (lambda x: x['decision'].strip() == 'info', branches["info"]),     # 添加了.strip()
    branches["unclear"] # 'unclear'或任何其他输出的默认分支
)

# 将路由器链和委托分支组合成单个可运行对象
# 路由器链的输出('decision')与原始输入('request')一起传递
# 到delegation_branch。
coordinator_agent = {
    "decision": coordinator_router_chain,
    "request": RunnablePassthrough()
} | delegation_branch | (lambda x: x['output']) # 提取最终输出

# --- 示例用法 ---
def main():
    if not llm:
        print("\n由于LLM初始化失败,跳过执行。")
        return
    
    print("--- 使用预订请求运行 ---")
    request_a = "为我预订到伦敦的航班。"
    result_a = coordinator_agent.invoke({"request": request_a})
    print(f"最终结果A: {result_a}")
    
    print("\n--- 使用信息请求运行 ---")
    request_b = "意大利的首都是什么?"
    result_b = coordinator_agent.invoke({"request": request_b})
    print(f"最终结果B: {result_b}")
    
    print("\n--- 使用不清楚的请求运行 ---")
    request_c = "告诉我关于量子物理学。"
    result_c = coordinator_agent.invoke({"request": request_c})
    print(f"最终结果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组合这些组件,首先为决策路由请求,然后将请求传递给选定的处理器。最终输出从处理器的响应中提取。

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

实践代码示例(Google ADK)

Agent Development Kit(ADK)是用于工程智能体系统的框架,为定义智能体的能力和行为提供结构化环境。与基于显式计算图的架构相比,ADK范式内的路由通常通过定义代表智能体功能的离散"工具"集合来实现。响应用户查询选择适当工具由框架的内部逻辑管理,该逻辑利用底层模型将用户意图匹配到正确的功能处理器。

这个Python代码演示了使用Google的ADK库的Agent Development Kit(ADK)应用程序示例。它设置了一个"协调器"智能体,根据定义的指令将用户请求路由到专门的子智能体(用于预订的"Booker"和用于一般信息的"Info")。然后子智能体使用特定工具来模拟处理请求,展示智能体系统内的基本委托模式

# Copyright (c) 2025 Marco Fago
#
# 此代码在MIT许可证下许可。
# 有关完整许可证文本,请参阅存储库中的LICENSE文件。

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

# --- 定义工具函数 ---
# 这些函数模拟专业智能体的行动。
def booking_handler(request: str) -> str:
    """
    处理航班和酒店的预订请求。
    参数:
        request: 用户的预订请求。
    返回:
        确认预订已处理的确认消息。
    """
    print("-------------------------- 预订处理器被调用 ----------------------------")
    return f"'{request}'的预订操作已被模拟。"

def info_handler(request: str) -> str:
    """
    处理一般信息请求。
    参数:
        request: 用户的问题。
    返回:
        表示信息请求已处理的消息。
    """
    print("-------------------------- 信息处理器被调用 ----------------------------")
    return f"'{request}'的信息请求。结果: 模拟信息检索。"

def unclear_handler(request: str) -> str:
    """处理无法委托的请求。"""
    return f"协调器无法委托请求: '{request}'。请澄清。"

# --- 从函数创建工具 ---
booking_tool = FunctionTool(booking_handler)
info_tool = FunctionTool(info_handler)

# 定义配备各自工具的专门子智能体
booking_agent = Agent(
    name="Booker",
    model="gemini-2.0-flash",
    description="通过调用预订工具处理所有航班和酒店预订请求的专门智能体。",
    tools=[booking_tool]
)

info_agent = Agent(
    name="Info",
    model="gemini-2.0-flash",
    description="通过调用信息工具提供一般信息和回答用户问题的专门智能体。",
    tools=[info_tool]
)

# 定义具有显式委托指令的父智能体
coordinator = Agent(
    name="Coordinator",
    model="gemini-2.0-flash",
    instruction=(
        "你是主协调器。你的唯一任务是分析 "
        "传入的用户请求 "
        "并将它们委托给适当的专业智能体。 "
        "不要尝试直接回答用户。\n"
        "- 对于与预订航班或酒店相关的任何请求, "
        "  委托给'Booker'智能体。\n"
        "- 对于所有其他一般信息问题,委托给'Info'智能体。"
    ),
    description="将用户请求路由到正确专业智能体的协调器。",
    # sub_agents的存在默认启用LLM驱动的委托(自动流程)。
    sub_agents=[booking_agent, info_agent]
)

# --- 执行逻辑 ---
async def run_coordinator(runner: InMemoryRunner, request: str):
    """使用给定请求运行协调器智能体并委托。"""
    print(f"\n--- 使用请求运行协调器: '{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:
                # 尝试直接从event.content获取文本
                # 避免迭代parts
                if hasattr(event.content, 'text') and event.content.text:
                    final_result = event.content.text
                elif event.content.parts:
                    # 后备: 迭代parts并提取文本(可能触发警告)
                    text_parts = [part.text for part in event.content.parts if part.text]
                    final_result = "".join(text_parts)
                # 假设循环应该在最终响应后中断
                break
        print(f"协调器最终响应: {final_result}")
        return final_result
    except Exception as e:
        print(f"处理您的请求时发生错误: {e}")
        return f"处理您的请求时发生错误: {e}"

async def main():
    """运行ADK示例的主函数。"""
    print("--- Google ADK路由示例(ADK自动流程样式) ---")
    print("注意: 这需要安装Google ADK并进行身份验证。")
    runner = InMemoryRunner(coordinator)
    
    # 示例用法
    result_a = await run_coordinator(runner, "为我在巴黎预订一家酒店。")
    print(f"最终输出A: {result_a}")
    
    result_b = await run_coordinator(runner, "世界上最高的山是什么?")
    print(f"最终输出B: {result_b}")
    
    result_c = await run_coordinator(runner, "告诉我一个随机事实。") # 应该去Info
    print(f"最终输出C: {result_c}")
    
    result_d = await run_coordinator(runner, "查找下个月到东京的航班。") # 应该去Booker
    print(f"最终输出D: {result_d}")

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

此脚本由主协调器智能体和两个专门的子智能体组成:Booker和Info。每个专门智能体都配备了一个FunctionTool,该工具包装了一个模拟行动的Python函数。booking_handler函数模拟处理航班和酒店预订,而info_handler函数模拟检索一般信息。unclear_handler作为协调器无法委托的请求的后备包含,尽管当前的协调器逻辑在main run_coordinator函数中没有明确使用它进行委托失败。

协调器智能体的主要角色,如其指令中定义的,是分析传入的用户消息并将它们委托给Booker或Info智能体。这种委托由ADK的自动流程机制自动处理,因为协调器定义了sub_agents。run_coordinator函数设置InMemoryRunner,创建用户和会话ID,然后使用runner通过协调器智能体处理用户的请求。runner.run方法处理请求并产生事件,代码从event.content中提取最终响应文本。

main函数通过使用不同请求运行协调器来演示系统的用法,展示它如何将预订请求委托给Booker,将信息请求委托给Info智能体。

一览

什么: 智能体系统必须经常响应各种输入和情况,这些无法通过单一、线性过程处理。简单的顺序工作流缺乏基于上下文做出决策的能力。没有机制为特定任务选择正确的工具或子进程,系统保持僵化和非自适应。这种限制使得难以构建能够管理现实世界用户请求的复杂性和可变性的复杂应用程序。

为什么: 路由模式通过将条件逻辑引入智能体的操作框架提供标准化解决方案。它使系统能够首先分析传入查询以确定其意图或性质。基于此分析,智能体动态引导控制流到最合适的专门工具、函数或子智能体。这种决策可以由各种方法驱动,包括提示LLM、应用预定义规则或使用基于嵌入的语义相似性。最终,路由将静态、预定的执行路径转变为能够选择最佳可能行动的灵活和上下文感知工作流。

经验法则: 当智能体必须根据用户输入或当前状态在多个不同工作流、工具或子智能体之间决定时使用路由模式。对于需要分类或分类传入请求以处理不同类型任务的应用程序至关重要,如客户支持机器人区分销售查询、技术支持和账户管理问题。

视觉总结:

image1 图1:路由器模式,使用LLM作为路由器

关键要点

  • 路由使智能体能够基于条件对工作流中的下一步做出动态决策。
  • 它允许智能体处理多样化的输入并适应其行为,超越线性执行。
  • 路由逻辑可以使用LLM、基于规则的系统或嵌入相似性实现。
  • LangGraph和Google ADK等框架提供在智能体工作流中定义和管理路由的结构化方法,尽管采用不同的架构方法。

结论

路由模式是构建真正动态和响应式智能体系统的关键步骤。通过实现路由,我们超越了简单、线性的执行流,并赋予我们的智能体对如何处理信息、响应用户输入以及利用可用工具或子智能体做出智能决策的能力。

我们已经看到路由如何应用于各个领域,从客户服务聊天机器人到复杂的数据处理管道。分析输入并条件性地引导工作流的能力对于创建能够处理现实世界任务固有可变性的智能体至关重要。

使用LangChain和Google ADK的代码示例演示了实现路由的两种不同但有效的方法。LangGraph的基于图的结构提供了定义状态和转换的可视化和显式方法,使其成为具有复杂路由逻辑的复杂多步工作流的理想选择。另一方面,Google ADK通常专注于定义不同的能力(工具)并依赖框架将用户请求路由到适当工具处理器的能力,这对于具有明确定义的离散行动集合的智能体可能更简单。

掌握路由模式对于构建能够智能导航不同场景并根据上下文提供定制响应或行动的智能体至关重要。它是创建多功能和健壮的智能体应用程序的关键组件。

参考文献

  1. LangGraph文档:https://www.langchain.com/
  2. Google Agent Developer Kit文档:https://google.github.io/adk-docs/