使用 Colang 2.0 和事件接口构建 Bot#
在使用 Colang 2.0 和事件接口构建 Bot 之前,请确保您已按照Docker 环境部分中的步骤操作。
NVIDIA ACE Agent 是一个 SDK,可帮助您使用大型语言模型 (LLM) 构建您的领域对话式 AI 代理。在本教程中,您将学习如何使用 ACE Agent 以及如何创建使用 Colang 2.0 和异步事件处理的简单 Bot。该 Bot 具有以下功能
不同的响应类型 - 该 Bot 将使用手势、话语以及在 UI 上显示信息。
LLM 集成 - 该 Bot 将不同地利用 LLM 来提供上下文答案并简化用户输入处理。
主动性 - 如果没有给出回复,Bot 将是主动的,并将尝试与用户互动。
可中断性 - 用户可以随时中断 Bot。
后台信道 - Bot 可以根据正在进行的用户输入实时做出反应,使您的互动更有趣。
业务逻辑 - Bot 可以利用自定义 Python 函数或 API 调用来集成业务逻辑。
在开始之前,您需要了解两个关键术语。
Colang - Colang 是 ACE Agent 使用的对话建模语言。Colang 使您可以轻松定义和控制对话系统的行为,尤其是在需要确定性行为的情况下。ACE Agent 构建于 NVIDIA NeMo Guardrails 之上,后者也使用 Colang 语言。当前的 ACE Agent 版本支持两个不同的版本(Colang 1.0 和 Colang 2.0-beta)。在本教程中,我们将使用 Colang 2.0-beta 语法。Colang 1.0 将在未来的版本中弃用。本教程不需要任何关于 Colang 的先验知识,因为我们将逐步引导您完成所有更改。有关 Colang 的更多信息,请参阅入门指南文档。
事件接口 - ACE Agent 事件接口提供了一个异步的、基于事件的接口,用于与以 Colang 2.0 编写的 Bot 进行交互。该接口由于其异步设计,使您在管理用户和 Bot 之间的交互方面具有更大的灵活性。使用该接口,您可以轻松构建打破轮流行为(用户说话、Bot 说话、用户说话...)的交互式系统,并且您可以处理同时进行的多个动作(例如,Bot 说话、做手势以强调某一点、用户四处张望并在短时间后中断 Bot)。
事件接口最适合更复杂的交互式系统。这方面的一个例子是交互式头像系统,您不仅要控制 Bot 响应,还要控制手势、姿势、声音、显示图片等等。NVIDIA Tokkio 参考应用程序(ACE 的一部分)是如何使用 ACE Agent 的事件接口构建真正的交互式头像系统的绝佳起点。有关更多信息,NVIDIA Tokkio 演示了通过 Web 浏览器访问的 3D 头像的此类交互。
先决条件#
设置作为 快速入门资源的一部分打包的事件模拟器,位于
clients/event_client/
。要运行事件模拟器脚本,您需要安装一些 Python 包。建议您创建一个新的 Python 环境来执行此操作。cd clients/event_client/ python3 -m venv sim-env source sim-env/bin/activate pip install -r requirements.txt
测试模板。
打开两个不同的终端。
在一个终端中,使用 快速入门资源中
samples/event_interface_tutorial_bot
中打包的 Bot 模板启动 ACE Agent。设置 OpenAI API 密钥环境变量。
export OPENAI_API_KEY=... export BOT_PATH=samples/event_interface_tutorial_bot/step_0 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d
在单独的终端中,启动事件模拟器 CLI。
# Make sure that you are in the correct folder and that you have activated the python environment cd clients/event_client/ source sim-env/bin/activate python event_client.py
要确认测试成功,您应该在左侧的聊天部分看到消息欢迎来到本教程。有关更多信息,请参阅示例事件客户端部分。
注意
您可以在快速入门资源的
samples/event_interface_tutorial_bot/step_x
下找到所有各个步骤的源代码。您可以按照教程逐步进行必要的更改,或者您可以尝试运行每个步骤对应的文件夹中的 Bot 版本(如下主题中所述)。
步骤 1:使问候语更有趣#
在本节中,您将看到如何使用不同类型的响应来使来自 Bot 的问候语或任何其他响应更有趣。如果您将 Bot 连接到 ACE 等系统以驱动 2D 或 3D 头像,则可以使用下面概述的完全相同的 Bot 代码来触发多模态响应,例如头像手势或语音。
如果您想继续学习,请从 samples/event_interface_tutorial_bot/step_0
中的 Bot 开始应用以下更改。步骤 1 中所有更改的最终 Bot 代码可以在 samples/event_interface_tutorial_bot/step_1
中找到。
更改
main.co
文件顶部的现有流bot express greetings
。该流应类似于@meta(bot_intent=True) flow bot express greeting (bot express "Hi there!" or bot express "Welcome!" or bot express "Hello!") and bot gesture "Wave with one hand"
通过在主流中的
bot express greeting
行之前添加语句start scene show short information "Welcome to this tutorial interaction" as $intro_ui
,在 UI 中显示问候语,如下所示。# The bot greets the user and a welcome message is shown on the UI start scene show short information "Welcome to this tutorial interaction" as $intro_ui bot express greeting
重启更新后的 Bot 和事件模拟器。
docker compose -f deploy/docker/docker-compose.yml down export BOT_PATH=samples/event_interface_tutorial_bot/step_1 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d python event_client.py
除了问候消息外,我们还在左侧的运动区域显示了一个 Bot 手势,持续两秒钟,并在右侧显示了一个 UI。要让 3D 交互式头像说“欢迎!”,向摄像头挥手,并在视图中显示适当的 UI,您可以使用完全相同的 Colang 代码。
步骤 2:利用 LLM 回答用户问题#
在本节中,您将使 Bot 能够根据大型语言模型 (LLM) 回答任何用户问题。
如果您想继续学习,请从 samples/event_interface_tutorial_bot/step_1
中的 Bot 开始应用以下更改。步骤 2 中所有更改的最终 Bot 代码可以在 samples/event_interface_tutorial_bot/step_2
中找到。
提供一般说明。打开文件
samples/event_interface_tutorial_bot/step_2/bot_config.yml
。在instructions
下是一个通用 LLM 说明的示例。暂时保留这些说明,但是,您可以尝试不同的说明,看看如何更改 Bot 提供的答案类型。instructions: - type: "general" content: | Below is a conversation between Emma, a helpful interactive avatar assistant (bot), and a user. The bot is designed to generate human-like actions based on the user actions that it receives. [...]
启用 LLM 回退。更新您的
main.co
文件,如下所示。您只需要更新CHANGE
部分。确保在进行更改时不要重复流。如果您定义了相同的流两次,则后面的定义将覆盖第一个定义。您的main.co
文件应如下所示import core import avatars import llm @meta(bot_intent=True) flow bot express greeting (bot express "Hi there!" or bot express "Welcome!" or bot express "Hello!") and bot gesture "Wave with one hand" # CHANGE 1 # Add two flows to handle ending the conversation: bot express goodbye, user expressed done @meta(bot_intent=True) flow bot express goodbye (bot express "Goodbye" or bot express "Talk to you soon!") and bot gesture "bowing in goodbye" @meta(user_intent=True) flow user expressed done user said (regex("(?i).*done.*|.*end.*demo.*|.*exit.*")) flow handling user requests until user expressed done # CHANGE 2 # This activates LLM-based responses for all unhandled user intents activate llm continuation # CHANGE 3 (optional) # This will generated a variation of the question (variations are generated by the LLM) bot say something like "How can I help you today?" # CHANGE 4 # When the user expressed we end the conversation user expressed done bot express goodbye # The main flow is the entry point @meta(exclude_from_llm=True) flow main # Technical flows, see Colang 2.0 documentation for more details activate notification of undefined flow start activate notification of colang errors activate tracking bot talking state # The bot greets the user and a welcome message is shown on the UI start scene show short information "Welcome to this tutorial interaction" as $intro_ui bot express greeting # CHANGE 5 # Start handling user requests handling user requests until user expressed done # This will prevent the main flow finishing ever wait indefinitely
重启更新后的 Bot 和事件模拟器。
docker compose -f deploy/docker/docker-compose.yml down export BOT_PATH=samples/event_interface_tutorial_bot/step_2 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d python event_client.py
向 Bot 提出任何问题,这些问题将由 LLM 根据提供的一般说明回答。
步骤 3:使 Bot 更主动#
在本节中,我们将添加一个主动性功能,使与您的 Bot 的对话感觉更自然。当用户沉默指定的时间量后,Bot 将生成适当的话语。这有助于推动对话向前发展,并可用于向用户提供更多信息或帮助。
如果您想继续学习,请从 samples/event_interface_tutorial_bot/step_2
中的 Bot 开始应用以下更改。步骤 3 中所有更改的最终 Bot 代码可以在 samples/event_interface_tutorial_bot/step_3
中找到。
向
main.co
添加新流,并在handling user requests until user expressed done
流内部激活该流。[...] [...] # CHANGE 1: Add new flow that reacts to the user being silent flow handling user silence user was silent 15.0 llm continue interaction flow handling user requests until user expressed done activate llm continuation # CHANGE 2: Activate the new flow activate handling user silence bot say something like "How can I help you today?" user expressed done bot express goodbye [...]
有了这个,当用户沉默至少 15 秒时,我们将利用 LLM 生成 Bot 响应。
重启更新后的 Bot 和事件模拟器。
docker compose -f deploy/docker/docker-compose.yml down export BOT_PATH=samples/event_interface_tutorial_bot/step_3 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d python event_client.py
在实用工具部分查找正在倒计时的计时器。当计时器结束时,Bot 将跟进用户。
步骤 4:中断 Bot#
当人类彼此交谈时,我们经常互相打断,以澄清某些观点或告诉某人您已经知道他们在说什么。借助 ACE Agent 事件接口和 Colang 2.0,我们可以通过对当前 Bot 进行一些小的更改来轻松实现这一点。
如果您想继续学习,请从 samples/event_interface_tutorial_bot/step_3
中的 Bot 开始应用以下更改。步骤 4 中所有更改的最终 Bot 代码可以在 samples/event_interface_tutorial_bot/step_4
中找到。
在
handling user requests until user expressed done
流内部激活以下流。flow handling user requests until user expressed done [...] # Allow the user to interrupt the bot at anytime activate interruption handling bot talking $mode="interrupt" bot say something like "How can I help you today?" [...]
此流处理用户的所有中断。
要求 Bot 讲述一个关于某事的故事,使 Bot 回复一个长句子。
当 Bot 正在响应时,输入一些内容来中断它。
重启更新后的 Bot 和事件模拟器。
docker compose -f deploy/docker/docker-compose.yml down export BOT_PATH=samples/event_interface_tutorial_bot/step_4 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d python event_client.py
步骤 5:后台信道#
后台信道意味着 Bot 可能会根据用户输入提供简短的反应,以使交互更具吸引力。对于本教程,我们将使用一个非常简单的示例:在交互结束时,我们将要求用户提供电子邮件地址。当用户输入电子邮件地址时,Bot 将提供上下文反馈。
如果您想继续学习,请从 samples/event_interface_tutorial_bot/step_4
中的 Bot 开始应用以下更改。步骤 5 中所有更改的最终 Bot 代码可以在 samples/event_interface_tutorial_bot/step_5
中找到。
将以下流添加到文件
main.co
的顶部。
# CHANGE 1: Add two new user intent flows @meta(user_intent=True) flow user confirmed user has selected choice "yes" or user said "yes" or user said "ok" or user said "that's ok" or user said "yes why not" or user said "sure" @meta(user_intent=True) flow user denied user has selected choice "no" or user said "no" or user said "don't do it" or user said "I am not OK with this" or user said "cancel" # CHANGE 2: Add flow that asks user for email address using UI and voice flow ask for user email start VisualChoiceSceneAction(prompt= "Would you share your e-mail?", support_prompts=["You can just type 'yes' or 'no'","Or just click on the buttons below"],choice_type="selection", allow_multiple_choices=False, options= [{"id": "yes", "text": "Yes"}, {"id": "no", "text": "No"}]) as $confirmation_ui bot say "I would love to keep in touch. Would you be OK to give me your e-mail address?" when user confirmed send $confirmation_ui.Stop() bot ask "Nice! Please enter a valid email address to continue" start VisualFormSceneAction(prompt="Enter valid email",inputs=[{"id": "email", "description": "email address", "value" : ""}]) as $action while True when VisualFormSceneAction.InputUpdated(interim_inputs=[{"id": "email", "value" : regex("@$")}]) bot say "And now only the domain missing!" or when VisualFormSceneAction.InputUpdated(interim_inputs=[{"id": "email", "value" : regex("^[-\w\.]+@([\w-]+\.)+[\w-]{2,4}$")}]) bot say "Looks like a valid email address to me, just click ok to confirm" and bot gesture "success" or when VisualFormSceneAction.ConfirmationUpdated(confirmation_status="confirm") bot say "Thank you" and bot gesture "bowing" break or when VisualFormSceneAction.ConfirmationUpdated(confirmation_status="cancel") bot say "OK. Maybe another time." break or when user denied bot say "That is OK"
在 Bot 表示
goodbye
之前,在handling user requests until user expressed done
流的末尾添加流ask for user email
。flow handling user requests until user expressed done [...] user expressed done # Run the flow that asks the user for email address and await it ask for user email bot express goodbye
重启更新后的 Bot 和事件模拟器。
docker compose -f deploy/docker/docker-compose.yml down export BOT_PATH=samples/event_interface_tutorial_bot/step_5 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d python event_client.py
通过结束对话来测试此交互。例如,在提示符中写入
I am done
。这将触发流ask for user email
。此流首先询问您是否Okay
提供您的电子邮件(您可以在提示符中写入确认信息,或单击右侧 UI 上的选项)。如果您确认,email
输入提示将出现在 UI 部分的右侧。
注意
如果您在电子邮件地址中键入
@
,Bot 将做出反应。
步骤 6:运行 Python 代码#
Colang 允许您从流中调用 Python 代码。这些被称为 Python 动作,可以包含任意 Python 代码。如果您需要更复杂的数据处理功能,而这些功能在 Colang 中不容易完成,则 Python 动作可能特别有用。
如果您想继续学习,请从 samples/event_interface_tutorial_bot/step_5
中的 Bot 开始应用以下更改。步骤 6 中所有更改的最终 Bot 代码可以在 samples/event_interface_tutorial_bot/step_6
中找到。
注意
所有 Python 动作都在 Colang 解释器的 Python 上下文中运行(共享库和依赖项)。如果您需要编写具有特定依赖项的更复杂代码,请参阅集成插件服务。
使用 Python 动作,我们可以轻松地在流中利用 Python 功能。让我们添加一个流来计算用户句子中的单词数。
在
bot
文件夹内创建一个名为actions.py
的文件,内容如下。
from nemoguardrails.actions.actions import action @action(name="CountWordsAction") async def count_words_action(transcript: str) -> int: return len(transcript.split())
更新
main.co
,如下所示(添加新的用户意图流和一个流来处理来自用户的单词计数请求)。
# CHANGE 1: Add a new user intent at the top of main.co (after the imports) @meta(user_intent=True) flow user requested to count words in sentence user has selected choice "can you please count the words in my next sentence" or user said "count how many words are in my next utterance" or user said "how many words" [...] # CHANGE 2: Add new flow that defines how to handle word counting requests flow handling word counting requests user requested to count words in sentence bot say "Sure, please say a sentence and I will count the words" user said something as $ref $count = await CountWordsAction(transcript=$ref.transcript) bot say "There were {$count} words in your sentence." [...] flow handling user requests until user expressed done [...] activate handling bot talking interruption $mode="interrupt" # CHANGE 3: Activate the new flow activate handling word counting requests bot say something like "How can I help you today?" [...]
重启更新后的 Bot 和事件模拟器。
docker compose -f deploy/docker/docker-compose.yml down export BOT_PATH=samples/event_interface_tutorial_bot/step_6 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d python event_client.py
步骤 7:集成插件服务#
在本节中,我们将添加支持以将外部 API 调用与我们的 Bot 集成。如果您设计的 Bot 依赖于外部信息或需要调用某些操作,则可以使用插件服务来托管更复杂的 Python 代码,与上一节中使用 Python 动作展示的方法相比。
如果您想继续学习,请从 samples/event_interface_tutorial_bot/step_6
中的 Bot 开始应用以下更改。步骤 7 中所有更改的最终 Bot 代码可以在 samples/event_interface_tutorial_bot/step_7
中找到。
使用插件服务,您可以使用 ACE Agent 的插件服务器与任何外部 API 或任何自定义代码进行交互。对于此示例,让我们使用 Yahoo Finance API 收集有关股票的信息,并使用自定义插件将该信息馈送到 Bot 中。
创建一个名为
plugin_config.yaml
的文件。这将包含 ACE Agent 需要部署的插件的详细信息。在 Bot 配置目录中创建一个
plugin
目录。在
plugin
目录内添加一个名为yahoo_fin.py
的文件。
samples └── event_interface_tutorial_bot └── bot_config.yaml └── main.co └── plugin └── yahoo_fin.py └── plugin_config.yaml
更新
yahoo_fin.py
以使用 Yahoo Finance API 获取有关股票的信息。对于自定义插件,必须导入APIRouter
对象。
import yfinance as yf from yahoo_fin import stock_info as si import requests from typing import Optional from fastapi import APIRouter # API to extract stock price Y_TICKER = "https://query2.finance.yahoo.com/v1/finance/search" Y_FINANCE = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=" router = APIRouter() # Prepare headers for requests session = requests.Session() user_agent = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" ) session.headers.update({"User-Agent": user_agent}) def get_ticker_symbol_alphavantage(stock_name: str) -> Optional[str]: # We do not need actual api key to get ticker info # But it is required as placeholder api_key = "YOUR_ALPHA_VANTAGE_API_KEY" url = f"https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords={stock_name}&apikey={api_key}" response = requests.get(url) data = response.json() if "bestMatches" in data and len(data["bestMatches"]) > 0: ticker_symbol = data["bestMatches"][0]["1. symbol"] return ticker_symbol return None @router.get("/get_ticker") def get_ticker(company_name: str) -> Optional[str]: """ Take company name returns ticker symbol used for trading param Args: company_name: company name like Microsoft Returns: Ticker Symbol used for trading like MSFT for microsoft """ try: params = {"q": company_name, "quotes_count": 1, "country": "United States"} return session.get(url=Y_TICKER, params=params).json()["quotes"][0]["symbol"] except Exception as e: return get_ticker_symbol_alphavantage(company_name) @router.get("/get_stock_price") def get_stock_price(company_name: str) -> Optional[float]: """ get a stock price from yahoo finance api """ try: # Find ticker symbol for stock name, eg. Microsoft : MSFT, Nvidia: NVDA ticker = get_ticker(company_name) live_price = si.get_live_price(ticker) return round(live_price, 2) except Exception as e: print(f"Unable to find stock price of {company_name}") return None
将插件注册到插件服务器。更新
plugin_config.yaml
以包含新插件的名称和路径。
config: workers: 1 timeout: 30 plugins: - name: stock path: "./plugin/yahoo_fin.py"
更新
main.co
以添加和激活新的流handling stock price questions
,以利用此插件为我们的 Bot 公开的端点,如下所示。该流将首先使用 LLM 提取公司名称,然后将名称发送到我们创建的插件。
define flow provide stock price user asks stock price # CHANGE 1: Add user intent at the top of main.co @meta(user_intent=True) flow user asked stock price user said "What is the stock price of Microsoft?" or user said "How much does an Nvidia stock cost" or user said "what is the value of amazon share price?" or user said "What is it's stock price?" [...] # CHANGE 2: Add flow that handles stock price questions flow handling stock price questions global $last_user_transcript user asked stock price $company_name = ..."Return the name of the company the user was referring to in quotes \"<company name here>\" or \"unknown\" if the user did not mention a company." if $company_name == "unknown" bot say "Sorry, I can't understand which company you are referring to here. Can you rephrase your query?" return else $price = await InvokeFulfillmentAction(endpoint="/stock/get_stock_price", company_name=$company_name) if not $price bot say "Could not find the stock price!" else bot say "Stock price of {$company_name} is {$price}" [...] flow handling user requests until user expressed done [...] activate handling bot talking interruption $mode="interrupt" # CHANGE 3: Activate the flow activate handling stock price questions [...]
重启更新后的 Bot 和事件模拟器。
docker compose -f deploy/docker/docker-compose.yml down export BOT_PATH=samples/event_interface_tutorial_bot/step_7 source deploy/docker/docker_init.sh docker compose -f deploy/docker/docker-compose.yml up event-bot -d python event_client.py