Skip to content

课 3 · SubAgent 与任务委派

本课目标

理解什么时候该把任务拆给 SubAgent,学会用受限工具集做安全委派。课后你会拿到一个能把独立检索子任务委派出去的 Agent。

这一课先掌握“什么时候该拆、为什么要做最小授权”即可,不要求一上来把委派系统做成无限嵌套的复杂架构。

前两课解决了两个问题:

  • Agent 怎么调用工具
  • Agent 怎么做多步循环

但还有一个常见问题没有解决:一个 Agent 什么都自己做,容易又长又乱。

当任务里包含多个彼此独立的子问题时,让主 Agent 亲自处理所有细节,往往会带来三个问题:

  • 上下文越来越长,模型容易丢失重点
  • 工具权限太大,不利于安全控制
  • 主 Agent 同时负责拆解、执行、汇总,职责不清

这时候就适合引入 SubAgent

什么是 SubAgent

SubAgent 不是“更强的大模型”,而是一个被主 Agent 委派、只负责局部任务的小执行单元。

它更像是把一个局部任务隔离出去的“小工作线程”或“小执行单元”:目标更单一、上下文更短、权限更小。对前端工程师来说,这节课的重点不是“又学一个新模型技巧”,而是学会把任务边界和权限边界拆清楚。

主 Agent 的职责:

  • 理解用户总目标
  • 判断是否需要委派
  • 决定给 SubAgent 哪些工具
  • 汇总最终结果并回复用户

SubAgent 的职责:

  • 只完成一个明确的子任务
  • 只使用被授权的工具
  • 返回简明的执行结果

什么时候该拆 SubAgent

适合拆的场景

  1. 多个独立检索子问题
  2. 需要最小授权
  3. 想降低主 Agent 上下文压力

不适合拆的场景

  1. 子任务强依赖共享状态
  2. 必须严格串行推进
  3. 任务边界根本不清楚

最小实现思路

核心做法很简单:

  1. 主 Agent 暴露一个 spawnSubAgent 工具
  2. 调用这个工具时传入 tasktoolNames
  3. 代码层根据 toolNames 做白名单过滤
  4. 用受限工具集启动一个单独的 generateText
  5. SubAgent 返回 { text, steps, toolCalls }
  6. 主 Agent 再负责最终汇总

你可以把它理解成:主 Agent 负责总控,SubAgent 负责局部代办;课程并不是要你把所有事都拆成 SubAgent,而是在“上下文太长”或“权限不该全开”时,多一个隔离手段。

SubAgent Helper

typescript
import { generateText, stepCountIs, type Tool } from 'ai'

export async function spawnSubAgent(options: {
  task: string
  tools: Record<string, Tool>
  maxSteps?: number
  stopWhen?: ReturnType<typeof stepCountIs>
}) {
  const stopWhen = options.stopWhen ?? stepCountIs(options.maxSteps ?? 10)

  const result = await generateText({
    model,
    tools: options.tools,
    stopWhen,
    system: `
你是一个专注执行子任务的 SubAgent。

规则:
- 只完成分配给你的具体任务
- 不主动扩展范围
- 如果失败,说明原因
- 完成后返回简明摘要
    `.trim(),
    messages: [{ role: 'user', content: options.task }],
  })

  return {
    text: result.text,
    steps: result.steps.length,
    toolCalls: result.steps.flatMap((step) =>
      step.staticToolCalls.map((tc) => ({
        name: tc.toolName,
        args: tc.input,
      }))
    ),
  }
}

关键点有两个:

  • stopWhen: stepCountIs(...) 限制 SubAgent 最多执行多少步
  • tools 是外部传入的受限工具集,不是主 Agent 的全量工具

白名单授权

真正的安全点不在 prompt,而在代码层白名单过滤。

这一点对前端工程师尤其重要:提示词可以约束模型“应该怎么做”,但真正能限制权限的还是代码。像前端里只把允许的 action 暴露给某个模块一样,这里也要把 SubAgent 能拿到的工具集合写死在应用层。

