跳转到内容

三层解耦架构:Frontend → Runtime → Agent

三层解耦架构:Frontend → Runtime → Agent

学习目标

理解 CopilotKit 的三层架构设计:

  • 每层的职责、核心组件和边界
  • 为什么需要拆三层而非一体化
  • 各层之间如何独立替换和升级
  • 请求在三层之间的完整生命周期

前置知识

本章涉及 Agent 核心循环设计的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 CopilotKit 的三层架构实现。

项目实践

三层架构总览

前端层(Browser)

前端层的核心是 CopilotKitCore——一个纯 TypeScript 的框架无关类,负责:

职责实现
Agent 注册AgentRegistry 管理多个 AbstractAgent 实例
工具注册工具注册表,按名称索引,支持 catch-all 通配符
上下文管理ContextStore 键值对存储,每次 Agent 运行自动附加
事件订阅CopilotKitCoreSubscriber 回调接口
状态管理StateManager + ThreadStoreRegistry
远程代理ProxiedCopilotRuntimeAgent 将 HTTP 远程 Agent 代理为本地 AbstractAgent

ProxiedCopilotRuntimeAgent 是关键设计:它继承自 AG-UI 的 HttpAgent,将远程 Agent 封装成本地对象。前端代码无需区分本地 Agent 和远程 Agent——两者都实现 AbstractAgent 接口。

两种运行时模式

  • SSE 模式(默认):通过 HTTP + SSE 与后端通信
  • Intelligence 模式:通过 WebSocket 实现持久化线程、实时事件和 Redis 锁

前端通过 ensureRuntimeMode() 在首次请求时自动探测运行时的能力。

运行时层(Server)

运行时层的核心是 CopilotRuntime(及其子类 CopilotSseRuntime / CopilotIntelligenceRuntime),负责:

职责实现
HTTP 路由Express / Hono / Bun / Elysia 适配器
端点处理run / connect / stop / threads / transcribe / info
中间件管线beforeRequestMiddleware + afterRequestMiddleware
Agent 解析静态注册或 AgentsFactory 动态工厂
AgentRunner管理线程状态、执行 Agent、发射事件流
License 验证createLicenseChecker 服务侧验证
遥测@copilotkit/license-verifier 集成的遥测客户端

HTTP 端点(以 Express 为例):

端点方法说明
/agent/{agentId}/runPOST启动 Agent 运行,返回 SSE 事件流
/agent/{agentId}/connectPOST连接到已有 Agent 运行
/agent/{agentId}/stop/{threadId}POST停止 Agent 运行
/threadsGET/POST线程管理
/infoGET运行时信息(模式、能力等)

单端点传输下,所有操作统一 POST 到 /api/copilotkit,通过 body 中的 method 字段区分:"agent/run""agent/connect""agent/stop""info"

Agent 层

Agent 层通过 AbstractAgent 抽象接入多种后端:

后端包/类说明
LangGraph@ag-ui/langgraph图状态机 Agent,支持中断和恢复
OpenAIOpenAIAdapter直接调用 OpenAI Chat Completion API
AnthropicAnthropicAdapter直接调用 Anthropic Messages API
Google GenAIGoogleGenAIAdapter直接调用 Google Gemini API
GroqGroqAdapter通过 Groq API 调用
BedrockBedrockAdapter通过 AWS Bedrock 调用
LangChainLangChainAdapter通过 Vercel AI SDK 接入任意 LangChain 模型
BuiltInBuiltInAgent内置 Agent,基于 Vercel AI SDK
Custom用户自定义实现 CopilotServiceAdapter 接口

CopilotRuntime 的 Agent 配置支持三种方式

  1. 静态注册(开发环境常见):
const runtime = new CopilotSseRuntime({
agents: { default: new LangGraphAgent({ ... }) }
});
  1. Promise 异步注册
const runtime = new CopilotSseRuntime({
agents: loadAgentsAsync()
});
  1. 工厂函数(多租户场景):
const runtime = new CopilotSseRuntime({
agents: ({ request }) => {
const tenantId = request.headers.get("x-tenant-id");
return { default: createAgentForTenant(tenantId) };
}
});

请求生命周期(源码验证)

完整请求流:

  1. 前端初始化CopilotKitCore 创建 → 注册工具/上下文 → ProxiedCopilotRuntimeAgent 连接到运行时
  2. 用户发送消息:消息添加到 Agent → 调用 runAgent()
  3. HTTP 请求:POST 发送到运行时,携带 RunAgentInput(消息、工具、上下文、threadId)
  4. 中间件执行beforeRequestMiddleware 处理请求(认证、日志等)
  5. Agent 解析:根据 agentId 查找或动态创建 Agent
  6. AgentRunner 执行:Runner 管理线程状态,执行 Agent
  7. SSE 流回前端:Agent 发射 AG-UI 事件 → 运行时编码为 SSE → 前端解码
  8. 前端工具执行:Agent 调用前端工具 → Core 查找 handler → 浏览器执行 → 结果回传
  9. 中间件后置afterRequestMiddleware 从 SSE 流解析 messages → 审计日志
  10. UI 更新:Core 更新消息存储 → React 重新渲染

问题与规避

问题规避策略
Runtime 成为单点瓶颈多实例部署 + 粘性会话(同一 threadId 路由到同一实例);Intelligence 模式通过 Redis 共享锁
前端 ProxiedAgent 与后端 Agent 状态不一致syncDelegate() 在每次操作前同步状态(messages、state、threadId)
Agent 工厂每次创建新实例的开销在工厂内部实现 Agent 缓存(按 tenantId 等 key 缓存)
SSE 流在长时间运行中被代理缓冲响应头设置 X-Accel-Buffering: no;使用单端点模式减少代理层

设计取舍

三层 vs 一体化

CopilotKit 选择三层而非一体化(如 Dify 将 UI、运行时、Agent 耦合在一起):

三层优势

  • 前端拥有完整的 UI 自定义能力(不局限于框架提供的组件)
  • 运行时独立部署,可做认证、审计、限流等横切关注点
  • Agent 后端可随意替换,前端和运行时不需要改动

三层代价

  • 增加了一层网络通信(前端 ↔ 运行时)
  • 调试需要追踪三层的事件流
  • 部署复杂度更高(需要分别维护前端和运行时服务)

前端 Agent 代理 vs 直接远程调用

ProxiedCopilotRuntimeAgent 将远程 Agent 代理为本地对象:

优势

  • 前端代码透明使用,不关心 Agent 是本地还是远程
  • 状态同步自动处理(messages、state、threadId)
  • 支持 Intelligence 模式的 WebSocket 长连接

代价

  • 增加了代理层的复杂度(路由、传输协商、错误处理)
  • runtimeAgentId vs agentId 的区分容易混淆(subscriber 用前者,注册用后者)

参考来源

  • CopilotKit 架构文档
  • packages/core/src/core/core.ts(CopilotKitCore 实现)
  • packages/runtime/src/v2/runtime/core/runtime.ts(CopilotRuntime 实现)