数据截至 (上游 commit 63392db20d55)
CopilotKit — 架构与原理
30 秒导读: CopilotKit 让你「把一个 agent 接进自己的 React 应用」。你用 hooks 声明几样东西——这些函数 agent 可以调(前端工具)、这些是页面当前状态(上下文)、这种工具调用这样渲染成 UI(生成式 UI)——CopilotKit 在浏览器里维护一个「前端大脑」
CopilotKitCore,把这些声明打包发给(通常跑在别处的)agent,再把 agent 流回来的事件翻译成聊天消息、就地在浏览器执行前端工具、把结果回灌给 agent 让它续跑。一句话:它是 agent 和你的网页之间的双向胶水。
1. 这是什么(零基础也能懂)
一句话定义。 CopilotKit 是一套 SDK,把「一个 AI agent」嵌进你已有的前端应用(React / Angular / Vue / React Native),让 agent 能调用页面里的函数、读取页面的状态、在聊天框里渲染你的组件。
它解决谁的什么问题。 假设你已经有一个 LangGraph / CrewAI / 自研的 agent 跑在某个服务器上,现在想给它配一个网页界面:用户在聊天框里说话,agent 不仅能回文字,还能
- 点亮你 App 里的某个开关(调用前端函数),
- 知道用户此刻在看哪个页面、购物车里有什么(读取前端状态),
- 在对话里弹出一张「确认订单」卡片等用户点确认(human-in-the-loop),
- 甚至现场生成一段 UI 塞进对话里(generative UI)。
手写这套「前端 ↔ agent」的双向通道很烦:要管 SSE 流式、要把模型说的「调用 searchFlights」精确落到你真实的 searchFlights 函数、要把工具结果回灌、要处理中断/取消/多 agent。CopilotKit 把这层全包了。
它能做什么(对外功能)。
| 能力 | 白话 |
|---|---|
| Chat UI | 一套可定制的聊天界面,支持流式、工具调用渲染 |
| Frontend Tools | agent 调用的函数在浏览器里执行(改 DOM、调你的 API) |
| Generative UI | agent 的工具调用 → 渲染成你的 React 组件 |
| Shared State | agent 和 UI 共享一份可读可写的状态 |
| Human-in-the-Loop | agent 暂停,等用户在 UI 上确认/编辑后再继续 |
用起来什么样。 最小例子——声明一个 agent 能调用的前端工具,再放一个聊天框:
// 示意,非源码——但贴近真实 API
import { CopilotKitProvider, useFrontendTool, CopilotChat } from "@copilotkit/react-core";
import { z } from "zod";
function ThemeTool() {
// 声明:agent 可以调用 "setTheme",handler 在浏览器里跑
useFrontendTool({
name: "setTheme",
parameters: z.object({ mode: z.enum(["light", "dark"]) }),
handler: async ({ mode }) => {
document.body.dataset.theme = mode; // 真·改页面
return `theme set to ${mode}`; // 结果回灌给 agent
},
});
return null;
}
export default function App() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit">
<ThemeTool />
<CopilotChat /> {/* 用户说"切到暗色" → agent 调 setTheme → 页面变暗 */}
</CopilotKitProvider>
);
}
一句话直觉。 把 CopilotKit 想成 agent 在你网页里的一只手 + 一双眼睛:工具是手(能动你的页面),上下文是眼睛(能看你的页面状态),聊天 UI 是嘴。中间那个协调一切的「神经中枢」就是 CopilotKitCore。
2. 顶层全景(它大概怎么转)
CopilotKit 是一个三层架构,本文聚焦的 agent-ui 是最靠近用户的前两层(前端 + 它对远端 agent 的代理),不深入服务端 runtime 的实现。
浏览器(本文重点) 别处
┌─────────────────────────────────────┐ ┌──────────────┐
│ React 组件 + hooks │ │ │
│ useFrontendTool / useAgentContext │ │ CopilotRuntime (服务端)
│ useRenderToolCall / CopilotChat │ │ │ │
│ │ 注册 │ │ ▼ │
│ ▼ │ │ 你的 agent │
│ ┌────── ────────────────┐ │ │ (LangGraph / │
│ │ CopilotKitCore │ ──HTTP──>│ ─── │ CrewAI /…) │
│ │ 「前端大脑」 │ <─SSE 事件─ │ │
│ └──────────────────────┘ │ └──────────────┘
│ │ 工具结果回灌 + 通知订阅者 │
│ ▼ │
│ 聊天重渲染 / 前端工具就地执行 │
└─────────────────────────────────────┘
怎么读这张图: 左边浏览器里,React hooks 把「工具/上下文/渲染器」注册进中央的 CopilotKitCore;Core 通过一个代理 agent把这些 + 用户消息发给右边真正的 agent;agent 用 AG-UI 协议(一种 SSE 事件流)流式回传;Core 把事件翻译成消息、就地执行前端工具、再把结果发回去触发续跑。
主要部件一句话职责:
| 部件 | 干什么 | 在哪 |
|---|---|---|
CopilotKitCore | 前端中 枢:管 agent 注册表、工具表、上下文、订阅、运行循环 | packages/core/src/core/core.ts |
RunHandler | 跑 agent + 工具执行循环 + 续跑 | packages/core/src/core/run-handler.ts |
AgentRegistry | 从 runtime /info 拉 agent 列表、造代理 agent | packages/core/src/core/agent-registry.ts |
ProxiedCopilotRuntimeAgent | 远端 agent 在前端的「替身」,把调用翻译成 HTTP/SSE | packages/core/src/agent.ts |
ContextStore | 存「页面状态」上下文,按 agent 过滤 | packages/core/src/core/context-store.ts |
StateManager | 订阅 agent 事件,按 run 记录 state/消息 | packages/core/src/core/state-manager.ts |
CopilotKitProvider | React 入口:把 hooks 的注册喂进 Core | packages/react-core/src/v2/providers/CopilotKitProvider.tsx |
@ag-ui/client(外部依赖) | 提供 AbstractAgent/HttpAgent、事件订阅模型、SSE 解析 | npm @ag-ui/[email protected] |
主线走一遍(高层,不进代码)。 用户在聊天框打字、回车:
1. CopilotChat 把用户消息塞进 agent.messages,调 core.runAgent()
2. RunHandler 把「消息 + 前端工具清单 + 上下文」打包,经代理 agent 发 HTTP POST
3. 远端 agent 流式回 AG-UI 事件:RUN_STARTED → 文本块 → (可能)工具调用 → RUN_FINISHED
4. @ag-ui/client 把事件累积进 agent.messages,Core 通知订阅者 → React 重渲染、聊天逐字出现
5. 若 agent 调了某个前端工具:RunHandler 在浏览器里查表、跑 handler、把结果塞成 tool 消息
6. 有工具结果 → RunHandler 自动再 runAgent() 一次(续跑),让 agent 看到结果继续 —— 回到第 2 步
第 5/6 步就是 CopilotKit 最有意思的地方:工具执行循环(第 3 章)。
3. 阅读地图(建议顺序)
这个项目子系统多,本 area 聚焦「前端把 agent 接进来」这条主线,拆成 4 章,由浅入深:
- 01-core-orchestrator.md — 先认识中枢
CopilotKitCore:它把职责切成 6 个 delegate 子系统、用一套订阅者模型对外广播变化。读完你知道「这个大脑里有哪些器官」。 - 02-proxied-agent-transport.md — 远端 agent 怎么在前端「现身」:
ProxiedCopilotRuntimeAgent把runAgent()翻译成对 runtime 的 HTTP/SSE 请求,支持 REST / 单端点 / Intelligence(WebSocket)三种传输。 - 03-tool-execution-loop.md — 全项目最核心的机制:工具调用循环。agent 说「调 X」→ 浏览器查表跑 handler → 结果回灌 → 自动续跑,以及中止、通配工具、参数容错。
- 04-react-binding.md — React 这层薄胶水:Provider 怎么把分散在各组件里的
useFrontendTool/useAgentContext/ 渲染器收集进 Core,useAgent怎么订阅 agent 让聊天重渲染。
建议按 1 → 2 → 3 → 4 顺序读;只想看「工具怎么落地」可直接跳第 3 章。
4. 巧妙之处(先剧透精华)
几个读完全文你会带走的设计点,正文各章会展开:
- Core 把自己切成 delegate,但只暴露一个门面。
CopilotKitCore几乎所有方法都是一行转发给某个 delegate(agentRegistry/runHandler/contextStore…),delegate 之间通过一个受控的CopilotKitCoreFriendsAccess接口互访,而不是互相import(core.ts:331)。这让每个子系统能单独测,门面保持稳定。 - 「续跑」是递归的
runAgent,但中止控制只在最外层装。 工具跑完需要让 agent 再看一眼结果,实现是processAgentResult直接再调runAgent;用_runDepth计数,只有 top-level 调用才建/拆AbortController(run-handler.ts:463)。 - 续跑前先「让一拍」给 React。 前端工具的 handler 可能调了
setState,React 的 commit 是异步的;若立刻续跑会读到旧上下文。React 子类用await setTimeout(0)把控制权让给 React 调度器,等useLayoutEffect把新上下文写进 store 再续跑(react-core.ts:153)。 - 同一个 runtime agent 不会被重复 new。 runtime
/info每次重连都会重跑,但同 id 的 agent 会复用已有实例,避免丢掉它攒的消息/订阅、避免把已渲染的对话卸载重挂(agent-registry.ts:444)。