AI Agents实战——构建自治助手

296 阅读41分钟

本章内容:

  • 用于机器人和AI应用的行为树
  • GPT助手游乐场和创建助手及动作
  • 代理行为树的自治控制
  • 通过代理行为树模拟对话式多代理系统
  • 使用反向推理创建复杂系统的行为树

现在我们已经讨论了如何通过动作扩展代理的能力,我们可以看看行为树如何引导代理系统。我们将从理解行为树的基础知识开始,了解它们如何控制机器人和游戏中的AI。

接下来,我们将回到代理动作,并研究如何在OpenAI助手平台上使用GPT助手游乐场项目实现动作。从这里开始,我们将看看如何使用OpenAI助手构建一个自治的代理行为树(ABT)。然后,我们将继续了解对自治代理的控制和保护措施的需求,以及如何使用控制屏障函数。

在本章的最后部分,我们将研究如何使用AgentOps平台监控我们的自治行为驱动代理系统。这将是一个充满挑战的激动人心的章节。让我们从介绍行为树的下一节开始。

6.1 介绍行为树

行为树是用于控制机器人和游戏中AI的长期存在的模式。Rodney A. Brooks在1986年通过他的论文《移动机器人强健的分层控制系统》中首次提出了这一概念。这为使用树和节点结构的模式奠定了基础,这种结构在今天得到了扩展。

如果你曾玩过带有非玩家角色(NPC)的电脑游戏或与先进的机器人系统互动过,那么你已经亲眼见证了行为树的实际应用。图6.1展示了一个简单的行为树。该树代表了所有主要节点:选择器或回退节点、序列节点、动作节点和条件节点。

image.png

表6.1描述了我们将在本书中探讨的主要节点的功能和目的。还有其他节点和节点类型,甚至可以创建自定义节点,但目前我们将重点讨论表中的节点。

表6.1 行为树中使用的主要节点
节点目的功能类型
选择器(回退)该节点通过选择第一个成功完成的子节点来工作。它通常被称为回退节点,因为它始终会回退到最后一个成功执行的节点。节点按顺序调用其子节点,当第一个子节点成功时停止执行。如果没有子节点成功,则返回失败。组合节点
序列该节点按顺序执行所有子节点,直到一个节点失败或它们全部成功完成。节点按顺序调用每个子节点,无论它们是成功还是失败。如果所有子节点成功,则返回成功;如果仅有一个子节点失败,则返回失败。组合节点
条件行为树不使用布尔逻辑,而是使用成功或失败作为控制手段。如果条件为真,节点返回成功,否则返回失败。节点根据条件返回成功或失败。任务节点
动作这是执行操作的地方。节点执行并返回成功(如果成功)或返回失败(否则)。任务节点
装饰器它们通过控制子节点的执行来工作。通常称为条件节点,因为它们可以决定是否执行节点或是否安全执行节点。节点控制子节点的执行。装饰器可以作为控制屏障函数,阻止或防止不需要的行为。装饰器节点
并行该节点并行执行其所有子节点。成功或失败的控制由成功所需的子节点数量阈值决定。节点按顺序执行其所有子节点,无论节点的状态如何。组合节点

表6.1中的主要节点提供了足够的功能来处理多个用例。然而,最初理解行为树可能令人畏惧。只有在开始使用它们之后,您才会真正理解它们的底层复杂性。在我们构建一些简单的树之前,我们想在下一节中更详细地了解行为树的执行。

6.1.1 理解行为树的执行

理解行为树是如何执行的,对于设计和实现行为树至关重要。与计算机科学中的大多数概念不同,行为树以成功和失败的方式进行操作。当行为树中的一个节点执行时,它将返回成功或失败;这甚至适用于条件节点和选择器节点。

行为树的执行顺序是从上到下、从左到右。图6.2展示了这个过程以及节点失败或成功时的情况。在这个示例中,行为树控制的AI有一个苹果,但没有梨。在第一个序列节点中,一个条件检查AI是否有苹果。由于AI没有苹果,它会中止序列并回退到选择器。选择器然后选择下一个子节点,一个新的序列,检查AI是否有梨,结果是AI有梨,于是AI吃了苹果。

image.png

行为树提供对AI系统在宏观或微观层面的执行控制。关于机器人技术,行为树通常设计为在微观层面上操作,每个动作或条件是一个小事件,例如检测苹果。相反,行为树也可以控制更宏观的系统,例如游戏中的NPC,其中每个动作可能是多个事件的组合,例如攻击玩家。

对于代理系统,行为树支持在您选择的层次上控制代理或助手。我们将探讨如何在任务层面以及后续章节中的规划层面控制代理。毕竟,借助LLM的强大能力,代理可以构建自己的行为树。

当然,还有其他几种AI控制形式可以用于控制代理系统。下一节将探讨这些不同的系统,并将它们与行为树进行比较。

6.1.2 决定使用行为树

许多其他AI控制系统具有优势,并且在控制代理系统方面值得探索。它们可以展示行为树的优点,并为特定用例提供其他选择。行为树是一个优秀的模式,但它并不是唯一的,值得学习其他模式。

