在这个简单的教程中,我们将使用CopilotKit来增强这个简单的旅行规划工具,并加入人工智能功能。
读完本文后,你会学到:
- 代理型副驾是什么样的,以及如何利用它将AI功能添加到您的应用中。
- 如何让副驾更新应用程序状态并在实时更新显示。
- 如何使用
useCoAgentStateRender
实现人机协作的工作流程。
💁 喜欢通过视频学习吗?可以看看这个视频教程。
https://youtu.be/9v3kXiOY3vg?si=b7hBDCMx5Xu44MIj
下面是我们即将构建的应用程序的预览:👇
💁 试试这个旅行规划器演示,访问这个链接。
……此处省略若干字……
查看仓库页面在这次演示中,我们将从一个包含基本功能、不支持AI的应用程序的分支开始。我们将使用CopilotKit来增强这个应用。🚀
查看起始分支
我们将从名为 coagents-travel-tutorial-start
的分支开始,该分支包含了我们旅行应用的初始代码:
下面的命令将会克隆一个特定分支的GitHub仓库到你的本地机器.
git clone -b coagents-travel-tutorial-start https://github.com/CopilotKit/CopilotKit.git
cd CopilotKit
执行完上述命令后,请确保你已经切换到 `CopilotKit` 目录.
全屏模式;退出全屏
教程中的代码位于 examples/coagents-travel
目录中,这两个目录分别是其中包含的两个不同的子目录。
-
ui/
: 这里有一个 Next.js 应用程序,我们将在其中集成 LangGraph 代理。 agent/
: 它存放了一个使用Python开发的LangGraph代理,
进入 examples/coagents-travel
文件夹:
cd examples/coagents-travel
全屏 全屏退出
安装依赖项
让我们来配置 Next.js 应用程序。确保您的系统上已经安装了 pnpm
,因为我们采用的启动代码使用 pnpm
作为包管理器。
在终端中输入以下命令来全局安装最新版本的 pnpm(10 版本):npm install -g pnpm@latest-10
全屏模式, 退全屏
现在,进入 ui
文件夹并安装所有必要的依赖:
cd ui
pnpm install
在上述命令中,首先切换到ui
目录,然后使用pnpm
安装所需的依赖。
点击全屏按钮可以进入全屏模式,点击退出全屏按钮可以退出全屏模式
获取 API 密钥
在 ui
目录中创建一个 .env
文件,并填入所需的环境变量
# 👇 ui/.env
OPENAI_API_KEY=<你的openai_api_key>
NEXT_PUBLIC_CPK_API_KEY=<你的公共copilotkit_api_key>
全屏模式 退出全屏
如果你需要CopilotKit API密钥,你可以在这里领取。这里 🔑
启动项目
现在,我们可以启动开发服务器了。
运行开发环境的命令是 `pnpm run dev`。
进入全屏,退出全屏
如果一切设置正确的话,访问http://localhost:3000看看你的旅行应用。希望你会喜欢。 😻
现在,让我们来看看LangGraph程序是如何工作的。
……
语言图谱代理在我们开始集成LangGraph代理之前先,花点时间了解它是如何工作的。
在这个教程中,我们不会从零开始构建 LangGraph 代理程序。相反,我们将使用该版本,它位于 agent
目录。
💁 想了解更多关于构建LangGraph代理的详细步骤指南?请查看LangGraph快速入门。
我们先来看看LangGraph代理内部是怎么运作的,然后再把它加到我们的程序里。
安装 LangGraph Studio
💡 LangGraph Studio 是一个出色的工具,用于可视化和调试 LangGraph 工作流程。虽然使用 CopilotKit 并不需要这个工具,但非常推荐使用它来理解 LangGraph 的运行机制。
要安装 LangChain Studio,可以参考安装指南。
获取API密钥
在 agent
目录下创建一个 .env
文件,并在里面放入以下环境变量设置:
# 👇 agent/.env
OPENAI_API_KEY=请填写你的openai_api_key
GOOGLE_MAPS_API_KEY=请填写你的google_maps_api_key
全屏模式 退出全屏 模式
需要 Google 地图 API 密钥吗?按照这个指南(点击这里^1)获取它。^1
LangGraph代理的可视化
安装了 LangGraph Studio 后,您可以打开工作室中的 examples/coagents-travel/agent
目录,来加载并可视化 LangGraph 智能代理。
💡 小贴士: 设置好一切可能需要一点时间,但一旦安装好并进入 agent
文件夹之后,可视化会看起来像这样:
注意:链接部分保持不变,因为链接不可翻译。
试试 LangGraph 代理
要测试LangGraph代理程序,只需将消息添加到变量中,然后点击“提交按钮”。
智能代理将处理收到的输入信息,在聊天中作出回应,并通过连接的节点遵循预定的工作流程。
在这个例子中,智能体触发了 search_node
来触发搜索。一旦获取到响应,它会使用 trips_node
来更新状态,根据找到的信息添加一个新的行程。🎯
理解断点
我们来谈谈代理驾驶助手中的一个重要概念:人在回路中。
想象你的代理非常乐于助人,但有时可能有点过头。断点就像是一个友好的暂停按钮,让用户在代理采取行动之前批准其决定,以防它失控或者仅仅犯错。LangGraph利用断点使这变得简单。
它是这样工作的,
-
点击
trips_node
,然后开启interrupt_after
选项。这次,尝试让代理创建一个新的行程。它会在执行中途停下来并征得你的同意。
你看,连AI也能学会讲礼貌。😉
让 LangGraph Studio 一直运行
你的代理需要一个家,目前就住在这里的LangGraph Studio。让它在本地运行,你可以在应用程序左下角看到它的网址。
我们稍后会用这个URL来连接LangGraph代理程序与我们的CopilotKit。
🤖到目前为止,我们做得非常出色。现在让我们把这一切应用到实际中,通过将LangGraph代理集成到我们的旅行应用里,作为代理来实现。一起出发吧!
等等
如何配置 CopilotKit现在到了有趣的部分——让我们添加CopilotKit,将所有东西整合起来。因为我们已经有了应用程序和代理在运行,只需要一步就能把CopilotKit集成到我们的应用里了。
在本教程中,我们将安装以下依赖包。
-
@copilotkit/react-core
: CopilotKit的核心库,主要提供CopilotKit提供器和实用的钩子。 @copilotkit/react-ui
: CopilotKit 的 UI 库,包含侧边栏、聊天弹出框、文本框等,例如侧边栏、聊天弹出框、文本框等组件。
安装依赖
首先,如果你还没有在 ui
目录里,请切换到 ui
目录。
cd ../ui
全屏 退出全屏
然后,安装 CopilotKit 的包(或组件)。
在终端中输入以下命令:
pnpm add @copilotkit/react-core @copilotkit/react-ui
点击全屏按钮可以切换到全屏模式,再点击一次可以切换回正常模式
这两个包就足以在React应用中安装CopilotKit。@copilotkit/react-core
包含了CopilotKit的核心功能,而@copilotkit/react-ui
则包含了一些可以直接使用的预构建UI组件。
添加代码助手套件
要配置 CopilotKit,有兩種方式:
-
Copilot Cloud:快速上手,简单易用,并且完全托管。
- 自己托管:更自由的控制权,但也增加了额外的复杂性。
在这个教程中,我们会走云端路线(因为目前为什么还要麻烦自己去处理额外的复杂性),但如果你有兴趣的话,也可以自己搭建。如果你想自己来,可以看看自我托管指南。
设置 Copilot 云端
这是开始使用Copilot Cloud的方法:首先,你可以...
- 创建账户
前往 Copilot Cloud 并注册一下。这只需要大约一分钟的时间。
- 获取你的API密钥
登录之后,按照屏幕上的指示操作以获取您的Copilot Cloud 公共 API 密钥。您还需要获取一个 OpenAI API 密钥。
设置你的 OpenAI API 密钥后,然后点击对勾,就这样简单,你就能拿到你的公钥了。
- 将 API密钥添加到你的
.env
中
在 ui
目录中的 .env
文件里更新你的 Copilot Cloud API 密钥:并在文件中加入你的 API 密钥。
# 👇 ui/.env
# 剩余的环境变量...
NEXT_PUBLIC_CPK_PUBLIC_API_KEY=<你的copilotkit_public_key>
全屏 / 退出全屏
- 设置 CopilotKit 供应商
现在,要在您的应用程序中集成CopilotKit,只需将其包裹在CopilotKit
提供器内。
通过使用提供者包装我们的应用,我们确保了即将从 @copilotkit/react-ui
组件添加的其他 UI 组件能够与 CopilotKit SDK 顺利交互。
编辑 ui/app/page.tsx
:添加以下代码:
// 👇 ui/app/page.tsx
"use client";
// 其他导入语句...
import { CopilotKit } from "@copilotkit/react-core";
// 其余代码...
export default function Home() {
return (
<CopilotKit
publicApiKey={process.env.NEXT_PUBLIC_CPK_PUBLIC_API_KEY}
>
<TooltipProvider>
<TripsProvider>
<main className="h-screen w-screen">
<MapCanvas />
</main>
</TripsProvider>
</TooltipProvider>
</CopilotKit>
);
}
点击全屏按钮来全屏显示,想要退出的话点击退出全屏模式按钮。
- 助手工具包 用户界面 组件
CopilotKit 配备了几个即插即用的组件,如 <CopilotPopup/>
, <CopilotSidebar/>
。只需放置这些组件,它们就会看起来非常棒。
如果你不想使用内置组件也没关系!CopilotKit 支持无头模式,使用 useCopilotChat
,所以如果你喜欢动手,可以完全自己动手一番。😉
在这篇教程中,我们将使用 <CopilotSidebar />
组件显示聊天侧边栏。同样的方法适用于其他任何预建的 UI 组件。
编辑 ui/app/page.tsx
文件,添加 <ChatSidebar />
组件,并确保 CSS 样式已导入。
// 👇 ui/app/page.tsx
"use client";
// Rest of the imports...
import { TasksList } from "@/components/TasksList";
import { TasksProvider } from "@/lib/hooks/use-tasks";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
// Rest of the code...
export default function Home() {
return (
<CopilotKit
publicApiKey={process.env.NEXT_PUBLIC_CPK_PUBLIC_API_KEY}
>
<CopilotSidebar
defaultOpen={true}
clickOutsideToClose={false}
labels={{
title: "行程规划器",
initial: "嗨!👋 我在这里帮你规划旅行。我可以帮你管理旅行,添加地点,或者一般地帮你规划一个新的行程。",
}}
/>
<TooltipProvider>
<TripsProvider>
<main className="h-screen w-screen">
<MapCanvas />
</main>
</TripsProvider>
</TooltipProvider>
</CopilotKit>
);
}
切换到全屏模式 退出全屏
首先,我们导入所需的模块和自定义样式,以便侧边栏看起来很棒。👌 然后,加入 <CopilotSidebar />
组件。
在 <CopilotSidebar />
组件中,你可以通过传递 labels 属性来更改标题和 AI 的初始消息。
现在回到你的应用。往右边看,voilà! 出现了闪亮的新聊天侧边栏,只需要几行代码就可以使用。 😻
不过,还是缺少一些东西,那就是副驾具备决策能力。我们会通过位于asset
目录中的LangGraph来增加这个功能。
此处省略内容
让你的副驾更主动一些
我们在 LangGraph Studio 运行着一个 LangGraph 代理,还有一个功能性但不够聪明的副驾,但它还没有那么聪明。让我们给这个副驾一些真正的决策能力!让它变得更有能力吧!😎
快速了解 React 状态管理
我们快速回顾一下应用的状态管理,然后打开一下这个文件:lib/hooks/use-trips.tsx
。
这里有个叫TripsProvider
的东西,它有很多有用的功能。真正的主角是state
对象,它是按照AgentState
类型定义的。
这个状态可以通过 useTrips
钩子在整个应用中可访问,为如 TripCard
、TripContent
和 TripSelect
这些组件提供数据。
如果你以前用过 React 应用,这感觉应该会很熟悉——通过 context 或库来管理状态是相当标准的。
将代理和状态结合
现在到了重要部分:将我们的LangGraph代理与其状态连接起来。要实现这一点,我们将设置一个远程端点来,并使用useCoAgent
钩子来实现这一魔法。🌟
- 搭建隧道
还记得之前的 LangGraph Studio 端点吗?你现在就要用到它了!如果你使用的是 Copilot Cloud,你已经准备好了。
如果你选择了自托管这条路,请按照这里的步骤来。
为了将我们本地运行的LangGraph服务与Copilot Cloud连接起来,我们首先找到LangGraph Studio端点的端口号,然后使用CopilotKit CLI。
💁 LangGraph Studio的位置:你可以在LangGraph Studio界面左下角找到它。
LangGraph Studio 端点
现在来,启动你的终端,然后运行这个命令:
# 将 <port_number> 占位符替换为实际的端口数字
npx @copilotkit/cli tunnel <port_number>
进入全屏,退出全屏
Boom! 你打通了一个隧道。🎉 你将看到以下内容:
✔ 隧道创建成功!
隧道详情:
本地:localhost:54209
公开 URL:https://light-pandas-argue.loca.lt
按 Ctrl+C 终止隧道
全屏 退出全屏
保存这个公共URL哦。🔗 它将成为我们在本地运行的LangGraph代理程序和CopilotKit Cloud之间的桥梁。它将连接我们在本地运行的LangGraph代理和CopilotKit Cloud。
💁 接下来,我们需要 LangSmith API Key(LangSmith API密钥)。请参阅此指南来获取它。
- 将隧道连接至Copilot云端
访问Copilot Cloud,在页面上滚动找到Remote Endpoints部分,然后点击+ 添加新
按钮。
- 选择
LangGraph
平台。 - 添加公共 URL(从 CopilotKit CLI 生成的 URL),并添加你的 LangSmith API 密钥。
- 点击 创建。
🎉 完成!您的代理端点现在已列出,当调用该代理时,CopilotKit 知道确切地向何处发送请求。
- 锁住代理
因为我们这里只有一个代理,让我们确保将 <CopilotKit />
提供者锁定,以便所有请求都只能发送给这个特定代理。要添加代理,只需在属性中加入 agent
的名字即可。
💁 对处理多个代理好奇吗?可以查看多代理概念指南
// 👇 ui/app/page.tsx
// Rest of the code...
<CopilotKit
// Rest of the code...
agent="旅游"
>
{/* Rest of the code... */}
</CopilotKit>
全屏模式 退出全屏
我们把这种程序或角色称为travel
,因为已经在agents/langgraph.json文件中定义。
就这样,副驾驶现在变得更主动了。它不仅能聊天,还能做决定。这也太酷了吧!🤯
设置代理和状态
现在,我们希望将LangGraph代理的状态与我们应用的状态关联起来。这将使我们能够实现实时动态交互。
LangGraph代理会记录自己的状态,正如你在LangGraph工作室界面左下角已经看到的那样。
🤔 现在是什么想法呢?
我们希望实现这些状态间的双向互动。为了实现这一点,友好的钩子useCoAgent
正好能帮我们实现这一点,来自CopilotKit。
打开并编辑 ui/lib/hooks/use-trips.tsx
文件,在文件中添加以下代码以在文件中添加 useCoAgent
钩子。
/*
// 👇 ui/lib/hooks/use-trips.tsx
// 其他导入...
import { AgentState, defaultTrips } from "@/lib/trips";
import { useCoAgent } from "@copilotkit/react-core";
export const TripsProvider = ({ children }: { children: ReactNode }) => {
const { state, setState } = useCoAgent<AgentState>({
name: "travel",
初始状态: {
trips: defaultTrips,
所选行程ID: defaultTrips[0].id,
},
});
// 其余代码...
}
切换到全屏 退出全屏
没错,就是这样。这就够同步这两个状态了。ὕ
接下来,让我们逐行分析代码,看看每行的意思。
💡
useCoAgent
钩子是通用的,这意味着你可以指定一个与 LangGraph 代理状态相似的类型。
在这一例子中,我们使用AgentState
以保持一定的统一性,虽然可以勉强将其类型强制转换为any
类型,但这通常不是一个好实践。还是尽量不要这么干。
name
参数将所有内容与你图中 agent/langgraph.json
定义的名称关联起来。确保正确命名,这样代理和我们应用才能始终保持同步。
在 initialState
中,我们使用 defaultTrips
(来自 @/lib/types.ts
),不过这并不是必需的。
我们先加几个初始行程,以便立即测试它是否正常工作。
这是初始状态,即 defaultTrips
,如下所示。
// 👇 ui/lib/types.ts
export const defaultTrips: Trip[] = [
{
id: "1",
name: "纽约商务旅行",
center_latitude: 40.7484,
center_longitude: -73.9857,
places: [
{
id: "1",
name: "中央公园",
address: "纽约市,NY 10024",
description: "位于纽约市著名的公园",
latitude: 40.785091,
longitude: -73.968285,
rating: 4.7,
},
{
id: "3",
name: "时代广场",
address: "纽约市,NY 10036",
description: "位于纽约市著名的广场",
latitude: 40.755499,
longitude: -73.985701,
rating: 4.6,
},
],
zoom_level: 14,
},
// 其余行程...
];
切换到全屏,退出全屏
来吧,该试试它了!
打开你的应用,问智能助手关于你的旅行的任何问题。
我有几次出行记录?
全屏切换 退出全屏
看到代理是怎么从应用状态里提取数据的吗?这简直是魔法,对吧? 😻
状态由应用程序和代理共同管理,所以试着手动删除或编辑一趟旅行,再问一遍,它应该会给出相应的答复:
我现在有什么安排?
进入全屏;退出全屏
这个代理很懂行情。更棒的是,你可以直接给他安排任务:
帮我添加一些酒店到我在巴黎的行程
全屏模式 退出全屏
就这样!状态更新了,你的界面也会随之更新。
目前,应用的核心功能已经完成了。我们只需要提升一下用户体验就可以了,通过增加实时显示的文字以及其他功能,来为用户提供实时反馈。
实时响应显示
现在我们可以与代理互动,获取和更新数据,为什么不加个文本流来增强用户体验?
这就像实时观看代理工作时的实时进度,就像你看其他很多流行AI一样,比如 ChatGPT
。
在这一步中,我们将实现 copilotkit_emit_state
SDK 函数,以便 LangGraph 代理在工作时不断更新进度。这样,代理在工作时会不断更新进度。🔥
让我们开始安装CopilotKit插件吧
首先,让我们来安装CopilotKit SDK。由于这里我们使用的是一个基于Python的代理软件(并且用poetry来管理它),所以我们将安装Python版本的SDK。
💁 不确定如何安装 poetry?可以在这里找到安装指南 这里。
poetry add copilotkit==0.1.31a4
在 poetry 中添加 copilotkit 版本 0.1.31a4
全屏模式。退出全屏
既然我们要编辑search_node
,我们就直接打开search.py
文件。
手动触发代理程序的状态
使用 CoAgents 时,当节点发生变化(即,当遍历边时)会发出代理的状态信息。但是如果我们希望在执行动作的过程中显示进度呢?好消息就是,我们可以通过手动调用 copilotkit_emit_state
来发送状态信息。
让我们给 search_node
添加一个自定义配置,这样我们就可以输出中间的计算状态。
打开 agent/travel/search.py
文件,并在里面加入以下代码行:
# 👇 agent/travel/search.py
# 其余导入...
from copilotkit.langchain import copilotkit_emit_state, copilotkit_customize_config
async def search_node(state: AgentState, config: RunnableConfig):
"""
搜索节点用于搜索地点。
"""
ai_message = cast(AIMessage, state["messages"][-1])
config = copilotkit_customize_config(
config,
emit_intermediate_state=[{
"state_key": "搜索进度状态",
"tool": "搜索地点工具",
"tool_argument": "搜索进度状态",
}],
)
# 其余代码...
全屏 退出全屏
发出中间状态
现在,让我们用 copilotkit_emit_state
手动发送状态,你会看到我们每次发送查询时的状态更新。
让我们再编辑一下 agent/travel/search.py
,在搜索开始时以及在结果返回时发出状态信息。
在agent/travel/search.py
文件中添加以下代码,如下:
# 👇 agent/旅行/搜索.py
# 其余代码...
async def 搜索节点函数(state: AgentState, config: RunnableConfig):
"""
搜索节点函数负责搜索地点。
"""
ai_message = cast(AIMessage, state["messages"][-1])
config = 自定义配置CoPilotKit(
config,
发送中间状态=[{
"状态键": "搜索进度信息",
"工具": "搜索地点功能",
"工具参数": "搜索进度信息",
}],
)
# ^ 之前的代码
state["搜索进度信息"] = state.get("搜索进度信息", [])
查询列表 = ai_message.工具调用[0]["args"]["queries"]
for query in 查询列表:
state["搜索进度信息"].append({
"查询": query,
"结果": [],
"已完成": False
})
await 发送状态CoPilotKit(config, state)
# 其余代码...
全屏 切换全屏
更新并显示进度
现在我们可以实时展示结果了,在搜索过程中我们会实时更新进度。
在文件 agent/travel/search.py
中用以下代码进行更新:
# 👇 agent/旅行/搜索.py
# 未列出的代码...
async def 搜索节点函数(state: AgentState, config: RunnableConfig):
"""
搜索节点用于搜索地点。
"""
ai_message = cast(AIMessage, state["messages"][-1])
config = copilotkit_自定义配置(
config,
发送中间状态信息=[{
"状态键": "搜索进度",
"工具": "搜索地点",
"工具参数": "搜索进度",
}],
)
state["搜索进度"] = state.get("搜索进度", [])
查询 = ai_message.工具调用请求[0]["args"]["queries"]
for query in 查询:
state["搜索进度"].append({
"查询": query,
"结果": [],
"是否完成": False
})
await copilotkit_发送状态(config, state)
# 先前的代码
地点信息 = []
for i, query in enumerate(查询):
响应结果 = gmaps.places(query)
for 结果 in 响应结果.get("results", []):
地点信息 = {
"id": 结果.get("place_id", f"{结果.get('name', '')}-{i}"),
"名称": 结果.get("name", ""),
"地址": 结果.get("formatted_address", ""),
"纬度坐标": 结果.get("geometry", {}).get("location", {}).get("lat", 0),
"经度坐标": 结果.get("geometry", {}).get("location", {}).get("lng", 0),
"评分": 结果.get("rating", 0),
}
地点信息.append(地点信息)
state["搜索进度"][i]["是否完成"] = True
await copilotkit_发送状态(config, state)
state["搜索进度"] = []
await copilotkit_发送状态(config, state)
# 后续的代码...
切换到全屏模式 退出全屏模式
显示进度
为了显示 UI 中的进度,我们将使用 useCoAgentStateRender 这个钩子。这个钩子会根据条件渲染 search_progress
状态。
我们只需要通过 useCoAgentStateRender
钩子根据条件来渲染 search_progress
状态值。
现在我们来修改 ui/lib/hooks/use-trips.tsx
来显示搜索的进度:
// 👇 ui/lib/hooks/use-trips.tsx
// 其他导入...
import { useCoAgent, useCoAgentStateRender } from "@copilotkit/react-core";
import { SearchProgress } from "@/components/SearchProgress";
export const TripsProvider = ({ children }: { children: React.ReactNode }) => {
// 其他代码...
useCoAgentStateRender<AgentState>({
name: "travel",
render: ({ state }) => {
if (state.search_progress) {
return <SearchProgress progress={state.search_progress} />;
}
return null;
},
});
// 其他代码...
}
全屏 退出全屏
<SearchProgress />
组件已经为你准备好了。如果你对它感兴趣,可以看看 ui/components/SearchProgress.tsx
中的实现。 🙌
💁 额外福利:
search_progress
状态键值已经在ui/lib/types.ts
文件的AgentState
类型中预定义好,所以你无需从零开始创建它。
现在,试试看吧!向代理提问吧,你就能实时看到进度更新了。🤩
通过加入人机协作来增加控制性
好吧,现在来点真家伙。如果代理即将做出你不同意的决定,该怎么办?
人机协作循环 (HITL) 允许你批准、拒绝或修改智能体想要执行的行动。
我们将在这里设置一个“断点”,让代理暂停,等待你的批准再继续。
💁 对断点感到好奇吗?可以在这里了解更多详情 点击这里。
一旦断点被命中,我们会将其发送到前端界面,用户会批准或拒绝该操作。然后,代理将根据用户的决定继续操作。整个过程会是这样的:
查看下方的信息图,看看整个流程是怎么运作的:👇
- 添加断点以供人工介入
使用我们的LangGraph,添加人工介入功能非常直接。trips_node
作为perform_trips_node
的中介,我们可以在trips_node
处设置断点,从而暂停执行。
在 agent/travel/agent.py
文件中,指示它在何处插入暂停。具体来说,我们在 compile
函数中这样做。
# 👇 agent/旅行/agent.py
# 其余的代码...
graph = graph_builder.compile(
checkpointer=MemorySaver(),
# 暂停在这里,等用户回复!
interrupt_after=["行程节点"],
)
全屏 退出全屏
现在,这样做了以后,代理就会问我们,“我是否应该继续?”而不是盲目地继续下去。
- 应对用户的决定
当用户点击暂停按钮时,我们得看看他们想干嘛。他们是不是同意这一步骤,还是点了“取消”然后重新来过?我们要搞清楚他们想干嘛。
在 perform_trips_node
这个步骤里,我们将获取工具消息,看看用户做了什么选择。
# 👇 agent/旅行/旅行.py
# 其余代码...
async def 处理旅行节点(state: AgentState, config: RunnableConfig):
"""处理旅行任务"""
ai_message = cast(AI消息, state["messages"][-2])
tool_message = cast(工具消息, state["messages"][-1])
# 余下的代码...
点击以进入全屏模式,再次点击退出
条件判断会检查用户的决定然后根据决定作出相应反应。
如果用户说“取消”的话,我们就停止所有操作并直接返回自定义消息。否则,对于其他任何回复,会继续处理。
# 👇 agent/旅行/行程.py
# 代码其余...
async def 处理行程节点(state: AgentState, config: RunnableConfig):
"""处理行程操作"""
ai_message = cast(AIMessage, state["messages"][-2])
工具消息对象 = cast(ToolMessage, state["messages"][-1])
if 工具消息对象.content == "CANCEL":
return {
"messages": AIMessage(content="已取消行程操作。"),
}
# 处理边缘情况,即AI消息不是AIMessage或没有工具调用,这种情况理论上不应发生。
if not isinstance(ai_message, AIMessage) or not ai_message.tool_calls:
return state
# 代码其余...
点击全屏 点击退出全屏
- 显示决策界面
现在是时候更新前端以显示工具调用并捕获用户的决定选择,传递给代理。为此,我们将会使用带有 renderAndWait
选项的 useCopilotAction
钩子。
编辑 ui/lib/hooks/use-trips.tsx
文件,并添加如下代码行:
// 👇 ui/lib/hooks/use-trips.tsx
// 其余导入...
import { AddTrips, EditTrips, DeleteTrips } from "@/components/humanInTheLoop";
import { useCoAgent, useCoAgentStateRender, useCopilotAction } from "@copilotkit/react-core";
// 其余代码...
export const TripsProvider = ({ children }: { children: ReactNode }) => {
// 其余代码...
useCoAgentStateRender<AgentState>({
name: "travel",
render: ({ state }) => {
return <SearchProgress progress={state.search_progress} />;
},
});
useCopilotAction({
name: "add_trips",
description: "添加一些行程",
parameters: [
{
name: "trips",
type: "object[]",
description: "需要添加的行程",
required: true,
},
],
renderAndWait: AddTrips,
});
useCopilotAction({
name: "update_trips",
description: "更新一些行程",
parameters: [
{
name: "trips",
type: "object[]",
description: "需要更新的行程",
required: true,
},
],
renderAndWait: EditTrips,
});
useCopilotAction({
name: "delete_trips",
description: "删除一些行程",
parameters: [
{
name: "trip_ids",
type: "string[]",
description: "需要删除的行程ID(必须提供)",
required: true,
},
],
renderAndWait: (props) => <DeleteTrips {...props} trips={state.trips} />,
});
// 其余代码...
全屏模式 退出全屏
有了这套配置,前端已经准备好渲染工具调用的界面并捕捉用户的操作决定。不过,还有一个重要的问题我们还没提到:我们怎样处理用户的输入并回传给代理?
- 可选:了解人在环中部分
我们快速了解一下前端是如何处理这个情况的。我们将使用 DeleteTrips
组件作为示例,但相同的逻辑也适用于 AddTrips
和 EditTrips
。
// 👇 ui/lib/components/humanInTheLoop/DeleteTrips.tsx
import { Trip } from "@/lib/types";
import { PlaceCard } from "@/components/PlaceCard";
import { X, Trash } from "lucide-react";
import { ActionButtons } from "./ActionButtons";
import { RenderFunctionStatus } from "@copilotkit/react-core";
export type DeleteTripsProps = {
args: any;
status: RenderFunctionStatus;
handler: any;
trips: Trip[];
};
export const DeleteTrips = ({ args, status, handler, trips }: DeleteTripsProps) => {
const tripsToDelete = trips.filter((trip: Trip) => args?.trip_ids?.includes(trip.id));
return (
<div className="space-y-4 w-full bg-secondary p-6 rounded-lg">
<h1 className="text-sm">以下行程将被删除:</h1>
{status !== "complete" && tripsToDelete?.map((trip: Trip) => (
<div key={trip.id} className="flex flex-col gap-4">
<>
<hr className="my-2" />
<div className="flex flex-col gap-4">
<h2 className="text-lg font-bold">{trip.name}</h2>
{trip.places?.map((place) => (
<PlaceCard key={place.id} place={place} />
))}
</div>
</>
</div>
))}
{ status !== "complete" && (
<ActionButtons
status={status}
handler={handler}
approve={<><Trash className="w-4 h-4 mr-2" /> 确认删除</>}
reject={<><X className="w-4 h-4 mr-2" /> 取消删除</>}
/>
)}
</div>
);
};
全屏 开 全屏 关
这里的关键在于 ActionButtons
组件,它允许用户批准,或拒绝这个操作。这就是用户做出决定的地方。
// 👇 ui/lib/components/人在循环中/ActionButtons.tsx
import { RenderFunctionStatus } from "@copilotkit/react-core";
import { Button } from "../ui/button";
export type ActionButtonsProps = {
status: RenderFunctionStatus;
handler: any;
approve: React.ReactNode;
reject: React.ReactNode;
}
export const ActionButtons = ({ status, handler, approve, reject }: ActionButtonsProps) => (
<div className="flex gap-4 justify-between">
<Button
className="w-full"
variant="outline"
disabled={status === "complete" || status === "inProgress"}
onClick={() => handler?.("CANCEL")}
>
{reject}
</Button>
<Button
className="w-full"
disabled={status === "complete" || status === "inProgress"}
onClick={() => handler?.("SEND")}
>
{approve}
</Button>
</div>
);
进入全屏 退出全屏
这里有两个按钮,点击“取消”按钮时,会调用 handler?.("CANCEL")
,点击“删除”按钮时,会调用 handler?.("SEND")
。这会把用户的选择(无论是“CANCEL”还是“SEND”)传回给代理。
重要的是,onClick
处理函数将用户的点击结果回传给代理,。
💡 如果你想允许用户在回传给代理之前编辑工具调用的参数,可以通过修改
onClick
事件处理程序并调整代理处理工具调用的方法来实现这一点。
就这样。😮💨 我们成功地加入了人工审核功能。现在它可以在添加、编辑或删除行程时提示用户批准或拒绝这些操作的,并将用户的决定传回代理。
结尾 ⚡
在这次教程里,希望你学会了如何使用CopilotKit为你的应用添加代理型副驾(CopilotKit),实时改变状态,同时了解人机循环的概念。
源代码:源代码
💁 你也可以看看这个项目的原始文档页面。
非常感谢你来看!🎉 🫡
在下面的评论区分享你的想法!👇
一只猫在打键盘。
共同学习,写下你的评论
评论加载中...
作者其他优质文章