CopilotKit: 给应用添加AI能力
无需外部代理框架,只需要你选择的 LLM。
大多数 AI 集成都过度设计了。
即使是最基础的添加 AI 助手教程也充斥着 LangGraph 工作流和 FastAPI 服务器。但你根本不需要这些。
今天,我们将不使用任何外部代理框架或编排层来构建一个 AI。它能知道你的应用里有什么,并且能真正执行操作。
你还将学习如何实现生成式 UI 模式,这种模式可以渲染实际的组件,而不仅仅是文本响应。
让我们开始吧。
1、为什么 AI 集成会变得复杂
给应用添加 AI 听起来很简单,直到你真正尝试。
当你超越基本的聊天界面时,你立即会遇到一个根本问题:LLM 本身不能做任何事情。它们只能生成文本。
要构建一个真正能执行操作的 AI——读取应用状态、更新数据、调用 API——你需要自己构建这些"管道"。
这些管道就是 AI 代理。核心上,每个代理都运行相同的循环:
- 观察:获取上下文(用户输入、工具结果、记忆)
- 推理:决定下一步做什么
- 行动:调用工具、写文件、访问 API,无论需要什么
理论上很简单。但实际上,你最终要编写管理这个循环的编排层、向 LLM 暴露应用函数的工具注册表、跟踪代理知道什么的state管理,以及处理模型出错时的错误处理。
context = [initial_event]
while True:
next_step = await llm.determine_next_step(context)
context.append(next_step)
if next_step.intent == "done":
return next_step.final_answer
result = await execute_step(next_step)
context.append(result)
在完整的代理设置中,LLM 处于中心位置——跨数据源、工具、模型和外部服务进行编排。
这就是为什么教程都教 LangGraph 和 FastAPI 服务器。对于复杂的自动化管道,这种复杂性是合理的。
但如果你的目标只是在产品中添加一个 AI 助手——一个能理解你的 UI、读取你的数据并能代表用户执行操作的 AI,你实际上在构建大量基础设施来解决一个更小的问题。
CopilotKit 处理循环、上下文、流式传输和前端集成,所以你不必自己连接任何这些。
对于本教程,我们将使用 CopilotKit 但不使用任何外部代理框架,只需要 hooks 和你选择的 LLM。
2、CopilotKit 实际做了什么
CopilotKit 是一个开源框架,用于向你的应用添加 AI 代理,它抽象了你刚才读到的所有内容。
你不再需要构建自己的代理循环、工具注册表和流式传输层,而是获得一组直接插入现有前端的 hooks 和预构建组件。
心智模型很简单,你向 AI 提供两样东西:
- 你应用的状态 — 让它理解屏幕上有什么以及用户正在操作什么
- 一组工具 — 让它能够真正做一些有用的事情,而不仅仅是回复文本
实际上,这映射到两个 hooks:
- useAgentContext — 与 AI 共享应用状态。你传入的任何东西都会成为 LLM 上下文的一部分:当前用户、选中的项目、加载的数据——AI 知道你应用知道的任何事情。
- useFrontendTool — 定义 AI 可以触发的操作。你给它一个名称、描述和处理程序。LLM 根据用户的问题决定何时调用它。
结果是,向你的应用添加 AI 助手变成了一个前端问题,而不是基础设施问题。
你的 UI、代理、工具都在一个统一的交互循环中。
3、如何连接所有内容(1 分钟)
你可以跟随官方文档或直接跟着这个教程。我会详细解释每个部分的作用和原因。
我使用 Next.js 和 TypeScript,但这适用于任何 React/Angular 框架!
// creates a nextjs app
npx create-next-app@latest .
安装你需要的三个 CopilotKit 包:
npm install @copilotkit/react-core @copilotkit/react-ui @copilotkit/runtime
@copilotkit/react-core/v2: hooks 和内置聊天组件@copilotkit/react-ui/v2: 样式@copilotkit/runtime: 后端运行时和 LLM 适配器
在 app/api/copilotkit/route.ts 创建 API 路由。
这是接收来自 UI 的消息、通过代理循环运行它们并流式传输响应的单一端点。
BuiltInAgent运行观察 → 推理 → 行动循环CopilotRuntime管理会话、流式传输和工具执行
这就是你的全部后端。
import { CopilotRuntime, copilotRuntimeNextJSAppRouterEndpoint } from "@copilotkit/runtime";
import { BuiltInAgent } from "@copilotkit/runtime/v2";
import { NextRequest } from "next/server";
const builtInAgent = new BuiltInAgent({
model: "openai:gpt-5",
// apiKey: process.env.OPENAI_API_KEY,
});
const runtime = new CopilotRuntime({
agents: { default: builtInAgent },
});
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
endpoint: "/api/copilotkit",
});
return handleRequest(req);
};
在根目录创建 .env.local 并添加你的 OpenAI API key。
OPENAI_API_KEY=sk-proj-...
如果你想切换到任何其他 LLM 提供商,只需要将修改后的字符串传递给 API 路由中的 BuiltInAgent。其他一切都保持不变。
// Anthropic
const builtInAgent = new BuiltInAgent({ model: "anthropic:claude-sonnet-4-5" });
// Google
const builtInAgent = new BuiltInAgent({ model: "google:gemini-2.0-flash" });
将匹配的 key 添加到 .env.local 就完成了。对于 Azure OpenAI、AWS Bedrock 或 Ollama 等自定义模型,请查看模型选择文档。
现在,通过在 app/layout.tsx 中包装你的应用来连接前端到后端。
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/v2/styles.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<CopilotKit runtimeUrl="/api/copilotkit">
{children}
</CopilotKit>
</body>
</html>
);
}
CopilotKit 是其下每个 hook 依赖的上下文提供者。runtimeUrl 指向你刚创建的路由。
有很多内置组件,如 CopilotSidebar、CopilotChat 和 CopilotPopup。对于这个例子,我将添加聊天侧边栏。
import { CopilotSidebar } from "@copilotkit/react-core/v2";
export default function Page() {
return (
<main>
<h1>Your App</h1>
<CopilotSidebar />
</main>
);
}
运行 npm run dev,你现在就拥有了一个可用的 AI 侧边栏助手!🎉
它可以回应任何问题,但它完全不知道你应用里实际有什么。它不知道你的数据、你的 UI 状态或用户正在查看的内容。接下来的两个步骤将解决这个问题。
4、给 AI 提供上下文
默认情况下,AI 对你应用里的内容一无所知。你可以问它"我在食物上花了多少钱?",它完全不知道——它只看到对话,而不是你应用的状态。
useAgentContext 解决了这个问题。它在每个回合都将你的 React 状态推送到代理的上下文窗口中,这样 AI 始终有你的 UI 中当前内容的快照。
让我们构建一个简单的支出追踪器来看看这是如何工作的。
"use client";
import { useState } from "react";
import { useAgentContext, CopilotSidebar } from "@copilotkit/react-core/v2";
type Expense = {
id: number;
description: string;
amount: number;
category: string;
};
const initialExpenses: Expense[] = [
{ id: 1, description: "Groceries", amount: 85, category: "Food" },
{ id: 2, description: "Netflix", amount: 15, category: "Entertainment" },
{ id: 3, description: "Uber", amount: 22, category: "Transport" },
];
export default function Page() {
const [expenses, setExpenses] = useState<Expense[]>(initialExpenses);
useAgentContext({
description: "The user's current expense list. Each item has an id, description, amount in dollars, and category.",
value: expenses,
});
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">My Expenses</h1>
<ul className="space-y-2">
{expenses.map((e) => (
<li key={e.id} className="flex justify-between border-b py-2">
<span>
{e.description}{" "}
<span className="text-gray-400 text-sm">({e.category})</span>
</span>
<span>${e.amount}</span>
</li>
))}
</ul>
<CopilotSidebar />
</main>
);
}
expenses 是你正常的 React 状态。useAgentContext 接收它并在每个回合将其注入 LLM 的上下文。
现在,如果用户问"我最大的支出是什么?",AI 正在查看与你的 UI 渲染的相同列表。
5、让 AI 执行操作
感知是一回事。能够行动才是让代理真正有用的关键。
useFrontendTool 让你给 AI 一组它可以触发的操作。你用一个名称、一个描述和一个处理程序定义一个工具——LLM 根据用户的问题决定何时调用它。
LLM 读取 description 字段来理解每个工具的作用以及每个参数期望什么,所以写清楚这些,模型就能从随意的对话输入中准确地填充它们。
在同一页面内添加 useFrontendTool。
parameters 接受一个 Zod schema(一个 TypeScript 优先的验证库)。如果你以前没用过,概念很简单:用 z.object({...}) 定义你的数据形状,每个字段都有一个 .describe() 调用,告诉 LLM 这意味着什么。
用 npm install zod 安装它。
"use client";
import { useState } from "react";
import { useAgentContext, useFrontendTool, CopilotSidebar } from "@copilotkit/react-core/v2";
import { z } from "zod";
// ... type and initialExpenses stay the same
export default function Page() {
const [expenses, setExpenses] = useState<Expense[]>(initialExpenses);
// ... useAgentContext
useFrontendTool({
name: "addExpense",
description:
"Add a new expense when the user mentions spending money on something.",
parameters: z.object({
description: z
.string()
.describe("What the expense was for, e.g. Lunch, Taxi, Coffee"),
amount: z.number().describe("How much was spent in dollars"),
category: z
.string()
.describe("Category: Food, Transport, Entertainment, Health, or Other"),
}),
handler: async ({ description, amount, category }) => {
setExpenses((prev) => [
...prev,
{ id: Date.now(), description, amount, category },
]);
},
});
return (
// ... remains the same
);
}
你可以发送一个示例查询,比如"我昨晚晚餐花了 40 美元"。代理用 { description: "Dinner", amount: 40, category: "Food" } 调用 addExpense,你的处理程序更新 expenses,新项目立即出现在列表中。
这就是观察 → 推理 → 行动的循环端到端运行。
代理通过 useAgentContext 观察你的消息和你当前的支出,推断出 addExpense 是正确的调用,然后用正确的参数调用你的处理程序来执行操作。CopilotKit 处理了中间的一切。
6、奖励:生成式 UI
AI 可以读取你的数据并更新它。但到目前为止,它只能以文本形式回复。
生成式 UI 是一个新想法:代理不是描述结果,而是渲染实际的 UI。例如,如果有人问他们的支出情况,应用会显示一个分类卡片。
CopilotKit 通过 useFrontendTool 上的 render 属性支持这个功能。不是文本回复,工具返回一个 React 组件——在聊天中内联渲染,使用你自己的设计系统。
让我们在同一组件中添加一个摘要工具。
import { ToolCallStatus, useFrontendTool } from "@copilotkit/react-core/v2";
useFrontendTool({
name: "showSpendingSummary",
description:
"Call this when the user asks for a summary or overview of their expenses.",
parameters: z.object({}),
handler: async () => {
const summary = expenses.reduce(
(acc, e) => {
acc[e.category] = (acc[e.category] ?? 0) + e.amount;
return acc;
},
{} as Record<string, number>,
);
const total = expenses.reduce((sum, e) => sum + e.amount, 0);
return JSON.stringify({ summary, total });
},
render: ({ result, status }) => {
return (
<div className="rounded-lg border p-4 mt-2 space-y-3">
<p className="font-semibold text-sm">
{status === ToolCallStatus.InProgress ? "Calculating..." : "Spending Breakdown"}
</p>
{status === ToolCallStatus.Complete && result && (
<>
{Object.entries(
(JSON.parse(result) as { summary: Record<string, number>; total: number }).summary
).map(([category, amount]) => (
<div key={category} className="flex justify-between text-sm">
<span className="text-gray-600">{category}</span>
<span className="font-medium">${amount}</span>
</div>
))}
<div className="flex justify-between text-sm font-semibold border-t pt-2">
<span>Total</span>
<span>${(JSON.parse(result) as { summary: Record<string, number>; total: number }).total}</span>
</div>
</>
)}
</div>
);
},
});
这里发生了什么:
parameters: z.object({})是空的。LLM 不需要传递任何东西,它只需要决定何时调用工具handler做实际的工作,从expenses状态计算类别总计并返回它们ToolCallStatus给你工具调用的确切生命周期状态result是你的处理程序返回的 JSON 字符串,在render内部解析回对象
当你问"总结我的支出"时,LLM 读取查询,查看上下文,识别出 showSpendingSummary 是要调用的正确工具并触发它。
这就是模式:LLM 决定何时行动,你的代码做工作,你的组件显示结果。
这个最小支出追踪器的工作实现在这个 GitHub 仓库 的 example/basic 分支中可用。
main 分支用完整的看板进一步延伸了相同的模式:
- 通过询问 AI 在列之间移动任务
- 用自然语言添加、删除和重新分配任务
- 用生成式 UI 获取可视化看板摘要
这里是演示!
大多数感觉神奇的 AI 功能并非如此。它们只是一个知道屏幕上有什么并且连接了一些操作的应用。就这样。
困难的部分一直是基础设施。事实证明,它不一定必须如此。
原文链接: Add AI to your app in 5 minutes汇智网翻译整理,版权归原作者所有,转载需标明出处