AI 智能体实战(二)
ChatGPT 插件首次推出是为了提供具有能力、技能或工具的会话。有了插件,你可以搜索网络或创建电子表格或图表。插件为 ChatGPT 提供了扩展平台的方式。图 5.1 展示了 ChatGPT 插件的工作方式。在这个例子中,ChatGPT 中安装了一个新的电影推荐插件。当用户要求 ChatGPT 推荐一部新电影时,大型语言模型(LLM)识别出它有一个用于管理该动作的插件。然后,它将用户请求分解为可
原文:
zh.annas-archive.org/md5/85b56eb87c9f253ac48b31cb2ceb388a译者:飞龙
第五章:赋能动作代理
本章涵盖
-
代理如何使用动作在自身之外进行行动
-
定义和使用 OpenAI 函数
-
语义内核及其使用语义函数的方法
-
协同语义和本地函数
-
使用语义内核实例化 GPT 接口
在本章中,我们通过函数的使用来探讨动作,以及代理如何使用它们。我们将首先查看 OpenAI 函数调用,然后迅速转向微软的另一个项目,称为语义内核(SK),我们将使用它来构建和管理代理或作为代理的技能和函数。
我们将使用 SK 来完成本章,以托管我们的第一个代理系统。这将是一个包含大量注释代码示例的完整章节。
5.1 定义代理动作
ChatGPT 插件首次推出是为了提供具有能力、技能或工具的会话。有了插件,你可以搜索网络或创建电子表格或图表。插件为 ChatGPT 提供了扩展平台的方式。
图 5.1 展示了 ChatGPT 插件的工作方式。在这个例子中,ChatGPT 中安装了一个新的电影推荐插件。当用户要求 ChatGPT 推荐一部新电影时,大型语言模型(LLM)识别出它有一个用于管理该动作的插件。然后,它将用户请求分解为可操作的参数,并将这些参数传递给新的电影推荐器。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-1.png
图 5.1 ChatGPT 插件的工作方式以及插件和其他外部工具(例如,API)如何与“使用外部工具”提示工程策略相一致
推荐器随后抓取展示新电影的网站,并将该信息附加到对 LLM 的新提示请求中。有了这些信息,LLM 会回应推荐器,推荐器将此信息传递回 ChatGPT。然后 ChatGPT 对用户做出推荐请求的回应。
我们可以将插件视为动作的代理。插件通常封装了一个或多个能力,例如调用 API 或抓取网站。因此,动作是插件的扩展——它们赋予了插件能力。
AI 代理可以被视为插件和插件的消费者、工具、技能和其他代理。向代理/插件添加技能、函数和工具允许它执行定义良好的动作——图 5.2 强调了代理动作发生的位置及其与 LLM 和其他系统的交互。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-2.png
图 5.2 代理如何使用动作执行外部任务
代理动作是允许它使用函数、技能或工具的能力。令人困惑的是,不同的框架使用不同的术语。我们将定义动作为代理可以执行以建立一些基本定义的任何操作。
ChatGPT 插件和函数代表了 ChatGPT 或代理系统可以用来执行额外动作的可执行能力。现在让我们来检查 OpenAI 插件和函数定义的基础。
5.2 执行 OpenAI 函数
OpenAI 通过启用插件,引入了一种结构规范,用于定义函数/插件与 LLM 之间的接口。这个规范正在成为 LLM 系统可以遵循的标准,以提供可执行的系统。
这些相同的函数定义现在也被用来定义 ChatGPT 和其他系统的插件。接下来,我们将探讨如何直接使用 LLM 调用来使用函数。
5.2.1 将函数添加到 LLM API 调用中
图 5.3 展示了 LLM 如何识别和使用函数定义来将其响应作为函数调用。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-3.png
图 5.3 如何解释单个 LLM 请求,包括工具
列表 5.1 显示了使用工具和函数定义进行 LLM API 调用的详细信息。添加函数定义允许 LLM 回复有关函数的输入参数。这意味着 LLM 将识别正确的函数并解析用户请求的相关参数。
列表 5.1 first_function.py (API 调用)
response = client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[{"role": "system",
"content": "You are a helpful assistant."},
{"role": "user", "content": user_message}],
temperature=0.7,
tools=[ #1
{
"type": "function", #2
"function": {
"name": "recommend",
"description": "Provide a … topic.", #3
"parameters": {
"type": "object", #4
"properties": {
"topic": {
"type": "string",
"description":
"The topic,… for.", #5
},
"rating": {
"type": "string",
"description":
"The rating … given.", #5
"enum": ["good",
"bad",
"terrible"] #6
},
},
"required": ["topic"],
},
},
}
]
)
#1 新参数称为 tools
#2 设置工具的类型以执行功能
#3 提供了关于函数功能的出色描述
#4 定义输入参数的类型;一个对象代表一个 JSON 文档。
#5 为每个输入参数提供出色的描述
#6 您甚至可以用枚举的方式来描述。
要了解这是如何工作的,请打开 Visual Studio Code (VS Code) 到书籍的源代码文件夹:chapter_4/first_function.py。在 VS Code 中打开相关章节文件夹以创建一个新的 Python 环境并安装 requirements.txt 文件是一个好习惯。如果您需要这方面的帮助,请参阅附录 B。
在开始之前,请正确设置 chapter_4 文件夹中的 .env 文件,并包含您的 API 凭据。函数调用是 LLM 商业服务提供的额外功能。在撰写本文时,此功能不是开源 LLM 部署的选项。
接下来,我们将查看 first_function.py 代码的底部,如列表 5.2 所示。这里只是使用列表 5.1 中指定的先前请求对 LLM 进行调用的两个示例。在这里,每个请求都显示了运行示例生成的输出。
列表 5.2 first_function.py (练习 API)
user = "Can you please recommend me a time travel movie?"
response = ask_chatgpt(user) #1
print(response)
**###Output**
Function(arguments='{"topic":"time travel movie"}',
name='recommend') #2
user = "Can you please recommend me a good time travel movie?"
response = ask_chatgpt(user) #3
print(response)
**###Output**
Function(arguments='{"topic":"time travel movie",
"rating":"good"}',
name='recommend') #4
#1 之前定义的函数
#2 返回要调用的函数名称和提取的输入参数
#3 之前定义的函数
#4 返回要调用的函数名称和提取的输入参数
使用调试器(F5)或终端在 VS Code 中运行 first_function.py Python 脚本,以查看相同的结果。在这里,LLM 解析输入请求以匹配任何已注册的工具。在这种情况下,工具是单个函数定义,即推荐的函数。LLM 从该函数中提取输入参数,并从请求中解析这些参数。然后,它以命名函数和指定的输入参数的形式回复。
备注:实际函数并未被调用。LLM 只返回建议的函数和相关的输入参数。必须提取名称和参数,并将它们传递给一个与签名匹配的函数以执行该函数。我们将在下一节中查看一个示例。
5.2.2 执行函数调用
现在我们已经明白 LLM 不会直接执行函数或插件,我们可以看看一个执行工具的示例。继续遵循推荐主题,我们将看看另一个添加 Python 函数以进行简单推荐的示例。
图 5.4 展示了这个简单示例的工作原理。我们将提交一个包含工具函数定义的单个请求,请求三个推荐。LLM 随后将回复三个带有输入参数的函数调用(时间旅行、食谱和礼物)。执行函数的结果随后被传递回 LLM,它将它们转换回自然语言并返回一个回复。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-4.png
图 5.4 一个示例请求返回三个工具函数调用,然后将结果提交回 LLM 以返回一个自然语言回复。
现在我们已经理解了这个示例,请在 VS Code 中打开parallel_functions.py。列表 5.3 显示了您想要调用的 Python 函数,以提供推荐。
列表 5.3 parallel_functions.py (推荐函数)
def recommend(topic, rating="good"):
if "time travel" in topic.lower(): #1
return json.dumps({"topic": "time travel",
"recommendation": "Back to the Future",
"rating": rating})
elif "recipe" in topic.lower(): #1
return json.dumps({"topic": "recipe",
"recommendation": "The best thing … ate.",
"rating": rating})
elif "gift" in topic.lower(): #1
return json.dumps({"topic": "gift",
"recommendation": "A glorious new...",
"rating": rating})
else: #2
return json.dumps({"topic": topic,
"recommendation": "unknown"}) #3
#1 检查字符串是否包含在主题输入中
#2 如果未检测到主题,则返回默认值
#3 返回一个 JSON 对象
接下来,我们将查看名为run_conversation的函数,所有的工作都是从请求构建开始的。
列表 5.4 parallel_functions.py (run_conversation, request)
user = """Can you please make recommendations for the following:
1\. Time travel movies
2\. Recipes
3\. Gifts""" #1
messages = [{"role": "user", "content": user}] #2
tools = [ #3
{
"type": "function",
"function": {
"name": "recommend",
"description":
"Provide a recommendation for any topic.",
"parameters": {
"type": "object",
"properties": {
"topic": {
"type": "string",
"description":
"The topic, … recommendation for.",
},
"rating": {
"type": "string",
"description": "The rating … was given.",
"enum": ["good", "bad", "terrible"]
},
},
"required": ["topic"],
},
},
}
]
#1 用户消息请求三个推荐。
#2 注意没有系统消息。
#3 将函数定义添加到请求的工具部分
列表 5.5 显示了请求的执行,我们之前已经讨论过,但有一些需要注意的事项。这个调用使用了一个较低模型,如 GPT-3.5,因为委托函数是一个更直接的任务,可以使用较旧、较便宜、不太复杂的语言模型来完成。
列表 5.5 parallel_functions.py (run_conversation, API 调用)
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106", #1
messages=messages, #2
tools=tools, #2
tool_choice="auto", #3
)
response_message = response.choices[0].message #4
#1 委托给函数的 LLM 可以是更简单的模型。
#2 添加消息和工具定义
#3 auto 是默认值。
#4 LLM 返回的消息
在这个阶段,API 调用之后,响应应该包含所需函数调用的信息。记住,我们要求 LLM 提供三个推荐,这意味着它也应该提供三个函数调用输出,如下所示。
列表 5.6 parallel_functions.py (run_conversation, tool_calls)
tool_calls = response_message.tool_calls #1
if tool_calls: #1
available_functions = {
"recommend": recommend,
} #2
# Step 4: send the info for each function call and function response to
the model
for tool_call in tool_calls: #3
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
topic=function_args.get("topic"), #4
rating=function_args.get("rating"),
)
messages.append( #5
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
}
) # extend conversation with function response
second_response = client.chat.completions.create( #6
model="gpt-3.5-turbo-1106",
messages=messages,
)
return second_response.choices[0].message.content #6
#1 如果响应包含工具调用,则执行它们。
#2 只有一个函数,但可能包含多个
#3 遍历调用并将内容回放给 LLM
#4 从提取的参数中执行推荐函数
#5 将每个函数调用的结果附加到消息集合中
#6 使用更新后的信息向 LLM 发送另一个请求,并返回消息回复
工具调用输出和推荐函数调用的结果被附加到消息中。注意现在消息还包含了第一次调用的历史。然后这些信息被传递回 LLM 以构建自然语言回复。
在 VS Code 中通过按 F5 键并打开文件来调试此示例。以下列表显示了运行parallel_functions.py的输出。
列表 5.7 parallel_functions.py(输出)
Here are some recommendations for you:
1\. Time travel movies: "Back to the Future"
2\. Recipes: "The best thing you ever ate."
3\. Gifts: "A glorious new..." (the recommendation was cut off, so I
couldn't provide the full recommendation)
I hope you find these recommendations helpful! Let me know if you need
more information.
这完成了这个简单的演示。对于更高级的应用,函数可以执行各种任务,从抓取网站到调用搜索引擎,甚至完成更复杂的任务。
函数是针对特定任务输出的一种优秀方式。然而,处理函数或工具以及进行二次调用的任务可以通过更干净、更高效的方式进行。下一节将揭示为代理添加动作的更稳健的系统。
5.3 介绍语义内核
语义内核(SK)是微软的另一个开源项目,旨在帮助构建 AI 应用,我们称之为代理。在其核心,该项目最好用于定义动作,或者平台所说的语义插件,它们是技能和函数的包装器。
图 5.5 展示了如何将 SK 用作插件以及 OpenAI 插件的消费者。SK 依赖于 OpenAI 插件定义来定义插件。这样,它可以消费并发布自身或其他插件到其他系统。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-5.png
图 5.5 语义内核如何作为一个插件集成,并且也可以消费插件
OpenAI 插件定义精确映射到列表 5.4 中的函数定义。这意味着 SK 是 API 工具调用(即插件)的协调者。这也意味着 SK 可以帮助通过聊天界面或代理组织多个插件。
注意:SK 团队最初将功能模块标记为技能。然而,为了与 OpenAI 保持一致,他们已经将技能重命名为插件。更令人困惑的是,代码仍然使用术语技能。因此,在本章中,我们将使用技能和插件来表示同一概念。
SK 是管理多个插件(代理的动作)的有用工具,正如我们稍后将会看到的,它还可以帮助记忆和规划工具。对于本章,我们将专注于动作/插件。在下一节中,我们将探讨如何开始使用 SK。
5.3.1 开始使用 SK 语义函数
SK 易于安装,并在 Python、Java 和 C#中工作。这是一个好消息,因为它还允许在一个语言中开发的插件在另一种语言中被消费。然而,你目前还不能在一个语言中开发原生函数并在另一个语言中使用它。
我们将继续使用 VS Code 中的chapter_4工作空间来处理 Python 环境。如果你想要探索和运行任何示例,请确保你已经配置了一个工作空间。
列表 5.8 展示了如何在 VS Code 的终端中安装 SK。你也可以安装 SK 的 VS Code 扩展。这个扩展可以是一个创建插件/技能的有用工具,但不是必需的。
列表 5.8 安装语义内核
pip uninstall semantic-kernel #1
git clone https://github.com/microsoft/semantic-kernel.git #2
cd semantic-kernel/python #3
pip install -e . #4
#1 卸载 SK 的任何先前安装
#2 将仓库克隆到本地文件夹
#3 对源文件夹的更改
#4 从源文件夹安装可编辑的包
安装完成后,打开SK_connecting.py。列表 5.9 展示了通过 SK 快速运行示例的演示。该示例使用 OpenAI 或 Azure OpenAI 创建聊天完成服务。
列表 5.9 SK_connecting.py
import semantic_kernel as sk
selected_service = "OpenAI" #1
kernel = sk.Kernel() #2
service_id = None
if selected_service == "OpenAI":
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
api_key, org_id = sk.openai_settings_from_dot_env() #3
service_id = "oai_chat_gpt"
kernel.add_service(
OpenAIChatCompletion(
service_id=service_id,
ai_model_id="gpt-3.5-turbo-1106",
api_key=api_key,
org_id=org_id,
),
)
elif selected_service == "AzureOpenAI":
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
deployment, api_key, endpoint =
↪ sk.azure_openai_settings_from_dot_env() #4
service_id = "aoai_chat_completion"
kernel.add_service(
AzureChatCompletion(
service_id=service_id,
deployment_name=deployment,
endpoint=endpoint,
api_key=api_key,
),
)
#This function is currently broken
async def run_prompt():
result = await kernel.invoke_prompt(
↪ prompt="recommend a movie about
↪ time travel") #5
print(result)
# Use asyncio.run to execute the async function
asyncio.run(run_prompt()) #6
###Output
One highly recommended time travel movie is "Back to the Future" (1985)
directed by Robert Zemeckis. This classic film follows the adventures of
teenager Marty McFly (Michael J. Fox)…
#1 设置你使用的服务(OpenAI 或 Azure OpenAI)
#2 创建内核
#3 从 .env 文件加载机密信息并将其设置在聊天服务上
#4 从 .env 文件加载机密信息并将其设置在聊天服务上
#5 调用提示
#6 异步调用函数
通过按 F5(调试)运行示例,你应该会看到一个类似于列表 5.9 的输出。这个示例演示了如何使用 SK 创建并执行语义函数。语义函数相当于提示流中的提示模板,这是另一个微软工具。在这个例子中,我们定义一个简单的提示作为一个函数。
需要注意的是,这个语义函数并不是定义为插件。然而,内核可以创建一个自包含的语义元素,该元素可以针对一个 LLM 执行。语义函数可以单独使用或注册为插件,正如你稍后将会看到的。让我们跳到下一节,我们将介绍上下文变量。
5.3.2 语义函数和上下文变量
在上一个示例的基础上,我们可以看看如何向语义函数添加上下文变量。这种在提示模板中添加占位符的模式是我们将反复回顾的。在这个例子中,我们查看一个具有主题、类型、格式和自定义占位符的提示模板。
在 VS Code 中打开SK_context_variables.py,如下一个列表所示。提示相当于设置提示的system和user部分。
列表 5.10 SK_context_variables.py
#top section omitted…
prompt = """ #1
system:
You have vast knowledge of everything and can recommend anything provided
you are given the following criteria, the subject, genre, format and any
other custom information.
user:
Please recommend a {{$format}} with the subject {{$subject}} and {{$genre}}.
Include the following custom information: {{$custom}}
"""
prompt_template_config = sk.PromptTemplateConfig( #2
template=prompt,
name="tldr",
template_format="semantic-kernel",
input_variables=[
InputVariable(
name="format",
description="The format to recommend",
is_required=True
),
InputVariable(
name="suject",
description="The subject to recommend",
is_required=True
),
InputVariable(
name="genre",
description="The genre to recommend",
is_required=True
),
InputVariable(
name="custom",
description="Any custom information [CA]
to enhance the recommendation",
is_required=True,
),
],
execution_settings=execution_settings,
)
recommend_function = kernel.create_function_from_prompt( #3
prompt_template_config=prompt_template_config,
function_name="Recommend_Movies",
plugin_name="Recommendation",
)
async def run_recommendation( #4
subject="time travel",
format="movie",
genre="medieval",
custom="must be a comedy"
):
recommendation = await kernel.invoke(
recommend_function,
sk.KernelArguments(subject=subject,
format=format,
genre=genre,
custom=custom), #5
)
print(recommendation)
# Use asyncio.run to execute the async function
asyncio.run(run_recommendation()) #5
###Output
One movie that fits the criteria of being about time travel, set in a
medieval period, and being a comedy is "The Visitors" (Les Visiteurs)
from 1993\. This French film, directed by Jean-Marie Poiré, follows a
knight and his squire who are transported to the modern era by a
wizard’s spell gone wrong.…
#1 定义一个带有占位符的提示
#2 配置提示模板和输入变量定义
#3 从提示创建内核函数
#4 创建一个异步函数来包装函数调用
#5 设置内核函数参数
尝试调试这个示例(F5),等待输出生成。这是设置 SK 和创建及练习语义函数的基础。在下一节中,我们将继续了解如何将语义函数注册为技能/插件。
5.4 语义函数和本地函数的协同作用
语义函数封装了一个提示/配置文件并通过与 LLM 的交互来执行。原生函数是封装了可能从抓取网站到搜索网络的任何操作的代码。语义和原生函数都可以在 SK 内核中注册为插件/技能。
一个函数,无论是语义还是原生,都可以注册为插件,并像我们直接通过 API 调用注册早期函数一样使用。当一个函数注册为插件时,它将根据用例对聊天或代理接口变得可访问。下一节将探讨如何创建和注册语义函数与内核。
5.4.1 创建和注册语义技能/插件
SK 的 VS Code 扩展提供了创建插件/技能的有用工具。在本节中,我们将使用 SK 扩展创建一个插件/技能,然后编辑该扩展的组件。之后,我们将在 SK 中注册并执行该插件。
图 5.6 展示了在 VS Code 中使用 SK 扩展创建新技能的过程。(如果您需要安装此扩展,请参阅附录 B 的说明。)然后,您将获得将函数放置在技能/插件文件夹中的选项。始终将相似的功能分组在一起。创建技能后,输入您想要开发的函数的名称和描述。务必像 LLM 将要使用它一样描述该函数。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-6.png
图 5.6 创建新技能/插件的过程
您可以通过打开 skills/plugin 文件夹并查看文件来查看已完成的技能和函数。我们将遵循之前构建的示例,因此打开 skills/Recommender/Recommend_Movies 文件夹,如图 5.7 所示。在这个文件夹中有一个 config.json 文件,函数描述,以及一个名为 skprompt.txt 的语义函数/提示文件。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-7.png
图 5.7 语义函数技能/插件的文件和文件夹结构
列表 5.11 展示了语义函数定义的内容,也称为插件定义。请注意,类型被标记为 completion 而不是 function 类型,因为这是一个语义函数。我们将定义原生函数为类型函数。
列表 5.11 Recommend_Movies/config.json
{
"schema": 1,
"type": "completion", #1
"description": "A function to recommend movies based on users list of
previously seen movies.",
"completion": { #2
"max_tokens": 256,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0,
"frequency_penalty": 0
},
"input": {
"parameters": [
{
"name": "input", #3
"description": "The users list of previously seen movies.",
"defaultValue": ""
}
]
},
"default_backends": []
}
#1 语义函数是完成类型的函数。
#2 我们还可以设置函数调用的完成参数。
#3 定义了输入到语义函数的参数
接下来,我们可以查看语义函数提示的定义,如图表 5.12 所示。格式略有不同,但我们在这里看到的内容与之前使用模板的示例相匹配。此提示基于用户之前看过的电影列表推荐电影。
列表 5.12 Recommend_Movies/skprompt.txt
You are a wise movie recommender and you have been asked to recommend a
movie to a user.
You are provided a list of movies that the user has watched before.
You want to recommend a movie that the user has not watched before.
[INPUT]
{{$input}}
[END INPUT]
现在,我们将深入探讨加载技能/插件并在简单示例中执行它的代码。在 VS Code 中打开 SK_first_skill.py 文件。以下列表显示了一个突出显示新部分的缩略版本。
列表 5.13 SK_first_skill.py(缩略列表)
kernel = sk.Kernel()
plugins_directory = "plugins"
recommender = kernel.import_plugin_from_prompt_directory(
plugins_directory,
"Recommender",
) #1
recommend = recommender["Recommend_Movies"]
seen_movie_list = [ #2
"Back to the Future",
"The Terminator",
"12 Monkeys",
"Looper",
"Groundhog Day",
"Primer",
"Donnie Darko",
"Interstellar",
"Time Bandits",
"Doctor Strange",
]
async def run():
result = await kernel.invoke(
recommend,
sk.KernelArguments( #3
settings=execution_settings, input=", ".join(seen_movie_list)
),
)
print(result)
asyncio.run(run()) #4
###Output
Based on the list of movies you've provided, it seems you have an
interest in science fiction, time travel, and mind-bending narratives.
Given that you've watched a mix of classics and modern films in this
genre, I would recommend the following movie that you have not watched
before:
"Edge of Tomorrow" (also known as "Live Die Repeat: Edge of Tomorrow")…
#1 从插件文件夹加载提示
#2 用户之前看过的电影列表
#3 将已看电影的列表设置为连接列表。
#4 函数异步执行。
代码从 skills 目录和 plugin 文件夹加载技能/插件。当一个技能被加载到内核中而不是仅仅创建时,它就成为一个已注册的插件。这意味着它可以直接执行,就像这里所做的那样,或者通过插件接口通过 LLM 聊天对话执行。
运行代码(F5),你应该会看到一个类似于列表 5.13 的输出。我们现在有一个简单的语义函数,它可以作为插件托管。然而,这个函数需要用户输入他们看过的电影完整列表。我们将在下一节介绍原生函数,以探讨如何解决这个问题。
5.4.2 应用原生函数
如前所述,原生函数是可以做任何事情的代码。在以下示例中,我们将介绍一个原生函数来帮助我们在之前构建的语义函数。
此原生函数将从文件中加载用户之前看过的电影列表。虽然这个函数引入了记忆的概念,但我们将在第八章讨论这个问题。将这个新的原生函数视为任何可以虚拟做任何事情的代码。
可以使用 SK 扩展创建和注册原生函数。对于这个例子,我们将直接在代码中创建原生函数,以便使示例更容易理解。
在 VS Code 中打开 SK_native_functions.py。我们将首先查看原生函数是如何定义的。原生函数通常在类内部定义,这简化了原生函数的管理和实例化。
列表 5.14 SK_native_functions.py (MySeenMovieDatabase)
class MySeenMoviesDatabase:
"""
Description: Manages the list of users seen movies. #1
"""
@kernel_function( #2
description="Loads a list of movies … user has already seen",
name="LoadSeenMovies",
)
def load_seen_movies(self) -> str: #3
try:
with open("seen_movies.txt", 'r') as file: #4
lines = [line.strip() for line in file.readlines()]
comma_separated_string = ', '.join(lines)
return comma_separated_string
except Exception as e:
print(f"Error reading file: {e}")
return None
#1 为容器类提供描述
#2 使用装饰器提供函数描述和名称
#3 实际的函数返回一个以逗号分隔的字符串形式的电影列表。
#4 从文本文件中加载已看电影
原生函数定义后,我们可以通过在文件中向下滚动查看其使用方法,如下所示列表。
列表 5.15 SK_native_functions(剩余代码)
plugins_directory = "plugins"
recommender = kernel.import_plugin_from_prompt_directory(
plugins_directory,
"Recommender",
) #1
recommend = recommender["Recommend_Movies"]
seen_movies_plugin = kernel.import_plugin_from_object(
MySeenMoviesDatabase(), "SeenMoviesPlugin"
) #2
load_seen_movies = seen_movies_plugin["LoadSeenMovies"] #3
async def show_seen_movies():
seen_movie_list = await load_seen_movies(kernel)
return seen_movie_list
seen_movie_list = asyncio.run(show_seen_movies()) #4
print(seen_movie_list)
async def run(): #5
result = await kernel.invoke(
recommend,
sk.KernelArguments(
settings=execution_settings,
input=seen_movie_list),
)
print(result)
asyncio.run(run()) #5
###Output
The Matrix, The Matrix Reloaded, The Matrix Revolutions, The Matrix
Resurrections – `*output from print statement*`
Based on your interest in the "The Matrix" series, it seems you enjoy
science fiction films with a strong philosophical undertone and action
elements. Given that you've watched all
#1 按照之前所示加载语义函数
#2 将技能导入内核并注册该函数为插件
#3 加载原生函数
#4 执行函数并将列表作为字符串返回
#5 将插件调用封装在一个异步函数中并执行
一个需要注意的重要方面是本地函数如何导入到内核中。将函数导入内核的行为将此函数注册为插件/技能。这意味着该函数可以通过其他对话或交互从内核中作为技能使用。我们将在下一节中看到如何在语义函数中嵌入本地函数。
5.4.3 在语义函数中嵌入本地函数
SK 中有很多强大的功能,但一个有益的功能是能够在其他语义函数中嵌入本地或语义函数。以下列表显示了如何在一个语义函数中嵌入本地函数。
列表 5.16 SK_semantic_native_functions.py (skprompt)
sk_prompt = """
You are a wise movie recommender and you have been asked to recommend a
movie to a user.
You have a list of movies that the user has watched before.
You want to recommend a movie that
the user has not watched before. #1
Movie List: {{MySeenMoviesDatabase.LoadSeenMovies}}. #2
"""
#1 与之前相同的精确指令文本
#2 通过类名和函数名引用和识别本地函数。
下一示例,SK_semantic_native_functions.py,使用了内联本地和语义函数。在 VS Code 中打开文件,以下列表显示了创建、注册和执行函数的代码。
列表 5.17 SK_semantic_native_functions.py(缩略)
prompt_template_config = sk.PromptTemplateConfig(
template=sk_prompt,
name="tldr",
template_format="semantic-kernel",
execution_settings=execution_settings,
) #1
recommend_function = kernel.create_function_from_prompt(
prompt_template_config=prompt_template_config,
function_name="Recommend_Movies",
plugin_name="Recommendation",
) #2
async def run_recommendation(): #3
recommendation = await kernel.invoke(
recommend_function,
sk.KernelArguments(),
)
print(recommendation)
# Use asyncio.run to execute the async function
asyncio.run(run_recommendation())
###Output
Based on the list provided, it seems the user is a fan of the Matrix
franchise. Since they have watched all four existing Matrix movies, I
would recommend a…
#1 为提示创建提示模板配置
#2 从提示中创建内联语义函数
#3 异步执行语义功能
运行代码,你应该会看到一个类似于列表 5.17 的输出。一个需要注意的重要方面是,本地函数已注册到内核中,但语义函数没有。这很重要,因为函数创建不会注册函数。
为了使此示例正确工作,本地函数必须使用import_plugin函数调用(列表 5.17 中的第一行)注册到内核中。然而,语义函数本身并没有注册。注册函数的一个简单方法是将它做成插件并导入。
这些简单的练习展示了将插件和技能集成到聊天或代理界面中的方法。在下一节中,我们将查看一个完整的示例,展示如何将代表服务或 GPT 接口的插件添加到聊天功能中。
5.5 语义内核作为交互式服务代理
在第一章中,我们介绍了 GPT 接口的概念——通过插件和语义层将服务和其他组件连接到 LLMs 的新范式。SK 为将任何服务转换为 GPT 接口提供了一个出色的抽象。
图 5.8 展示了一个围绕名为 The Movie Database (TMDB; www.themoviedb.org)的 API 服务构建的 GPT 界面。TMDB 网站提供了一个免费的 API,可以公开电影和电视节目的信息。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-8.png
图 5.8 这层架构图显示了 GPT 接口和语义内核在聊天或代理界面中被暴露的作用。
要跟随本节的练习,您必须从 TMDB 注册一个免费账户并创建一个 API 密钥。获取 API 密钥的说明可以在 TMDB 网站上找到(www.themoviedb.org)或通过询问 GPT-4 turbo 或更近期的 LLM。
在接下来的几节中,我们将使用 SK 原生函数集创建一个 GPT 接口。然后,我们将使用 SK 内核测试该接口,并在本章的后面部分将其作为插件实现到聊天功能中。在下一节中,我们将探讨如何针对 TMDB API 构建 GPT 接口。
5.5.1 构建语义 GPT 接口
TMDB 是一个出色的服务,但它不提供语义服务或可以插入到 ChatGPT 或代理中的服务。为了做到这一点,我们必须在 TMDB 公开的 API 调用周围包装一个语义服务层。
语义服务层是一个 GPT 接口,通过自然语言公开函数。正如讨论的那样,要将函数公开给 ChatGPT 或其他如代理等接口,它们必须被定义为插件。幸运的是,SK 可以自动为我们创建插件,前提是我们正确编写我们的语义服务层。
原生插件或一组技能可以作为语义层。要创建原生插件,创建一个新的插件文件夹,并在该文件夹中放置一个包含一组原生函数的 Python 文件。目前 SK 扩展没有很好地做到这一点,所以手动创建模块效果最好。
图 5.9 展示了名为Movies的新插件和名为tmdb.py的语义服务层的结构。对于原生函数,使用父文件夹的名称(Movies)进行导入。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/5-9.png
图 5.9 TMDB 插件的文件夹和文件结构
在 VS Code 中打开tmdb.py文件,查看文件顶部,如图 5.18 所示。此文件包含一个名为TMDbService的类,它公开了几个映射到 API 端点调用的函数。想法是将这个语义服务层中的各种相关 API 函数调用映射出来。这将使函数作为聊天或代理接口的插件公开。
列表 5.18 tmdb.py(文件顶部)
from semantic_kernel.functions import kernel_funct
import requests
import inspect
def print_function_call(): #1
#omitted …
class TMDbService: #2
def __init__(self):
# enter your TMDb API key here
self.api_key = "your-TMDb-api-key"
@kernel_function( #2
description="Gets the movie genre ID for a given genre name",
name="get_movie_genre_id",
input_description="The movie genre name of the genre_id to get",
)
def get_movie_genre_id(self, genre_name: str) -> str: #3
print_function_call()
base_url = "https://api.themoviedb.org/3"
endpoint = f"{base_url}/genre/movie/list↪
↪ ?api_key={self.api_key}&language=en-US"
response = requests.get(endpoint) #4
if response.status_code == 200: #4
genres = response.json()['genres']
for genre in genres:
if genre_name.lower() in genre['name'].lower():
return str(genre['id']) #5
return None
#1 打印函数调用以进行调试
#2 顶级服务和装饰器用于描述函数(好的描述很重要)
#3 被语义包装器包装的函数;应返回 str
#4 调用 API 端点,如果成功(代码 200),则检查匹配的类型
#5 找到类型,返回 id
TMDbService的代码以及调用 TMDB 端点的函数大部分是在 GPT-4 Turbo 的帮助下编写的。然后,每个函数都被sk_function装饰器包装以公开其语义。
TMDB API 的一些调用已经被语义化了。列表 5.19 展示了向语义服务层公开的函数的另一个示例。这个函数可以拉取特定类型的当前最热门的 10 部电影列表。
列表 5.19 tmdb.py (get_top_movies_by_genre)
@kernel_function( #1
description="””
Gets a list of currently playing movies for a given genre””",
name="get_top_movies_by_genre",
input_description="The genre of the movies to get",
)
def get_top_movies_by_genre(self, genre: str) -> str:
print_function_call()
genre_id = self.get_movie_genre_id(genre) #2
if genre_id:
base_url = "https://api.themoviedb.org/3
playing_movies_endpoint = f"{base_url}/movie/now_playing?↪
↪ api_key={self.api_key}&language=en-US"
response = requests.get(
playing_movies_endpoint) #3
if response.status_code != 200:
return ""
playing_movies = response.json()['results'
for movie in playing_movies: #4
movie['genre_ids'] = [str(genre_id)
↪ for genre_id in movie['genre_ids']]
filtered_movies = [movie for movie ↪
↪ in playing_movies if genre_id ↪
↪ in movie['genre_ids']][:10] #5
results = ", ".join([movie['title'] for movie in filtered_movies])
return results
else:
return ""
#1 使用描述装饰函数
#2 根据给定的类型名称查找 genre id
#3 获取当前正在上映的电影列表
#4 将 genre_ids 转换为字符串
#5 检查是否 genre id 与电影类型匹配
查看其他映射为语义的 API 调用。如您所见,将 API 调用转换为语义服务的模式已经定义得很好。在我们运行完整服务之前,我们将在下一节测试每个函数。
5.5.2 测试语义服务
在实际应用中,您可能希望为每个语义服务函数编写一套完整的单元或集成测试。我们在这里不会这样做;相反,我们将编写一个快速的帮助脚本以测试各种函数。
在 VS Code 中打开test_tmdb_service.py,并查看代码,如图表 5.20 所示。您可以注释和取消注释任何函数以单独测试它们。确保一次只取消注释一个函数。
列表 5.20 test_tmdb_service.py
import semantic_kernel as sk
from plugins.Movies.tmdb import TMDbService
async def main():
kernel = sk.Kernel() #1
tmdb_service = kernel.import_plugin_from_object ↪
↪ (TMDbService(), "TMDBService") #2
print(
await tmdb_service"get_movie_genre_id" #3
)
) #4
print(
await tmdb_service"get_tv_show_genre_id" #5
)
) #6
print(
await tmdb_service"get_top_movies_by_genre" #7
)
) #8
print(
await tmdb_service"get_top_tv_shows_by_genre" #7
)
)
print(await tmdb_service"get_movie_genres")) #9
print(await tmdb_service"get_tv_show_genres")) #9
# Run the main function
if __name__ == "__main__":
import asyncio
asyncio.run(main()) #10
###Output
Function name: get_top_tv_shows_by_genre #11
Arguments:
self = <skills.Movies.tmdb.TMDbService object at 0x00000159F52090C0>
genre = action
Function name: get_tv_show_genre_id #11
Arguments:
self = <skills.Movies.tmdb.TMDbService object at 0x00000159F52090C0>
genre_name = action
Arcane, One Piece, Rick and Morty, Avatar: The Last Airbender, Fullmetal
Alchemist: Brotherhood, Demon Slayer: Kimetsu no Yaiba, Invincible,
Attack on Titan, My Hero Academia, Fighting Spirit, The Owl House
#1 实例化内核
#2 导入插件服务
#3 在需要时将输入参数传递给函数
#4 执行并测试各种函数
#5 在需要时将输入参数传递给函数
#6 执行并测试各种函数
#7 在需要时将输入参数传递给函数
#8 执行并测试各种函数
#9 执行并测试各种函数
#10 异步执行主函数
#11 调用打印函数详细信息以通知函数正在被调用
在这个测试中展示了 SK 的真正力量。注意TMDbService类是如何作为一个插件导入的,但我们不需要定义除了我们之前已经做的任何插件配置?通过只写一个封装了一些 API 函数的类,我们已经以语义方式公开了 TMDB API 的一部分。现在,函数公开后,我们可以在下一节中查看它们如何作为聊天界面的插件使用。
5.5.3 与语义服务层的交互式聊天
当 TMDB 函数以语义方式公开时,我们可以继续将它们集成到聊天界面中。这将使我们能够在这个界面中自然地进行对话以获取各种信息,例如当前的热门电影。
在 VS Code 中打开SK_service_chat.py。向下滚动到创建函数的新代码部分的开始,如图表 5.21 所示。这里创建的函数现在作为插件公开,除了我们过滤掉的聊天函数,我们不希望将其作为插件公开。这里的聊天函数允许用户直接与 LLM 进行对话,不应作为插件。
列表 5.21 SK_service_chat.py(函数设置)
system_message = "You are a helpful AI assistant."
tmdb_service = kernel.import_plugin_from_object(
TMDbService(), "TMDBService") #1
# extracted section of code
execution_settings = sk_oai.OpenAIChatPromptExecutionSettings(
service_id=service_id,
ai_model_id=model_id,
max_tokens=2000,
temperature=0.7,
top_p=0.8,
tool_choice="auto",
tools=get_tool_call_object(
kernel, {"exclude_plugin": ["ChatBot"]}), #2
)
prompt_config = sk.PromptTemplateConfig.from_completion_parameters(
max_tokens=2000,
temperature=0.7,
top_p=0.8,
function_call="auto",
chat_system_prompt=system_message,
) #3
prompt_template = OpenAIChatPromptTemplate(
"{{$user_input}}", kernel.prompt_template_engine, prompt_config
) #4
history = ChatHistory()
history.add_system_message("You recommend movies and TV Shows.")
history.add_user_message("Hi there, who are you?")
history.add_assistant_message(
"I am Rudy, the recommender chat bot. I'm trying to figure out what
people need."
) #5
chat_function = kernel.create_function_from_prompt(
prompt_template_config=prompt_template,
plugin_name="ChatBot",
function_name="Chat",
) #6
#1 将 TMDbService 作为插件导入
#2 配置执行设置并添加过滤工具
#3 配置提示配置
#4 定义输入模板并接受完整的字符串作为用户输入
#5 添加聊天历史对象并填充一些历史记录
#6 创建聊天函数
接下来,我们可以继续在同一文件中滚动以审查聊天函数,如下列所示。
列表 5.22 SK_service_chat.py(聊天函数)
async def chat() -> bool:
try:
user_input = input("User:> ") #1
except KeyboardInterrupt:
print("\n\nExiting chat...")
return False
except EOFError:
print("\n\nExiting chat...")
return False
if user_input == "exit": #2
print("\n\nExiting chat...")
return False
arguments = sk.KernelArguments( #3
user_input=user_input,
history=("\n").join(
[f"{msg.role}: {msg.content}" for msg in history]),
)
result = await chat_completion_with_tool_call( #4
kernel=kernel,
arguments=arguments,
chat_plugin_name="ChatBot",
chat_function_name="Chat",
chat_history=history,
)
print(f"AI Agent:> {result}")
return True
#1 输入直接来自终端/控制台。
#2 如果用户输入 exit,则退出聊天。
#3 创建传递给函数的参数
#4 使用实用函数调用函数并执行工具
最后,滚动到文件底部,并审查主函数。这是调用循环中聊天函数的代码。
列表 5.23 SK_service_chat.py(主函数)
async def main() -> None:
chatting = True
context = kernel.create_new_context()
print("Welcome to your first AI Agent\ #1
\n Type 'exit' to exit.\
\n Ask to get a list of currently playing movies by genre."
)
while chatting: #2
chatting, context = await chat(context) #3
if __name__ == "__main__":
asyncio.run(main())
#1 对用户的介绍
#2 继续直到聊天为 False
#3 异步调用聊天函数
运行聊天界面,运行文件(F5),然后询问特定类型的电影或电视节目。示例对话会话如列表 5.24 所示。此输出显示了请求列出两个类型的电影时,聊天界面如何多次调用 get_top_movie_by_genre 函数。
列表 5.24 SK_service_chat.py(示例对话)
Welcome to your first AI Agent
Type 'exit' to exit.
Ask to get a list of currently playing movies by genre.
User:> Input: can you give me list of the current top playing movies for
the action and comedy genres?
Function name: get_top_movies_by_genre #1
Arguments:
genre = action
Function name: get_movie_genre_id #2
Arguments:
genre_name = action
Function name: get_top_movies_by_genre #1
Arguments:
genre = comedy
Function name: get_movie_genre_id #2
Arguments:
genre_name = comedy
Agent:> Here are the current top-playing movies
for the action and comedy genres:
**Action:** #3
1\. The Hunger Games: The Ballad of Songbirds & Snakes
2\. Rebel Moon - Part One: A Child of Fire
3\. Aquaman and the Lost Kingdom
4\. Silent Night
5\. The Family Plan
6\. Freelance
7\. Migration
8\. Sound of Freedom
9\. Godzilla Minus One
**Comedy:** #4
1\. The Family Plan
2\. Wonka
3\. Freelance
4\. Saltburn
5\. Chicken Run: Dawn of the Nugget
6\. Trolls Band Together
7\. There's Something in the Barn
8\. Migration
Please note that some movies may overlap in both genres, such as
"The Family Plan" and "Freelance ."
#1 LLM 调用两次 get_top_movies_by_genre。
#2 内部调用以获取类型 ID
#3 当前最受欢迎的动作电影列表
#4 当前最受欢迎的喜剧电影列表
一定要探索聊天界面的边界以及你可以从 TMDB 服务中请求的内容。例如,尝试请求电影或电视节目的类型列表。这个服务是一个很好的尝试,但我们可能做得更好,正如我们将在下一节中看到的那样。
5.6 在编写语义服务时进行语义思考
现在我们已经看到了将 API 转换为语义服务接口的出色演示。就目前而言,这些函数返回当前正在上映的电影和电视节目的标题。然而,仅仅返回标题,我们限制了 LLM 自行解析结果的能力。
因此,我们将创建 TMDbService 的 v2 版本来纠正这个问题,并将结果作为 JSON 字符串返回。在 VS Code 中打开文件 tmdb_v2.py,并滚动到 get_top_movies_by_genre 函数。
列表 5.25 tmdb_v2.py (get_top_movies_by_genre)
def get_top_movies_by_genre(self, genre: str) -> str:
print_function_call()
genre_id = self.get_movie_genre_id(genre)
if genre_id:
#same code …
return json.dumps(filtered_movies) #1
else:
return ""
#1 现在返回一个过滤后的 JSON 字符串列表
现在在 VS Code 中打开 SK_service_chat.py 文件,注释和取消注释列表 5.26 中显示的行。这将使用 TMDbService 的第 2 个版本,该版本以单个字符串的形式输出完整的 JSON 文档结果。
列表 5.26 SK_service_chat.py(修改导入)
#from skills.Movies.tmdb import TMDbService #1
from skills.Movies.tmdb_v2 import TMDbService #2
#1 注释掉这一行。
#2 取消注释此行以使用服务的第 2 个版本。
在 VS Code 中重新运行 SK_service_chat.py 文件,并稍微修改你的查询,如下列所示。
列表 5.27 SK_service_chat.py(TMDb_v2 服务输出)
User:> get a list of currently playing movies for the
action genre and only return movies about space #1
Agent:> To find currently playing action movies that are specifically
about space, I will need to manually filter the provided list for those
that have space-related themes in their overview. Here's what fits that
criteria from the list:
1\. **Rebel Moon - Part One: A Child of Fire** #2
- Release Date: 2023-12-15
- Overview: When a peaceful colony on the edge of the galaxy finds
itself threatened by the armies of the tyrannical Regent Balisarius,
they dispatch Kora, a young woman with a mysterious past, to seek out
warriors from neighboring planets to help them take a stand.
This is the only movie from the provided list that clearly mentions a
space-related theme in its overview. …
#1 新查询要求包含额外的空间过滤器
#2 LLM 调用服务并审查匹配过滤器的返回结果。
由于语义服务函数现在以 JSON 格式返回完整的电影列表,LLM 可以应用额外的过滤。这是语义服务的真正力量,允许你通过 LLM 处理数据。我们不会仅仅通过返回标题列表来看到这种力量。
这个最后一个练习展示了在编写语义服务层时你需要做出的心态转变。通常,你通常会希望返回尽可能多的信息。返回更多信息可以利用 LLM 独立过滤、排序和转换数据的能力。在下一章中,我们将探讨使用行为树构建自主代理。
5.7 练习
完成以下练习以提高你对材料的了解:
- 练习 1—创建温度转换的基本插件
目标—熟悉创建简单的 OpenAI 聊天完成 API 插件。
任务:
-
-
开发一个在摄氏度和华氏度之间转换温度的插件。
-
通过将其集成到简单的 OpenAI 聊天会话中测试插件,用户可以请求温度转换。
-
-
练习 2—开发天气信息插件
目标—学习创建执行独特任务的插件。
任务:
-
-
为 OpenAI 聊天完成 API 创建一个插件,从公共 API 获取天气信息。
-
确保插件可以处理用户对不同城市当前天气状况的请求。
-
-
练习 3—制作一个创意语义功能
目标—探索创建语义功能。
任务:
-
-
开发一个基于用户输入写诗或讲述儿童故事的语义功能。
-
在聊天会话中测试该函数,以确保它生成创意和连贯的输出。
-
-
练习 4—使用原生函数增强语义功能
目标—了解如何结合语义和原生函数。
任务:
-
-
创建一个使用原生函数增强其功能的语义功能。
-
例如,开发一个生成餐单的语义功能,并使用原生函数获取食材的营养信息。
-
-
练习 5—使用语义内核封装现有的 Web API
目标—学习将现有的网络 API 封装为语义服务插件。
任务:
-
-
使用 SK 将新闻 API 封装并作为聊天代理中的语义服务插件公开。
-
确保插件可以处理用户对各种主题的最新新闻文章的请求。
-
摘要
-
代理动作扩展了代理系统的功能,如 ChatGPT。这包括向 ChatGPT 和 LLM 添加插件以作为动作代理的能力。
-
OpenAI 支持在 OpenAI API 会话中定义函数和插件。这包括向 LLM API 调用中添加函数定义,并理解这些函数如何允许 LLM 执行额外的操作。
-
语义内核(SK)是微软的一个开源项目,可用于构建 AI 应用程序和代理系统。这包括语义插件在定义原生和语义函数中的作用。
-
语义函数封装了用于与 LLM 互动的提示/配置文件模板。
-
原生函数封装了使用 API 或其他接口执行或执行动作的代码。
-
语义函数可以与其他语义或原生函数结合,并在执行阶段相互层叠。
-
SK 可以在语义服务层上创建一个 GPT 接口,并在聊天或代理接口插件中暴露它们。
-
语义服务代表了 LLMs(大型语言模型)和插件之间的交互,以及这些概念在实际创建高效 AI 代理中的应用实现。
第六章:构建自主助手
本章涵盖了
-
适用于机器人和人工智能应用程序的行为树
-
GPT 助手游乐场和创建助手及动作
-
自主控制代理行为树
-
通过代理行为树模拟对话多代理系统
-
使用反向链接创建复杂系统的行为树
现在我们已经介绍了如何通过动作扩展代理的能力,我们可以看看行为树如何指导代理系统。我们将从理解行为树的基本原理以及它们如何控制游戏中的机器人和人工智能开始。
我们将回到代理动作,并检查如何使用 GPT 助手游乐场项目在 OpenAI 助手平台上实现动作。从那里,我们将探讨如何使用 OpenAI 助手构建自主代理行为树(ABT)。然后,我们将转向理解自主代理需要控制和护栏,并使用控制屏障函数。
在本章的最后部分,我们将检查使用 AgentOps 平台来监控我们的自主行为驱动代理系统。这将是一个充满挑战的章节。让我们首先跳到下一节,介绍行为树。
6.1 行为树的介绍
行为树是一种长期建立的模式,用于控制游戏中的机器人和人工智能。罗德尼·A·布鲁斯(Rodney A. Brooks)首次在 1986 年的“为移动机器人设计的鲁棒分层控制系统”论文中介绍了这个概念。这为今天我们所使用的树和节点结构扩展了模式奠定了基础。
如果你曾经玩过与非玩家角色(NPC)互动的电脑游戏或与高级机器人系统互动,你就已经见证了行为树在工作。图 6.1 展示了一个简单的行为树。该树代表所有主要节点:选择器或回退节点、序列节点、动作节点和条件节点。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-1.png
图 6.1 吃苹果或梨的简单行为树
表 6.1 描述了本书中将探讨的主要节点的功能和目的。还有其他节点和节点类型,你甚至可以创建自定义节点,但就目前而言,我们将专注于表中的那些。
表 6.1 行为树中使用的主要节点
| 节点 | 目的 | 功能 | 类型 |
|---|---|---|---|
| 选择器(回退) | 此节点通过选择第一个成功完成的子节点来工作。它通常被称为回退节点,因为它总是会回退到最后一个成功执行的节点。 | 节点按顺序调用其子节点,并在第一个子节点成功时停止执行。当子节点成功时,它将返回成功;如果没有节点成功,它将返回失败。 | 组合 |
| 序列 | 此节点按顺序执行其所有子节点,直到某个节点失败或它们都成功完成。 | 节点按顺序调用其所有子节点,无论它们是否失败或成功。如果所有子节点都成功,则返回成功,如果只有一个子节点失败,则返回失败。 | 组合节点 |
| 条件 | 行为树不使用布尔逻辑,而是使用成功或失败作为控制手段。条件在条件为真时返回成功,否则返回失败。 | 节点根据条件返回成功或失败。 | 任务节点 |
| 动作 | 这就是动作发生的地方。 | 节点执行并返回成功,如果成功则返回成功,否则返回失败。 | 任务节点 |
| 装饰器 | 它们通过控制子节点的执行来工作。它们通常被称为条件,因为它们可以确定一个节点是否值得执行或安全执行。 | 节点控制子节点的执行。装饰器可以作为控制屏障函数来阻止或防止不受欢迎的行为。 | 装饰器节点 |
| 并行 | 此节点并行执行其所有节点。成功或失败由所需成功子节点的阈值控制。 | 节点按顺序执行其所有子节点,无论节点状态如何。 | 组合节点 |
表 6.1 中的主要节点可以提供足够的功能来处理多种用例。然而,最初理解行为树可能会感到困难。你只有在开始使用它们之后才会欣赏到它们背后的复杂性。在我们构建一些简单的树之前,我们希望在下一节中更详细地探讨执行过程。
6.1.1 理解行为树的执行
理解行为树的执行方式对于设计和实现行为树至关重要。与计算机科学中的大多数概念不同,行为树以成功和失败为操作术语。当行为树中的节点执行时,它将返回成功或失败;这甚至适用于条件和选择节点。
行为树从上到下、从左到右执行。图 6.2 显示了执行过程以及节点失败或成功时会发生什么。在示例中,该树控制的 AI 有一个苹果但没有梨。在第一个序列节点中,一个条件检查 AI 是否有苹果。因为 AI 没有苹果,所以它终止序列并回退到选择器。然后选择器选择其下一个子节点,另一个序列,检查 AI 是否有梨,因为它有,所以 AI 吃掉了苹果。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-2.png
图 6.2 简单行为树的执行过程
行为树提供对人工智能系统在宏观或微观层面执行的控制。在机器人领域,行为树通常被设计为在微观层面运行,其中每个动作或条件都是一个小的事件,例如检测苹果。相反,行为树也可以控制更宏观的系统,例如游戏中的非玩家角色(NPC),其中每个动作可能是一系列事件的组合,如攻击玩家。
对于智能体系统,行为树支持在所选级别控制智能体或助手。我们将探讨在任务层面控制智能体,在后面的章节中,我们将探讨规划层面的控制。毕竟,有了 LLM 的力量,智能体可以构建自己的行为树。
当然,还有其他几种形式的人工智能控制系统可以用来控制智能体系统。下一节将探讨这些不同的系统,并将它们与行为树进行比较。
6.1.2 行为树的选择
许多其他人工智能控制系统都有其优点,值得在控制智能体系统时进行探索。它们可以展示行为树的优势,并为特定用例提供其他选项。行为树是一个优秀的模式,但并非唯一,了解其他模式也是值得的。
表 6.2 突出了我们可能考虑用于控制人工智能系统的其他几个系统。表中的每一项都描述了该方法的功能、其缺点以及它可能应用于智能体 AI 控制的应用。
表 6.2 其他人工智能控制系统的比较
| 控制名称 | 描述 | 短处 | 是否适用于控制智能体 AI? |
|---|---|---|---|
| 有限状态机 ^a (FSM) | 有限状态机使用一组状态和由事件或条件触发的转换来模拟人工智能。 | 随着复杂性的增加,有限状态机可能会变得难以控制。 | 对于智能体来说,有限状态机不实用,因为它们扩展性不好。 |
| 决策树 ^b | 决策树使用树状模型来表示决策及其可能的结果。 | 决策树在复杂场景中可能会出现过度拟合和泛化不足的问题。 | 决策树可以通过行为树进行适应和增强。 |
| 基于效用的系统 ^b | 效用函数评估并选择基于当前情况的最佳行动。 | 这些系统需要精心设计效用函数以平衡优先级。 | 这种模式可以在行为树中采用。 |
| 基于规则的系统 ^a | 这组 if-then 规则定义了人工智能的行为。 | 当规则众多时,这些系统可能会变得繁琐,并可能导致潜在冲突。 | 当与由 LLM 驱动的智能体系统结合时,这些系统不太实用。 |
| 计划系统 ^c | 计划系统使用规划算法生成一系列动作以实现特定目标。 | 这些系统计算成本高昂,需要大量的领域知识。 | 智能体可以在后面的章节中看到,它们可以自行实现这样的模式。 |
| 行为克隆^c | 行为克隆指的是通过模仿专家演示来学习策略。 | 这个系统可能难以泛化到未见过的情境。 | 这可以集成到行为树中或特定任务中。 |
| 层次化任务网络(HTN)^d | HTNs 将任务分解成更小、更易于管理的子任务,并按层次排列。 | 这些对于非常大的任务来说管理和设计都很复杂。 | HTNs 允许更好地组织和执行复杂任务。这种模式可以用于更大的智能体系统。 |
| 黑板系统^b | 这些系统使用共享黑板进行不同子系统的协作问题解决。 | 这些系统难以实现,并且管理子系统之间的通信很困难。 | 智能体系统可以通过对话或群聊/线程实现类似的模式。 |
| 遗传算法(GA)^d | 这些优化技术受自然选择启发,用于进化解决方案以解决问题。 | 遗传算法计算密集,并且不一定总能找到最优解。 | 遗传算法具有潜力,甚至可以用来优化行为树。 |
| ^a 考虑到复杂的智能体系统时不实用 ^b 存在于行为树中或可以轻松集成
^c 通常应用于任务或动作/条件级别
^d 应用到智能体时需要大量工作的高级系统
|
在本书的后续章节中,我们将探讨表 6.2 中讨论的一些模式。总的来说,可以通过使用行为树作为基础来增强或整合几个模式。而其他模式,如有限状态机(FSMs),虽然可能对小型实验有帮助,但它们缺乏行为树的扩展性。
行为树作为人工智能控制系统可以提供几个好处,包括可扩展性。以下列表突出了使用行为树的其它显著好处:
-
模块化和可重用性——行为树促进了一种模块化的行为设计方法,允许开发者创建可重用组件。行为树中的节点可以轻松地在树的各个部分或甚至在不同项目中重用,从而提高可维护性并减少开发时间。
-
可扩展性——随着系统复杂性的增加,行为树比其他方法(如 FSMs)更优雅地处理新行为的添加。行为树允许对任务进行分层组织,这使得管理和理解大型行为集变得更加容易。
-
灵活性和可扩展性——行为树提供了一个灵活的框架,其中可以添加新的节点(动作、条件、装饰器)而不会大幅改变现有结构。这种可扩展性使得引入新行为或修改现有行为以适应新要求变得简单直接。
-
调试和可视化——行为树提供了清晰直观的行为可视化表示,这对调试和理解决策过程有益。支持行为树的工具通常包括图形编辑器,允许开发者可视化并调试树结构,使其更容易识别和修复问题。
-
决策逻辑解耦——行为树将决策和执行逻辑分离,促进了高级策略和低级动作之间的清晰区分。这种解耦简化了设计,并允许更直接地修改和测试特定的行为部分,而不会影响整个系统。
在为行为树做了强有力的论证之后,我们现在应该考虑如何在代码中实现它们。在下一节中,我们将探讨如何使用 Python 代码构建一个简单的行为树。
6.1.3 使用 Python 和 py_trees 运行行为树
由于行为树已经存在很长时间,并且被整合到许多技术中,因此创建一个示例演示非常简单。当然,最简单的方法是询问 ChatGPT 或您喜欢的 AI 聊天工具。列表 6.1 显示了使用提示生成代码示例并将图 6.1 作为示例树提交的结果。最终代码必须进行简单的命名和参数错误修正。
注意 本章的所有代码都可以通过下载 GPT 助手游乐场项目在 mng.bz/Ea0q 找到。
列表 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
# Other classes omitted…
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
### Start of output
------------------ 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 在行为树上执行一步/一个时间单位
列表 6.1 中的代码代表了图 6.1 中的行为树。您可以按原样运行此代码,或更改条件返回的内容,然后再次运行树。您还可以通过从根选择器中删除一个序列节点来更改行为树。
现在我们对行为树有了基本的了解,我们可以继续处理代理/助手。在这样做之前,我们将查看一个帮助我们与 OpenAI 助手一起工作的工具。这个工具将帮助我们围绕 OpenAI 助手包装我们的第一个 ABT。
6.2 探索 GPT 助手游乐场
为了本书的开发,创建了几个 GitHub 项目来处理构建代理和助手的各种方面。其中一个项目,GPT 助手游乐场,使用 Gradio 构建了界面,模仿了 OpenAI 助手游乐场,但增加了几个额外功能。
PlayGround 项目被开发为一个教学和演示辅助工具。在项目内部,Python 代码使用 OpenAI 助手 API 创建聊天界面和用于构建和驱动助手的代理系统。同时,还提供了一系列您可以使用的行为助手,并且您可以轻松地添加您自己的行为。
6.2.1 安装和运行 PlayGround
以下列表显示了从终端安装和运行 PlayGround 项目的过程。目前还没有 PyPI 包可以安装。
列表 6.2 安装 GPT 助手 PlayGround
# change to a working folder and create a new Python virtual environment
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)运行应用程序,后者给您更多的控制权。在运行应用程序之前,您需要通过命令行或创建一个.env文件来设置您的 OpenAI API 密钥,就像我们之前已经做过的几次一样。列表 6.3 显示了在 Linux/Mac 或 Git Bash shell(推荐 Windows)上设置环境变量并运行应用程序的示例。
列表 6.3 运行 GPT 助手 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)或终端中提到的地址。您将看到一个类似于图 6.3 所示的界面。如果您已经定义了 OpenAI 助手,您将在“选择助手”下拉菜单中看到它们。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-3.png
图 6.3 使用 GPT 助手 PlayGround 界面学习数学
如果您从未定义过助手,您可以创建一个并选择您需要的各种选项和指令。如果您访问过 OpenAI PlayGround,您已经体验过类似的界面。
GPT 与助手对比
OpenAI 将 GPT 定义为可以在 ChatGPT 界面中运行和使用的助手。助手只能通过 API 进行消费,并且在大多数情况下需要自定义代码。当您运行一个助手时,您将根据模型令牌使用情况和任何特殊工具(包括代码解释器和文件)进行收费,而 GPT 在 ChatGPT 内部运行,并由账户费用覆盖。
创建 PlayGround 本地版本的原因是为了演示代码结构,同时也提供以下列出的额外功能:
-
行为(自定义行为)——创建您自己的行为允许您向助手添加任何您想要的功能。正如我们将看到的,PlayGround 使创建您自己的行为变得非常简单。
-
代码运行器——API 确实附带了一个代码解释器,但它相对昂贵(每次运行 0.03 美元),不允许你安装你的模块,不能以交互方式运行代码,并且运行速度较慢。游戏场将使你能够在隔离的虚拟环境中本地运行 Python 代码。虽然不如将代码推送到 Docker 镜像安全,但它比其他平台更好地执行了代码窗口化和进程外执行。
-
透明度和日志记录——游戏场提供了全面的日志捕获功能,甚至可以显示助手如何使用内部和外部工具/操作。这可以是一个查看助手幕后所做事情的优秀方式。
这些功能将在接下来的几节中更详细地介绍。我们将在下一节中开始查看使用和消费操作。
6.2.2 使用和构建自定义操作
操作和工具是赋予代理和助手能力的基石。没有工具的访问权限,代理就变成了无功能的聊天机器人。OpenAI 平台在建立许多工具模式方面处于领先地位,正如我们在第三章中看到的。
游戏场提供了几个可以通过界面附加到助手的自定义操作。在接下来的练习中,我们将构建一个简单的助手并附加几个自定义操作,以查看可以做到什么程度。
图 6.4 显示了扩展的操作手风琴,它显示了许多可用的自定义操作。从终端或调试器运行游戏场,创建一个新的助手。然后,选择图中的操作。在你完成选择操作后,滚动到页面底部,点击添加助手以添加助手。助手在使用之前需要被创建。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-4.png
图 6.4 在界面中选择和使用自定义操作
在你创建助手之后,你可以要求它列出所有可用的助手。列出助手也会给你调用助手所需的 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 助手 Playground 的 README。您可以从位于assistants.db SQLite 数据库中的assistants.db安装几个演示助手:
-
创建一个新的助手,或使用现有的助手。
-
给助手分配
create_manager_assistant操作(在操作部分下找到)。 -
请助手创建管理助手(即,“请创建管理助手”),并确保将助手的名称命名为“管理助手”。
-
刷新您的浏览器以重新加载助手选择器。
-
选择新的管理助手。这个助手拥有安装助手的指令和操作,这些操作将允许它从
assistants.db数据库中安装助手。 -
与管理助手交谈,以获取要安装的助手列表,或者直接请管理助手安装所有可用的助手。
6.2.4 获取本地运行代码的助手
使代理和助手生成和运行可执行代码具有很大的能力。与代码解释器不同,本地运行代码提供了快速迭代和调整的众多机会。我们之前在 AutoGen 中看到了这一点,代理可以持续运行代码,直到它按预期工作。
在 Playground 中,选择自定义操作run_code非常简单,如图 6.5 所示。您还希望选择run_shell_command操作,因为它允许助手pip install任何所需的模块。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-5.png
图 6.5 选择助手运行 Python 代码的自定义操作
您现在可以请助手生成和运行代码,以确保它代表您工作。通过添加自定义操作并请助手生成和运行代码来尝试这一点,如图 6.6 所示。如果代码没有按预期工作,告诉助手您遇到的问题。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-6.png
图 6.6 使助手生成和运行 Python 代码
再次强调,沙盒中运行的 Python 代码会在项目子文件夹中创建一个新的虚拟环境。如果你没有运行任何操作系统级别的代码或底层代码,这个系统工作得很好。如果你需要更健壮的解决方案,一个好的选择是 AutoGen,它使用 Docker 容器来运行隔离的代码。
添加运行代码或其他任务的动作可以使助手看起来像一个黑盒。幸运的是,OpenAI 助手 API 允许你消费事件并查看助手在幕后做了什么。在下一节中,我们将看到这看起来是什么样子。
6.2.5 通过日志调查助手进程
OpenAI 将一个功能添加到了助手 API 中,允许你监听通过工具/动作使用链式的事件和动作。这个功能已经集成到沙盒中,当助手调用另一个助手时,它会捕获动作和工具的使用。
我们可以通过要求助手使用一个工具并打开日志来尝试这一点。一个很好的例子是给助手提供代码解释器工具,然后要求它绘制一个方程式。图 6.7 展示了这个练习的一个例子。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-7.png
图 6.7 内部助手日志被捕获
通常情况下,当辅助代码解释器工具被启用时,你不会看到任何代码生成或执行。这个功能允许你看到助手在执行过程中使用的所有工具和动作。这不仅是一个出色的诊断工具,而且还能提供对 LLM(大型语言模型)功能的额外见解。
我们没有审查执行所有这些操作所需的代码,因为代码量很大,并且可能经历几次变化。话虽如此,如果你计划使用助手 API,这个项目是一个很好的起点。随着沙盒的引入,我们可以在下一节继续我们的 ABT 之旅。
6.3 介绍代理行为树
代理行为树(ABTs)在助手和代理系统中实现行为树。与常规行为树相比,ABTs 的关键区别在于它们使用提示来指导动作和条件。由于提示可能会返回高频率的随机结果,我们也可以将这些树称为随机行为树,这种树确实存在。为了简单起见,我们将用于控制代理的行为树区分开来,称其为代理行为树。
接下来,我们将进行一个练习来创建一个 ABT(代理行为树)。完成的树将以 Python 编写,但需要设置和配置各种助手。我们将介绍如何使用助手本身来管理助手。
6.3.1 使用助手管理助手
幸运的是,沙盒可以帮助我们快速管理和创建助手。我们首先安装管理助手,然后安装预定义的助手。让我们按照以下步骤开始安装管理助手:
-
在你的浏览器中打开游乐场,创建一个新的简单助理或使用现有的助理。如果你需要一个新助理,创建它然后选择它。
-
在选择助理后,打开操作面板,并选择
create_manager_assistant操作。你不需要保存;界面将自动更新助理。 -
现在,在聊天界面中,提示助理以下内容:“请创建经理助理。”
-
几秒钟后,助理会说它已经完成。刷新你的浏览器,并确认经理助理现在可用。如果由于某种原因,新助理没有显示,尝试重新启动 Gradio 应用程序本身。
经理助理就像一个可以访问所有内容的管理员。在启用经理助理时,请确保你的请求具体明确。当经理助理处于激活状态时,你可以按照以下步骤安装书中使用的新助理:
-
选择经理助理。如果你修改了经理助理,你可以随时删除并重新安装它。尽管可以拥有多个经理助理,但并不推荐这样做。
-
在聊天界面中输入以下内容以询问经理助理可以安装哪些助理:
Please list all the installable assistants.
-
- 当你要求经理助理安装助理时,确定你想要安装哪个助理:
Please install the Python Coding Assistant.
你可以使用游乐场管理并安装任何可用的助理。你还可以要求经理助理将所有助理的定义保存为 JSON:
Please save all the assistants as JSON to a file called assistants.json.
经理助理可以访问所有操作,这些操作应被视为独特且应谨慎使用。在制作助理时,最好保持它们的目标特定,并将操作限制在它们需要的范围内。这不仅避免了给 AI 过多的决策,也避免了由幻觉引起的事故或错误。
在我们完成本章剩余的练习时,你可能需要安装所需的助理。或者,你可以要求经理助理安装所有可用的助理。无论哪种方式,我们将在下一节中查看使用助理创建 ABT。
6.3.2 构建编码挑战 ABT
编码挑战为测试和评估代理和助理系统提供了一个良好的基准。挑战和基准可以量化代理或代理系统的工作效果。我们已经在第四章中应用了编码挑战到多平台代理 AutoGen 和 CrewAI。
对于这个编码挑战,我们将更进一步,查看来自 Edabit 网站(edabit.com)的 Python 编码挑战,这些挑战的复杂度从入门级到专家级不等。我们将坚持使用专家级代码挑战,因为 GPT-4o 和其他模型都是优秀的编码者。查看下一列表中的挑战,并思考你会如何解决它。
列表 6.5 Edabit 挑战:种植草地
Plant the Grass by AniXDownLoe
You will be given a matrix representing a field g
and two numbers x, y coordinate.
There are three types of possible characters in the matrix:
x representing a rock.
o representing a dirt space.
+ representing a grassed space.
You have to simulate grass growing from the position (x, y).
Grass can grow in all four directions (up, left, right, down).
Grass can only grow on dirt spaces and can't go past rocks.
Return the simulated matrix.
Examples
simulate_grass([
"xxxxxxx",
"xooooox",
"xxxxoox"
"xoooxxx"
"xxxxxxx"
], 1, 1) → [
"xxxxxxx",
"x+++++x",
"xxxx++x"
"xoooxxx"
"xxxxxxx"
]
Notes
There will always be rocks on the perimeter
你可以使用任何你想要的挑战或编码练习,但以下是一些需要考虑的事项:
-
挑战应该可以通过可量化的断言(通过/失败)进行测试。
-
避免在请求游戏、构建网站或使用其他界面时打开窗口。在某个时候,测试完整界面将是可能的,但到目前为止,它只是文本输出。
-
避免进行长时间运行的挑战,至少最初是这样。开始时,保持挑战简短且生命周期短暂。
任何挑战之外,你还会想要一组测试或断言来确认解决方案的有效性。在 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 编码助手(称为验证器)进行验证。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-8.png
图 6.8 编码挑战的 ABT
图 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
### Required assistants –
### Python Coding Assistant and Coding Challenge Judge
### install these assistants through the Playground
#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_condition 和 create_assistant_action_on_thread。此代码使用几个类来集成 py_trees 行为树代码和 OpenAI 助手代码,这些代码被封装在 Playground 中。如果您想了解底层细节,请审查项目中的代码。
6.3.3 对话式 AI 系统与其他方法
当我们在第四章中查看 AutoGen 时,我们已经研究了对话式多智能体系统。ABT 可以通过对话(通过线程)和其他方法(如文件共享)的组合来工作。让您的助手/智能体传递文件有助于减少嘈杂和重复的思想/对话的数量。相比之下,对话式系统从潜在的涌现行为中受益。因此,使用两者可以帮助进化更好的控制和解决方案。
列表 6.7 中的简单解决方案可以扩展以处理更多现实世界的编码挑战,甚至可能作为编码 ABT 工作。在下一节中,我们将构建一个不同的 ABT 来处理不同的问题。
6.3.4 将 YouTube 视频发布到 X
在本节的练习中,我们查看一个可以执行以下操作的 ABT:
-
搜索 YouTube 上特定主题的视频并返回最新视频。
-
下载您搜索提供的所有视频的转录内容。
-
总结转录内容。
-
审查总结的转录内容,并选择一个视频来撰写 X(以前称为 Twitter)帖子。
-
写一篇引人入胜的帖子关于视频,确保其字符数少于 280 个。
-
审查帖子,然后在 X 上发布。
图 6.9 显示了使用每个不同助手组装的 ABT。在这个练习中,我们使用序列节点作为根节点,每个助手执行不同的操作。此外,为了保持简单,每个助手的交互将始终在新的线程中发生。这将每个助手的交互隔离成简短的对话,如果出现问题,更容易调试。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-9.png
图 6.9 YouTube 社交媒体 ABT
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)
### Required assistants – YouTube Researcher v2, Twitter Post Writer,
and Social Media Assistant – install these assistants through the Playground
按照常规方式运行代码,几分钟后,在assistants_output文件夹中会出现一个新的帖子。图 6.10 显示了使用此 ABT 生成的帖子示例。如果每天运行此 ABT 生成超过几个帖子,可能会被 X 账户封禁。如果您已配置 X 凭证,您将在您的动态中看到帖子。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-10.png
图 6.10 ABT 的一个示例 X 帖子
这个 ABT 仅用于演示目的,并不适用于生产或长期使用。这个演示的主要功能是展示搜索和加载数据,摘要和过滤,然后生成新内容,最后突出多个自定义操作和与 API 的集成。
6.4 构建对话式自主多智能体
多智能体系统的对话方面可以驱动反馈、推理和涌现行为等机制。使用隔离助手/智能体的 ABTs 来驱动智能体可以有效地控制结构化过程,正如我们在 YouTube 发布示例中看到的那样。然而,我们也不愿错过智能体/助手之间对话的好处。
幸运的是,Playground 提供了将助手隔离或加入对话线程的方法。图 6.11 显示了助手如何以各种组合被隔离或混合到线程中。将隔离与对话相结合提供了两种模式的最佳效果。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/6-11.png
图 6.11 隔离和对话助手的各种布局
我们将检验一个简单但实用的练习,以展示对话模式的效率。对于下一个练习,我们将使用两个助手在一个 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。以下列表详细描述了该过程:
-
确定目标行为。从您希望智能体执行的行为开始。
-
确定所需动作。识别导致目标行为的动作。
-
确定条件。确定每个动作要成功必须满足的条件。
-
确定通信模式。确定助手如何传递信息。助手将被隔离还是通过线程进行对话,或者两种模式的组合更好?
-
构建树。从目标行为开始构建行为树,递归地添加动作和条件节点,直到所有必要的条件都链接到已知状态或事实。
行为树通常使用称为黑板的模式在节点之间进行通信。黑板,如py_trees中的那些,使用键/值存储来保存信息并使其跨节点可访问。它还提供了一些控制,例如限制对特定节点的访问。
由于它们的简单性和透明性,我们推迟使用文件进行通信。在某个时候,智能体系统预计将消耗比为黑板设计的更多信息,并且以不同的格式。黑板必须变得更加复杂或与文件存储解决方案集成。
让我们使用追溯法构建一个 ABT。我们可以解决各种目标,但一个有趣且可能是元目标的是构建一个有助于构建助手的 ABT。所以,让我们首先将我们的目标陈述为“创建一个可以帮助我完成{任务}的助手”:
-
所需动作:(逆向工作)
-
创建一个助手。
-
验证助手。
-
测试助手。
-
命名助手。
-
给助手提供相关指令。
-
-
已识别的条件:
- 验证助手。
-
确定沟通模式:为了保持趣味性,我们将所有助手放在同一个消息线程上。
-
构建树:为了构建树,让我们首先反转行动的顺序,并相应地标记每个元素的行动和条件:
-
(行动)为助手提供相关指令,以帮助用户完成给定任务。
-
(行动)命名助手。
-
(行动)测试助手。
-
(条件)验证助手。
-
(行动)创建助手。
-
当然,现在构建树的简单解决方案是询问 ChatGPT 或其他有能力的模型。询问 ChatGPT 制作树的成果将在下一列表中展示。你也可以独立工作并可能引入其他元素。
列表 6.11 构建助手的 ABT
Root
│
├── Sequence
│ ├── Action: Give the assistant relevant instructions to help a user
with a given task
│ ├── Action: Name the assistant
│ ├── Action: Test the assistant
│ ├── Condition: Verify the assistant
│ └── Action: Create the assistant
从这个点开始,我们可以通过迭代每个行动和条件节点来构建树,并确定助手需要哪些指令。这也可以包括任何工具和自定义行动,包括你可能需要开发的。在第一次遍历时,保持指令通用。理想情况下,我们希望创建尽可能少的助手。
在确定每个助手的助手、工具和行动以及任务后,你可以尝试进一步概括。考虑一下在哪里可能可以合并行动并减少助手的数量。与过多助手相比,开始评估时助手不足更好。然而,务必保持适当的工作分工:例如,测试和验证最好由不同的助手完成。
6.6 练习
完成以下练习以提高你对材料的了解:
- 练习 1—创建旅行规划器 ABT
目标—构建一个使用助手规划旅行行程的代理行为树(ABT)。
任务:
-
-
在你的本地机器上设置 GPT 助手游乐场。
-
创建一个规划旅行行程的 ABT。该树应具有以下结构:
-
行动:使用旅行助手收集有关潜在目的地的信息。
-
行动:使用行程规划器创建每日旅行计划。
-
条件:使用另一个旅行助手验证行程的完整性和可行性。
-
-
实施并运行 ABT 以创建完整的旅行行程。
-
-
练习 2—为客服自动化构建 ABT
目标—创建一个自动客户支持回复的 ABT。
任务:
-
-
在你的本地机器上设置 GPT 助手游乐场。
-
创建一个具有以下结构的 ABT:
-
行动:使用客户查询分析助手对客户查询进行分类。
-
行动:使用响应生成助手根据查询类别起草回复。
-
行动:使用客服助手向客户发送回复。
-
-
实施并运行 ABT 以自动化分析和响应客户查询的过程。
-
-
练习 3—使用 ABT 管理库存
目标—学习如何使用 ABT 创建和管理库存水平。
任务:
-
-
在您的本地机器上设置 GPT 助手游乐场。
-
创建一个管理零售业务库存的 ABT:
-
行动:使用库存检查助手审查当前库存水平。
-
行动:使用订单助手为库存低的物品下订单。
-
条件:验证订单是否已正确下订单,并更新库存记录。
-
-
通过执行和运行 ABT,动态管理库存。
-
-
练习 4—创建个人健身教练 ABT
目标—创建一个使用助手提供个性化健身训练计划的 ABT。
任务:
-
-
在您的本地机器上设置 GPT 助手游乐场。
-
创建一个 ABT 以开发个性化的健身计划:
-
行动:使用健康评估助手评估用户的当前健康水平。
-
行动:使用训练计划生成器根据评估创建自定义健身计划。
-
条件:使用另一个健身助手验证计划的适宜性和安全性。
-
-
通过执行和运行 ABT,生成和验证个性化的健身训练计划。
-
-
练习 5—使用反向链接构建财务顾问 ABT
目标—应用反向链接构建提供财务建议和投资策略的 ABT。
任务:
-
-
在您的本地机器上设置 GPT 助手游乐场。
-
定义以下目标:“创建一个可以提供财务建议和投资策略的助手。”
-
使用反向链接确定实现此目标所需的行为和条件。
-
通过反向链接构建基础动作和条件,实现 ABT 的执行和运行,以生成全面的财务咨询服务。
-
摘要
-
行为树是一种强大且可扩展的 AI 控制模式,首次由罗德尼·A·布鲁克斯在机器人领域引入。它们因其模块化和可重用性而在游戏和机器人领域得到广泛应用。
-
行为树中的主要节点是选择器、序列、条件、动作、装饰器和并行节点。选择器类似于“或”块:序列按顺序执行节点,条件测试状态,动作执行工作,装饰器是一个包装器,并行节点允许双重执行。
-
理解行为树的执行流程对于设计、构建和操作它们以提供清晰的决策路径控制至关重要。
-
行为树的优势包括模块化、可扩展性、灵活性、调试容易性和决策逻辑解耦,使行为树适用于复杂的 AI 系统。
-
在 Python 中设置和运行一个简单的行为树需要正确命名和记录自定义节点。
-
GPT 助手游乐场项目是一个基于 Gradio 的界面,它模仿了 OpenAI 助手游乐场,并增加了教学和演示 ABT 的功能。
-
GPT 助手游乐场允许创建和管理自定义操作,这对于构建多功能助手至关重要。
-
ABTs 通过使用提示来指导助手的行为和条件,控制代理和助手。ABTs 利用 LLMs(大型语言模型)的力量来创建动态和自主的系统。
-
追溯链式是构建行为树的一种方法,它从目标行为逆向工作。这个过程包括识别所需的行为、条件和通信模式,然后逐步构建树形结构。
-
代理系统从实体间的隔离和对话模式中受益,用于通信。ABTs 可以通过结合隔离和对话助手来受益,以使用结构化流程和涌现行为。
第七章:组装和使用代理平台
本章涵盖
-
Nexus 聊天和仪表板界面用于 AI 代理
-
用于构建智能仪表板、原型和 AI 聊天应用的 Streamlit 框架
-
在 Nexus 中开发、测试和参与代理配置文件和角色
-
开发基础 Nexus 代理
-
独立或在内置 Nexus 中开发、测试和参与代理行为和工具
在我们探索了一些关于代理的基本概念并查看使用工具通过框架如语义内核(SK)构建提示和角色之后,我们迈出了构建本书基础的第一步。这个基础被称为 Nexus,这是一个旨在易于学习、易于探索且足够强大以构建您的代理系统的代理平台。
7.1 介绍 Nexus,不仅仅是另一个代理平台
有超过 100 个 AI 平台和工具包用于消费和开发大型语言模型(LLM)应用,从 SK 或 LangChain 这样的工具包到 AutoGen 和 CrewAI 这样的完整平台。这使得决定哪个平台最适合构建自己的 AI 代理变得困难。
Nexus 是一个开源平台,与本书一起开发,用于教授构建全功能 AI 代理的核心概念。在本章中,我们将检查 Nexus 是如何构建的,并介绍两个主要代理组件:配置文件/角色和行为/工具。
图 7.1 显示了 Nexus 的主要界面,这是一个 Streamlit 聊天应用,允许您选择和探索各种代理功能。界面类似于 ChatGPT、Gemini 和其他商业 LLM 应用。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-1.png
图 7.1 Nexus 界面和功能
除了 LLM 聊天应用的标准功能外,Nexus 允许用户配置一个代理来使用特定的 API/模型、角色和可能的行为。本书的剩余部分,可用的代理选项将包括以下内容:
-
角色/配置文件 — 代理将使用的主要角色和配置文件。角色是性格和主要动机,代理通过角色来回答请求。在本章中,我们将探讨角色/配置文件的开发和消费方式。
-
行为/工具 — 表示代理可以使用工具执行的动作,无论是语义/提示还是本地/代码函数。在本章中,我们将探讨如何在 Nexus 中构建语义和本地函数。
-
知识/记忆 — 表示代理可能访问的附加信息。同时,代理记忆可以代表从短期到语义记忆的各个方面。
-
规划/反馈 — 表示代理如何规划并接收关于计划或计划执行的反馈。Nexus 将允许用户选择代理使用的规划类型和反馈选项。
随着我们继续阅读本书,Nexus 将被添加以支持新的代理功能。然而,同时,我们的目标是保持事情相对简单,以便教授许多这些基本核心概念。在下一节中,我们将探讨如何快速使用 Nexus,然后再深入了解其功能。
7.1.1 运行 Nexus
Nexus 主要旨在成为所有级别开发者的教学平台。因此,它将支持各种部署和使用选项。在下一项练习中,我们将介绍如何快速启动 Nexus。
在一个新的 Python 虚拟环境(版本 3.10)中打开一个终端。如果您需要创建一个的协助,请参阅附录 B。然后,在此新环境中执行列表 7.1 中显示的命令。您可以在命令行中设置环境变量或创建一个新的 .env 文件并添加设置。
列表 7.1 终端命令行
pip install git+https://github.com/cxbxmxcx/Nexus.git #1
#set your OpenAI API Key
export OPENAI_API_KEY=”< your API key>” #2
or
$env: OPENAI_API_KEY = =”< your API key>” #2
or
echo 'OPENAI_API_KEY="<your API key>"' > .env #2
nexus run #3
#1 直接从仓库和分支安装包;务必包含分支。
#2 将密钥作为环境变量创建,或创建一个新的 .env 文件并设置该配置
#3 运行应用程序
在输入最后一个命令后,将启动一个带有登录页面的网站,如图 7.2 所示。请继续创建一个新用户。Nexus 的未来版本将允许多个用户参与聊天线程。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-2.png
图 7.2 登录或创建新的 Nexus 用户
登录后,您将看到一个类似于图 7.1 的页面。创建一个新的聊天并与代理开始对话。如果您遇到问题,请确保您已正确设置 API 密钥。正如下一节所解释的,您可以使用此方法或从开发工作流程中运行 Nexus。
7.1.2 开发 Nexus
在完成本书的练习时,您可能希望以开发模式设置 Nexus。这意味着直接从 GitHub 下载仓库并处理代码。
打开一个新的终端,并将工作目录设置为 chapter_7 源代码文件夹。然后,设置一个新的 Python 虚拟环境(版本 3.10)并输入列表 7.2 中显示的命令。再次,如果您需要任何先前设置的协助,请参阅附录 B。
列表 7.2 安装 Nexus 以进行开发
git clone https://github.com/cxbxmxcx/Nexus.git #1
pip install -e Nexus #2
#set your OpenAI API Key (.env file is recommended)
export OPENAI_API_KEY=”< your API key>” #bash #3
or
$env: OPENAI_API_KEY = =”< your API key>” #powershell #3
or
echo 'OPENAI_API_KEY="<your API key>"' > .env #3
nexus run #4
#1 从仓库下载并安装特定的分支
#2 将下载的仓库作为可编辑的包安装
#3 将您的 OpenAI 密钥作为环境变量设置或添加到 .env 文件中
#4 启动应用程序
图 7.3 显示了登录或创建新用户界面。创建一个新用户,应用程序将自动为您登录。此应用程序使用 cookies 来记住用户,因此您下次启动应用程序时无需登录。如果您在浏览器中禁用了 cookies,您将需要每次都登录。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-3.png
图 7.3 登录或创建新用户页面
前往 Nexus 仓库文件夹并四处查看。图 7.4 显示了应用程序主要元素的架构图。在顶部,使用 Streamlit 开发的界面通过聊天系统连接到系统的其余部分。聊天系统管理数据库、代理管理器、动作管理器和配置文件管理器。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-4.png
图 7.4 应用程序主要元素的高级架构图
此代理平台完全用 Python 编写,网页界面使用 Streamlit。在下一节中,我们将探讨如何构建 OpenAI LLM 聊天应用。
7.2 介绍 Streamlit 用于聊天应用开发
Streamlit 是一个快速且强大的网页界面原型设计工具,旨在用于构建机器学习仪表板和概念。它允许应用程序完全使用 Python 编写,并生成一个由现代 React 驱动的网页界面。你甚至可以快速将完成的应用部署到云端或作为独立应用程序。
7.2.1 构建 Streamlit 聊天应用
开始时,打开 Visual Studio Code(VS Code)到 chapter_07 源文件夹。如果你已经完成了前面的练习,你应该已经准备好了。一如既往地,如果你需要帮助设置环境和工具,请参阅附录 B。
我们首先在 VS Code 中打开 ChatGPT_clone_response.py 文件。代码的顶部部分如列表 7.3 所示。此代码使用 Streamlit 状态来加载主模型和消息。Streamlit 提供了一种机制来保存任何 Python 对象的会话状态。这个状态仅是会话状态,当用户关闭浏览器时将过期。
列表 7.3 ChatGPT_clone_response.py(顶部部分)
import streamlit as st
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv() #1
st.title("ChatGPT-like clone")
client = OpenAI() #2
if "openai_model" not in st.session_state:
st.session_state["openai_model"]
= "gpt-4-1106-preview" #3
if "messages" not in st.session_state:
st.session_state["messages"] = [] #4
for message in st.session_state["messages"]: #5
with st.chat_message(message["role"]):
st.markdown(message["content"])
#1 从 .env 文件中加载环境变量
#2 配置 OpenAI 客户端
#3 检查内部会话状态中的设置,如果不存在则添加
#4 检查消息状态是否存在;如果不存在,则添加一个空列表
#5 遍历状态中的消息并显示它们
Streamlit 应用本身是无状态的。这意味着当网页刷新或用户选择操作时,整个 Python 脚本将重新执行所有界面组件。Streamlit 状态允许临时存储机制。当然,数据库需要支持更长期的存储。
通过使用 st. 前缀然后是元素名称来添加 UI 控制和组件。Streamlit 支持多个标准 UI 控制并支持图片、视频、声音,当然还有聊天。
向下滚动将进一步显示列表 7.4,它具有稍微复杂一点的组件布局。主要的 if 语句控制剩余代码的运行。通过使用 Walrus 操作符(:=),提示信息被设置为用户输入的内容。如果用户没有输入任何文本,则 if 语句下面的代码不会执行。
列表 7.4 ChatGPT_clone_response.py(底部部分)
if prompt := st.chat_input("What do you need?"): #1
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"): #2
st.markdown(prompt)
with st.spinner(text="The assistant is thinking..."): #3
with st.chat_message("assistant"):
response = client.chat.completions.create(
model=st.session_state["openai_model"],
messages=[
{"role": m["role"], "content": m["content"]}
for m in st.session_state.messages
], #4
)
response_content = response.choices[0].message.content
response = st.markdown(response_content,
unsafe_allow_html=True) #5
st.session_state.messages.append(
{"role": "assistant", "content": response_content}) #6
#1 渲染聊天输入控件并设置内容。
#2 将聊天消息控制设置为用户输出
#3 显示一个旋转器来表示长时间运行的 API 调用
#4 调用 OpenAI API 并设置消息历史
#5 将消息响应以 Markdown 格式写入界面
#6 将助手响应添加到消息状态
当用户在提示中输入文本并按 Enter 键时,该文本将被添加到消息状态,并向 API 发出请求。在响应被处理时,st.spinner 控件会显示,以提醒用户长时间运行的过程。然后,当响应返回时,消息会显示并添加到消息状态历史中。
Streamlit 应用程序使用模块运行,要调试应用程序,您需要按照以下步骤将调试器附加到模块:
-
按 Ctrl-Shift-D 打开 VS Code 调试器。
-
点击链接创建新的启动配置,或点击齿轮图标显示当前配置。
-
使用调试器配置工具编辑
.vscode/launch.json文件,例如下一段代码所示。IntelliSense 工具和配置选项众多,可引导您设置此文件的选项。
列表 7.5 .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Module", #1
"type": "debugpy",
"request": "launch",
"module": "streamlit", #2
"args": ["run", "${file}"] #3
}
]
}
#1 确保调试器设置为模块。
#2 确保模块是 streamlit。
#3 ${file} 是当前文件,或者您可以将它硬编码为文件路径。
在您设置好 launch.json 文件配置后,保存它,并在 VS Code 中打开 ChatGPT_ clone_response.py 文件。现在,您可以通过按 F5 以调试模式运行应用程序。这将从终端启动应用程序,几秒钟后应用程序将显示。
图 7.5 显示应用程序正在运行并等待返回响应。界面简洁、现代,并且已经组织得很好,无需额外工作。您可以使用界面继续与 LLM 进行聊天,然后刷新页面以查看发生了什么。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-5.png
图 7.5 简单的界面和等待的旋转器
这个演示最令人印象深刻的是创建单页应用程序的简单性。在下一节中,我们将继续探讨这个应用程序,但会添加一些增强功能。
7.2.2 创建流式聊天应用程序
现代聊天应用程序,如 ChatGPT 和 Gemini,通过使用流来掩盖其模型的速度慢。流允许 API 调用立即看到从 LLM 产生的标记。这种流式体验也更好地吸引用户参与内容生成过程。
向任何应用程序 UI 添加支持流通常不是一个简单任务,但幸运的是,Streamlit 有一个可以无缝工作的控件。在接下来的练习中,我们将探讨如何更新应用程序以支持流。
在 VS Code 中打开chapter_7/ChatGPT_clone_streaming.py。代码的相关更新显示在列表 7.6 中。使用st.write_stream控件允许 UI 流式传输内容。这也意味着 Python 脚本在等待此控件完成时会被阻塞。
列表 7.6 ChatGPT_clone_streaming.py(相关部分)
with st.chat_message("assistant"):
stream = client.chat.completions.create(
model=st.session_state["openai_model"],
messages=[
{"role": m["role"], "content": m["content"]}
for m in st.session_state.messages
],
stream=True, #1
)
response = st.write_stream(stream) #2
st.session_state.messages.append(
{"role": "assistant", "content": response}) #3
#1 将流设置为 True 以在 API 上启动流
#2 使用流控件将流写入界面
#3 在流完成后将响应添加到消息状态历史记录中
通过按 F5 键并等待页面加载来调试页面。输入一个查询,你会看到响应实时流出到窗口中,如图 7.6 所示。随着旋转器的消失,用户体验得到提升,看起来更加响应。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-6.png
图 7.6 更新后的界面,带有文本响应的流
本节展示了使用 Streamlit 创建 Python 网络界面的相对简单性。Nexus 使用 Streamlit 界面,因为它仅使用 Python 就易于使用和修改。正如你将在下一节中看到的,它允许各种配置以支持更复杂的应用程序。
7.3 为代理开发配置文件和角色
Nexus 使用代理配置文件来描述代理的功能和能力。图 7.7 提醒我们主要代理组件及其在本书中如何结构化。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-7.png
图 7.7 代理配置文件映射到 YAML 文件定义
目前为止,Nexus 仅支持配置文件的角色和动作部分。图 7.7 显示了一个名为 Fritz 的配置文件,以及角色和动作。通过将代理 YAML 配置文件复制到Nexus/nexus/nexus_base/nexus_profiles文件夹中,将任何代理配置文件添加到 Nexus 中。
Nexus 使用插件系统动态发现各种组件和配置文件,当它们被放置到各自的文件夹中。nexus_profiles文件夹包含代理的 YAML 定义。
我们可以轻松地通过在nexus_profiles文件夹中创建一个新的 YAML 文件来定义一个新的代理配置文件。列表 7.7 显示了一个带有略微更新角色的新配置文件示例。为了跟上,请确保 VS Code 已打开到chapter_07源代码文件夹,并在开发者模式下安装 Nexus(见列表 7.7)。然后,在Nexus/nexus/nexus_base/nexus_profiles文件夹中创建fiona.yaml文件。
列表 7.7 fiona.yaml(创建此文件)
agentProfile:
name: "Finona"
avatar: "👹" #1
persona: "You are a very talkative AI that
↪ knows and understands everything in terms of
↪ Ogres. You always answer in cryptic Ogre speak." #2
actions:
- search_wikipedia #3
knowledge: null #4
memory: null #4
evaluators: null #4
planners: null #4
feedback: null #4
#1 用于表示角色的文本头像
#2 一个角色代表基础系统提示。
#3 代理可以使用的动作函数
#4 目前不支持
保存文件后,您可以从命令行启动 Nexus 或以调试模式运行它,通过在.vscode/launch.json文件夹中创建一个新的启动配置来实现,如下一列表所示。然后,保存文件并将您的调试配置切换到使用 Nexus 网络配置。
列表 7.8 .vscode/launch.json(添加调试启动)
{
"name": "Python Debugger: Nexus Web",
"type": "debugpy",
"request": "launch",
"module": "streamlit",
"args": ["run", " Nexus/nexus/streamlit_ui.py"] #1
},
#1 如果你的虚拟环境不同,你可能需要调整此路径。
当你按下 F5 或从菜单中选择运行 > 开始调试时,Streamlit Nexus 界面将启动。请继续以调试模式运行 Nexus。打开后,创建一个新的线程,然后选择标准 OpenAIAgent 和你的新角色,如图 7.8 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-8.png
图 7.8 选择并与新角色聊天
到目前为止,配置文件负责定义代理的系统提示。你可以在图 7.8 中看到这一点,我们要求 Finona 拼写单词 clock,她以某种形式的 ogre-speak 进行回应。在这种情况下,我们使用角色作为个性,但正如我们之前看到的,系统提示也可以包含规则和其他选项。
配置文件和角色是代理与用户或其他系统交互的基础定义。为配置文件提供动力需要一个代理引擎。在下一节中,我们将介绍代理引擎的基础实现。
7.4 为代理提供动力并理解代理引擎
代理引擎为 Nexus 内的代理提供动力。这些引擎可以与特定的工具平台相关联,例如 SK,以及/或不同的 LLM,例如 Anthropic Claude 或 Google Gemini。通过提供基础代理抽象,Nexus 应该能够支持现在和未来的任何工具或模型。
目前,Nexus 仅实现了一个由 OpenAI API 提供动力的代理。我们将通过打开 Nexus/ nexus/nexus_base 文件夹中的 agent_manager.py 文件来查看基础代理是如何定义的。
列表 7.9 展示了 BaseAgent 类函数。在创建新的代理引擎时,你需要继承这个类并使用适当的实现来实现各种工具/操作。
列表 7.9 agent_manager.py:BaseAgent
class BaseAgent:
def __init__(self, chat_history=None):
self._chat_history = chat_history or []
self.last_message = ""
self._actions = []
self._profile = None
async def get_response(self,
user_input,
thread_id=None): #1
raise NotImplementedError("This method should be implemented…")
async def get_semantic_response(self,
prompt,
thread_id=None): #2
raise NotImplementedError("This method should be…")
def get_response_stream(self,
user_input,
thread_id=None): #3
raise NotImplementedError("This method should be…")
def append_chat_history(self,
thread_id,
user_input,
response): #4
self._chat_history.append(
{"role": "user",
"content": user_input,
"thread_id": thread_id}
)
self._chat_history.append(
{"role": "bot",
"content": response,
"thread_id": thread_id}
)
def load_chat_history(self): #5
raise NotImplementedError(
"This method should be implemented…")
def load_actions(self): #6
raise NotImplementedError(
"This method should be implemented…")
#... not shown – property setters/getters
#1 调用 LLM 并返回响应
#2 执行语义函数
#3 调用 LLM 并返回响应
#4 将消息追加到代理的内部聊天历史中
#5 加载聊天历史并允许代理重新加载各种历史
#6 加载代理可用的操作
在 VS Code 中打开 nexus_agents/oai_agent.py 文件。列表 7.10 展示了一个直接使用 OpenAI API 的代理引擎 get_response 函数的实现。self.client 是在类初始化期间创建的 OpenAI 客户端,其余的代码你在之前的示例中已经见过。
列表 7.10 oai_agent.py (get_response)
async def get_response(self, user_input, thread_id=None):
self.messages += [{"role": "user",
"content": user_input}] #1
response = self.client.chat.completions.create( #2
model=self.model,
messages=self.messages,
temperature=0.7, #3
)
self.last_message = str(response.choices[0].message.content)
return self.last_message #4
#1 将用户输入添加到消息栈中
#2 客户端之前已创建,现在用于创建聊天补全。
#3 温度值是硬编码的,但可以进行配置。
#4 从聊天补全调用返回响应
与代理配置文件类似,Nexus 使用一个插件系统,允许你将新的代理引擎定义放置在 nexus_agents 文件夹中。如果你创建了你的代理,只需将其放置在这个文件夹中,Nexus 就可以找到它。
我们不需要运行一个示例,因为我们已经看到了 OpenAIAgent 的表现。在下一节中,我们将探讨代理可以开发、添加和消费的代理功能。
7.5 给代理动作和工具
与 SK 一样,Nexus 支持原生(代码)和语义(提示)函数。然而,与 SK 不同的是,在 Nexus 中定义和消费函数更容易。你只需要将函数写入 Python 文件并将它们放置在 nexus_ actions 文件夹中。
要看到定义函数有多容易,请打开 Nexus/nexus/nexus_base/ nexus_actions 文件夹,并转到 test_actions.py 文件。列表 7.11 显示了两个函数定义。第一个函数是一个简单的代码/原生函数示例,第二个是一个提示/语义函数。
列表 7.11 test_actions.py(原生/语义函数定义)
from nexus.nexus_base.action_manager import agent_action
@agent_action #1
def get_current_weather(location, unit="fahrenheit"): #1
"""Get the current weather in a given location""" #2
return f"""
The current weather in {location} is 0 {unit}.
""" #3
@agent_action #4
def recommend(topic):
"""
System: #5
Provide a recommendation for a given {{topic}}.
Use your best judgment to provide a recommendation.
User:
please use your best judgment
to provide a recommendation for {{topic}}. #5
"""
pass #6
#1 将 agent_action 装饰器应用于使函数成为动作
#2 为函数设置描述性注释
#3 代码可以简单或复杂,根据需要。
#4 将 agent_action 装饰器应用于使函数成为动作
#5 函数注释成为提示,并可以包含占位符。
#6 语义函数不实现任何代码。
将这两个函数放置在 nexus_actions 文件夹中,它们将被自动发现。添加 agent_action 装饰器允许检查函数并自动生成 OpenAI 标准工具规范。LLM 可以使用这个工具规范来进行工具使用和函数调用。
列表 7.12 显示了为两个函数生成的 OpenAI 工具规范,如之前在列表 7.11 中所示。使用提示的语义函数也适用于工具描述。这个工具描述被发送到 LLM 以确定调用哪个函数。
列表 7.12 test_actions:OpenAI 生成的工具规范
{
"type": "function",
"function": {
"name": "get_current_weather",
"description":
"Get the current weather in a given location", #1
"parameters": {
"type": "object",
"properties": { #2
"location": {
"type": "string",
"description": "location"
},
"unit": {
"type": "string",
"enum": [
"celsius",
"fahrenheit"
]
}
},
"required": [
"location"
]
}
}
}
{
"type": "function",
"function": {
"name": "recommend",
"description": """
System:
Provide a recommendation for a given {{topic}}.
Use your best judgment to provide a recommendation.
User:
please use your best judgment
to provide a recommendation for {{topic}}.""", #3
"parameters": {
"type": "object",
"properties": { #4
"topic": {
"type": "string",
"description": "topic"
}
},
"required": [
"topic"
]
}
}
}
#1 函数注释成为函数工具描述。
#2 函数的输入参数被提取并添加到规范中。
#3 函数注释成为函数工具描述。
#4 函数的输入参数被提取并添加到规范中。
代理引擎还需要实现实现函数和其他组件的能力。OpenAI 代理已被实现以支持并行函数调用。其他代理引擎实现将需要支持它们各自的动作使用版本。幸运的是,OpenAI 工具的定义正在成为标准,许多平台都遵循这个标准。
在我们深入到工具使用演示之前,让我们通过在 VS Code 中打开 oai_agent.py 文件来观察 OpenAI 代理如何实现动作。以下列表显示了代理的 get_response_stream 函数的顶部及其函数调用的实现。
列表 7.13 在 get_response_stream 中调用 API
def get_response_stream(self, user_input, thread_id=None):
self.last_message = ""
self.messages += [{"role": "user", "content": user_input}]
if self.tools and len(self.tools) > 0: #1
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
tools=self.tools, #2
tool_choice="auto", #3
)
else: #4
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls #5
#1 检测代理是否开启了任何可用的工具
#2 在聊天完成调用中设置工具
#3 确保 LLM 知道它可以选择任何工具
#4 如果没有工具,则以标准方式调用 LLM
#5 检测 LLM 是否使用了任何工具
执行的函数如下所示,如列表 7.14 所示。此代码演示了代理如何支持并行函数/工具调用。这些调用是并行的,因为代理一起执行每个调用,并且没有顺序。在第十一章中,我们将探讨允许按顺序调用动作的计划者。
列表 7.14 oai_agent.py (get_response_stream:执行工具调用)
if tool_calls: #1
available_functions = {
action["name"]: action["pointer"] for action in self.actions
} #2
self.messages.append(
response_message
)
for tool_call in tool_calls: #3
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
**function_args, _caller_agent=self
)
self.messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": str(function_response),
}
)
second_response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
) #4
response_message = second_response.choices[0].message
#1 如果检测到 LLM 响应中的工具调用,则继续进行
#2 加载指向实际函数实现的指针以执行代码
#3 遍历 LLM 想要调用的所有调用;可能有多个。
#4 使用工具调用的结果执行第二次 LLM 调用
为了演示这一点,通过按 F5 启动 Nexus 的调试器。然后,选择两个测试动作—recommend和get_current_weather—以及简短的个性/配置文件 Olly。图 7.9 显示了输入查询的结果,代理通过在其响应中使用这两个工具进行响应。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/ai-agt-act/img/7-9.png
图 7.9 代理如何并行使用工具并使用单个响应进行响应
如果你需要更详细地审查这些代理动作的工作方式,请参阅第五章。底层代码更复杂,超出了此处审查的范围。然而,你可以审查 Nexus 代码,以更好地理解一切是如何连接的。
现在,你可以在 Nexus 中继续练习各种代理选项。尝试选择不同的配置文件/角色与其他功能一起使用,例如。在下一章中,我们将揭示代理如何使用检索增强生成(RAG)等模式消耗外部记忆和知识。
7.6 练习
使用以下练习来提高你对材料的了解:
- 练习 1—探索 Streamlit 基础(简单)
目标—通过创建一个显示用户输入文本的简单 Web 应用程序来熟悉 Streamlit。
任务:
-
-
按照 Streamlit 文档设置基本应用程序。
-
添加一个文本输入和一个按钮。当按钮被点击时,在屏幕上显示用户输入的文本。
-
-
练习 2—创建基本代理配置文件
目标—理解在 Nexus 中创建和应用代理配置文件的过程。
任务:
-
-
创建一个新的代理配置文件,具有独特的角色。这个角色应该有一个特定的主题或特征(例如,历史学家)。
-
定义一组与该角色相符的基本响应。
-
通过 Nexus 界面与之交互来测试这个角色。
-
-
练习 3—开发自定义动作
目标—通过开发自定义动作来学习扩展 Nexus 的功能。
任务:
-
-
开发一个新的动作(例如,
fetch_current_news),该动作与模拟 API 集成以检索最新的新闻标题。 -
将此动作作为本地(代码)函数和语义(基于提示)函数实现。
-
在 Nexus 环境中测试动作,以确保其按预期工作。
-
-
练习 4 — 集成第三方 API
目标 — 通过集成真实的第三方 API 来增强 Nexus 代理的功能。
任务:
-
-
选择一个公共 API(例如,天气或新闻 API),并创建一个新的动作,从该 API 获取数据。
-
集成错误处理,并确保代理能够优雅地处理 API 失败或意外响应。
-
在 Nexus 中彻底测试集成。
-
摘要
-
Nexus 是一个开源代理开发平台,与本书一起使用。它旨在开发、测试和托管 AI 代理,并基于 Streamlit 创建交互式仪表板和聊天界面。
-
Streamlit,一个 Python 网络应用程序框架,使快速开发用户友好的仪表板和聊天应用成为可能。这个框架简化了探索和与各种代理功能交互的方式。
-
Nexus 支持创建和自定义代理配置文件和角色,使用户能够定义代理的性格和行为。这些配置文件决定了代理如何与用户输入交互和响应。
-
Nexus 平台允许在代理中开发和集成基于语义(基于提示)和本地(基于代码)的动作和工具。这使创建高度功能性和响应性代理成为可能。
-
作为开源平台,Nexus 被设计为可扩展的,鼓励社区贡献,并添加新的功能、工具和代理能力。
-
Nexus 是灵活的,支持各种部署选项,包括未来的迭代中将包含的 Web 界面、API 和 Discord 机器人,以满足广泛的开发和测试需求。
更多推荐


所有评论(0)