构建具备自主性的人工智能系统——使代理能够使用工具和进行规划

在上一章中,我们探讨了智能代理中反思和内省的复杂概念。这些能力使代理能够推理自己的认知过程,从经验中学习,并动态地调整其行为。

AI代理向前迈出的一大步是将代理的规划能力与工具使用结合起来。本章将探讨工具的工作原理、不同的规划算法、它们如何结合,以及实际示例展示它们在实践中的有效性。我们将探索智能代理如何使用工具的概念,这些工具扩展了代理的能力,使其超越决策和问题解决。我们将查看代理可以利用的不同类型的工具,如API、数据库和软件功能。接着,我们将深入研究代理所需的规划算法,包括状态空间搜索、强化学习和分层任务网络规划。我们还将讨论如何通过推理可用工具、根据目标评估其适用性、选择合适的工具,并生成高效的行动序列来整合工具使用和规划。

本章分为以下几个主要部分:

  • 理解代理中工具使用的概念
  • 代理的规划算法
  • 整合工具使用与规划
  • 探索实际应用

到本章结束时,您将了解什么是工具,如何使用它们来增强您的代理系统,以及它们如何与规划算法协同工作。

技术要求

您可以在GitHub上找到本章的代码文件,网址为:github.com/PacktPublis...。在本章中,我们还将使用如CrewAI、AutoGen和LangChain等代理Python框架,展示AI代理的各个方面。

理解代理中工具使用的概念

在其核心,智能代理的工具使用指的是LLM代理利用外部资源或工具来增强代理的固有功能和决策过程的能力。这个概念超越了传统上将代理视为一个自包含(孤立)实体的观念,后者仅依赖于其内部知识(训练数据)和算法。相反,它承认代理通过战略性地利用外部工具和系统的力量,可以超越自身的固有限制。

例如,当你向一个孤立的代理发送查询("天气怎么样?")时,模型可能会随便给出一个编造的答案,或者它可能回答说不知道如何获取天气信息。在这种情况下,代理将依赖LLM的训练数据,而这些数据并没有关于实时天气数据的最新信息。另一方面,如果LLM代理可以访问一个实时天气查询工具,它就有可能准确地回答这个问题。工具的使用使得代理能够访问实时数据,执行专业任务,并管理超出其内建知识和算法的复杂工作流。图5.1展示了这种孤立行为与工具增强行为的对比:

工具使用的意义在于它能够扩展代理(进而扩展驱动代理的LLM)的能力范围,使其能够应对一些可能超出其原生问题解决能力的复杂现实挑战。通过整合和协调使用各种工具,代理可以有效地分担特定任务或访问补充数据和功能,从而提高整体性能并扩大可实现目标的范围。在深入探讨工具之前,让我们首先了解LLM工具调用是如何工作的。

工具和函数调用

虽然在LLM的背景下,工具调用和函数调用常常被交替使用,但它们在技术上有明显的区别。函数调用是指LLM在同一运行时生成对预定义函数的结构化调用,通常用于执行内部任务,如数据库查询或计算。另一方面,工具调用使LLM能够与外部API、服务或系统进行交互,从而访问实时数据并执行超出其固有能力的专业任务。例如,使用函数调用的LLM可能会从本地数据库中检索用户的个人资料,而工具调用则涉及查询天气API以获取实时更新。理解这一区别对于设计能够无缝集成内部逻辑与外部系统的AI代理至关重要,从而增强其功能。

当LLM调用工具或函数时,它实际上并不会执行任何代码。相反,它生成一个结构化的响应,指示以下内容:

  • 它希望使用哪个工具/函数
  • 应传递给该工具/函数哪些参数
  • 这些参数应如何格式化

可以把它当作写出详细指令,而不是执行实际的操作。LLM充当一个复杂的调度员,决定需要做什么以及如何做,但实际的工具或函数执行必须由外部运行时环境或代理控制器来处理。例如,当被问及波士顿的天气时,LLM可能会识别需要天气查询函数,并以结构化调用的形式回应,如下所示:

json 复制代码
{
    "function": "weather_lookup",
    "parameters": {
        "location": "Boston",
        "date": "10/01/2024"
    }
}

这个结构化响应随后由代理控制器解释并执行,代理控制器实际上有能力使用提供的参数运行指定的函数。天气查询工具(或函数)可能像这样:

python 复制代码
import requests

def 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代理、工具和代理控制器之间的流程:

值得注意的是,并非所有的LLM都能够有效(或者说准确)地进行工具/函数调用。虽然较大的模型在工具调用方面更具能力,但一些较大的模型(如OpenAI的GPT-4和-4o,Anthropic的Claude Sonnet、Haiku、Opus,以及Meta的Llama 3模型)是专门针对工具调用行为进行训练的。而其他模型虽然没有专门进行工具调用的训练,但通过积极的提示工程,它们仍然能够实现类似的功能,虽然成功的程度有所不同。

