跳到主要内容

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 Toolsagent 调用的函数在浏览器里执行(改 DOM、调你的 API)
Generative UIagent 的工具调用 → 渲染成你的 React 组件
Shared Stateagent 和 UI 共享一份可读可写的状态
Human-in-the-Loopagent 暂停,等用户在 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 列表、造代理 agentpackages/core/src/core/agent-registry.ts
ProxiedCopilotRuntimeAgent远端 agent 在前端的「替身」,把调用翻译成 HTTP/SSEpackages/core/src/agent.ts
ContextStore存「页面状态」上下文,按 agent 过滤packages/core/src/core/context-store.ts
StateManager订阅 agent 事件,按 run 记录 state/消息packages/core/src/core/state-manager.ts
CopilotKitProviderReact 入口:把 hooks 的注册喂进 Corepackages/react-core/src/v2/providers/CopilotKitProvider.tsx
@ag-ui/client(外部依赖)提供 AbstractAgent/HttpAgent、事件订阅模型、SSE 解析npm @ag-ui/client@0.0.57

主线走一遍(高层,不进代码)。 用户在聊天框打字、回车:

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 章,由浅入深:

  1. 01-core-orchestrator.md — 先认识中枢 CopilotKitCore:它把职责切成 6 个 delegate 子系统、用一套订阅者模型对外广播变化。读完你知道「这个大脑里有哪些器官」。
  2. 02-proxied-agent-transport.md — 远端 agent 怎么在前端「现身」:ProxiedCopilotRuntimeAgentrunAgent() 翻译成对 runtime 的 HTTP/SSE 请求,支持 REST / 单端点 / Intelligence(WebSocket)三种传输。
  3. 03-tool-execution-loop.md全项目最核心的机制:工具调用循环。agent 说「调 X」→ 浏览器查表跑 handler → 结果回灌 → 自动续跑,以及中止、通配工具、参数容错。
  4. 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:293)。这让每个子系统能单独测,门面保持稳定。
  • 「续跑」是递归的 runAgent,但中止控制只在最外层装。 工具跑完需要让 agent 再看一眼结果,实现是 processAgentResult 直接再调 runAgent;用 _runDepth 计数,只有 top-level 调用才建/拆 AbortController(run-handler.ts:324)。
  • 续跑前先「让一拍」给 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)。