详细教程展示如何集成 CopilotKit、LangGraph 和 Google Maps API 构建智能旅行助手。涵盖 Agent 框架实装、多步工作流、地图 API 集成等实战技能。
在这个简单易懂的教程中,我们将使用 CopilotKit 来增强一个简单的旅行计划应用,并为其添加 AI 功能。
到本文末尾,你将学到:
什么是 AI 智能体 copilot,以及如何使用它为应用添加 AI。
如何让 copilot 更新应用状态并实时呈现变化。
如何使用 useCoAgentStateRender 进行人工参与型工作流。
💁 更喜欢通过视频学习?查看这个视频教程。
这是我们将构建的应用预览:👇
💁 访问此链接与旅行计划演示互动。
为了这个演示,我们将从包含基础、功能性应用(不包含 AI 支持)的分支开始,然后使用 CopilotKit 来增强它。🚀
我们将从 coagents-travel-tutorial-start 分支开始,它包含我们旅行应用的启动代码:
git clone -b coagents-travel-tutorial-start https://github.com/CopilotKit/CopilotKit.git
cd CopilotKit
教程代码位于 examples/coagents-travel 目录中,其中包括两个不同的目录:
ui/:它包含一个 Next.js 应用,我们将在其中集成 LangGraph agent。
ui/:它包含一个 Next.js 应用,我们将在其中集成 LangGraph agent。
agent/:它包含一个基于 Python 的 LangGraph agent。
agent/:它包含一个基于 Python 的 LangGraph agent。
导航到 examples/coagents-travel 目录:
cd examples/coagents-travel
让我们设置 Next.js 应用。确保你的系统上安装了 pnpm,因为我们的启动代码使用它作为包管理器:
npm install -g pnpm@latest-10
现在,进入 ui 目录并为项目安装所有必需的依赖:
cd ui
pnpm install
在 ui 目录中创建一个 .env 文件,并用必要的环境变量填充它:
# 👇 ui/.env
OPENAI_API_KEY=<your_openai_api_key>
NEXT_PUBLIC_CPK_PUBLIC_API_KEY=<your_public_copilotkit_api_key>
如果你需要 CopilotKit API 密钥,你可以在这里获取一个。🔑
现在,我们已经安装了所有依赖,启动开发服务器:
pnpm run dev
如果一切设置正确,请访问 http://localhost:3000 以查看旅行应用的运行效果。😻
现在,我们将查看 LangGraph agent 并了解它的工作原理。
在我们开始集成 LangGraph agent 之前,让我们花点时间来理解它的工作原理。
对于本教程,我们不会从零开始构建 LangGraph agent。相反,我们将使用位于 agent 目录中的预构建版本。
💁 有兴趣了解构建 LangGraph agent 的详细、逐步指南吗?查看 LangGraph 快速入门指南。
让我们逐步了解 LangGraph agent,以理解其内部工作原理,然后再将其添加到我们的应用中。
💡 LangGraph Studio 是一个用于可视化和调试 LangGraph 工作流的优秀工具。虽然不是使用 CopilotKit 的必需工具,但强烈推荐用于理解 LangGraph 的工作方式。
要安装 LangGraph Studio,请参考 LangChain Studio 设置指南。
在 agent 目录中创建一个 .env 文件,并用以下环境变量填充它:
# 👇 agent/.env
OPENAI_API_KEY=<your_openai_api_key>
GOOGLE_MAPS_API_KEY=<your_google_maps_api_key>
需要 Google Maps API 密钥?按照本指南获取一个。🔑
安装 LangGraph Studio 后,在 studio 中打开 examples/coagents-travel/agent 目录以加载和可视化 LangGraph agent。
💡 提示:设置所有内容可能需要一点时间,但一旦安装完成并导航到 agent 文件夹,可视化会看起来像这样:
要测试 LangGraph agent,只需向 messages 状态变量添加一条消息并点击"Submit"。
该 agent 将处理输入、在聊天中响应,并通过连接的节点遵循定义的工作流。
在此示例中,该 agent 触发 search_node 来执行搜索。一旦检索到响应,它就使用 trips_node 通过根据其发现添加新行程来更新状态。🎯
让我们讨论 AI 智能体 copilot 中的一个关键概念:人工参与型。
想象一下,你的 agent 渴望帮助,但有时也有点过度热心。断点就像一个友好的暂停按钮,让用户可以介入并在 agent 做出错误决定(或只是犯一个错误)之前批准 agent 的决定。LangGraph 通过断点使这变得容易。
点击 trips_node 并启用 interrupt_after 选项。
点击 trips_node 并启用 interrupt_after 选项。
尝试让 agent 创建一个新的行程。这次,它将在中途停止并请求你的批准。
尝试让 agent 创建一个新的行程。这次,它将在中途停止并请求你的批准。
看吧?连 AI 都能学会好礼貌。🙃
你的 agent 需要一个家,现在 LangGraph Studio 就是它所在的地方。保持 studio 在本地运行;你将在应用的左下角看到它的 URL。
我们稍后将使用此 URL 将我们的 CopilotKit 设置连接到 LangGraph agent。
到目前为止我们已经做得很好了,现在让我们通过将 LangGraph agent 集成到我们的旅行应用中作为 AI 智能体 copilot 来付诸实施。🤖
现在是有趣的部分——让我们添加 CopilotKit 以将一切整合在一起。由于我们已经让应用和 agent 运行,我们只差一步就可以将 CopilotKit 集成到我们的应用中。
对于本教程,我们将安装以下依赖:
@copilotkit/react-core:CopilotKit 的核心库,包含 CopilotKit 提供者和有用的 hooks。
@copilotkit/react-core:CopilotKit 的核心库,包含 CopilotKit 提供者和有用的 hooks。
@copilotkit/react-ui:CopilotKit 的 UI 库,包含 CopilotKit UI 组件,如侧边栏、聊天弹窗、文本区域等。
@copilotkit/react-ui:CopilotKit 的 UI 库,包含 CopilotKit 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 Cloud:快速、超级容易入门,完全托管。
自托管:更多控制但伴随额外的复杂性。
自托管:更多控制但伴随额外的复杂性。
对于本教程,我们选择云路由(为什么现在要费力处理额外的复杂性呢?),但如果你好奇,可以随意自托管。如果那是你的菜,请查看自托管指南。
以下是如何开始使用 Copilot Cloud 的方法:
前往 Copilot Cloud 并注册。只需大约一分钟。
登录后,按照屏幕上显示的步骤获取你的 Copilot Cloud Public API Key。你还需要一个 OpenAI API 密钥。
设置你的 OpenAI API 密钥,点击复选标记,就这样,你将获得你的公开密钥。
使用你的 Copilot Cloud API 密钥更新 ui 目录中的 .env 文件:
# 👇 ui/.env
# Rest of the env variables...
NEXT_PUBLIC_CPK_PUBLIC_API_KEY=<your_copilotkit_public_key>
现在,要将 CopilotKit 集成到你的应用中,需要用 CopilotKit 提供者包装你的应用。
通过用提供者包装我们的应用,我们确保从 @copilotkit/react-ui 组件添加的其他 UI 组件可以与 CopilotKit SDK 交互。
编辑 ui/app/page.tsx,添加以下代码行:
// 👇 ui/app/page.tsx
"use client";
// Rest of the imports...
import { CopilotKit } from "@copilotkit/react-core";
// Rest of the code...
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 的无头模式,所以如果你想发挥创意,可以完全 DIY。😉
在本教程中,我们将使用 <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: "Travel Planner",
initial: "Hi! 👋 I'm here to plan your trips. I can help you manage your trips, add places to them, or just generally work with you to plan a new one.",
}}
/>
<TooltipProvider>
<TripsProvider>
<main className="h-screen w-screen">
<MapCanvas />
</main>
</TripsProvider>
</TooltipProvider>
</CopilotKit>
);
}
首先,我们导入所需的模块和自定义样式,让侧边栏开箱即用就拥有出色的外观 👌。然后,加入 <CopilotSidebar /> 组件。
在 <CopilotSidebar /> 组件中,你可以通过 labels 属性修改标题以及 AI 的初始聊天消息。
现在回到应用,看看右侧——瞧!只需几行代码,一个崭新的聊天侧边栏就已经准备就绪了。😻
不过,目前还缺少一项能力:让 copilot 具备决策能力。我们将借助资源目录中的 LangGraph 来添加这项功能。
现在,我们已经有一个运行在 LangGraph Studio 中的 LangGraph AI 智能体,以及一个可以正常工作但还不够聪明的非智能体式 copilot。接下来,让我们赋予 copilot 真正的决策能力!😎
让我们快速回顾一下应用的状态是如何工作的。打开 lib/hooks/use-trips.tsx 文件。
你会在这里看到 TripsProvider,它定义了许多有用的内容。其中最重要的是什么?当然是由 AgentState 类型塑造的 state 对象。
整个应用都可以通过 useTrips Hook 访问该状态,而 TripCard、TripContent 和 TripSelect 等组件都会使用它。
如果你以前开发过 React 应用,这种方式应该很熟悉——通过 Context 或状态管理库管理状态是非常标准的做法。
现在来到重要环节:将 LangGraph AI 智能体连接到它的状态。为此,我们将设置一个远程端点,并使用 useCoAgent Hook 来实现这一切。🌟
还记得之前的 LangGraph Studio 端点吗?现在需要用到它了!如果你使用的是 Copilot Cloud,那么已经准备就绪。
如果你选择了自行托管,请按照这里列出的步骤操作。
为了连接本地运行的 LangGraph AI 智能体和 Copilot Cloud,我们将使用 CopilotKit CLI。先获取 LangGraph Studio 端点的端口号。
💁 LangGraph Studio 端口:你可以在 Studio 界面的左下角找到它。
现在打开终端,运行以下命令:
# Replace <port_number> placeholder with the actual port number
npx @copilotkit/cli tunnel <port_number>
好了!你已经创建了一条隧道。🎉 终端将显示以下内容:
✔ Tunnel created successfully!
Tunnel Information:
Local: localhost:54209
Public URL: https://light-pandas-argue.loca.lt
Press Ctrl+C to stop the tunnel
保存这个公开 URL。🔖 它将作为本地运行的 LangGraph AI 智能体与 CopilotKit Cloud 之间的网关。
💁 接下来的步骤还需要 LangSmith API Key。请按照这份指南获取一个。
前往 Copilot Cloud,向下滚动到 Remote Endpoints 部分,然后单击 + Add New 按钮。
选择 LangGraph 平台。
添加公开 URL(即 CopilotKit CLI 生成的 URL),然后添加你的 LangSmith API Key。
🎉 完成!你的 AI 智能体端点现在已经显示在列表中。当该 AI 智能体被调用时,CopilotKit 也准确知道应该将请求发送到哪里。
由于这里只有一个 AI 智能体,因此需要确保将 <CopilotKit /> Provider 锁定到这个特定的 AI 智能体,使所有请求都发送给它。要添加该 AI 智能体,只需修改属性并加入 AI 智能体的名称即可。
💁 想了解如何处理多个 AI 智能体?请查看多 AI 智能体概念指南。
// 👇 ui/app/page.tsx
// Rest of the code...
<CopilotKit
// Rest of the code...
agent="travel"
>
{/* Rest of the code... */}
</CopilotKit>
我们将 AI 智能体的名称设置为 travel,因为已经在 agents/langgraph.json 文件中定义了它。
就这样,copilot 现在真正具备了智能体能力。它不仅可以进行对话,还能作出决策。很酷,对吧?🤯
此时,我们需要将 LangGraph AI 智能体的状态与应用状态连接起来。这样就能实现实时的动态交互!
正如你已经在 LangGraph Studio 左下角看到的那样,LangGraph AI 智能体会维护自己的状态。
🤔 那么,现在的思路是什么?
我们希望在这些状态之间建立双向连接。为此,可以使用 CopilotKit 提供的 useCoAgent Hook,它能帮助我们实现这一目标。
编辑 ui/lib/hooks/use-trips.tsx 文件,添加以下代码来引入 useCoAgent Hook。
// 👇 ui/lib/hooks/use-trips.tsx
// Rest of the imports...
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",
initialState: {
trips: defaultTrips,
selected_trip_id: defaultTrips[0].id,
},
});
// Rest of the code...
没错,你没有看错。只需要这些代码就能实现状态的双向同步。😯
现在,让我们逐行解析这段代码:
💡 useCoAgent Hook 是泛型 Hook,这意味着你可以指定一个与 LangGraph AI 智能体状态相对应的类型。在这个示例中,我们使用 AgentState 来保持一致性。虽然也可以将它强制转换成 any,但通常不建议这样做。因此,大多数时候都应该避免这么做。
name 参数会将一切与 agent/langgraph.json 中定义的图名称关联起来。请确保名称填写正确,因为它能保证 AI 智能体与应用始终保持一致。
对于 initialState,我们使用来自 @/lib/types.ts 的 defaultTrips,不过这并不是必需的。
我们添加了一些初始行程,以便立即测试其运行效果。
初始状态 defaultTrips 的结构如下:
// 👇 ui/lib/types.ts
export const defaultTrips: Trip[] = [
{
id: "1",
name: "Business Trip to NYC",
center_latitude: 40.7484,
center_longitude: -73.9857,
places: [
{
id: "1",
name: "Central Park",
address: "New York, NY 10024",
description: "A famous park in New York City",
latitude: 40.785091,
longitude: -73.968285,
rating: 4.7,
},
{
id: "3",
name: "Times Square",
address: "Times Square, New York, NY 10036",
description: "A famous square in New York City",
latitude: 40.755499,
longitude: -73.985701,
rating: 4.6,
},
],
zoom_level: 14,
},
// Rest of the trips...
];
启动应用,然后向 Copilot 询问一些与行程有关的问题。
How many trips do I have?
看到 AI 智能体如何从应用状态中获取数据了吗?是不是很神奇?😻
应用与 AI 智能体共享状态,因此可以尝试手动删除或编辑某个行程,然后再次提问,它应该会根据最新状态作出回答:
What trips do I have now?
AI 智能体清楚当前发生了什么。更棒的是,你还可以直接向 AI 智能体分配任务:
Add some hotels to my Paris trip
瞧!状态得到了更新,UI 也会反映这些变化。
目前,应用的核心功能已经完成。接下来只需要添加文本流式输出及其他功能来改善用户体验,从而向用户提供实时响应。
现在,我们已经可以与 AI 智能体交互、检索并更新数据,为什么不通过添加文本流式输出功能进一步提升用户体验呢?
这就像在 AI 智能体工作时查看实时进度,类似于 ChatGPT 等许多热门 AI 的响应方式。
在这一步中,我们将在 LangGraph AI 智能体内实现 copilotkit_emit_state SDK 函数,使 AI 智能体能够在工作过程中持续发送进度。🔥
首先,让我们安装 CopilotKit SDK。由于这里使用的是基于 Python 的智能体(并通过 poetry 进行管理),因此我们将安装 Python SDK。
💁 不知道如何安装 poetry?你可以在此查看安装指南。
poetry add copilotkit==0.1.31a4
由于我们要编辑 search_node,因此直接进入 search.py 文件。
手动发送智能体状态
使用 CoAgents 时,当节点发生变化(即沿边跳转)时,智能体状态会被发送。但如果我们想在某个操作执行过程中显示进度,该怎么办?好消息!我们可以使用 copilotkit_emit_state 手动发送状态。
让我们为 search_node 添加自定义配置,以便发送中间状态。
打开 agent/travel/search.py,添加以下代码:
# 👇 agent/travel/search.py
# Rest of the imports...
from copilotkit.langchain import copilotkit_emit_state, copilotkit_customize_config
async def search_node(state: AgentState, config: RunnableConfig):
"""
The search node is responsible for searching the for places.
"""
ai_message = cast(AIMessage, state["messages"][-1])
config = copilotkit_customize_config(
config,
emit_intermediate_state=[{
"state_key": "search_progress",
"tool": "search_for_places",
"tool_argument": "search_progress",
}],
)
# Rest of the code...
发送中间状态
现在,让我们使用 copilotkit_emit_state,随着搜索的进行手动发送状态。我们发送的每条查询都会产生相应的更新。
让我们再次编辑 agent/travel/search.py,在搜索开始时以及返回结果时发送状态。
使用以下代码编辑 agent/travel/search.py:
# 👇 agent/travel/search.py
# Rest of the code...
async def search_node(state: AgentState, config: RunnableConfig):
"""
The search node is responsible for searching the for places.
"""
ai_message = cast(AIMessage, state["messages"][-1])
config = copilotkit_customize_config(
config,
emit_intermediate_state=[{
"state_key": "search_progress",
"tool": "search_for_places",
"tool_argument": "search_progress",
}],
)
# ^ Previous code
state["search_progress"] = state.get("search_progress", [])
queries = ai_message.tool_calls[0]["args"]["queries"]
for query in queries:
state["search_progress"].append({
"query": query,
"results": [],
"done": False
})
await copilotkit_emit_state(config, state)
# Rest of the code...
更新并发送进度
现在该实时显示结果了。搜索完成时,我们将更新进度。
最后,使用以下代码更新 agent/travel/search.py:
# 👇 agent/travel/search.py
# Rest of the code...
async def search_node(state: AgentState, config: RunnableConfig):
"""
The search node is responsible for searching the for places.
"""
ai_message = cast(AIMessage, state["messages"][-1])
config = copilotkit_customize_config(
config,
emit_intermediate_state=[{
"state_key": "search_progress",
"tool": "search_for_places",
"tool_argument": "search_progress",
}],
)
state["search_progress"] = state.get("search_progress", [])
queries = ai_message.tool_calls[0]["args"]["queries"]
for query in queries:
state["search_progress"].append({
"query": query,
"results": [],
"done": False
})
await copilotkit_emit_state(config, state)
# ^ Previous code
places = []
for i, query in enumerate(queries):
response = gmaps.places(query)
for result in response.get("results", []):
place = {
"id": result.get("place_id", f"{result.get('name', '')}-{i}"),
"name": result.get("name", ""),
"address": result.get("formatted_address", ""),
"latitude": result.get("geometry", {}).get("location", {}).get("lat", 0),
"longitude": result.get("geometry", {}).get("location", {}).get("lng", 0),
"rating": result.get("rating", 0),
}
places.append(place)
state["search_progress"][i]["done"] = True
await copilotkit_emit_state(config, state)
state["search_progress"] = []
await copilotkit_emit_state(config, state)
# Rest of the code...
在 UI 中渲染进度
为了在 UI 中显示进度,我们将使用 useCoAgentStateRender Hook。此 Hook 会根据条件渲染 search_progress 状态。
我们只需通过 useCoAgentStateRender Hook 告诉 CopilotKit,根据条件渲染 search_progress 状态键。
现在,让我们修改 ui/lib/hooks/use-trips.tsx 来显示搜索进度:
// 👇 ui/lib/hooks/use-trips.tsx
// Rest of the imports...
import { useCoAgent, useCoAgentStateRender } from "@copilotkit/react-core";
import { SearchProgress } from "@/components/SearchProgress";
export const TripsProvider = ({ children }: { children: ReactNode }) => {
// Rest of the code...
useCoAgentStateRender<AgentState>({
name: "travel",
render: ({ state }) => {
if (state.search_progress) {
return <SearchProgress progress={state.search_progress} />
}
return null;
},
});
// Rest of the code...
}
<SearchProgress /> 组件已经为你配置好了。如果你好奇它是如何实现的,可以查看 ui/components/SearchProgress.tsx。🙌
💁 额外提示:search_progress 状态键已经在 ui/lib/types.ts 的 AgentState 类型中预先定义,因此无需担心从头创建它!
现在来试一试吧!向智能体提问,你将看到进度实时更新。🤩
通过添加人在回路机制实现控制
好了,是时候来点真实场景中的魔法了。什么是 i