为代理定义工具

工具的定义通常包括清晰的描述,通常使用文档字符串或JSON模式来传达它们的目的、所需输入和预期输出。根据是否使用框架或直接使用LLM API,定义工具有两种主要方法。

框架方法 -- 使用文档字符串

在如CrewAI或LangGraph等框架中,工具是通过文档字符串定义的------这种描述性文本出现在函数的开头。以下是一个天气查询工具的示例:

python 复制代码
def weather_lookup(location: str, date: str = None):
    """
    一个可以查询实时天气数据的工具。
    参数:
      location (str):要查询天气的地点
      date (str) 可选:日期,格式为 MM/DD/YYYY
    """
    # 函数代码和逻辑

文档字符串(用三引号(""")括起来)提供了以下关键信息:

  • 工具的目的
  • 所需和可选的参数
  • 预期的返回值

这种方法使得工具的创建对开发者来说直观易懂,因为它采用了标准的编程实践。虽然Python使用三引号来定义文档字符串,其他编程语言可能有不同的约定来定义此类文档。

直接LLM集成

在没有框架的情况下直接使用LLM API(例如Anthropic的Claude或OpenAI的GPT)时,工具必须使用特定的JSON模式格式来定义:

json 复制代码
{
  "name": "weather_lookup",
  "description": "一个可以查询实时天气数据的工具",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "城市和州,例如:San Francisco, CA"
      }
    },
    "required": ["location"]
  }
}

当调用模型时,可以使用多个工具作为一个JSON模式对象的列表(或数组),例如:

ini 复制代码
tools = [
  { 
    "name": "weather_lookup",
    "description": "一个可以检查天气数据的工具",
    ... 
  },
  {
    "name": "flight_booking",
    "description": "一个可以预定机票的工具",
    ... 
  },
  ...
]

请注意,这取决于模型,因此您必须参考模型的文档,了解如何通过API指定工具。如果您的项目使用多个具有不同工具定义方式的模型,那么定义、管理和维护工具定义可能会变得相当繁琐。这也是越来越多的开发者倾向于使用如CrewAI、LangGraph和AutoGen等库或框架的原因,这些库或框架提供了一种简化的工具定义方法,无论用于代理的LLM是什么。

工具种类

LLM代理可以利用各种类型的工具包来增强其功能并执行复杂任务。以下是主要的工具类别:

应用程序接口(API):API作为代理访问外部服务和数据的主要门户,提供标准化的方法与第三方系统进行交互,使代理能够与各种服务无缝集成。例如,在旅行规划的场景中,API允许代理访问天气服务、支付处理系统、导航与地图服务以及航班和酒店预订系统。这种实时连接确保代理可以向用户提供最新的信息和服务。

数据库工具:数据库工具使代理能够高效地存储、检索和管理结构化(或半结构化)数据。这些工具支持读取和写入操作,允许代理在会话之间保持持久的信息。代理通常使用数据库来存储客户档案和偏好,维护历史交易记录,管理产品目录,并访问特定领域的知识库。这种持久存储能力使代理能够从过去的互动中学习并提供个性化服务。

实用功能:实用功能是为专门任务设计的自定义软件组件,在代理的环境中本地运行。这些功能处理数据处理和分析、格式转换、数学计算和自然语言处理任务等基本操作。它们作为更复杂操作的构建模块,帮助代理高效地处理信息。实用功能在需要一致性、可重复操作的任务中尤其有价值。

集成工具:集成工具专注于连接不同的系统和服务,实现无缝的工作流自动化。这些工具处理日历同步、文档处理、文件管理和通信系统集成等关键任务。它们充当不同平台和服务之间的桥梁,使代理能够协调跨多个系统和数据源的复杂工作流。

硬件接口工具:硬件接口工具使代理能够与物理设备和系统交互,架起数字世界与物理世界之间的桥梁。这些工具对于控制物联网设备、集成机器人系统、处理传感器数据和管理物理自动化系统至关重要。通过硬件接口工具,代理可以将其影响力从数字交互扩展到实际世界的变化和物理环境的监控。

每种工具类型都有特定的用途,并且可以结合使用以创建强大的代理能力。工具的选择取决于代理的角色、需求和需要执行任务的复杂性。

理解代理如何与这些工具协作,涉及以下几个关键考虑因素,这些因素影响代理的有效性和可靠性。这些方面对于开发能够处理复杂现实任务的健壮代理系统至关重要,同时保持安全性、优雅地处理错误,并适应不断变化的需求:

工具组合与链式操作:代理通常需要组合多个工具来完成复杂的任务。工具组合允许代理通过将工具串联起来创建复杂的工作流。例如,一个旅行规划代理可能首先使用API检查航班的可用性,然后使用数据库工具检索用户的偏好,最后使用实用功能计算最佳行程。这个链式操作大大扩展了代理的能力,超越了仅使用单一工具。

工具选择与决策:工具使用的一个最关键方面是代理选择适合任务的工具的能力。代理必须评估上下文,理解需求,并选择最合适的工具或工具组合。这涉及到考虑工具的能力、可靠性、性能和成本等因素。代理还必须处理多种工具可以解决同一问题的情况,选择最有效的选项。

错误处理与回退机制:在使用工具时,代理必须为潜在的失败做好准备,并制定相应的处理策略。这包括检测失败的API调用、管理数据库连接问题或处理不正确的函数输出。健壮的错误处理通常涉及实现回退机制,在主要方法失败时,代理可以切换到备用工具或方法。

工具状态管理:许多工具维持状态或需要特定的初始化和清理程序。代理需要有效地管理这些工具的状态,确保适当的资源分配和释放。这包括管理数据库连接、保持API认证令牌,并处理各种服务的会话状态。

工具更新与版本管理:工具随着时间的推移会发展,具有新的版本和功能。代理需要有策略来处理工具的更新、版本兼容性和弃用功能。这可能涉及维持对多个版本的兼容性、优雅地处理弃用功能,并适应新的工具接口。

工具安全性与访问控制:当代理与工具交互时,尤其是那些访问敏感数据或关键系统的工具,安全性考虑至关重要。这包括管理认证凭证、实施适当的授权检查,并确保安全的通信通道。代理还必须遵守各种工具施加的速率限制和使用配额。

考虑一个实际的例子,展示用户如何与我们的AI旅行代理通过有效利用工具进行交互。

用户:"我需要6月15日至22日,2024年,两名成人前往罗马的航班和酒店选项,总预算为3000美元。"

使用CrewAI框架,以下代码片段演示了代理如何在这个专注的旅行规划场景中使用工具:

python 复制代码
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="为两名成人查找6月15日至22日的罗马航班和酒店,预算3000美元",
    agent=travel_agent
)

crew = Crew(agents=[travel_agent], tasks=[search_task])
result = crew.kickoff()

在这个例子中,我们可以看到几个关键概念的实际应用:

工具定义:TravelTools类实现了针对特定旅行任务的工具 代理配置:旅行代理配置了适当的工具并设定了明确的目标 任务定义:任务通过精确的参数进行定义,供代理使用 工具集成:代理无缝地集成多个工具(航班和酒店搜索)以完成任务 执行流程:CrewAI框架管理代理及其工具的整体执行和协调

这个简化的实现展示了代理如何有效使用工具,同时保持操作的清晰性和目标性。在我们的示例中,TravelTools类使用简化的JSON响应以增强清晰度。然而,在实际应用中,这些工具将与实际的外部服务进行交互,并处理更复杂的数据。

需要注意的是,这只是一个简单的实现,实际应用将涉及与各种API、数据库和专门的旅行领域软件工具的集成。此外,可以使用高级AI规划算法来优化行程构建和活动规划步骤。通过这种全面的工具使用,AI旅行代理能够提供一个无缝的端到端旅行规划体验,远超仅仅是搜索航班和酒店。您可以在GitHub仓库中的Python笔记本(Chapter_05.ipynb)中找到完整的代码。

工具在代理系统中的重要性

向工具使用转变的范式变革源于这样一个认识:许多复杂问题需要多种专业工具和资源,而每种工具都提供了独特的能力。与其试图将所有必需的知识和功能封装到代理本身中,更高效且可扩展的方法是根据需要智能地利用适当的工具。

例如,负责提供个性化医疗推荐的代理可以利用医疗数据库、临床决策支持系统和先进的诊断算法等工具。通过明智地将这些外部资源与代理自身的推理能力相结合,代理可以提供更准确、更全面的指导,量身定制符合每个患者的个人档案和病情的建议。

在智能代理的概念中,工具的使用不仅限于基于软件的工具。在某些领域,如机器人技术和自动化,代理可能与物理工具、机器或专用设备进行交互,以将其能力扩展到物理领域。例如,在制造厂中的机器人代理可以利用各种工具和机械执行复杂的组装任务、质量检查或物料处理操作。

最终,能够有效利用外部工具和资源是标志着真正智能代理的特征,这些代理能够在动态复杂的环境中适应和繁荣。通过突破其本土能力的局限,这些代理能够不断进化,利用各种工具和系统的集体力量来实现雄心勃勃的目标。

另一个很好的例子是虚拟旅行代理,它能够访问多个API、数据库和软件工具,为用户规划和预订完整的旅行行程。这样的旅行代理可以利用来自航空公司、酒店、租车公司和旅行评论网站的API,收集航班时刻表、可用性、定价和客户评分等实时数据。它还可以访问旅行警告、旅行文件要求和目的地信息的数据库。通过整合和推理来自各种工具的所有数据,代理可以提供个性化推荐,做出智能权衡,并无缝地预订和协调旅行的各个方面,以满足用户的偏好和约束条件。当然,在这种情况下使用的工具集是多样的,并且它们以各自独特的方式运行。

我们已经了解了什么是工具以及它们是如何工作的。接下来,我们将探索代理系统中的另一个关键方面------规划,以及一些规划算法。

代理的规划算法

规划是智能代理的基本能力,使其能够推理自己的行为,并设计有效的策略来实现目标。规划算法构成了LLM代理确定和排序其行为的基础。算法是解决特定问题或完成任务的一系列逐步指令或规则。它是一个明确且有限的步骤序列,接受输入并在有限的时间内生成预期输出。

AI中有多种规划算法,每种算法都有其自身的优势和方法。然而,在与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

对于LLM代理,MCTS变得不实用的主要原因有两个。首先,每次"模拟"都需要实际调用LLM,这在计算和成本上都非常昂贵;其次,广泛的语言交互空间使得随机采样在寻找有意义的模式或策略时效率低下。该算法在类似游戏的场景中表现出的优势,在开放式语言交互中变成了弱点。

中等实用的规划算法 -- FF

FF规划被认为是一个中等实用的规划算法,可以用于LLM代理。它通过启发式搜索,使用简化版的规划问题来引导搜索。它的目标导向规划方法可以适应LLM代理,尽管需要对其进行修改,以有效处理自然语言。FF规划通过启发式搜索,使用简化版的规划问题来引导搜索。

对于LLM代理,FF规划提供了几个值得考虑的优点。其目标导向的方法自然与LLM处理任务完成的方式对接,而其松弛的规划机制为复杂的语言任务提供了有用的近似。启发式指导有助于管理语言规划中固有的庞大搜索空间,并且其灵活性允许在部分状态描述下进行修改,这在自然语言上下文中尤为重要。

然而,FF规划在应用于LLM代理时也面临着重大挑战。FF在传统规划中有效的原始数值启发式方法无法平滑地转化为语言状态,并且松弛的规划有可能简化语言交互中丰富的上下文。此外,定义清晰的删除效应------即一个动作删除或改变对话状态的哪些方面------在基于语言的规划中存在相当大的困难。或许最具挑战性的是,基本的状态表示需要大量适配才能有效地与自然语言配合使用。在实际应用中,FF可以按照以下方式适应LLM代理:

python 复制代码
class LLMFastForward:
    def create_relaxed_plan(self,
                            current_state: str,
                            goal: str) -> list:
        """创建简化的计划,忽略复杂性"""
        # 使用LLM生成高层次的计划
        prompt = f"给定当前状态: {current_state}\n目标: {goal}\n"
        prompt += "生成一个简化的逐步计划"
        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代理需要仔细考虑如何在语言模型上下文中表示状态、动作和松弛问题。这使得它成为一种中等实用的规划方法------可行,但需要对其原始形式进行显著修改。

最实用的规划算法

对于LLM代理的规划算法,两个方法特别突出,尤其有效:基于LLM的规划和HTN规划。这些算法被证明特别适合语言模型,因为它们自然契合LLM处理信息和应对复杂任务的方式。尽管传统规划算法通常难以应对自然语言的模糊性和复杂性,但这些方法拥抱了基于语言的规划的流动性和上下文性质。让我们探索每个算法,并理解为什么它们成为现代AI代理框架的首选。

基于LLM的规划

现代方法利用LLM以更灵活和自然的方式生成计划。这种方法能够处理复杂的现实场景,并且比传统规划算法更好地理解上下文。基于LLM的规划基于以下原则:语言模型能够理解复杂的目标,生成实现这些目标的适当步骤,并根据不断变化的上下文调整这些步骤。与需要明确状态表示的传统规划器不同,LLM规划器使用自然语言描述的状态和动作,使得它们本质上更灵活、更具表现力。我们可以通过图5.3来可视化规划过程:

让我们通过一个使用CrewAI的实际实现来展示这种规划方法。在这个例子中,我们将创建一个旅行规划系统,包含两个专门的代理:一个旅行规划策略师,负责将旅行请求分解为可管理的步骤;一个旅行研究员,负责验证并寻找具体的选项。该系统处理自然语言的旅行请求,并通过代理协作生成全面的旅行计划。以下是实现代码:

ini 复制代码
class TravelPlanner:
    def __init__(self):
        self.planner = Agent(
            role='Travel Planning Strategist',
            goal='Create comprehensive, personalized travel plans',
            ... # 其他参数
        )
        self.researcher = Agent(
            role='Travel Researcher',
            goal='Find and validate travel options and opportunities',
            ... # 其他参数
        )

    def create_travel_plan(self, request: str) -> Dict:
        planning_task = Task(
            description=f"""
            Analyze the following travel request and
            create a detailed plan:
            {request}
            Break this down into actionable steps by:
            1. Understanding client requirements
            2. Specific booking requirements
            3. Required validations
            """, agent=self.planner )

        research_task = Task(
            description="""
            Based on the initial plan, research and
            validate: Flight availability, hotel options,
            and local transportation
            """, 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特别适合处理那些任务自然分解为子任务的现实世界规划问题。HTN规划的工作原理是将高层次任务分解为逐渐更小的子任务。考虑以下示例代码:

ruby 复制代码
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规划的原理是任务分解,高层次任务(复合任务)被分解为更小、更易管理的子任务,直到达到可以直接执行的原始任务。这种层次结构使得问题的表示更加直观,并能高效地找到解决方案。在我们的示例中,buy_groceries_task是一个高层次的任务,它被分解为四个子任务。其中一个子任务,select_items,进一步分解为三个更具体的动作,依此类推。在我们旅行代理的例子中,我们可以使用类似的层次化分解,将复杂任务分解为更小的任务。从视觉上看,图5.4展示了这种分解的方式:

为了在CrewAI中实现这一点,我们可以使用CrewAI的层次化处理,其中任务按照HTN规划算法的方式进行层次化分解。使用CrewAI框架时,层次化方法需要一个经理单元,负责将任务分解并将单个任务委托给代理人。经理可以是一个代理人,也可以是LLM本身。如果经理是一个代理人,那么您可以根据工作流的需要控制经理如何将任务分解成n级任务。如果经理是LLM,那么它将根据用户的查询使用LLM本身生成的任意计划。使用经理LLM时,您可能能够通过一些提示工程来控制任务分解和委托的方式;然而,它通常不太灵活,适用于更简单的工作流。以下是一个用于旅行规划的HTN式工作流的示例代码:

ini 复制代码
1 flight_specialist = Agent(
2     role='Flight Planning Specialist',
3     goal='Handle all aspects of flight arrangements',
4     backstory='Expert in airline bookings and flight logistics.')
5
6 accommodation_specialist = Agent(
7     role='Accommodation Specialist',
8     goal='Manage all accommodation-related planning',
9     backstory='Expert in hotel and accommodation booking')
10
11 activity_specialist = Agent(
12     role='Vacation Activity Specialist',
13     goal='Manage all activity-related planning',
14     backstory="Expert in recreational activity arrangements.",)
15
16 manager_llm = ChatOpenAI(model="gpt-4o-mini")
17 travel_planning_task = Task(
18     description=f"""
19       Plan a comprehensive flight itinerary based on the
20       following request:
21       {request}
22       The plan should include: Flight arrangements,
23       Accommodation bookings, other relevant travel
24         components
25       """,
26     expected_output="A detailed flight itinerary covering all requested aspects.",
27     agent=None) #No agent; the manager will delegate subtasks
28
29 crew = Crew(
30           agents=[self.flight_specialist,
31                   self.accommodation_specialist,
32                   self.activity_specialist],
33           tasks=[travel_planning_task],
34           process=Process.hierarchical,
35           manager_llm=self.manager_llm,)
36      return crew.kickoff()

此执行的输出可能如下所示(为了简洁起见,输出已被截断):

最终旅行计划: 这是从纽约到巴黎的五天行程的完整旅行行程:

巴黎之行旅行行程 从纽约(JFK)到巴黎(CDG) 旅行者:2名成人,时长:5天

  1. 航班:
  • 出发航班:...
  • 总航班费用:$2,960

  1. 酒店住宿:
  • 酒店:...
  • 预计总费用 = €800。

  1. 机场接送:
  • 选项1:...
  • 选项2:...

  1. 凡尔赛一日游:
  • 交通:往返于RER C火车从...

    • 费用:大约...
    • 出发时间:上午9:00从...
    • 返回时间:下午5:00从凡尔赛。
      ...
    • 一日游总费用:大约€364.20。

总估计费用:

  • 航班:$2,960
  • 住宿:€800(在Le Fabe酒店)
  • 机场接送:€100(可能有所不同)
  • 凡尔赛一日游:大约€364.20
  • 根据需要转换总费用为美元。
    ...

请注意,在此示例中,代理系统无法访问外部工具或查询,因此它生成的任何响应都是完全虚构和非事实的。这突出了工具的重要性,我们将在下一节中探讨这一点。目前,前面的示例展示了如何使用框架进行任务分解,并让经理管理多个代理来执行从用户请求中分解的简化任务。您可以在GitHub仓库中的Python笔记本(Chapter_05.ipynb)中查看完整代码。

HTN规划提供了几个显著的优点,使其在复杂的规划场景中特别有效。其自然的问题表示方式反映了人类的思维模式,易于理解和维护。层次化的方法通过将复杂问题分解为可管理的子任务,从而实现更好的可扩展性,有效减少了搜索空间。HTN的结构通过其任务层次实现了专家知识的编码,允许在类似问题之间重用模式。此外,它在处理抽象任务和基础任务时的灵活性使其能够适应各种规划情况,允许规划者根据需要在不同抽象层次上工作。

到目前为止,我们已经了解了工具和几种规划算法,但它们可以使LLM代理通过结合战略规划和有效的工具使用,执行更复杂的多步骤任务。让我们进一步探讨如何在代理系统中有效地将工具使用与规划集成。

工具使用与规划的集成

早期的人工智能规划和工具使用大多是孤立进行的,重点关注规划算法或工具能力,而没有将这两个方面有效地结合起来。然而,为了实现真正智能的代理,必须有效地将工具使用与规划结合起来。正如我们在上一节所看到的,我们的旅行规划器给出了详细的旅行计划,但其中没有任何细节是真实的------也就是说,它包含了LLM随意编造的信息。为了使我们的系统包含实际的航班、酒店和活动数据,从而使旅行计划与事实相符,我们需要在规划算法的基础上结合工具使用。本节将讨论如何将这两个方面结合起来,以生成相关的响应并准确完成任务。

关于工具的推理

代理需要能够推理其可用工具的能力,理解每个工具的功能、能力和局限性,以及它们可以在什么样的上下文和条件下有效地应用。推理过程包括基于当前目标和任务评估可用的工具,然后选择最适合在给定情境或问题领域中应用的工具。

例如,在我们的旅行规划器案例中,代理将能够访问各种工具,如航班预订API、酒店预订系统和活动规划软件。代理需要推理每个工具的能力,例如,哪些工具可以用于预订航班或酒店,哪些工具可以提供有关当地景点的信息。

当与LLM代理合作时,关于工具的推理主要由语言模型的固有能力处理。现代LLM经过训练,可以理解工具的描述、目的和适用的使用场景。这意味着我们不需要显式编程复杂的推理机制------相反,我们提供清晰的工具描述,让LLM决定何时以及如何使用这些工具。例如,让我们来看一下我们的旅行规划代理场景:

ini 复制代码
1 from crewai import Agent
2
3 travel_agent = Agent(
4   role='Travel Planner',
5   goal='Plan comprehensive travel itineraries',
6   tools=[
7       flight_search_tool,    # 用于查找和预订航班的工具
8       hotel_booking_tool,    # 用于酒店预订的工具
9       activity_planner_tool  # 用于规划当地活动和景点的工具
10    ])

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如何通过基于工具的推理来实现这一点。该框架的Python库提供了一个@tool装饰器,允许我们定义具有明确描述和文档的工具。以下是如何创建一组与旅行相关的工具:

python 复制代码
1 @tool("Search for available flights between cities")
2 def search_flights(...) -> dict:
3    """Search for available flights between cities."""
4    # Call flight API and other tool logic
5
6 @tool("Find available hotels in a location")
7 def find_hotels(...) -> dict:
8   """Search for available hotels in a location."""
9   # Call hotels API and other tool logic
10
11 @tool("Find available activities in a location")
12 def find_activities(...) -> dict:
13   """Find available activities in a location."""
14  # Call activities API and other tool logic

然后,将这些工具分配给一个代理,代理了解如何根据上下文使用它们。代理是以特定角色、目标和背景故事创建的,这有助于指导其决策:

ini 复制代码
1 Agent(
2    role='An expert travel concierge',
3    goal='Handle all aspects of travel planning',
4    backstory="Expert in airline bookings and flight logistics, hotel bookings, and booking vacation activities.",
5    tools=[search_flights, find_hotels, find_activities],
6    verbose=False
7 )

当给定任务时,代理根据上下文和需求使用这些工具:

ini 复制代码
1 travel_planning_task = Task(
2    description=f"""
3    Plan a comprehensive travel and leisure itinerary
4    based on the following request:
5    {request}
6    The plan should include:
7    - Flight arrangements
8    - Accommodation bookings
9    - Any other relevant travel components
10   """,
11   expected_output="A detailed travel itinerary covering all requested aspects.",
12   agent=self.travel_specialist )

当调用crew.kickoff()时,CrewAI以以下方式协调工具的使用:

  • 通过任务描述理解任务要求
  • 根据代理的角色和任务目标确定需要哪些工具
  • 以逻辑顺序使用工具来构建旅行计划
  • 处理工具输出并将其整合到最终的响应中

这个实现展示了CrewAI如何将工具定义、代理能力和任务规格结合起来,创建一个连贯的规划系统。该框架处理了工具推理的复杂性,同时允许开发人员专注于定义清晰的工具接口和代理行为。

AutoGen 示例

AutoGen 提供了一个开发 AI 代理的平台,这些代理可以进行对话,并通过这些互动为给定任务得出解决方案。AutoGen 通过 RoundRobinGroupChat 系统实现多代理协作,其中专业化的代理互相合作,以创建一个全面的旅行计划。该实现定义了四个关键代理:航班规划员、酒店规划员、活动规划员和总结代理,每个代理都有特定的职责和工具。

每个代理的初始化包括以下内容:

  • 名称和描述
  • 模型客户端(在这个例子中是 OpenAI 的 GPT-4o-mini)
  • 它们可以访问的特定工具
  • 定义它们角色和职责的系统消息

与 CrewAI 的主要区别在于执行模型:

  • 代理通信:虽然 CrewAI 使用基于任务的层次化方法,但 AutoGen 实现了一个轮流交替的组聊(round-robin group chat),代理们轮流贡献解决方案。RoundRobinGroupChat 类负责协调这一对话流程,允许代理在彼此的建议上进行拓展。

  • 终止处理:AutoGen 通过 TextMentionTermination 类使用显式的终止条件。当完整计划准备好时,旅行总结代理可以通过提到"TERMINATE"来结束对话。这与 CrewAI 的基于任务完成的终止方式不同。以下是 TextMentionTermination 的参数:

    • mention_text(str) :触发终止的关键字或短语(例如,"TERMINATE")
    • case_sensitive (bool, optional) :是否区分大小写
    • strip_whitespace (bool, optional) :是否忽略检测文本中的前导/尾随空格
    • regex_match (bool, optional) :是否使用正则表达式进行更灵活的终止触发
  • 工具集成:与 CrewAI 使用装饰器定义工具不同,AutoGen 在初始化过程中直接将工具与代理关联。每个代理可以访问与其角色相关的特定工具。

  • 协作模式:虽然 CrewAI 通常使用管理者-工作者模式,AutoGen 的轮流方式创造了一个更具协作性的环境,在这个环境中,代理平等地贡献解决方案,总结代理负责创建最终的集成计划。

该实现展示了 AutoGen 在处理复杂的多代理对话方面的优势,同时保持了明确的角色分离和每个代理的专业工具使用。以下是如何在 AutoGen 中定义代理:

ini 复制代码
1 flight_agent = AssistantAgent(
2    name="flight_planner",
3    model_client=model_client,
4    tools=[travel_tools.search_flights],
5    description="A helpful assistant that can plan flights itinerary for vacation trips.",
6    system_message="You are a helpful assistant that can plan flight itinerary for a travel plan for a user based on their request." )
7
8 hotel_agent = AssistantAgent(
9    name="hotel_planner",
10   model_client=model_client,
11   tools=[travel_tools.search_flights],
12   description="...", system_message="..." )

一旦代理被定义,可以使用这些代理定义一个 RoundRobinGroupChat 类,并启动多代理系统的对话:

ini 复制代码
2 group_chat = RoundRobinGroupChat(
3     [flight_agent, hotel_agent],
4     termination_condition=termination)
6  await Console(group_chat.run_stream(task="I need to plan a trip to Paris from New York for 5 days."))

LangGraph 示例

LangChain 提供了一个框架,用于开发可以利用大语言模型(LLM)和其他工具及数据源的应用。在代理系统的上下文中,LangChain 提供了一个名为 LangGraph 的子框架,用于构建强大的基于 LLM 代理的工作流。LangGraph 通过工作流图系统处理基于代理的旅行规划,提供了一种不同于 CrewAI 和 AutoGen 的新范式。让我们来看看这个实现是如何工作的,以及它的独特特点。

LangGraph 使用状态机方法,其中工作流被定义为具有节点和边的图。该实现围绕两个主要节点展开:

  • 代理节点:处理消息并做出决策
  • 工具节点:执行请求的工具(如航班搜索、酒店预订和活动规划)

工作流遵循一个循环,代理节点评估当前状态,决定是调用工具还是提供最终响应。这是通过一个函数来控制的,该函数解释模型的下一步操作(即调用工具或结束响应),决定是否路由到工具节点或结束对话。与 CrewAI 类似,LangGraph 也使用了 Python 中的 @tool 装饰器,通过该装饰器定义工具函数:

python 复制代码
1 @tool
2 def search_flights(...) -> dict:
3    """Search for available flights between cities."""
4   # Emulate JSON data from an API
5   return data

一旦定义了节点(可以有工具,也可以没有工具),它们就可以互相连接,构建工作流的完整图结构。例如,在我们的案例中,以下代码定义了基于状态图的工作流,在该工作流中,任务在两个节点之间循环:代理和工具。图从代理节点(定义为入口点)开始,代理调用一个函数(call_model)来处理输入。代理运行后,一个条件函数(should_continue)决定下一个节点------要么回到工具节点,要么结束工作流。工具节点(tool_node)处理中间任务,并始终回到代理节点,形成一个重复的循环,直到条件函数决定停止。MemorySaver 检查点用于在运行间保存状态,图被编译为 LangChain 兼容的可执行文件。最后,通过关于规划旅行的初始输入消息来调用工作流,并在图执行结束后打印最终的消息内容:

ini 复制代码
1 workflow = StateGraph(MessagesState)
2 workflow.add_node("agent", call_model)
3 workflow.add_node("tools", tool_node)
4 workflow.add_edge(START, "agent")
5 workflow.add_conditional_edges("agent", should_continue)
6 workflow.add_edge("tools", 'agent')
7 checkpointer = MemorySaver()
8 app = workflow.compile(checkpointer=checkpointer)
9 final_state = app.invoke(
10   {"messages": [HumanMessage(content="I need to plan a trip to Paris from New York for 5 days")]},
11   config={"configurable": {"thread_id": 42}})

LangGraph 的方法提供了几个显著的优势。例如,它的图结构提供了显式的流程控制,使得工作流易于可视化和理解,同时内置的状态管理和检查点功能确保了应用状态的稳健处理。然而,这些优势也伴随着一些权衡。该框架需要对基于图的编程概念有较为深刻的理解,并且相比于 CrewAI 更直接的代理定义,初始设置会涉及更多的开销。完整的代码实现可以在 GitHub 仓库中的 Chapter_05.ipynb Python 笔记本中找到。

表 5.1 展示了 LangGraph、CrewAI 和 AutoGen 之间的一些关键区别:

LangGraph CrewAI AutoGen
状态管理 使用显式的状态管理 通过代理实例及其任务上下文管理状态
工具集成 通过专门的工具节点管理工具 使用装饰器定义工具,并与代理直接关联
流程控制 使用基于图的工作流 使用层次化任务分解或顺序流程

表 5.1 --- LangGraph、CrewAI 和 AutoGen 实现方法对比

前表展示了基于我们实现的 LangGraph、CrewAI 和 AutoGen 之间的区别。

总结

在本章中,我们了解了工具和规划在AI代理系统中的关键作用。我们讨论了什么是工具/函数调用,以及LLM代理如何展现这一特性。我们还学习了各种工具类型,并通过示例展示了如何在框架中或与LLM本地结合使用工具。随后,我们探讨了各种规划算法,从传统的方法如STRIPS和HTN到现代的基于LLM的规划方法,理解了它们在语言模型背景下的相对实用性。通过一个实际的旅行规划示例,我们看到如何在每个框架中定义、集成和利用工具,创建复杂的规划系统。

我们学习了如何将工具调用与规划相结合,从而增强代理系统的能力,使其能够处理复杂的任务。我们还回顾了三个框架(CrewAI、AutoGen和LangGraph)的实现模式,揭示了它们在代理协调和工具使用上的不同方法。

在下一章中,我们将深入探讨代理系统中的协调者、工作者和委托者方法的概念,并学习它们如何帮助完成复杂的现实世界任务。

相关推荐
何双新2 小时前
第1讲:Transformers 的崛起:从RNN到Self-Attention
人工智能·rnn·深度学习
AIGC大时代2 小时前
高质量学术引言如何妙用ChatGPT?如何写提示词
人工智能·深度学习·chatgpt·学术写作·chatgpt-o3·deep reaserch
RUNTIME4 小时前
大模型微调实操记录
llm
数据智能老司机4 小时前
构建具备自主性的人工智能系统——探索协调者、工作者和委托者方法
深度学习·llm·aigc
2301_769624404 小时前
基于Pytorch的深度学习-第二章
人工智能·pytorch·深度学习
-一杯为品-5 小时前
【深度学习】#9 现代循环神经网络
人工智能·rnn·深度学习
硅谷秋水5 小时前
ORION:通过视觉-语言指令动作生成的一个整体端到端自动驾驶框架
人工智能·深度学习·机器学习·计算机视觉·语言模型·自动驾驶
亿牛云爬虫专家6 小时前
深度学习在DOM解析中的应用:自动识别页面关键内容区块
深度学习·爬虫代理·dom·性能·代理ip·内容区块·东方财富吧