表6.2 其他AI控制系统的比较
控制名称描述缺点控制代理AI?
有限状态机(FSM)FSM使用一组状态和通过事件或条件触发的转换来建模AI。随着复杂性增加,FSM可能变得笨重。不适用于代理,因为扩展性差。
决策树决策树使用决策及其可能后果的树状模型。在复杂场景中,决策树可能出现过拟合,缺乏泛化能力。可以与行为树结合改进。
基于效用的系统效用函数根据当前情况评估并选择最佳行动。这些系统需要仔细设计效用函数以平衡优先级。可以在行为树中采用该模式。
基于规则的系统通过一组if-then规则定义AI的行为。这些系统在规则过多时可能变得繁琐,导致潜在冲突。与LLM驱动的代理系统不太实用。
规划系统规划系统使用规划算法生成一系列动作以实现特定目标。这些系统计算开销大,并且需要大量的领域知识。代理可以自行实现这种模式。
行为克隆行为克隆是通过模仿专家演示来学习策略。该系统可能在面对未见过的情况时难以泛化。可以集成到行为树或特定任务中。
层次任务网络(HTN)HTN将任务分解为较小的、可管理的子任务,按层次排列。对于非常大的任务,管理和设计起来很复杂。对于较大的代理系统有更好的组织和执行能力。
黑板系统这些系统通过不同子系统共享黑板进行协作式问题解决。这些系统很难实现,并且需要管理子系统之间的通信。代理系统可以使用类似的模式,如对话或群组聊天/线程。
遗传算法(GA)这些优化技术受到自然选择的启发,用于进化解决问题的方案。GA计算开销大,并且可能无法始终找到最优解。GA有潜力,甚至可以用来优化行为树。

注:

  • a 不适用于复杂代理系统
  • b 存在于行为树中或可以轻松集成
  • c 通常应用于任务或动作/条件层面
  • d 高级系统,应用于代理时需要大量工作

在本书的后续章节中,我们将探讨表6.2中讨论的某些模式。总体而言,许多模式可以使用行为树作为基础进行增强或集成。虽然其他模式(如FSM)可能对小规模实验有帮助,但它们没有行为树的可扩展性。

行为树作为AI控制系统提供了几个优点,其中包括可扩展性。以下是使用行为树的其他显著优点:

  • 模块化和可重用性 — 行为树促进了设计行为的模块化方法,允许开发者创建可重用的组件。行为树中的节点可以在树的不同部分或不同项目中轻松重用,从而提高了可维护性并减少了开发时间。
  • 可扩展性 — 随着系统复杂性的增加,行为树比其他方法(如FSM)更优雅地处理新增行为。行为树允许任务的层次化组织,使得管理和理解庞大的行为集变得更加容易。
  • 灵活性和可扩展性 — 行为树提供了一个灵活的框架,可以在不显著改变现有结构的情况下添加新节点(动作、条件、装饰器)。这种可扩展性使得引入新行为或修改现有行为以适应新需求变得简单。
  • 调试和可视化 — 行为树提供了清晰直观的行为可视化表示,这对于调试和理解决策过程非常有帮助。支持行为树的工具通常包括图形编辑器,允许开发人员可视化和调试树结构,使得识别和修复问题变得更容易。
  • 决策逻辑的解耦 — 行为树将决策逻辑和执行逻辑分离,促进了高层策略与低层行动之间的明确区分。这种解耦简化了设计,并允许在不影响整个系统的情况下对特定行为部分进行修改和测试。

通过为行为树提供有力的支持,我们现在应考虑如何在代码中实现它们。在下一节中,我们将探讨如何使用Python代码构建一个简单的行为树。

6.1.3 使用Python和py_trees运行行为树

由于行为树已经存在了很长时间,并且被广泛应用于许多技术中,因此创建一个示例演示非常简单。当然,最简单的方法是询问ChatGPT或你最喜欢的AI聊天工具。列表6.1展示了使用提示生成代码示例,并提交图6.1作为示例树的结果。最终的代码需要修正一些简单的命名和参数错误。

注意:本章的所有代码可以通过下载GPT Assistants Playground项目来获取。

列表6.1 first_btree.py
import py_trees

class HasApple(py_trees.behaviour.Behaviour):      #1
    def __init__(self, name):
        super(HasApple, self).__init__(name)

    def update(self):        
        if True:  
            return py_trees.common.Status.SUCCESS
        else:
            return py_trees.common.Status.FAILURE
# 其他类省略…

has_apple = HasApple(name="Has apple")      #2
eat_apple = EatApple(name="Eat apple")      #2
sequence_1 = py_trees.composites.Sequence(name="Sequence 1", memory=True)
sequence_1.add_children([has_apple, eat_apple])                              #3

has_pear = HasPear(name="Has pear")         #4
eat_pear = EatPear(name="Eat pear")         #4
sequence_2 = py_trees.composites.Sequence(name="Sequence 2", memory=True)
sequence_2.add_children([has_pear, eat_pear])               #3                

root = py_trees.composites.Selector(name="Selector", memory=True)
root.add_children([sequence_1, sequence_2])          #3                       

behavior_tree = py_trees.trees.BehaviourTree(root)     #5

py_trees.logging.level = py_trees.logging.Level.DEBUG   
for i in range(1, 4):                                                      #6
    print("\n------------------ Tick {0} ------------------".format(i))
    behavior_tree.tick()                                                  #6

输出开始

------------------ Tick 1 ------------------
[DEBUG] Selector             : Selector.tick()
[DEBUG] Selector             : Selector.tick() [!RUNNING->reset current_child]
[DEBUG] Sequence 1           : Sequence.tick()
[DEBUG] Has apple            : HasApple.tick()
[DEBUG] Has apple            : HasApple.stop(Status.INVALID->Status.SUCCESS)
[DEBUG] Eat apple            : EatApple.tick()
Eating apple
[DEBUG] Eat apple            : EatApple.stop(Status.INVALID->Status.SUCCESS)
[DEBUG] Sequence 1           : Sequence.stop()[Status.INVALID->Status.SUCCESS]

#1 创建一个类来实现一个动作或条件
#2 创建动作和条件节点
#3 将节点添加到各自的父节点
#4 创建动作和条件节点
#5 创建整个行为树
#6 执行行为树的一步/一次tick

列表6.1中的代码代表了图6.1中的行为树。你可以按照当前的代码运行它,或者改变条件的返回值然后再次运行树。你还可以通过从根选择器中删除其中一个序列节点来改变行为树。

现在我们对行为树有了基本的了解,我们可以继续处理代理/助手。在此之前,我们将看看一个工具,帮助我们与OpenAI助手进行工作。这个工具将帮助我们将第一个ABT包裹到OpenAI助手中。