typescript
const availableTools: Record<string, Tool> = {
  searchKnowledge,
}

const subTools: Record<string, Tool> = {}
for (const name of toolNames) {
  if (availableTools[name]) {
    subTools[name] = availableTools[name]
  }
}

这样即使模型生成了其他工具名,真正传给 SubAgent 的也只有白名单工具。

主 Agent 中如何暴露委派能力

typescript
spawnSubAgent: tool({
  description: '将独立检索子任务委派给受限工具的 SubAgent 执行',
  inputSchema: z.object({
    task: z.string().describe('要执行的子任务描述'),
    toolNames: z.array(z.string()).describe('授予 SubAgent 的工具白名单'),
  }),
  execute: async ({ task, toolNames }) => {
    const availableTools: Record<string, Tool> = {
      searchKnowledge,
    }
    const subTools: Record<string, Tool> = {}
    for (const name of toolNames) {
      if (availableTools[name]) {
        subTools[name] = availableTools[name]
      }
    }
    return spawnSubAgent({
      task,
      tools: subTools,
      stopWhen: stepCountIs(10),
    })
  },
})

注意这里的设计取舍:

  • 主 Agent 可以决定要不要委派
  • 主 Agent 可以决定给哪些工具
  • 但 SubAgent 不能再递归创建新的 SubAgent

也就是说,这一课强调的是“受控委派”,不是把系统做成层层递归的多级代理网络。

实际例子

用户说:

分别检索 React 性能优化、TypeScript 泛型、Next.js Server Components,然后给我一份学习建议。

主 Agent 的理想行为是:

  1. 识别出这是三个独立检索维度
  2. 调用 spawnSubAgent
  3. 给 SubAgent 一个明确子任务,例如“检索 React 性能优化相关内容并总结核心点”
  4. 授权工具 ['searchKnowledge']
  5. 拿到结果后继续汇总

失败处理

SubAgent 失败时,不应该把异常细节直接甩给用户。更好的方式是返回结构化摘要:

typescript
{
  text: '未找到与 React 性能优化相关的足够信息',
  steps: 2,
  toolCalls: [
    { name: 'searchKnowledge', args: { query: 'React 性能优化' } }
  ]
}

然后由主 Agent 决定换关键词重试,或者直接告诉用户“知识库里信息不足”。

并入基础项目

这一课并入 basic/project 后,主线项目会多一个能力:

  • 主 Agent 在处理多角度检索任务时,可以自动调用 spawnSubAgent
  • SubAgent 首版只允许使用 searchKnowledge
  • 最终回答仍由主 Agent 汇总

本课产物

  • ✅ 理解什么时候该拆 SubAgent
  • ✅ 学会 spawnSubAgent 的最小实现
  • ✅ 学会用 toolNames 做白名单授权
  • ✅ 理解主 Agent 与 SubAgent 的职责边界
  • ✅ 把委派能力并入 basic/project 主线项目

完整代码在 basic/examples/05-agent/03-subagent/index.ts

试试看

bash
cd daqi-ai-agent
pnpm exec tsx basic/examples/05-agent/03-subagent/index.ts
  1. 先输入一个简单检索问题,观察主 Agent 是否直接回答
  2. 再输入一个多角度检索问题,观察是否触发 spawnSubAgent
  3. 故意给一个知识库中不存在的主题,确认系统会说信息不足,而不是编造
  4. 观察 SubAgent 返回的 toolCalls,理解它到底做了哪些动作

面试追问

Q:为什么不让主 Agent 直接做完所有事?

因为当任务包含多个独立子问题时,主 Agent 同时负责拆解、执行、汇总,容易让上下文膨胀、权限过大、职责不清。SubAgent 的价值不只是多一个模型调用,而是让执行边界更清楚。

Q:为什么一定要做白名单授权?

因为 prompt 不是安全边界,代码才是。只有在代码里过滤 toolNames,才能确保 SubAgent 实际拿到的工具确实是最小集合。

面向前端工程师和独立开发者的 AI 应用工程课程