跳转到内容

多 Agent 编排:同一运行时中的隔离与协作

多 Agent 编排:同一运行时中的隔离与协作

学习目标

理解 CopilotKit 如何在一个运行时中管理多个 Agent:

  • Agent 注册表与隔离策略
  • 线程管理与锁定机制
  • 作用域工具 vs 全局工具
  • 动态 Agent 工厂(多租户场景)
  • ProxiedAgent 模式:前端透明访问远程 Agent

前置知识

本章涉及多 Agent 路由与 Agent 核心循环的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 CopilotKit 的多 Agent 实现。

项目实践

Agent 注册表

AgentRegistryCopilotKitCore 内部的 Agent 管理器:

注册表结构(伪代码):
{
agents: Map<agentId, AbstractAgent>,
subscribers: Map<agentId, AgentSubscriber[]>,
register(agentId, agent) → 注册新 Agent
get(agentId) → AbstractAgent,
unregister(agentId) → 移除 Agent
}

每个 Agent 拥有独立的:

  • agentId:唯一标识
  • description:Agent 描述(Agent 用此理解自身角色)
  • threadId:当前会话的线程 ID
  • messages[]:消息历史
  • state:自定义状态

线程隔离

每个 Agent 实例通过 threadId 隔离对话状态:

线程生命周期:
1. 创建:首次 run/connect 时自动创建 threadId
2. 运行:同一 threadId 的所有消息追加到同一历史
3. 切换:切换 threadId 时,清除本地 messages/state
4. 停止:stop 操作针对特定 threadId

线程锁定机制(Intelligence 模式):

当 Agent 正在运行某个 threadId 时,该线程被锁定。其他 run 请求会收到 AGENT_THREAD_LOCKED 错误码:

错误码体系(CopilotKitCoreErrorCode):
- AGENT_THREAD_LOCKED:线程被其他 run 占用
- AGENT_RUN_FAILED_EVENT:协议级运行失败
- AGENT_RUN_ERROR_EVENT:协议级运行错误

锁定通过 Redis 实现(Intelligence 模式):

  • lockTtlSeconds:锁过期时间(默认 20s,最大 3600s)
  • lockHeartbeatIntervalSeconds:心跳续约间隔(默认 15s,最大 3000s)
  • lockKeyPrefix:自定义 Redis key 前缀

TTL 兜底:如果用户断开连接,锁最终会因 TTL 过期而自动释放,防止永久锁定。

作用域工具

工具可以通过 agentId 绑定到特定 Agent:

工具作用域模式:
{
name: "getCalendar",
agentId: "calendar-agent", // 仅 calendar-agent 可用
handler: () => { ... }
}
{
name: "getWeather",
// 无 agentId → 所有 Agent 可用
handler: () => { ... }
}

作用域工具的查找流程

  1. 检查是否有匹配当前 agentId 的同名工具
  2. 若无,查找无 agentId 的全局工具
  3. 若仍无,触发 catch-all 或返回 “tool not found”

动态 Agent 工厂

AgentsFactory 支持根据请求上下文动态创建 Agent:

const runtime = new CopilotSseRuntime({
agents: ({ request }) => {
const userId = request.headers.get("x-user-id");
const locale = request.headers.get("x-locale");
return {
default: createAgentForUser(userId, locale)
};
}
});

resolveAgents() 函数的解析逻辑(源码验证):

  1. agents 是函数 → 调用 agents({ request }) 并 await 结果
  2. agents 是对象/Promise → 直接 await

典型应用场景

  • 多租户:每个租户独立的 Agent 配置
  • A/B 测试:根据用户分组返回不同 Agent
  • 灰度发布:部分用户使用新 Agent

ProxiedAgent 模式

前端通过 ProxiedCopilotRuntimeAgent 将远程 Agent 代理为本地对象:

前端代码透明使用:
const agent = core.getAgent("support-agent");
// agent 是 ProxiedCopilotRuntimeAgent 实例
// 但前端代码只需知道它实现了 AbstractAgent 接口

关键设计

  • runtimeAgentId vs agentId:本地注册用 agentId,远程路由用 runtimeAgentId
  • runtimeAgentId 可在注册时覆盖(registerProxiedAgent({ runtimeAgentId: "xxx" })
  • runtimeAgentIdagentId 都为空 → 抛出异常(防止产生 /agent//run 这样的畸形 URL)

前端 Agent 切换

React hook useAgent({ agentId }) 切换当前交互的 Agent:

const { messages, runAgent } = useAgent({ agentId: selectedAgentId });

切换 Agent 时:

  1. 旧 Agent 的订阅取消
  2. 新 Agent 的消息历史加载
  3. 前端 UI 反映新 Agent 的状态

问题与规避

问题规避策略
线程锁定冲突(并发 run)前端使用 isRunning 状态阻止重复 run;Intelligence 模式的 Redis 锁做服务端兜底
Agent 工厂缓存爆炸(每次创建新实例)在工厂内部实现 LRU 缓存;限制同时存在的 Agent 实例数
runtimeAgentIdagentId 混淆文档约定:agentId 是本地注册 key,runtimeAgentId 是远程路由 key
切换 Agent 时消息闪烁(两个 run 的事件交错)connectAgent 前先 detachActiveRun;桥接订阅确保消息实时同步
ProxiedAgentclone() 不完整clone() 方法需要深拷贝 headers、messages、state、delegate(Intelligence 模式)

设计取舍

平行多 Agent vs 嵌套多 Agent

CopilotKit 的多 Agent 是”平行”关系:

平行模式(CopilotKit)

  • 每个 Agent 独立运行,前端选择与哪个 Agent 交互
  • Agent 之间不直接通信
  • 适合:不同领域的独立 Agent(如”客服 Agent” vs “技术 Agent”)

嵌套模式(LangGraph 多图)

  • Agent 内部包含子 Agent
  • 子 Agent 的结果合并到父 Agent
  • 适合:复杂任务的子任务分解

两者不冲突:CopilotKit 的每个 Agent 本身可以是 LangGraph 多图。

静态注册 vs 动态工厂

维度静态注册动态工厂
适用场景开发环境、少量固定 Agent生产环境、多租户
性能无创建延迟每次请求需创建/查找
灵活性低(Agent 列表硬编码)高(根据请求动态创建)
缓存天然缓存(启动时创建)需要手动实现缓存

参考来源

  • packages/core/src/core/core.ts(AgentRegistry、Agent 管理)
  • packages/core/src/agent.ts(ProxiedCopilotRuntimeAgent)
  • packages/runtime/src/v2/runtime/core/runtime.ts(AgentsFactory、resolveAgents)
  • packages/runtime/src/v2/runtime/runner/intelligence.ts(IntelligenceAgentRunner 线程锁)