6.2 探索GPT Assistants Playground

为了本书的开发,创建了多个GitHub项目来解决构建代理和助手的各个方面。其中一个项目,GPT Assistants Playground,是使用Gradio构建的界面,模拟了OpenAI助手游乐场,但添加了多个附加功能。

Playground项目作为教学和演示工具开发。在该项目中,Python代码使用OpenAI助手API来创建聊天界面和代理系统,用于构建和驱动助手。这里还有一个完整的助手动作集合,可以使用,并且您可以轻松添加自己的动作。

6.2.1 安装和运行Playground

以下列表展示了从终端安装和运行Playground项目。目前没有PyPI包可供安装。

列表6.2 安装GPT Assistants Playground
# 切换到工作文件夹并创建一个新的Python虚拟环境
git clone https://github.com/cxbxmxcx/GPTAssistantsPlayground     #1
cd GPTAssistantsPlayground      #2
pip install -r requirements.txt      #3

#1 从GitHub拉取源代码
#2 切换到项目源代码文件夹
#3 安装依赖项

你可以通过终端或使用Visual Studio Code (VS Code) 运行该应用,后者能提供更多的控制。在运行应用之前,你需要通过命令行设置你的OpenAI API密钥,或者创建一个.env文件,就像我们之前做过的那样。列表6.3展示了如何在Linux/Mac或Git Bash(Windows推荐)终端上设置环境变量并运行应用。

列表6.3 运行GPT Assistants Playground
export OPENAI_API_KEY="your-api-key"      #1
python main.py     #2

#1 设置API密钥为环境变量
#2 从终端或通过VS Code运行应用

