SubAgent 架构与设计思想解读
面向工程师与架构师的深度解析
本文从”为什么需要子代理(SubAgent)””它如何运行””哪些设计保障了可靠性与可扩展性”三个层面,通俗但尽量完整地解读 packages/core/src/core/subagent.ts
的架构与设计哲学。
我们为什么需要 SubAgent?
check_circle
职责分离与自治执行:将一个复杂目标拆解成可自治运行的子任务,每个子任务由一个”子代理”负责,在明确的预算与工具权限下独立推进。
check_circle
非交互式工作流:很多自动化场景无法依赖人工问答。SubAgent 被设计为”非交互模式”,能在没有用户输入的情况下完成工作、调用工具并达成目标。
check_circle
结构化出参与可编排性:通过内置的
self.emitvalue
工具,SubAgent 能以结构化方式”发射”结果变量,便于上层编排收集和传递。
check_circle
安全与确定性:在工具调用前进行”需确认”的预检,执行过程中有”时间/回合数”双重预算,避免无限循环或交互卡死。
架构总览:关键构件与数据流

SubAgent 架构组件示意图
关键构件
- 终止原因枚举
SubagentTerminateMode
:ERROR | TIMEOUT | GOAL | MAX_TURNS,准确表达为什么停止。 - 输出对象
OutputObject
:emitted_vars
存储通过self.emitvalue
发射的变量;terminate_reason
标示终止原因。 - 提示与模型配置:
PromptConfig
:二选一提供systemPrompt
或initialMessages
(few-shot 历史)。ModelConfig
:model
、temp
、top_p
控制生成行为。RunConfig
:max_time_minutes
与可选max_turns
控制预算。ToolConfig
:允许以”注册名”或直接给出FunctionDeclaration
暴露工具。OutputConfig
:声明期望输出变量的键与语义描述。
- 运行态上下文
ContextState
:一个简单的键值容器,用来为系统提示模板${key}
替换提供值;在替换前会强校验缺失键。 - 模板函数
templateString
:校验并替换systemPrompt
内${...}
占位符,缺失即抛错,保障提示完整性与确定性。 - 作用域类
SubAgentScope
:SubAgent 的核心执行体,负责:- 工具预检查与实例化(
create
) - 构建聊天对象(
createChatObject
) - 主事件循环(
runNonInteractive
) - 工具调用执行与结果回灌(
processFunctionCalls
) - 构建系统提示与本地工具注入(
buildChatSystemPrompt
/getScopeLocalFuncDefs
)
- 工具预检查与实例化(
数据流(简化)
- 创建 Scope →
- 构建 Chat(环境上下文 + systemPrompt/initialMessages)→
- 进入循环(发送消息 → 接收函数调用 → 执行工具 → 结果作为”用户消息”回灌)→
- 若未满足输出约束则”提醒”继续 →
- 满足目标或达到预算而终止,回填
OutputObject
。
生命周期与关键步骤
1) 创建与工具预检:SubAgentScope.create
加载 ToolRegistry
,对配置里以”名称”声明的工具做”非交互可用性”预检:
- 若工具无必填参数,则尝试
shouldConfirmExecute
;若需要用户确认,直接抛错禁止在非交互子代理中使用。 - 若工具存在必填参数,无法构造通用的空入参校验,默认”假定安全”(保守权衡:避免创建时崩溃优先于运行时潜在挂起)。
lightbulb设计理念
在创建阶段尽早发现不可用工具,避免运行期才暴露”需要用户确认”的卡死问题。
2) 构建对话体:createChatObject
- 注入环境上下文(
getEnvironmentContext
)形成start_history
,帮助模型”落地”当前会话的系统状态。 - 若使用
systemPrompt
:将ContextState
注入做模板替换;否则使用initialMessages
做 few-shot 引导。 - 组装
GenerateContentConfig
(温度、topP、可选systemInstruction
),并通过createContentGenerator
与GeminiChat
封装出聊天对象。 - 将选定模型名写入
runtimeContext
(便于统一上报/可观测)。
lightbulb设计理念
在”提示治理”层面将环境与系统约束前置,减少”工具-模型”不一致与幻觉。
3) 非交互主循环:runNonInteractive
初始消息为一个轻量”点火”指令(Get Started!
)。每轮:
- 检查预算:
max_turns
/max_time_minutes
,命中则以MAX_TURNS
/TIMEOUT
终止。 - 发送消息(附
tools
与abortSignal
),流式接收模型产生的functionCalls
。 - 若存在函数调用 → 进入”工具执行阶段”。
- 若没有函数调用:
- 如未声明输出要求,直接以
GOAL
终止。 - 如声明了输出要求:
- 若已全部
emit
,以GOAL
终止; - 否则给出”nudge”提示,要求调用
self.emitvalue
发射缺失变量,继续下一轮。
- 若已全部
- 如未声明输出要求,直接以
lightbulb设计理念
让模型”自监督”完成工具调用与收尾动作;当不再调用工具但未满足”结构化出参”时,通过 nudge 将其拉回正轨。
4) 工具执行与结果回灌:processFunctionCalls
对每个 FunctionCall
:
- 若为作用域内工具
self.emitvalue
:将emit_variable_name/value
记录进output.emitted_vars
,并构造成功响应。 - 否则通过
executeToolCall
调用注册工具,携带AbortSignal
;收集工具返回的responseParts
。
若所有工具调用均失败,会向模型回灌一条”全部失败,请分析错误并换一种方法”的文本,促使其调整策略。
将工具响应作为”用户消息”放回对话,以闭环”观察-行动-反馈”。
lightbulb设计理念
严格区分”本地作用域工具”(出参)与”外部工具”(环境交互);失败要能被模型”感知并重试”。
系统提示治理与可观测性
buildChatSystemPrompt
会在 systemPrompt
基础上追加两类关键指令:
- 输出约束:逐条告知如何用
self.emitvalue
发射每个期望变量。 - 非交互规则:明确”不得向用户提问””完成目标后停止调用工具”。
promptId
由 sessionId#subagentId#turn
组成,贯穿工具调用链路,便于追踪与诊断。
可扩展性与二次开发建议
新增工具
- 若已经在
ToolRegistry
注册,可在ToolConfig.tools
中以字符串引用; - 若是临时/作用域内工具,可直接提供
FunctionDeclaration
。
输出协议
- 在
OutputConfig.outputs
中声明期望 key 及其语义描述,系统提示会自动追加”如何发射”的指引; - 模型完成主任务后,应逐一调用
self.emitvalue
发射所有 key。
提示模板化
- 使用
ContextState.set(key, value)
注入上下文变量; - 确保
systemPrompt
中的${key}
均有对应值,否则会在创建 Chat 前被强校验拦截(fail fast)。
预算治理
- 先以较保守的
max_time_minutes
与max_turns
启动; - 结合日志与
terminate_reason
调整参数,达到”既不超时也不早停”的平衡。
与 GeminiChat 的协作关系
SubAgent 自身不直接与模型交互,而是经由 GeminiChat
与 ContentGenerator
层对接 @google/genai
:
- 从工程上看,这一分层让 SubAgent 聚焦”编排(prompt/工具/预算/收尾)”,而将”对话与生成”的复杂度下沉到更通用的封装中。
- 这也为未来的”模型路由/回退/观测”提供了稳定扩展点,例如引入”自动模型选择”或”token 预算”只需扩展
ModelConfig
与ContentGenerator
。
一个最小可用示例(TypeScript 伪代码)
import { SubAgentScope, ContextState } from 'packages/core/src/core/subagent'; import { Config } from 'packages/core/src/config/config'; // 1) 准备运行时上下文(含工具注册、会话信息等) const runtimeContext: Config = /* 你的实现 */; // 2) 声明各类配置 const promptConfig = { systemPrompt: `你是一个文档提取子代理。目标是从 ${'${doc_path}'} 中提取标题和作者。`, }; const modelConfig = { model: 'gemini-2.5-pro', temp: 0.2, top_p: 0.9 }; const runConfig = { max_time_minutes: 2, max_turns: 8 }; const toolConfig = { tools: ['read_file', 'grep'] }; // 假设已在 ToolRegistry 中注册 const outputConfig = { outputs: { title: '文档标题', author: '作者姓名' } }; // 3) 构建上下文(用于 systemPrompt 模板化) const ctx = new ContextState(); ctx.set('doc_path', '/abs/path/to/paper.md'); // 4) 创建并运行子代理 const scope = await SubAgentScope.create( 'doc-extractor', runtimeContext, promptConfig, modelConfig, runConfig, toolConfig, outputConfig, ); await scope.runNonInteractive(ctx); // 5) 收集结构化出参 console.log(scope.output.emitted_vars, scope.output.terminate_reason);
要点:
systemPrompt
与initialMessages
不能同时提供;- 所有
${...}
占位都必须在ContextState
中有值; - 若未满足
outputs
的发射要求,SubAgent 会通过 nudge 提醒模型调用self.emitvalue
; - 若某工具需要用户确认,将在
create
阶段直接阻止使用。
易错点与最佳实践清单
warning
二选一提示源:
systemPrompt
与 initialMessages
不可同时设置。
warning
模板强校验:
systemPrompt
中的 ${key}
缺失会立即抛错,尽早发现配置问题。
warning
工具交互性:避免在非交互子代理中配置”需要确认”的工具;对有必填参数的工具要确保在调用时提供正确入参。
warning
结果要”发射”:仅在所有
outputs
都通过 self.emitvalue
发射后,才会以 GOAL
结束;否则会被反复提醒。
warning
预算与幂等:设置合理的
max_time_minutes
/max_turns
;工具实现保持幂等,便于模型在失败后重试。
设计哲学小结
stars
可编排的自治体:SubAgent 将”执行逻辑”与”对话生成”解耦,使其可以被上层编排自由组合。
stars
以提示为界面:通过模板化的 system prompt 与环境上下文,将”约束/目标/接口(outputs)”显式化为”模型契约”。
stars
工具即能力单元:统一用
FunctionDeclaration
/注册名暴露,可预检、可拦截、可观测。
stars
稳健为先:Fail fast(模板缺失即抛错)、双预算限制、错误回灌与 nudge,尽可能减少挂死与漂移。
stars
面向扩展:从
ToolConfig
到 ModelConfig
到 GeminiChat
封装,均保留了日后路由、预算化、回退与更复杂自治策略的空间。
