CopilotKit: 给应用添加AI能力
大多数 AI 集成都过度设计了。即使是最基础的添加 AI 助手教程也充斥着 LangGraph 工作流和 FastAPI 服务器。但你根本不需要这些。
无需外部代理框架,只需要你选择的 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汇智网翻译整理,版权归原作者所有,转载需标明出处