打开浏览器并访问显示的URL(通常是http://127.0.0.1:7860)或终端中提到的URL。你将看到一个类似于图6.3所示的界面。如果你已经定义了OpenAI助手,你将在“选择助手”下拉菜单中看到它们。

image.png

如果你从未定义过助手,你可以创建一个并选择所需的各种选项和指令。如果你曾访问过OpenAI Playground,你已经体验过类似的界面。

GPT VS.助手

OpenAI将GPT定义为你可以在ChatGPT界面中运行和使用的助手。而助手通常只能通过API来消费,并且在大多数情况下需要自定义代码。当你运行助手时,你会根据模型令牌使用量以及任何特殊工具(包括代码解释器和文件)收费,而GPT则是在ChatGPT中运行,并且由账户费用覆盖。

创建本地版本的Playground的目的是展示代码结构,同时提供以下附加功能:

  • 动作(自定义动作) —创建你自己的动作,允许你为助手添加任何你需要的功能。正如我们所看到的,Playground使得快速创建自定义动作变得非常简单。
  • 代码运行器—API确实提供了代码解释器,但它相对较贵(每次运行$0.03),不允许安装自己的模块,不能交互式运行代码,而且运行较慢。Playground将允许你在隔离的虚拟环境中本地运行Python代码。尽管它没有将代码推送到Docker镜像那样安全,但它在窗口化和独立进程运行方面比其他平台做得更好。
  • 透明度和日志记录—Playground提供了全面的日志记录,并且会显示助手如何使用内部和外部工具/动作。这是查看助手在幕后做了什么的绝佳方式。

接下来的几节内容将更详细地介绍每个功能。我们将从使用和消费动作开始。

6.2.2 使用和构建自定义动作

动作和工具是赋能代理和助手的基础。如果没有访问工具,代理就只是一个没有功能的聊天机器人。OpenAI平台在建立许多工具模式方面处于领先地位,正如我们在第3章中所见。

Playground提供了几个自定义动作,可以通过界面将它们附加到助手上。在接下来的练习中,我们将构建一个简单的助手并附加几个自定义动作,看看可以实现什么。

图6.4展示了扩展的“动作”手风琴,显示了许多可用的自定义动作。从终端或调试器运行Playground,创建一个新的助手。然后,选择图中显示的动作。选择完动作后,向下滚动并点击“添加助手”以添加该助手。必须先创建助手才能使用它。

image.png

在创建助手后,您可以要求它列出所有可用的助手。列出助手还会为您提供调用助手所需的ID。您还可以调用其他助手并要求它们完成其专长领域的任务。

添加自定义动作非常简单,只需将代码添加到文件中并将其放入正确的文件夹中即可。从主项目文件夹打开playground/assistant_actions文件夹,您会看到几个定义各种动作的文件。在VS Code中打开file_actions.py文件,如列表6.4所示。

列表6.4 playground/assistant_actions/file_actions.py
import os

from playground.actions_manager import agent_action

OUTPUT_FOLDER = "assistant_outputs"


@agent_action     #1
def save_file(filename, content):      #2
    """
    Save content to a file.      #3

    :param filename: The name of the file including extension.
    :param content: The content to save in the file.
    """
    file_path = os.path.join(OUTPUT_FOLDER, filename)
    with open(file_path, "w", encoding="utf-8") as file:
        file.write(content)
    print(f"File '{filename}' saved successfully.")      #4

#1 该装饰器会自动将函数作为动作添加。
#2 给函数起一个清晰的名称,符合其功能。
#3 描述是助手用来确定功能的,所以需要做好文档说明。
#4 通常返回一条消息,说明成功或失败。

您可以通过将文件放入assistant_actions文件夹并使用agent_action装饰器来添加任何自定义动作。只需确保给函数命名合理,并为函数的使用编写高质量的文档。当Playground启动时,它会加载文件夹中所有正确装饰并具有描述/文档的动作。

就是这么简单。根据需要,您可以添加多个自定义动作。在下一节中,我们将看看一个特殊的自定义动作,它允许助手在本地运行代码。

6.2.3 安装助手数据库

为了运行本章中的一些示例,您需要安装助手数据库。幸运的是,这可以通过界面轻松完成,只需向代理请求即可。接下来的指令详细说明了安装助手的过程,这些指令直接来自GPT Assistants Playground的README。您可以安装位于assistants.db SQLite数据库中的多个演示助手:

  1. 创建一个新助手,或者使用现有助手。
  2. 给助手分配create_manager_assistant动作(该动作位于“Actions”部分)。
  3. 请求助手创建经理助手(例如,“请创建经理助手”),并确保将助手命名为“Manager Assistant”。
  4. 刷新您的浏览器以重新加载助手选择器。
  5. 选择新的Manager Assistant。这个助手具有可以让它从assistants.db数据库安装助手的指令和动作。
  6. 与Manager Assistant对话,要求它列出要安装的助手,或者直接要求Manager Assistant安装所有可用的助手。

6.2.4 获取助手在本地运行代码

让代理和助手生成并运行可执行代码具有强大的功能。与代码解释器不同,本地运行代码提供了许多快速迭代和调整的机会。我们之前在AutoGen中看到过,代理可以持续运行代码,直到它按预期工作为止。

在Playground中,选择自定义动作run_code非常简单,如图6.5所示。您还需要选择run_shell_command动作,因为它允许助手使用pip install安装任何所需的模块。

image.png

现在,您可以要求助手生成并运行代码,以确保它能够代表您正常工作。通过添加自定义动作并要求助手生成并运行代码,您可以尝试此操作,如图6.6所示。如果代码没有按预期工作,告诉助手您遇到的问题。

image.png

同样,Playground中运行的Python代码会在项目的子文件夹中创建一个新的虚拟环境。如果您没有运行任何操作系统级别的代码或低级代码,这个系统工作得很好。如果您需要更强大的功能,一个不错的选择是AutoGen,它使用Docker容器来运行隔离的代码。

添加运行代码或其他任务的动作可能会让助手变得像一个黑盒子。幸运的是,OpenAI助手API允许您消费事件并查看助手在幕后做了什么。在下一节中,我们将看到这是什么样子。

6.2.5 通过日志调查助手进程

OpenAI在助手API中添加了一个功能,允许您监听事件和通过工具/动作使用链式调用的动作。这个功能已经集成到Playground中,当助手调用另一个助手时,捕捉到动作和工具的使用。

我们可以通过要求助手使用一个工具,然后打开日志来尝试这个功能。一个很好的示例是给助手提供代码解释器工具,然后要求它绘制一个方程。图6.7展示了这个练习的示例。

image.png

通常,当启用助手代码解释器工具时,您不会看到任何代码生成或执行。这个功能允许您查看助手在使用的所有工具和动作,实时捕捉它们的发生。它不仅是一个出色的诊断工具,还为LLM的功能提供了额外的洞察。

我们没有回顾执行所有这些操作的代码,因为它非常庞大,并且可能会经过多次更改。话虽如此,如果您计划使用助手API进行工作,这个项目是一个很好的起点。随着Playground的介绍,我们可以继续进入下一节,探索ABT。

6.3 介绍代理行为树(ABTs)

代理行为树(ABTs)是在助手和代理系统中实现的行为树。常规行为树与ABT之间的主要区别在于,ABT使用提示来指导动作和条件。由于提示可能会返回较高频率的随机结果,我们也可以将这些树称为随机行为树(stochastic behavior trees),这些树是存在的。为了简单起见,我们将用“代理行为树”来区分用于控制代理的行为树。

接下来,我们将进行一个练习来创建一个ABT。完成的树将用Python编写,但需要设置和配置各种助手。我们将讨论如何使用助手来管理助手。

6.3.1 使用助手管理助手

幸运的是,Playground可以帮助我们快速管理和创建助手。我们将首先安装经理助手(Manager Assistant),然后安装预定义的助手。让我们从安装经理助手开始,按照以下步骤进行操作:

  1. 在浏览器中打开Playground,创建一个新的简单助手或使用现有助手。如果需要新的助手,创建它并选择它。
  2. 选择助手后,打开“动作”手风琴,选择create_manager_assistant动作。无需保存;界面会自动更新助手。
  3. 现在,在聊天界面中提示助手以下内容:“请创建经理助手。”
  4. 几秒钟后,助手会说它完成了。刷新您的浏览器,确认现在经理助手可用。如果由于某种原因没有显示新助手,尝试重新启动Gradio应用程序。

经理助手就像一个管理员,拥有访问所有内容的权限。在与经理助手交互时,请确保对您的请求具体明确。在经理助手处于活动状态时,您现在可以使用以下步骤安装书中使用的新助手:

  1. 选择经理助手。如果您修改了经理助手,可以随时删除并重新安装它。尽管可以有多个经理助手,但不推荐这样做。

  2. 询问经理助手可以安装哪些助手,在聊天界面中输入以下内容:

    • 请列出所有可以安装的助手。
  3. 确定您希望安装哪个助手,并请求经理助手安装它:

    • 请安装Python编码助手。

您可以使用Playground管理并安装任何可用的助手。您还可以要求经理助手将所有助手的定义保存为JSON:

  • 请将所有助手保存为JSON文件,名为assistants.json

经理助手可以访问所有动作,应该视为独特的,并且要谨慎使用。在创建助手时,最好使其目标明确,并限制动作仅限于它们所需的内容。这不仅避免了给AI过多的决策权,还能防止由于幻觉导致的事故或错误。

在本章接下来的练习中,您可能需要安装所需的助手。或者,您可以要求经理助手安装所有可用的助手。无论哪种方式,我们将在下一节中探讨如何与助手一起创建一个ABT。

6.3.2 构建一个编码挑战ABT

编码挑战为测试和评估代理和助手系统提供了一个良好的基准。挑战和基准可以量化代理或代理系统的操作效果。在第4章中,我们已经通过AutoGen和CrewAI将编码挑战应用于多平台代理。

对于这个编码挑战,我们将走得更远,查看来自Edabit网站(edabit.com)的Python编码挑战,挑战的复杂度从初学者到专家不等。我们将坚持选择专家级别的编码挑战,因为GPT-4和其他模型在编码方面表现非常出色。请查看下一个列表中的挑战,并思考您将如何解决它。

列表6.5 Edabit挑战:种植草坪
**种植草坪** by AniXDownLoe

您将获得一个代表田地的矩阵 g 和两个坐标 x, y。

矩阵中有三种可能的字符:

-   `x` 代表岩石。
-   `o` 代表泥土区域。
-   `+` 代表草坪区域。

您必须模拟从位置 (x, y) 开始的草生长。草可以向四个方向(上、左、右、下)生长。草只能在泥土区域生长,不能穿过岩石。

返回模拟后的矩阵。

示例

simulate_grass([    "xxxxxxx",    "xooooox",    "xxxxoox",    "xoooxxx",    "xxxxxxx"], 1, 1) → [    "xxxxxxx",    "x+++++x",    "xxxx++x",    "xoooxxx",    "xxxxxxx"]

备注

  • 周围的边缘总是有岩石。

您可以使用任何您喜欢的挑战或编码练习,但这里有几点需要考虑:

  • 挑战应可通过可量化的断言(通过/失败)进行测试。
  • 避免要求构建游戏、网站或使用其他界面时打开窗口。虽然在某些时候测试完整的界面是可能的,但目前它只是文本输出。
  • 避免长时间运行的挑战,至少一开始是如此。首先保持挑战简洁且生命周期短。

每个挑战,您还需要一组测试或断言来确认解决方案是否有效。在Edabit上,挑战通常提供一整套测试。以下列表显示了挑战提供的附加测试。

列表6.6 种植草坪测试
Test.assert_equals(simulate_grass(
    ["xxxxxxx","xooooox","xxxxoox","xoooxxx","xxxxxxx"],
    1, 1), 
    ["xxxxxxx","x+++++x","xxxx++x","xoooxxx","xxxxxxx"])

Test.assert_equals(simulate_grass(
    ["xxxxxxx","xoxooox","xxoooox","xooxxxx",    "xoxooox","xoxooox","xxxxxxx"],
    2, 3), 
    ["xxxxxxx","xox+++x","xx++++x","x++xxxx",    "x+xooox","x+xooox","xxxxxxx"])

Test.assert_equals(simulate_grass(
    ["xxxxxx","xoxoox","xxooox","xoooox","xoooox","xxxxxx"], 
    1, 1), 
    ["xxxxxx","x+xoox","xxooox","xoooox","xoooox","xxxxxx"])

Test.assert_equals(simulate_grass(
    ["xxxxx","xooox","xooox","xooox","xxxxx"], 
    1, 1),
    ["xxxxx","x+++x","x+++x","x+++x","xxxxx"])

Test.assert_equals(simulate_grass(
    ["xxxxxx","xxxxox","xxooox","xoooxx","xooxxx",    "xooxxx","xxooox","xxxoxx","xxxxxx"], 
    4, 1),
    ["xxxxxx","xxxx+x","xx+++x","x+++xx","x++xxx",    "x++xxx","xx+++x","xxx+xx","xxxxxx"])

Test.assert_equals(simulate_grass(
    ["xxxxxxxxxxx", "xoxooooooox", "xoxoxxxxxox",     "xoxoxoooxox", "xoxoxoxoxox", "xoxoxoxoxox",     "xoxoxxxoxox", "xoxoooooxox", "xoxxxxxxxox",     "xooooooooox", "xxxxxxxxxxx"], 1, 1), 
    ["xxxxxxxxxxx", "x+x+++++++x", "x+x+xxxxx+x",     "x+x+x+++x+x", "x+x+x+x+x+x", "x+x+x+x+x+x",     "x+x+xxx+x+x", "x+x+++++x+x", "x+xxxxxxx+x",     "x+++++++++x", "xxxxxxxxxxx"])

这些测试将作为两步验证的一部分运行,以确认解决方案是否有效。我们还将按原样使用测试和挑战,这将进一步测试AI。

图6.8展示了一个简单行为树的构成,将用于解决各种编程挑战。您会注意到,这个ABT为动作和条件使用了不同的助手。第一步,Python编码助手(称为黑客)生成一个解决方案,然后由编码挑战评审员(称为评审员)进行审核,产生一个经过精炼的解决方案,最后由另一个Python编码助手(称为验证员)进行验证。

image.png

图6.8还展示了每个代理在不同线程中的对话方式。助手使用消息线程,类似于Slack或Discord频道,其中所有在同一线程中对话的助手将看到所有消息。对于这个ABT,我们为黑客和评审员保留了一个主要的对话线程来共享消息,而验证员则在一个单独的消息线程中工作。将验证员保持在独立的线程中,可以将其与解决方案处理过程中的噪音隔离开来。

现在,在代码中构建ABT的关键是将py_trees包和Playground API函数结合起来。列表6.7展示了一个代码片段,它创建了每个动作/条件节点,并为它们分配了助手及指令。

列表6.7 agentic_btree_coding_challenge.py
root = py_trees.composites.Sequence("RootSequence", memory=True)

thread = api.create_thread()     #1
challenge = textwrap.dedent("""
 #2
""")
judge_test_cases = textwrap.dedent("""
 #3
""")

hacker = create_assistant_action_on_thread(   
    thread=thread,      #4
    action_name="Hacker",
    assistant_name="Python Coding Assistant",
    assistant_instructions=textwrap.dedent(f"""
    Challenge goal: 
    {challenge}      #5
    Solve the challenge and output the 
final solution to a file called solution.py        
    """),
)
root.add_child(hacker)

judge = create_assistant_action_on_thread(    
    thread=thread,      #6
    action_name="Judge solution",
    assistant_name="Coding Challenge Judge",
    assistant_instructions=textwrap.dedent(
        f"""
    Challenge goal: 
    {challenge}      #7
    Load the solution from the file solution.py.
    Then confirm is a solution to the challenge 
and test it with the following test cases:
    {judge_test_cases}      #8
    Run the code for the solution and confirm it passes all the test cases.
    If the solution passes all tests save the solution to a file called 
judged_solution.py
    """,
    ),
)
root.add_child(judge)

# verifier operates on a different thread, essentially in closed room
verifier = create_assistant_condition(     #9
    condition_name="Verify solution",
    assistant_name="Python Coding Assistant",
    assistant_instructions=textwrap.dedent(
        f"""
    Challenge goal: 
    {challenge}      #10
    Load the file called judged_solution.py and 
verify that the solution is correct by running the code and confirm it passes 
all the test cases:
    {judge_test_cases}      #11
    If the solution is correct, return only the single word SUCCESS, otherwise 
return the single word FAILURE.
    """,
    ),
)
root.add_child(verifier)

tree = py_trees.trees.BehaviourTree(root)

while True:
    tree.tick()
    time.sleep(20)      #12
    if root.status == py_trees.common.Status.SUCCESS:    #13
        break

代码说明:

#1 创建一个消息线程,黑客和评审员共享
#2 挑战的内容,如列表6.5中所示
#3 测试用例,如列表6.6中所示
#4 创建一个消息线程,黑客和评审员共享
#5 挑战目标,如列表6.5中所示
#6 创建一个消息线程,黑客和评审员共享
#7 挑战目标,如列表6.5中所示
#8 测试用例,如列表6.6中所示
#9 创建一个新的消息线程
#10 挑战目标,如列表6.5中所示
#11 测试用例,如列表6.6中所示
#12 可根据需要调整睡眠时间,以限制向LLM发送的消息
#13 该过程将继续,直到验证成功为止

通过在VS Code中加载文件或使用命令行运行ABT。查看终端中的输出,观察助手如何逐步处理树中的每个步骤。

如果解决方案在条件节点处无法验证,流程将继续按树的结构进行。即使是这个简单的解决方案,您也可以快速创建多个变体。例如,您可以扩展树,增加更多的节点/步骤和子树。或许您想要一组黑客来分解并分析挑战。

这个示例的工作主要通过Playground代码完成,使用了create_assistant_conditioncreate_assistant_action_on_thread等辅助函数。该代码使用几个类将py_trees行为树代码和Playground中封装的OpenAI助手代码集成。如果您希望了解更低级的细节,可以查看项目中的代码。

6.3.3 会话式AI系统与其他方法的比较

我们在第4章中已经探讨了会话式多代理系统,特别是在讨论AutoGen时。ABT可以通过会话(跨线程)与其他方法的结合来工作,例如文件共享。让助手/代理传递文件有助于减少嘈杂和重复的想法/对话。相比之下,会话系统则能从潜在的涌现行为中受益。因此,结合使用两者可以帮助演化出更好的控制和解决方案。

列表6.7中的简单解决方案可以扩展以处理更多现实世界的编码挑战,甚至可能作为一个编码ABT工作。在下一节中,我们将构建一个不同的ABT来处理另一个问题。

6.3.4 将YouTube视频发布到X(原Twitter)

在本节的练习中,我们来看一个ABT,它能够执行以下操作:

  • 根据给定的主题在YouTube上搜索视频,并返回最新的视频。
  • 下载所有搜索结果的视频的转录文本。
  • 总结这些转录文本。
  • 审查总结后的转录文本,并选择一个视频来写关于它的X(前Twitter)帖子。
  • 写一个激动人心且引人入胜的帖子,确保帖子不超过280个字符。
  • 审查帖子,然后将其发布到X。

图6.9展示了与每个不同助手组成的ABT。在这个练习中,我们使用一个序列节点作为根节点,每个助手执行不同的动作。同时,为了保持简单,每个助手的互动将总是发生在一个新的线程中。这将助手的每次互动隔离成一个简洁的对话,如果出现问题,这样更容易进行调试。

image.png

6.3.5 所需的X设置

如果您计划运行本节中的代码,您必须将X凭证添加到.env文件中。.env.default文件展示了凭证的配置示例,如列表6.8所示。您不必输入您的凭证。这意味着最后的步骤——发布,将会失败,但您仍然可以查看文件(youtube_twitter_post.txt),看看生成的内容。

列表6.8 配置凭证
X_EMAIL = "twitter email here"
X_USERNAME = "twitter username here"
X_PASSWORD = "twitter password here"

YouTube 搜索与垃圾邮件

如果您计划真正运行这个练习并让它发布到您的X账户,请注意,YouTube存在一些垃圾邮件问题。助手已经配置为尽量避免视频垃圾邮件,但有些垃圾邮件可能会穿透。构建一个能够在浏览视频时避免垃圾邮件的有效ABT具有一些合适的应用。

列表6.9 只包含创建助手动作的代码。这个ABT使用了三个不同的助手,每个助手都有自己的任务指令。请注意,每个助手都有一组独特的指令来定义它的角色。您可以通过使用Playground查看每个助手的指令。

列表6.9 agentic_btree_video_poster_v1.py
root = py_trees.composites.Sequence("RootSequence", memory=True)

search_term = "GPT Agents"
search_youtube_action = create_assistant_action(
    action_name=f"Search YouTube({search_term})",
    assistant_name="YouTube Researcher v2",
    assistant_instructions=f"""
    Search Term: {search_term}
    Use the query "{search_term}" to search for videos on YouTube.
    then for each video download the transcript and summarize it 
for relevance to {search_term}
    be sure to include a link to each of the videos,
    and then save all summarizations to a file called youtube_transcripts.txt
    If you encounter any errors, please return just the word FAILURE.
    """,
)
root.add_child(search_youtube_action)

write_post_action = create_assistant_action(
    action_name="Write Post",
    assistant_name="Twitter Post Writer",
    assistant_instructions="""
    Load the file called youtube_transcripts.txt,
    analyze the contents for references to search term at the top and 
then select
    the most exciting and relevant video related to: 
    educational, entertaining, or informative, to post on Twitter.
    Then write a Twitter post that is relevant to the video,
    and include a link to the video, along
    with exciting highlights or mentions, 
    and save it to a file called youtube_twitter_post.txt.
    If you encounter any errors, please return just the word FAILURE.
    """,
)
root.add_child(write_post_action)

post_action = create_assistant_action(
    action_name="Post",
    assistant_name="Social Media Assistant",
    assistant_instructions="""
    Load the file called youtube_twitter_post.txt and post the content 
to Twitter.
    If the content is empty please do not post anything.
    If you encounter any errors, please return just the word FAILURE.
    """,
)
root.add_child(post_action)

### 所需的助手 – YouTube Researcher v2, Twitter Post Writer, 
### 和 Social Media Assistant – 通过Playground安装这些助手

运行代码,如您通常操作的方式,几分钟后,您将在assistants_output文件夹中看到一个新帖子。图6.10展示了使用这个ABT生成的帖子示例。每天生成超过几个帖子可能会导致您的X账户被封禁。如果您已经配置了X凭证,您将在您的动态中看到该帖子。

image.png

这个ABT仅用于演示目的,不适用于生产或长期使用。该演示的主要特点是展示搜索和加载数据、总结和筛选,然后生成新内容,最后突出多个自定义动作和与API的集成。

6.4 构建会话式自主多代理系统

多代理系统的会话方面可以推动反馈、推理和涌现行为等机制。通过ABT驱动助手/代理系统,可以有效地控制结构化过程,就像我们在YouTube发布示例中看到的那样。然而,我们也不想错过代理/助手之间对话带来的好处。

幸运的是,Playground提供了将助手分隔或连接到会话线程的方法。图6.11展示了助手如何在不同的组合中被分隔或混合到多个线程中。将分隔模式与对话结合使用,能够提供两者模式的最佳效果。

image.png

我们将通过一个简单但实际的练习来演示会话模式的有效性。在下一个练习中,我们将使用两个助手在一个ABT中,通过同一个线程进行对话。以下列表展示了树在代码中的构建及其相应的助手。

列表6.10 agentic_conversation_btree.py
root = py_trees.composites.Sequence("RootSequence", memory=True)
bug_file = """
# code not shown
"""

thread = api.create_thread()     #1

debug_code = create_assistant_action_on_thread(     #2
    thread=thread,
    action_name="Debug code",
    assistant_name="Python Debugger",
    assistant_instructions=textwrap.dedent(f"""    
    Here is the code with bugs in it:
    {bug_file}
    Run the code to identify the bugs and fix them. 
    Be sure to test the code to ensure it runs without errors or throws 
any exceptions.
    """),
)
root.add_child(debug_code)

verify = create_assistant_condition_on_thread(     #3
    thread=thread,
    condition_name="Verify",
    assistant_name="Python Coding Assistant",
    assistant_instructions=textwrap.dedent(
        """
    Verify the solution fixes the bug and there are no more issues.
    Verify that no exceptions are thrown when the code is run.
    Reply with SUCCESS if the solution is correct, otherwise return FAILURE.
    If you are happy with the solution, save the code to a file called 
fixed_bug.py.
    """,
    ),
)
root.add_child(verify)
tree = py_trees.trees.BehaviourTree(root)
while True:
    tree.tick()    
    if root.status == py_trees.common.Status.SUCCESS:
        break    #4
    time.sleep(20)

代码说明

#1 创建一个消息线程,供助手共享和进行对话
#2 创建调试代码动作,使用一个特定助手
#3 创建验证条件,测试代码是否修复
#4 该树将继续运行,直到根序列成功完成

树由三个节点组成:根序列、调试代码动作和验证修复条件。因为树的根是一个序列,两个助手将继续一个接一个地工作,直到它们都返回成功。两个助手在同一个线程上对话,但它们的控制方式提供了持续的反馈。

通过在VS Code中加载文件或直接从命令行执行该练习来运行。示例代码有一些小错误,助手将解决这些问题并修复它们。ABT成功运行后,您可以打开assistants_output/fixed_bug.py文件,并验证结果是否正确。

我们已经看到几个ABT的实际应用,并理解了使用隔离或对话的细微差别。下一节将教您一些构建自己ABT的技巧。

6.5 使用回溯链构建ABT

回溯链是一种源自逻辑和推理的方法,帮助通过从目标出发反向构建行为树。本节将使用回溯链过程构建一个ABT,使其实现目标。以下列表详细描述了这一过程:

  1. 确定目标行为:从您希望代理执行的行为开始。
  2. 确定所需的动作:识别出通向目标行为的动作。
  3. 确定条件:确定每个动作成功所需满足的条件。
  4. 确定通信模式:确定助手之间如何传递信息。助手是分隔的,还是通过线程进行对话,或者使用组合模式会更好?
  5. 构建树:从目标行为开始构建行为树,递归地为动作和条件添加节点,直到所有必要的条件与已知状态或事实相关联。

行为树通常使用一种称为黑板的模式进行节点间通信。黑板,如py_trees中的黑板,使用键/值存储来保存信息,并使其可以在节点之间访问。它还提供了多个控制功能,例如限制对特定节点的访问。

我们推迟使用文件进行通信,因为它们简单且透明。最终,代理系统预计将消耗更多信息,并且这些信息的格式将不同于黑板设计的格式。黑板必须变得更加复杂,或者与文件存储解决方案集成。

现在,让我们通过回溯链来构建一个ABT。我们可以处理各种目标,但一个有趣且可能是元目标的任务是构建一个ABT,帮助构建助手。所以,让我们首先把目标陈述为:“创建一个可以帮助我完成{任务}的助手”:

所需的动作:(从后向前)

  • 创建助手。
  • 验证助手。
  • 测试助手。
  • 给助手命名。
  • 给助手相关的指令。

确定条件:

  • 验证助手。

确定通信模式:

为了保持趣味性,我们将让所有助手在同一个消息线程上运行。

构建树:

构建树时,让我们首先反向排列动作的顺序,并根据每个元素的动作和条件进行标记:

  • (动作) 给助手相关的指令,以帮助用户完成特定任务。
  • (动作) 给助手命名。
  • (动作) 测试助手。
  • (条件) 验证助手。
  • (动作) 创建助手。

当然,构建树的简单方法是询问ChatGPT或其他有能力的模型。让ChatGPT构建树的结果如下所示。您也可以独立构建树,并可能引入其他元素。

列表6.11 构建助手的ABT
Root

├── Sequence
    ├── Action: 给助手相关的指令,以帮助用户完成特定任务
    ├── Action: 给助手命名
    ├── Action: 测试助手
    ├── Condition: 验证助手
    └── Action: 创建助手

从这个点开始,我们可以通过迭代每个动作和条件节点来构建树,并确定助手需要什么指令。这还可以包括任何工具和自定义动作,包括您可能需要开发的动作。在第一次构建时,保持指令通用是理想的。理想情况下,我们希望创建尽可能少的助手。

在确定了每个助手所需的助手、工具和动作后,您可以进一步尝试将其通用化。考虑是否可以结合一些动作,减少助手的数量。最好从评估不足的助手开始,而不是过多的助手。然而,请确保保持适当的工作划分:例如,测试和验证最好由不同的助手来执行。

6.6 练习

完成以下练习,以提高您对材料的理解:

练习1—创建旅行规划ABT

目标:构建一个代理行为树(ABT)来使用助手规划旅行行程。 任务

  • 在本地机器上设置GPT Assistants Playground。

  • 创建一个ABT来规划旅行行程。树应具有以下结构:

    • 动作:使用旅行助手收集潜在目的地的信息。
    • 动作:使用行程规划助手创建逐日旅行计划。
    • 条件:使用另一个旅行助手验证行程的完整性和可行性。
  • 实现并运行ABT,以创建完整的旅行行程。

练习2—构建客户支持自动化ABT

目标:创建一个ABT,使用助手自动化客户支持响应。 任务

  • 在本地机器上设置GPT Assistants Playground。

  • 创建一个ABT,具有以下结构:

    • 动作:使用客户查询分析助手对客户查询进行分类。
    • 动作:使用响应生成助手根据查询类别起草回应。
    • 动作:使用客户支持助手将回应发送给客户。
  • 实现并运行ABT,以自动化分析和回应客户查询的过程。

练习3—使用ABT管理库存

目标:学习如何使用ABT创建和管理库存水平。 任务

  • 在本地机器上设置GPT Assistants Playground。

  • 创建一个ABT来管理零售业务的库存:

    • 动作:使用库存检查助手查看当前的库存水平。
    • 动作:使用订单助手为低库存项目下订单。
    • 条件:验证订单是否正确下达,并更新库存记录。
  • 实现并运行ABT,以动态管理库存。

练习4—创建个人健身教练ABT

目标:创建一个ABT,使用助手提供个性化的健身训练计划。 任务

  • 在本地机器上设置GPT Assistants Playground。

  • 创建一个ABT来制定个性化的健身计划:

    • 动作:使用健身评估助手评估用户当前的健身水平。
    • 动作:使用训练计划生成助手根据评估结果创建自定义健身计划。
    • 条件:使用另一个健身助手验证计划的适用性和安全性。
  • 实现并运行ABT,以生成并验证个性化的健身训练计划。

练习5—使用回溯链构建财务顾问ABT

目标:应用回溯链构建一个ABT,为用户提供财务建议和投资策略。 任务

  • 在本地机器上设置GPT Assistants Playground。
  • 定义以下目标:“创建一个能够提供财务建议和投资策略的助手。”
  • 使用回溯链,确定实现此目标所需的动作和条件。
  • 实现并运行ABT,通过回溯链构建树的基础动作和条件,生成一个全面的财务顾问服务。

总结

  • 行为树是一种强大且可扩展的AI控制模式,最早由Rodney A. Brooks在机器人学中引入。由于其模块化和可重用性,它们在游戏和机器人领域得到了广泛应用。

  • 行为树的主要节点包括选择器、序列、条件、动作、装饰器和并行节点。选择器像“或”块;序列按顺序执行节点;条件测试状态;动作执行工作;装饰器是包装器;并行节点允许双重执行。

  • 理解行为树的执行流程对于设计、构建和操作它们至关重要,能够为决策提供清晰的路径。

  • 行为树的优势包括模块化、可扩展性、灵活性、调试方便性以及决策逻辑的解耦,使得行为树非常适合复杂的AI系统。

  • 在Python中设置并运行简单的行为树需要正确命名和文档化自定义节点。

  • GPT Assistants Playground项目是一个基于Gradio的界面,模仿了OpenAI Assistants Playground,并添加了用于教学和展示ABTs的额外功能。

  • GPT Assistants Playground允许创建和管理自定义动作,这是构建多功能助手的关键。

  • ABTs通过使用提示来指导助手的动作和条件,从而控制代理和助手。ABTs利用LLMs的强大能力来创建动态和自主的系统。

  • 回溯链是一种通过从目标行为倒推构建行为树的方法。这个过程包括识别所需的动作、条件和通信模式,然后一步步构建树。

  • 代理系统通过隔离和对话模式来促进实体之间的通信。ABTs可以通过结合隔离和会话助手,利用结构化流程和涌现行为,从而获得两者模式的最佳效果。