SubAgent 架构与设计思想解读

SubAgent 架构与设计思想解读

SubAgent 架构与设计思想解读

面向工程师与架构师的深度解析

本文从”为什么需要子代理(SubAgent)””它如何运行””哪些设计保障了可靠性与可扩展性”三个层面,通俗但尽量完整地解读 packages/core/src/core/subagent.ts 的架构与设计哲学。

我们为什么需要 SubAgent?

check_circle
职责分离与自治执行:将一个复杂目标拆解成可自治运行的子任务,每个子任务由一个”子代理”负责,在明确的预算与工具权限下独立推进。
check_circle
非交互式工作流:很多自动化场景无法依赖人工问答。SubAgent 被设计为”非交互模式”,能在没有用户输入的情况下完成工作、调用工具并达成目标。
check_circle
结构化出参与可编排性:通过内置的 self.emitvalue 工具,SubAgent 能以结构化方式”发射”结果变量,便于上层编排收集和传递。
check_circle
安全与确定性:在工具调用前进行”需确认”的预检,执行过程中有”时间/回合数”双重预算,避免无限循环或交互卡死。

架构总览:关键构件与数据流

SubAgent架构图

SubAgent 架构组件示意图

关键构件

  • 终止原因枚举 SubagentTerminateMode:ERROR | TIMEOUT | GOAL | MAX_TURNS,准确表达为什么停止。
  • 输出对象 OutputObjectemitted_vars 存储通过 self.emitvalue 发射的变量;terminate_reason 标示终止原因。
  • 提示与模型配置
    • PromptConfig:二选一提供 systemPromptinitialMessages(few-shot 历史)。
    • ModelConfigmodeltemptop_p 控制生成行为。
    • RunConfigmax_time_minutes 与可选 max_turns 控制预算。
    • ToolConfig:允许以”注册名”或直接给出 FunctionDeclaration 暴露工具。
    • OutputConfig:声明期望输出变量的键与语义描述。
  • 运行态上下文 ContextState:一个简单的键值容器,用来为系统提示模板 ${key} 替换提供值;在替换前会强校验缺失键。
  • 模板函数 templateString:校验并替换 systemPrompt${...} 占位符,缺失即抛错,保障提示完整性与确定性。
  • 作用域类 SubAgentScope:SubAgent 的核心执行体,负责:
    • 工具预检查与实例化(create
    • 构建聊天对象(createChatObject
    • 主事件循环(runNonInteractive
    • 工具调用执行与结果回灌(processFunctionCalls
    • 构建系统提示与本地工具注入(buildChatSystemPrompt / getScopeLocalFuncDefs

数据流(简化)

  1. 创建 Scope →
  2. 构建 Chat(环境上下文 + systemPrompt/initialMessages)→
  3. 进入循环(发送消息 → 接收函数调用 → 执行工具 → 结果作为”用户消息”回灌)→
  4. 若未满足输出约束则”提醒”继续 →
  5. 满足目标或达到预算而终止,回填 OutputObject

生命周期与关键步骤

1) 创建与工具预检:SubAgentScope.create

加载 ToolRegistry,对配置里以”名称”声明的工具做”非交互可用性”预检:

  • 若工具无必填参数,则尝试 shouldConfirmExecute;若需要用户确认,直接抛错禁止在非交互子代理中使用。
  • 若工具存在必填参数,无法构造通用的空入参校验,默认”假定安全”(保守权衡:避免创建时崩溃优先于运行时潜在挂起)。
lightbulb设计理念

在创建阶段尽早发现不可用工具,避免运行期才暴露”需要用户确认”的卡死问题。

2) 构建对话体:createChatObject

  • 注入环境上下文(getEnvironmentContext)形成 start_history,帮助模型”落地”当前会话的系统状态。
  • 若使用 systemPrompt:将 ContextState 注入做模板替换;否则使用 initialMessages 做 few-shot 引导。
  • 组装 GenerateContentConfig(温度、topP、可选 systemInstruction),并通过 createContentGeneratorGeminiChat 封装出聊天对象。
  • 将选定模型名写入 runtimeContext(便于统一上报/可观测)。
lightbulb设计理念

在”提示治理”层面将环境与系统约束前置,减少”工具-模型”不一致与幻觉。

3) 非交互主循环:runNonInteractive

初始消息为一个轻量”点火”指令(Get Started!)。每轮:

  1. 检查预算:max_turns / max_time_minutes,命中则以 MAX_TURNS/TIMEOUT 终止。
  2. 发送消息(附 toolsabortSignal),流式接收模型产生的 functionCalls
  3. 若存在函数调用 → 进入”工具执行阶段”。
  4. 若没有函数调用:
    • 如未声明输出要求,直接以 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 发射每个期望变量。
  • 非交互规则:明确”不得向用户提问””完成目标后停止调用工具”。

promptIdsessionId#subagentId#turn 组成,贯穿工具调用链路,便于追踪与诊断。

可扩展性与二次开发建议

新增工具

  • 若已经在 ToolRegistry 注册,可在 ToolConfig.tools 中以字符串引用;
  • 若是临时/作用域内工具,可直接提供 FunctionDeclaration

输出协议

  • OutputConfig.outputs 中声明期望 key 及其语义描述,系统提示会自动追加”如何发射”的指引;
  • 模型完成主任务后,应逐一调用 self.emitvalue 发射所有 key。

提示模板化

  • 使用 ContextState.set(key, value) 注入上下文变量;
  • 确保 systemPrompt 中的 ${key} 均有对应值,否则会在创建 Chat 前被强校验拦截(fail fast)。

预算治理

  • 先以较保守的 max_time_minutesmax_turns 启动;
  • 结合日志与 terminate_reason 调整参数,达到”既不超时也不早停”的平衡。

与 GeminiChat 的协作关系

SubAgent 自身不直接与模型交互,而是经由 GeminiChatContentGenerator 层对接 @google/genai

  • 从工程上看,这一分层让 SubAgent 聚焦”编排(prompt/工具/预算/收尾)”,而将”对话与生成”的复杂度下沉到更通用的封装中。
  • 这也为未来的”模型路由/回退/观测”提供了稳定扩展点,例如引入”自动模型选择”或”token 预算”只需扩展 ModelConfigContentGenerator

一个最小可用示例(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);

要点:

  • systemPromptinitialMessages 不能同时提供;
  • 所有 ${...} 占位都必须在 ContextState 中有值;
  • 若未满足 outputs 的发射要求,SubAgent 会通过 nudge 提醒模型调用 self.emitvalue
  • 若某工具需要用户确认,将在 create 阶段直接阻止使用。

易错点与最佳实践清单

warning
二选一提示源systemPromptinitialMessages 不可同时设置。
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
面向扩展:从 ToolConfigModelConfigGeminiChat 封装,均保留了日后路由、预算化、回退与更复杂自治策略的空间。

© 2023 SubAgent 架构与设计思想解读 | 面向工程师与架构师的深度解析

d84ee35a.jpg

发表评论

人生梦想 - 关注前沿的计算机技术 acejoy.com 🐾 步子哥の博客 🐾 背多分论坛 🐾 知差(chai)网 🐾 DeepracticeX 社区 🐾 老薛主机 🐾