在上一章中,我们探讨了智能体中的反思与内省这两个复杂概念。这些能力使智能体能够对自身的认知过程进行推理,从经验中学习,并动态调整其行为。
AI 智能体的重要进步之一来自于智能体如何规划与使用工具的结合。本章将介绍工具的工作原理、不同的规划算法、它们如何协同配合,以及展示工具使用在实际应用中的真实案例。我们将探讨智能体如何通过工具使用扩展其能力,超越单纯的决策和问题解决。内容涵盖智能体可以利用的不同类型的工具,如 API、数据库和软件函数。随后,我们将深入介绍智能体必备的规划算法,包括状态空间搜索、强化学习和分层任务网络规划。我们还将讨论如何整合工具使用与规划,即基于目标推理可用工具,评估工具的适用性,选择合适的工具,并生成有效的动作序列以充分利用这些工具。
本章划分为以下主要部分:
- 理解智能体中的工具使用概念智能体的规划算法工具使用与规划的整合实践实现案例探讨
完成本章后,您将理解工具是什么,它们如何为您的智能体系统赋能,以及它们如何与规划算法协同工作。
技术要求
本章的代码文件可在 GitHub 上找到:github.com/PacktPublis… 。本章还将使用如 CrewAI、AutoGen 和 LangChain 等智能体 Python 框架,演示 AI 智能体的各个方面。
理解智能体中工具使用的概念
工具使用的核心,是指智能体(尤其是大型语言模型代理,LLM代理)利用外部资源或工具,来增强自身固有功能和决策过程的能力。这个概念超越了传统意义上将智能体视为一个完全自给自足(孤立)实体、仅依赖其内部知识(训练数据)和算法的局限。它承认智能体能够通过战略性地借助外部工具和系统的力量,突破自身内在的限制。
举例来说,当你向一个孤立的智能体发送查询(“天气怎么样?”)时,该模型可能随意生成一个答案,或者回答它不知道如何获取天气信息。在这种情况下,智能体仅依赖于其训练数据,而这通常无法包含最新的实时天气信息。相反,如果LLM智能体能够访问一个实时天气查询工具,它就可能准确回答这个问题。
工具使用使得智能体能够访问实时数据、执行专门任务、管理复杂工作流,这些都超出了其内置知识和算法的范畴。图5.1 展示了孤立智能体与工具驱动智能体的行为对比:
工具使用的重要性在于其能够拓宽智能体(进而驱动该智能体的LLM模型)的能力范围,使其能够应对那些超出其原生问题解决能力的复杂现实挑战。通过整合和协调多种工具的使用,智能体可以有效地将特定任务卸载出去,或访问补充数据和功能,从而提升整体性能,扩展可达成的目标范围。在深入了解工具细节之前,我们先来理解一下LLM调用工具的工作原理。
工具调用与函数调用
虽然在LLM上下文中,工具调用和函数调用常被交替使用,但它们在技术上存在显著区别。函数调用是指LLM生成结构化的调用,调用同一运行时环境中的预定义函数,通常执行内部任务,如数据库查询或计算。工具调用则是指LLM与外部API、服务或系统交互,访问实时数据并执行其固有能力之外的专门任务。举例来说,使用函数调用的LLM可能从本地数据库检索用户档案,而工具调用则可能是查询天气API获取实时天气更新。理解这一差异对于设计能够无缝整合内部逻辑与外部系统的AI智能体至关重要。
当LLM调用工具或函数时,它本身并不执行任何代码,而是生成一个结构化响应,指明以下内容:
- 想要调用的工具/函数名称传递给该工具/函数的参数这些参数的格式
可以把它想象成写一份详细的指令,而非执行动作本身。LLM充当一个复杂的调度者,决定要做什么以及如何做,但实际的工具或函数执行由外部运行环境或智能体控制器(Agent Controller)完成。例如,当被问及波士顿的天气时,LLM可能识别出需要调用天气查询功能,并返回如下结构化调用:
{ "function": "weather_lookup", "parameters": { "location": "Boston", "date": "10/01/2024" }}
此结构化响应随后由具备执行指定函数能力并提供相应参数的Agent Controller进行解析和执行。天气查询工具(或函数)可能类似如下:
import requestsdef weather_lookup(location: str, date: str) -> dict: """查询天气数据的函数,输入为地点和日期""" API_KEY = "api_key" base_url = "<api URL>" params = { "q": location, "appid": API_KEY, "units": "imperial" # 使用华氏度 } response = requests.get(base_url, params=params) if response.status_code == 200: data = response.json() return data
至少,LLM智能体需要获得工具的描述,包括工具的功能和所期望的输入。你还可以指定哪些参数(如地点和日期)是必需的,哪些是可选的。图5.2展示了LLM智能体、工具和Agent Controller之间的流程关系:
值得注意的是,并非所有大语言模型(LLM)都具备工具或函数调用的能力,也不一定高效(或者说准确)。虽然更大的模型在工具调用方面能力更强,但部分大型模型(例如 OpenAI 的 GPT-4 和 GPT-4o,Anthropic 的 Claude Sonnet、Haiku、Opus,以及 Meta 的 Llama 3 系列)是专门针对工具调用行为进行训练的。而其他模型虽然没有明确针对工具调用训练,但通过激进的提示工程(prompt engineering),仍可能实现类似的功能,成功率则有高有低。
工具定义与代理(Agents)
工具通常会用明确的描述进行定义,常见做法是使用文档字符串(docstring)或 JSON schema,来向代理说明工具的用途、所需输入和期望输出。定义工具主要有两种方法,取决于你是使用框架,还是直接调用 LLM API。
框架方式 — 使用文档字符串
在 CrewAI、LangGraph 等框架中,工具通过文档字符串定义——即函数开头的描述性文本。以下是一个天气查询工具的示例:
def weather_lookup(location: str, date: str = None): """ 一个用于查询实时天气数据的工具。 参数: location (str): 要查询天气的地点 date (str, 可选): 日期,格式为 MM/DD/YYYY """ # 函数代码和逻辑
三重引号(""")内的文档字符串提供了关键的信息:
- 工具的用途必需和可选参数预期的返回值
这种方式符合开发者习惯,使用了标准的编程实践。Python 使用三重引号表示文档字符串,而其他编程语言可能有不同的文档约定。
直接调用 LLM API
如果不借助框架,直接使用如 Anthropic Claude 或 OpenAI GPT 的 API,工具需要用特定的 JSON schema 格式定义,例如:
{ "name": "weather_lookup", "description": "一个用于查询实时天气数据的工具", "input_schema": { "type": "object", "properties": { "location": { "type": "string", "description": "城市和州,例如 San Francisco, CA" } }, "required": ["location"] }}
调用模型时,可以将多个工具定义为 JSON schema 对象数组,例如:
tools = [ { "name": "weather_lookup", "description": "一个可查询天气数据的工具", ... }, { "name": "flight_booking", "description": "一个可预订机票的工具", ... }, ...]
需要注意的是,具体定义方式依赖于模型本身,必须查阅对应模型的文档以了解 API 对工具定义的具体要求。如果项目中涉及多个模型,而它们定义工具的方式不统一,将很快变得难以维护和管理。正因如此,越来越多的开发者倾向于使用如 CrewAI、LangGraph 和 AutoGen 这类库或框架,它们提供了一套简化的工具定义方式,无论底层使用何种 LLM,均可通用。
工具的类型
LLM 代理可以利用多种工具类型来增强能力和完成复杂任务,主要类别包括:
- 应用编程接口(API)
API 是代理访问外部服务和数据的主要通道,支持实时数据交互。它们提供与第三方系统交互的标准方法,使代理可以无缝集成多种服务。例如在旅行规划场景,API 允许代理访问天气服务、支付处理、导航地图、航班和酒店预订系统等,确保代理能提供最新信息和服务。数据库工具
使代理能高效地存储、检索和管理结构化或半结构化数据。支持读写操作,帮助代理在会话间保存持久信息,如客户档案、历史交易记录、产品目录和领域知识库,从而实现基于历史数据的学习和个性化服务。实用函数(Utility Functions)
是在代理本地环境中执行的定制软件组件,用于完成数据处理、格式转换、数学计算和自然语言处理等专门任务。它们是更复杂操作的基础模块,帮助代理高效处理信息,适合需要一致且可重复执行的任务。集成工具
专注于连接不同系统和服务,实现流程自动化,如日历同步、文档处理、文件管理及通讯系统集成。它们充当跨平台桥梁,使代理能够协调多个系统和数据源间的复杂工作流。硬件接口工具
允许代理与物理设备和系统交互,连接数字与物理世界。此类工具对物联网设备控制、机器人系统集成、传感器数据处理和物理自动化系统管理至关重要,使代理能够影响现实世界,监控物理环境。
每种工具类型都有其特定用途,可组合使用以打造功能强大的代理。选择工具需依据代理角色、需求及任务复杂度。
代理与工具协作的关键考量
为了开发能够处理复杂现实任务、保证安全性、优雅应对错误、并能适应需求变化的稳健代理系统,需关注以下几个方面:
- 工具组合与链式调用
代理往往需要结合多个工具完成复杂任务。通过链式调用,代理可构建复杂工作流,例如旅游代理先调用 API 查询航班,再用数据库获取用户偏好,最后用实用函数计算最佳行程。链式调用极大扩展了代理的能力。工具选择与决策
代理必须根据上下文评估需求,选择最合适的工具或组合。考虑因素包括工具能力、可靠性、性能和成本。遇到多个工具均能解决问题时,需选出最高效方案。错误处理与备用方案
代理应准备应对工具调用失败的情况,如 API 请求失败、数据库连接异常、函数返回错误等。强健的错误处理机制通常包括备用方案,代理可在主工具失效时切换到替代方案。工具状态管理
许多工具需要维护状态或执行初始化和清理操作。代理需有效管理这些状态,合理分配和释放资源,包括数据库连接、API 认证令牌和各种服务的会话状态。工具更新与版本管理
工具会随着版本升级而变化,代理需有策略应对版本兼容性和弃用功能,支持多版本共存,平滑过渡到新接口。工具安全与访问控制
代理与工具交互时,尤其涉及敏感数据和关键系统,安全问题尤为重要。需妥善管理认证凭证,实施授权检查,确保通信安全,并遵守各种工具的调用频率限制和配额规则。
实例:用户与 AI 旅行代理的交互
用户:“我需要为两位成人预订2024年6月15日至22日去罗马的机票和酒店,预算总共3000美元。”
以下示例使用 CrewAI 框架,展示代理如何利用工具处理此类旅行规划任务:
class TravelTools: def search_flights(self, ...) -> dict: """基础航班搜索模拟""" return { "flights": [ {"airline": "Alitalian airlines", "price": 800, "duration": "9h"} ] } def check_hotels(self, ...) -> dict: """基础酒店搜索模拟""" return { "hotels": [ {"name": "Roma Inn", "price": 150, "rating": 4.0} ] }travel_agent = Agent( role='Travel Agent', goal='在预算内找到合适的航班和酒店', tools=[TravelTools().search_flights, TravelTools().check_hotels])search_task = Task( description="为两位成人寻找2024年6月15日至22日罗马的航班和酒店,预算3000美元", agent=travel_agent)crew = Crew(agents=[travel_agent], tasks=[search_task])result = crew.kickoff()
这个示例体现了几个核心概念:
- 工具定义:TravelTools 类实现了针对旅行相关任务的专用工具代理配置:旅行代理配备了相应工具并明确了目标任务指定:任务参数清晰明确工具集成:代理协同使用多个工具完成任务执行流程:CrewAI 框架管理代理与工具的执行和协调
此简化实现演示了代理如何有效调用工具并保持清晰的操作逻辑。示例中 TravelTools 返回简化的 JSON 响应,实际应用中,这些工具会连接真实外部服务,处理更复杂的数据。此外,还可以采用先进的 AI 规划算法优化行程和活动安排,提供从查询航班、预订酒店到行程规划的端到端无缝体验。
完整代码可在 GitHub 仓库的 Python 笔记本(Chapter_05.ipynb)中找到。
工具在智能代理系统中的重要性
向工具使用范式的转变,源自于对复杂问题往往需要多种专业工具和资源的认识,每种工具都带来了独特的能力。与其试图将所有必需的知识和功能都封装在代理自身内,不如采用更高效、更具可扩展性的方式——根据需要智能地调用合适的工具。
例如,一个负责提供个性化医疗建议的代理,可以利用医疗数据库、临床决策支持系统以及先进的诊断算法等工具。通过将这些外部资源与自身的推理能力相结合,代理能够为不同患者的具体情况提供更准确、更全面的指导。
智能代理中的工具使用概念不仅限于基于软件的工具。在某些领域,如机器人和自动化,代理还可能与物理工具、机械设备或专用装备互动,将其能力扩展到物理世界。例如,制造厂中的机器人代理可以利用各种工具和机械设备,执行复杂的组装任务、质量检查或物料搬运操作。
归根结底,有效利用外部工具和资源的能力,是智能代理的显著特征,使其能够在动态复杂的环境中适应并发展。通过突破自身原生能力的限制,这些代理能够不断进化,借助多样化工具和系统的集体力量,实现更宏大的目标。
另一个典型例子是虚拟旅行代理,它能够访问多个 API、数据库和软件工具,为用户规划和预订完整的旅行行程。这样的代理可以调用航空公司、酒店、租车公司及旅游点评网站的 API,获取航班时刻、可用性、价格和用户评分的实时数据;同时还可查询旅行警示、证件要求和目的地信息数据库。通过整合和推理这些来自多种工具的数据,代理能够提供个性化推荐,做出智能权衡,并无缝预订和协调旅行各方面,满足用户的偏好和限制。显然,这类代理所用的工具种类繁多,且各自以独特方式运作。
我们已经了解了工具的定义及其工作原理。接下来,我们将探讨智能代理系统的另一个关键方面——规划,以及部分规划算法。
智能代理的规划算法
规划是智能代理的核心能力,使其能够推理自身行为并制定策略,有效实现目标。规划算法构成了大型语言模型(LLM)代理确定和安排行动步骤的基础。算法是一系列逐步指令或规则,旨在解决特定问题或完成任务。它由明确且有限的步骤组成,在有限时间内接收输入并产生预期输出。
人工智能中存在多种规划算法,各有优劣和方法。但在处理 LLM 代理时,我们需要考虑算法在自然语言处理、不确定性以及庞大状态空间(即代理在执行任务时可能遇到的所有情况或配置)中的实用性。比如在简单的机器人导航任务中,状态空间可能包括所有可能的位置和方向,但在 LLM 代理中,状态空间变得极为复杂,涵盖所有可能的对话状态、知识上下文及潜在回应。
已知的规划算法包括斯坦福研究院问题求解器(STRIPS)、层次任务网络(HTN)、A*规划、蒙特卡洛树搜索(MCTS)、GraphPlan、快速前进算法(Fast Forward,FF)以及基于 LLM 的规划。根据它们对 LLM 代理的适用性,可以进行分类:
- STRIPS、A*规划、GraphPlan 和 MCTS 尽管在传统 AI 中强大,但因结构刚性且难以处理自然语言,对 LLM 代理不够实用。FF 有一定潜力,但需要大量改造。最实用的方式是基于 LLM 的规划和 HTN,因为它们自然契合语言模型的任务处理和分解方式。
下面详细介绍这些规划算法。
不太实用的规划算法
如前所述,不太实用的算法包括 STRIPS、A*规划、GraphPlan 和 MCTS,具体如下:
STRIPS
STRIPS 使用由逻辑谓词定义的状态和动作,适合明确的二元条件。然而,它不适合 LLM 代理,因为自然语言交互无法有效简化为简单的真假条件。例如,STRIPS 能轻松建模真假状态,却难以处理诸如部分理解概念或对回复略感满意等复杂语言状态,过于刚性,不适合语言规划。
A*规划
A* 规划在路径寻找问题中很强大,但对 LLM 代理来说存在根本性难题。该算法需要明确计算已采取动作的成本和到目标的启发式估计成本。在语言交互中,如何量化不同对话状态间的“距离”,或者估算达到特定理解的“代价”,极其困难。数学层面的需求使 A* 不适用于自然语言规划。
GraphPlan
GraphPlan 构建分层图结构,表示每个时间步的可能动作及其影响。应用于 LLM 代理时,这种方法不奏效,因为语言交互无法整齐划分为具有清晰因果关系的离散层。语言状态的组合爆炸及难以确定不同对话动作的互斥关系,使得 GraphPlan 在语言规划中计算难度过大,难以实际应用。
蒙特卡洛树搜索(MCTS)
MCTS 对 LLM 代理不实用的原因主要有两点。第一,每次“模拟”都需调用实际的 LLM,计算和费用代价极高;第二,庞大的语言交互空间使得随机采样难以高效发现有意义的模式或策略。该算法在游戏场景中表现优异,但在开放式语言交互中则成了劣势。
中等实用性的规划算法——快速前进(FF)
FF 规划被认为是一个在大型语言模型(LLM)代理中中等实用的规划算法。它通过启发式搜索,使用简化版的规划问题来引导搜索过程。FF 规划专注于目标导向的规划,这一点可以适配到 LLM 代理中,但需要做出调整以有效处理自然语言。FF 利用启发式搜索和简化的规划问题辅助其搜索。
对于 LLM 代理来说,FF 规划具有若干吸引人的优势,使其值得考虑。它的目标导向方法自然契合 LLM 处理任务完成的方式,而其松弛规划机制(relaxed planning)则为复杂语言任务提供了有用的近似解。启发式引导有助于管理语言规划中固有的庞大搜索空间,其灵活性允许对部分状态描述进行修改,这在自然语言上下文中特别有价值。
然而,FF 规划在应用于 LLM 代理时也面临重大挑战。传统 FF 有效的数值启发式无法顺利转化为语言状态;松弛规划可能过度简化语言交互中丰富的上下文;在语言规划中,很难定义明确的“删除效果”(即动作会移除或改变对话状态的哪些方面)。更具挑战性的是,基础的状态表示需大幅调整才能有效适用于自然语言环境。
在实践中,FF 可按如下方式适配 LLM 代理:
class LLMFastForward: def create_relaxed_plan(self, current_state: str, goal: str) -> list: """生成一个忽略复杂性的简化计划""" # 利用 LLM 生成高层次规划 prompt = f"Given current state: {current_state}\nAnd goal: {goal}\n" prompt += "Generate a simplified step-by-step plan" return self.llm.generate_plan(prompt) def select_next_action(self, relaxed_plan: list): """根据简化计划选择下一步动作""" # 实现动作选择逻辑 return relaxed_plan[0] # 简化选择
这段代码展示了 FF 规划在 LLM 代理中的简化适配。关键部分如下:
- create_relaxed_plan:接收当前状态和目标(文本形式),利用 LLM 生成一个简化的步骤计划。相当于询问 LLM:“考虑当前状况和目标,我们应该采取哪些主要步骤?”这一过程忽略了许多复杂因素,类似传统 FF 规划忽略删除效果。select_next_action:从简化计划中选择下一步动作。此处仅简单返回计划中的第一个动作(
relaxed_plan[0]
)。更复杂的实现会采用更精细的逻辑选择最合适的动作。总体而言,这段代码展示了 FF 规划核心思想——利用简化计划指导决策——如何被调整用于语言模型,尽管这对 FF 规划和 LLM 能力都是大幅简化。该适配方案虽有潜力,但实际将 FF 应用于 LLM 代理时,需仔细考虑如何在语言模型上下文中表示状态、动作和松弛问题。因此,FF 规划对 LLM 来说属于“中等实用”——可行但需较大修改。
最实用的规划算法
在针对大型语言模型(LLM)代理的规划算法中,有两种方法尤为有效:基于 LLM 的规划和层次任务网络(HTN)规划。这些算法特别适合语言模型,因为它们自然契合 LLM 处理信息和执行复杂任务的方式。传统规划算法常常难以应对自然语言的歧义和复杂性,而这两种方法则拥抱语言规划的流动性和上下文特性。接下来,我们将逐一探讨这两种算法,理解它们为何成为现代 AI 代理框架的首选。
基于 LLM 的规划
现代方法利用 LLM 以更灵活、更自然的方式生成规划。相比传统规划算法,这种方法能更好地处理复杂的真实场景和理解上下文。基于 LLM 的规划基于以下原则:语言模型能够理解复杂目标,生成实现目标的适当步骤,并能根据变化的上下文动态调整这些步骤。与需要明确状态表示的传统规划器不同,LLM 规划器处理的是自然语言描述的状态和动作,因此本质上更加灵活和表达力强。
下面通过图 5.3 来形象展示这一规划过程:
让我们来看一个基于 CrewAI 的实际实现示例,该示例展示了这种规划方法。在这个例子中,我们将创建一个旅游规划系统,包含两个专门的代理:
- 旅行规划策略师(Travel Planning Strategist),负责将旅游请求拆解成可执行步骤;旅行调研员(Travel Researcher),负责验证和寻找具体的选项。
系统处理自然语言的旅游请求,并通过代理间协作生成详尽的旅行计划。具体实现如下:
class TravelPlanner: def __init__(self): self.planner = Agent( role='Travel Planning Strategist', goal='创建全面且个性化的旅行计划', ... # 其他参数 ) self.researcher = Agent( role='Travel Researcher', goal='寻找并验证旅行选项和机会', ... # 其他参数 ) def create_travel_plan(self, request: str) -> Dict: planning_task = Task( description=f""" 分析以下旅游请求并制定详细计划: {request} 将任务拆分为可执行步骤,包括: 1. 理解客户需求 3. 具体预订要求 4. 必要的验证 """, agent=self.planner) research_task = Task( description=""" 基于初步计划,调研并验证: 航班可用性、酒店选项及当地交通 """, agent=self.researcher) crew = Crew( agents=[self.planner, self.researcher], tasks=[planning_task, research_task], process=Process.sequential) return crew.kickoff(inputs={"request": request})
该实现展示了基于 LLM 规划的若干关键优势。规划者能理解复杂的自然语言请求,动态生成适当步骤,并适应不同类型的旅行规划场景。代理间可共享上下文,基于彼此的输出协同工作。系统的复杂性体现在其处理细腻需求的能力上。例如,当用户请求“轻松的海滩假期加上一些文化活动”时,规划者能理解这些抽象概念并转化为具体推荐。
然而,开发者应注意一些注意事项。若缺乏约束,基于 LLM 的规划系统有时可能生成过于乐观或不切实际的计划。它们也可能难以处理高度具体的数值限制或严格的时间要求,除非这些在实现中被明确处理。基于 LLM 规划相较传统算法的显著优势在于其适应性。STRIPS 或 A* 规划需对每种旅行场景做出明确状态表示,而基于 LLM 的规划则可依赖其语言和上下文理解处理新颖场景,特别适合需求常常模糊或变化的领域。这种规划方法也擅长应对不确定性和部分信息,这是传统规划器的难点。当信息缺失或模糊时,系统能够生成合理假设,并在计划中包含应急步骤。
层次任务网络(HTN)
HTN 规划通过将复杂任务拆解成更简单的子任务,创建动作层级结构。与仅处理基本动作的 STRIPS 不同,HTN 能处理抽象任务,并将其分解为更具体的步骤,特别适合现实中自然分解为子任务的规划问题。
HTN 通过逐步将高层任务拆解为更小子任务,直到达到可直接执行的原子任务来工作。其层次结构使问题表示直观,解决方案查找高效。
举例代码:
def buy_groceries_task(): return [ ('go_to_store', []), ('select_items', []), ('checkout', []), ('return_home', []) ]def select_items_task(): return [ ('check_list', []), ('find_item', []), ('add_to_cart', []) ]
HTN 的核心是任务分解,高层(复合)任务被拆分为更小、更易管理的子任务,直到细化为可直接执行的基本任务。层级结构有助于直观表示问题并高效求解。
在我们的旅行代理示例中,也可以采用类似的层次分解,将复杂任务拆分为更小任务。图 5.4 直观展示了这种结构:
我们可以通过 CrewAI 的分层处理功能来实现这一点,任务会按照层次结构进行拆解,正如 HTN 规划算法所解释的那样。在 CrewAI 框架中,分层方法需要一个管理单元(Manager),负责将任务拆分并将子任务分配给各代理。这个管理单元既可以是一个代理,也可以是 LLM 本身。
- 如果管理者是代理,则你可以根据工作流程需要控制它如何将任务拆解成多级子任务。如果管理者是 LLM,它将基于用户查询,使用自身生成的任意计划。通过提示工程,你可以在一定程度上控制任务拆解和分配,但通常灵活性较差,适合更简单的工作流。
以下是一个基于 HTN 风格的旅行规划工作流示例代码:
flight_specialist = Agent( role='Flight Planning Specialist', goal='负责所有航班安排相关事务', backstory='航空预订和航班物流专家。')accommodation_specialist = Agent( role='Accommodation Specialist', goal='负责所有住宿相关规划', backstory='酒店及住宿预订专家。')activity_specialist = Agent( role='Vacation Activity Specialist', goal='负责所有活动相关规划', backstory='休闲活动安排专家。')manager_llm = ChatOpenAI(model="gpt-4o-mini")travel_planning_task = Task( description=f""" 根据以下请求规划完整航班行程: {request} 计划需包含:航班安排、住宿预订及其他相关旅游内容 """, expected_output="涵盖所有请求内容的详细旅行行程。", agent=None # 无指定代理,管理者负责分配子任务)crew = Crew( agents=[flight_specialist, accommodation_specialist, activity_specialist], tasks=[travel_planning_task], process=Process.hierarchical, manager_llm=manager_llm,)return crew.kickoff()
执行结果示例(为简洁起见,已截断部分内容):
最终旅行计划:
以下为两位成人从纽约前往巴黎的五天旅行完整行程:
航班:
- 出发航班:…总航班费用:$2,960
酒店住宿:
- 酒店:…预计总费用:€800
机场接送:
- 方案1:…方案2:…
凡尔赛一日游:
- 交通:往返 RER C 线,…费用:约 €364.20出发时间:上午9点,…返回时间:下午5点,…
预计总费用汇总:
- 航班:$2,960住宿:€800(Le Fabe 酒店)机场接送:€100(可能有所波动)凡尔赛一日游:约 €364.20费用汇总可按需折算为美元。
需要注意的是,此案例中的代理系统没有访问外部工具或查询功能,因此它生成的内容完全是虚构的,不具备事实依据。这强调了工具的重要性,我们将在下一节深入讨论。当前示例展示了如何使用框架进行任务拆解,并通过管理者协调多个代理完成用户请求中分解出的简化任务。完整代码可见 GitHub 仓库中的 Python 笔记本(Chapter_05.ipynb)。
HTN 规划具有多项显著优势,使其在复杂规划场景中特别有效。它的自然问题表示反映了人类思维模式,便于理解和维护。层级方法通过将复杂问题拆解成可管理的子任务,提高了可扩展性,有效缩小搜索空间。HTN 结构擅长通过任务层级编码专家知识,实现相似问题的模式复用。此外,它能灵活处理抽象任务和基本任务,适应各种规划情境,使规划者可根据需要在不同抽象层次上工作。
到目前为止,我们已经了解了工具和多种规划算法。结合它们,LLM 代理能够执行更复杂的多步任务,将战略规划与高效工具使用有机结合。接下来,我们将进一步探讨如何在智能代理系统中有效地整合工具使用与规划。
整合工具使用与规划
早期关于 AI 规划和工具使用的研究大多是分开进行的,分别聚焦于规划算法或工具能力。然而,要实现真正智能的代理,必须有效地将工具使用与规划结合起来。正如前面章节中的旅行规划示例所示,虽然规划器能给出详细的旅行计划,但其中的细节并非真实数据,而是 LLM 的虚构。为了让系统基于真实的航班、酒店和活动数据生成切实可行的旅行计划,就必须结合工具与规划算法使用。本节将讨论如何将这两者结合,准确生成相关响应和完成任务。
关于工具的推理
代理需要具备推理其可用工具的能力,理解每个工具的功能、能力与限制,以及它们在哪些情境和条件下能被有效应用。推理过程基于当前目标和任务,评估可用工具,选择最合适的工具来解决当前问题。
举例来说,在旅行规划场景中,代理可访问航班预订 API、酒店预订系统和活动规划软件。代理需要判断哪些工具能用于预订航班或酒店,哪些能提供当地景点信息。
对于 LLM 代理,工具推理大多由语言模型自身能力处理。现代 LLM 训练时已习惯理解工具描述、用途和适用场景。因此无需显式编写复杂的推理机制,只需提供清晰的工具描述,LLM 会自动决定何时及如何使用工具。例如,旅行规划代理可以定义如下:
from crewai import Agenttravel_agent = Agent( role='Travel Planner', goal='规划全面的旅行行程', tools=[ flight_search_tool, # 查找和预订航班的工具 hotel_booking_tool, # 酒店预订工具 activity_planner_tool # 当地活动与景点规划工具 ])
LLM 代理可以自然理解:
- 哪个任务用哪个工具(如航班查询用 flight_search_tool)何时组合使用工具(如协调航班和酒店日期)如何根据用户需求调整工具使用(如预算限制)
这种内置推理能力使我们能专注于提供定义良好、描述清晰的工具,而不必设计复杂的推理逻辑。LLM 会基于上下文和需求自行完成工具选择与应用的决策。
然而,并非所有语言模型都具备同等的工具推理能力。通常需专门训练或微调过以支持工具调用的模型才较为有效。较小模型或未接受工具训练的模型可能会遇到:
- 无法判断何时需要工具错误估计工具能力工具调用顺序错误漏用可用工具忽视工具限制或需求
即便是能力较强的模型,也可能存在:
- 处理复杂多步骤工具组合困难在相似场景下工具选择不一致面对功能细微差异的工具时难以区分工具调用失败时恢复能力差
这也是为什么 CrewAI、LangGraph、AutoGen 等框架通常与表现出良好工具推理能力的高级模型配合使用效果最佳,并且在部署前需充分测试代理的工具使用模式。
规划中的工具使用
现代 AI 代理的规划过程本质上由 LLM 能力驱动,建立在我们之前讨论的基于 LLM 规划和 HTN 方法的基础上。代理不再依赖刻板的规划算法,而是利用语言模型的理解力,制定灵活且具上下文感知的工具使用计划。图 5.5 形象展示了这一过程:
当代理收到请求时,它首先通过自然语言处理理解目标。以旅行代理为例,它需要理解家庭度假请求不仅涉及航班预订,还包括适合家庭的住宿和活动。这个目标理解阶段直接依赖于 LLM 的训练理解能力。
接下来,规划过程转向识别所需工具及其使用顺序。这类似于 HTN 规划中的层次分解,但结合了基于 LLM 的决策灵活性。代理不会仅按预定义分解规则执行,而是根据具体上下文和需求动态调整规划。
工具自然地被整合进计划。代理通过工具描述了解各工具能力,并合理安排顺序。例如在规划假期时,代理知道必须先确认航班日期,再预订酒店,活动规划需考虑地点和时间。
这种规划方法结合了传统规划算法的结构性与语言模型的适应性。代理能根据新信息或变化调整计划,就像人工旅行代理根据客户反馈或资源变化调整方案一样。
该规划过程的成功很大程度上依赖于 LLM 理解上下文和生成合理行动序列的能力。因此,CrewAI 等框架常采用此类规划,帮助代理发挥语言理解优势,同时保持完成复杂任务所需的系统性。
实践实现探索
为展示如何利用各种 AI/ML 框架创建能通过工具使用与规划执行复杂任务的智能代理,下面我们以 CrewAI、AutoGen 和 LangGraph(LangChain 的代理框架)为例。各框架示例的完整代码可在 GitHub 仓库的 Chapter_05.ipynb Python 笔记本中找到。
CrewAI 示例
我们以旅行规划示例查看 CrewAI 如何实现基于工具的推理。CrewAI 的 Python 库提供了 @tool
装饰器,允许我们用清晰描述和文档定义工具。示例如下:
@tool("Search for available flights between cities")def search_flights(...) -> dict: """搜索城市间可用航班。""" # 调用航班 API 及其他逻辑@tool("Find available hotels in a location")def find_hotels(...) -> dict: """搜索指定地点的可用酒店。""" # 调用酒店 API 及其他逻辑@tool("Find available activities in a location")def find_activities(...) -> dict: """搜索指定地点的可用活动。""" # 调用活动 API 及其他逻辑
然后,这些工具被分配给能理解上下文中如何使用它们的代理。代理创建时指定角色、目标和背景故事,以指导其决策:
Agent( role='An expert travel concierge', goal='负责所有旅行规划相关事务', backstory="精通航空预订与航班物流、酒店预订及假期活动安排。", tools=[search_flights, find_hotels, find_activities], verbose=False)
当收到任务时,代理会基于上下文和需求使用这些工具:
travel_planning_task = Task( description=f""" 根据以下请求规划完整旅行和休闲行程: {request} 计划应包括: - 航班安排 - 住宿预订 - 任何其他相关旅游内容 """, expected_output="涵盖所有请求内容的详细旅行行程。", agent=self.travel_specialist)
调用 crew.kickoff()
时,CrewAI 会以如下方式协调工具使用:
- 通过任务描述理解需求根据代理角色和任务目标识别所需工具按逻辑顺序调用工具构建旅行计划处理工具输出并整合至最终响应
该实现展示了 CrewAI 如何结合工具定义、代理能力和任务规范,构建连贯的规划系统。框架处理了工具推理的复杂性,开发者则可专注于定义清晰的工具接口和代理行为。
AutoGen 示例
AutoGen 提供了一个开发 AI 代理的平台,代理通过对话协作,共同解决指定任务。AutoGen 采用 RoundRobinGroupChat 机制,多个专业代理轮流互动,协同生成全面的旅行计划。实现中定义了四个核心代理:航班规划师、酒店规划师、活动规划师和汇总代理,各司其职,配备特定工具。
每个代理初始化时包含:
- 名称与描述模型客户端(此处为 OpenAI 的 GPT-4o-mini)可访问的工具定义其角色与职责的系统消息
与 CrewAI 的主要区别在于执行模型:
- 代理通信:CrewAI 用层级任务方式,AutoGen 实现轮流对话,RoundRobinGroupChat 类协调交流,让代理基于彼此建议构建方案。终止处理:AutoGen 通过 TextMentionTermination 类显式终止,当汇总代理发出“TERMINATE”关键词时结束对话,区别于 CrewAI 基于任务完成的终止。TextMentionTermination 参数包括触发关键词(如“TERMINATE”)、大小写敏感选项、是否忽略空白字符、正则匹配支持等。工具整合:AutoGen 在代理初始化时直接关联工具,不像 CrewAI 用装饰器定义。协调模式:CrewAI 多用管理者-执行者架构,AutoGen 的轮流机制创造更协作环境,汇总代理负责最终整合计划。
该实现展示了 AutoGen 在处理复杂多代理对话时的优势,同时保持角色清晰分工和工具专业化。定义代理示例:
flight_agent = AssistantAgent( name="flight_planner", model_client=model_client, tools=[travel_tools.search_flights], description="能规划假期航班行程的助手。", system_message="你是帮助用户规划旅行航班的助理。")hotel_agent = AssistantAgent( name="hotel_planner", model_client=model_client, tools=[travel_tools.search_flights], description="...", system_message="...")
定义代理后,可用 RoundRobinGroupChat 类组建对话系统并启动:
group_chat = RoundRobinGroupChat( [flight_agent, hotel_agent], termination_condition=termination)await Console(group_chat.run_stream(task="我需要规划一次纽约到巴黎的五天旅行。"))
LangGraph 示例
LangChain 提供了一个框架,支持结合 LLM 与其他工具和数据源开发应用。在智能代理系统中,LangChain 的子框架 LangGraph 用于构建强大的 LLM 代理工作流。LangGraph 通过工作流图系统实现代理旅行规划,与 CrewAI 和 AutoGen 采用不同范式。
LangGraph 采用状态机方法,将工作流定义为含节点和边的图结构。核心节点有:
- 代理节点,负责消息处理与决策工具节点,执行请求的工具(如航班搜索、酒店预订、活动规划)
工作流循环执行,代理节点评估当前状态,决定调用工具或给出最终响应。通过一个函数解释模型下一步动作(调用工具或结束对话),控制流程走向工具节点或结束。LangGraph 同样支持 Python 中的 @tool
装饰器定义工具:
@tooldef search_flights(...) -> dict: """搜索城市间可用航班。""" # 模拟 API 返回的 JSON 数据 return data
定义节点后,连接它们构建完整的工作流图。例如下面代码定义了基于状态图的工作流,任务在“agent”与“tools”两个节点间循环:
- 从“agent”节点(入口点)调用
call_model
处理输入运行后由条件函数 should_continue
决定是返回“tools”节点还是结束“tools”节点处理中间任务,总是回到“agent”节点,形成循环直到条件停止使用 MemorySaver 检查点保存状态,工作流编译成 LangChain 兼容可执行文件最后以初始输入调用工作流,执行完毕后打印最终消息workflow = StateGraph(MessagesState)workflow.add_node("agent", call_model)workflow.add_node("tools", tool_node)workflow.add_edge(START, "agent")workflow.add_conditional_edges("agent", should_continue)workflow.add_edge("tools", "agent")checkpointer = MemorySaver()app = workflow.compile(checkpointer=checkpointer)final_state = app.invoke( {"messages": [HumanMessage(content="我需要规划一次纽约到巴黎的五天旅行")]}, config={"configurable": {"thread_id": 42}})
LangGraph 的优势包括:
- 图结构带来显式流程控制,易于可视化与理解内建状态管理及检查点确保应用状态稳健
但也有权衡:需要扎实的图编程知识相较 CrewAI 代理定义,初始配置更复杂
完整代码见 GitHub 仓库 Chapter_05.ipynb。
表 5.1 比较了 LangGraph、CrewAI 和 AutoGen 的主要差异:
特性 | LangGraph | CrewAI | AutoGen |
---|---|---|---|
状态管理 | 明确的状态管理 | 通过代理实例及任务上下文管理 | 通过群聊消息历史管理 |
工具整合 | 通过专门的工具节点管理 | 基于装饰器定义,直接关联代理 | 工具直接与特定代理关联 |
流程控制 | 基于图的工作流 | 层级任务分解或顺序流程 | 代理轮流协作 |
表 5.1 – LangGraph、CrewAI 和 AutoGen 实现方法对比
以上表格展示了基于本次实现的三者差异。
总结
本章我们学习了工具和规划在人工智能代理系统中的关键作用。我们讨论了什么是工具/函数调用以及大型语言模型(LLM)代理如何展现这一特性。还了解了各种工具类型,并通过示例展示了如何使用框架或直接通过 LLM 原生方式调用工具。随后,我们探索了多种规划算法,从传统的 STRIPS、HTN 到现代的基于 LLM 的规划方法,理解了它们在语言模型环境中的实际适用性。
通过一个实际的旅行规划示例,我们看到如何在各框架中定义、整合并使用工具,从而构建复杂的规划系统。
我们学习了将工具调用与规划结合,如何极大提升代理系统的能力,使其更好地处理复杂任务。还回顾了 CrewAI、AutoGen 和 LangGraph 三个框架的实现模式,揭示了它们在代理协调和工具使用上的不同方法。
下一章,我们将深入探讨代理系统中的协调者(Coordinator)、执行者(Worker)和委托者(Delegator)模式,了解它们如何助力完成复杂的现实任务。