My Little World

CopilotKit 源码分析

前端组件如何注册到后端agent服务中的

通过CopilotKit 生成式UI框架 了解到,根据开放程度可以分成3类组件

  1. controrolled Generative UI
  2. declarative Generative UI
  3. open Generative UI

其中第2和3类都是在CopilotRuntime 中注册的,注册后在后续发给agent的http请求中会以上下文的方式(其实也是参数)传递给agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
onst langGraphAgent = new LangGraphHttpAgent({ url: "http://localhost:8004" });

const runtime = new CopilotRuntime({
agents: { default: langGraphAgent },
a2ui: { injectA2UITool: true }, // 启用 declarative Generative UI
openGenerativeUI: true, // 启用 open Generative UI 代理可以生成任意类型的用户界面——包括 HTML、CSS、JavaScript 等代码,并可以直接在聊天界面中展示
mcpApps: { // 注册 MCP 应用程序
servers: [
{
type: "http",
url: "https://mcp.excalidraw.com", // <- Exalidraw MCP Server
serverId: "example_mcp_server",
},
],
},
});

const app = createCopilotEndpoint({
runtime,
basePath: "/api/copilotkit",
});

serve({ fetch: app.fetch, port: 4004 }, () => {
console.log("\u2713 CopilotKit API server running at http://localhost:4004");
});

源码分析:
已知CopilotKit 是一个基于agui协议的框架,agui协议是基于http的上层协议
LangGraphHttpAgent 从httpAgent 中继承
本身的作用就是通过http协议与agent进行通信

The HttpAgent extends AbstractAgent to provide HTTP-based connectivity to remote AI agents. It handles the request/response cycle and transforms the HTTP event stream into standard Agent User Interaction Protocol events.

CopilotRuntime 是一个桥接器,用于前端和后端agent服务之间的通信,控制请求发送,信息处理等操作

createCopilotEndpoint 会创建一个node服务,利用CopilotRuntime实例,将tools 信息给到agent 并处理请求过程中的信息,返回给前端

对于第1类 使用 useComponent 进行组件注册的过程会使用useFrontendTool 方法,将组件注册到copilotkit 实例的tools 中


对于 使用 组件传入的tools 在实例化过程中会自动注册到copilotkit 实例的tools 中

从输入框提交信息后会经过copiloykit 实例发起runAgent

在初始化创建的时候就会传入tools, 后续在connectAgent 和 runAgent 中都会将tools 信息传递给后端agent

agent 本身是一个http-agent

1
2
3
4
5
var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAgent {
constructor(options) {
super(options);
}
}

多样的UI界面是谁渲染的

UI 的渲染依然通过前端组件渲染,只不过在渲染时会根据messages 中的返回信息决定使用组件的类型,选择不同的组件进行渲染。
因此, agent 会决定渲染的组件类型,前端组件只是根据组件类型进行渲染。

源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const BoundMessageView = renderSlot(messageView, CopilotChatMessageView, {
messages,
isRunning,
});

CopilotChatMessageView:

// Build the flat element list only when we're not virtualizing (avoids
// creating 500 React elements that we'd immediately discard).
const messageElements: React.ReactElement[] = shouldVirtualize
? []
: deduplicatedMessages.flatMap(renderMessageBlock);

// ---------------------------------------------------------------------------
// children render prop (custom layout, always non-virtual)
// ---------------------------------------------------------------------------
if (children) {
return (
<div data-copilotkit style={{ display: "contents" }}>
{children({ messageElements, messages, isRunning, interruptElement })}
</div>
);
}



注册时的处理