多 Agent 路由与委托
Gemini CLI — 多 Agent 路由与委托
学习目标
- 理解 Gemini CLI 的 Agent 定义系统和 YAML 配置格式
- 掌握本地/远程子 Agent 的通信协议差异
- 分析 Agent 工具封装模式和活动追踪机制
前置知识
本章涉及多 Agent 路由的通用原理,建议先阅读:
下文假设你已理解 Agent 隔离和消息路由的通用概念,直接聚焦 Gemini CLI 的具体实现。
项目实践
Agent 定义系统
Gemini CLI 使用 YAML 配置文件声明 Agent,而非硬编码。每个 Agent 定义包含:
name: code-reviewerdescription: 专门负责代码审查的 Agentmodel: gemini-2.5-promax_turns: 30max_time_minutes: 10tools: - read_file - grep - glob - shell # 受限的 shell 命令加载机制:AgentLoader 从以下目录扫描 YAML 文件:
- 内置 Agent 目录
- 用户
.agents目录 - 工作区
.agents目录(需文件夹信任)
配置校验(packages/core/src/agents/agentLoader.ts):
- 名称必须符合
[a-z0-9-_]+slug 格式 - 工具名称必须通过
isValidToolName验证 - MCP 服务器配置支持完整的 OAuth 和 Google Credentials 认证
Agent 作为工具(AgentTool 模式)
在 Gemini CLI 中,子 Agent 被封装为可调用工具。当主 Agent 的 LLM 响应请求调用某个 Agent 时:
主 Agent LLM: functionCall("agent_code-reviewer", { query: "审查 auth.ts" }) ↓ToolRegistry: 识别为 Agent 调用(AGENT_TOOL_NAME) ↓AgentTool.execute(): ├── 查找已注册的 Agent 定义 ├── 创建 AgentSession(本地或远程) ├── 发送 query 到子 Agent └── 返回子 Agent 的输出结果关键优势:主 Agent 的 LLM 无需知道子 Agent 的内部实现,只需像调用工具一样使用它。这使得 Agent 的添加和替换对主 Agent 透明。
本地 vs 远程子 Agent 协议
| 维度 | 本地子 Agent | 远程子 Agent |
|---|---|---|
| 通信方式 | 同一进程内,直接调用 | A2A 协议,跨进程/网络 |
| 实现 | LocalSessionInvocation | RemoteSessionInvocation |
| 隔离级别 | 独立的 GeminiClient 实例,共享进程 | 独立进程,完全隔离 |
| 延迟 | 低 | 较高(网络往返) |
| 资源 | 共享 Node.js 事件循环 | 独立资源 |
本地子 Agent 协议(local-subagent-protocol.ts):
- 通过
LocalAgentExecutor在同一进程中创建独立的GeminiClient实例 - 每个子 Agent 有独立的
Scheduler、ToolRegistry、MessageBus - 子 Agent 的工具调用受父 Agent 的 Policy 约束
远程子 Agent 协议(remote-subagent-protocol.ts):
- 基于 A2A(Agent-to-Agent)协议
- 通过
A2AClientManager管理远程 Agent 连接 - 支持 HTTP/SSE 和 WebSocket 传输
- A2A 认证:支持
google-credentials和oauth两种认证类型
SubagentActivity 事件追踪
子 Agent 执行过程中会向主 Agent 推送结构化事件:
interface SubagentActivityEvent { isSubagentActivityEvent: true; agentName: string; type: 'TOOL_CALL_START' | 'TOOL_CALL_END' | 'THOUGHT_CHUNK' | 'ERROR'; data: Record<string, unknown>;}主 Agent 侧展示:
- TUI 中显示子 Agent 的实时进度(运行中/完成/错误/取消)
- 子 Agent 的工具调用作为活动日志记录
- 错误类型区分:
REJECTED(用户拒绝)、CANCELLED(用户取消)、GENERIC(执行错误)
终止模式(AgentTerminateMode):
| 模式 | 触发条件 |
|---|---|
GOAL | 子 Agent 调用了 complete_task 工具 |
ERROR | 执行中抛出未捕获异常 |
TIMEOUT | 超过 max_time_minutes 限制 |
MAX_TURNS | 超过 max_turns 限制 |
ABORTED | 用户或父 Agent 主动取消 |
ERROR_NO_COMPLETE_TASK_CALL | 未调用 complete_task 即退出 |
A2A 服务器
Gemini CLI 还包含一个 a2a-server 包(packages/a2a-server/),允许将本地 Gemini CLI 实例作为远程 Agent 暴露给其他 Agent 调用:
- 实现了 A2A 协议的服务器端
- 支持通过 HTTP/SSE 接收 Agent 任务
- 与 CLI 共享相同的 Agent 定义和配置
这使得 Gemini CLI 可以作为多 Agent 网络中的节点,既可以是调用者,也可以是被调用者。
问题与规避
子 Agent 的 Token 消耗
问题:每个子 Agent 有独立的 LLM 会话,多个子 Agent 并行时 Token 消耗叠加。
对策:
- 每个 Agent 定义可设置
max_turns(默认 30)和max_time_minutes(默认 10) - 超时/超限自动终止子 Agent
max_turns限制防止子 Agent 陷入自己的循环
远程 Agent 的网络故障
问题:A2A 协议依赖网络,连接中断导致子 Agent 调用失败。
对策:
- A2A 客户端内置重试逻辑
- 超时配置(Agent 定义中的
timeout字段) - 失败后返回
SubagentActivityErrorType.GENERIC错误,主 Agent 可选择重试或降级
Agent 工具的参数传递
问题:Agent 作为工具调用时,如何将查询参数模板化为子 Agent 的系统提示。
对策:AgentLoader 支持 YAML 中的模板变量,调用时将 params 注入到子 Agent 的系统提示中。参数通过 Zod schema 验证,确保类型正确。
设计取舍
Agent 作为工具 vs 直接委托
| 方案 | 优势 | 代价 |
|---|---|---|
| Agent 作为工具 | 对主 Agent 透明、统一调用接口、易注册到工具表 | Agent 输出受工具的返回值格式限制,灵活性降低 |
| 直接委托 | 子 Agent 可以完全控制交互流程 | 主 Agent 需要了解子 Agent 的协议,耦合度高 |
Gemini CLI 的选择:将 Agent 封装为工具,保持了调用接口的一致性,同时通过 SubagentActivityEvent 提供了足够的可观测性。
本地进程内隔离 vs 独立进程
| 方案 | 优势 | 代价 |
|---|---|---|
| 进程内(独立实例) | 低延迟、共享内存、资源效率高 | 崩溃可能影响主进程 |
| 独立进程 | 完全隔离、独立资源管理 | 通信延迟高、需要序列化 |
Gemini CLI 的选择:默认使用进程内隔离(适合大多数场景),同时通过 A2A 协议支持独立进程模式(适合需要强隔离或分布式部署的场景)。