跳到正文

探索型子 Agent

探索器是最简单构建的子 Agent,也是最实用的起步工具。

它能读取文件。它可以搜索。它别无选择。没有write,没有 bash,也没有向用户询问。它调查一个问题,总结发现,然后消失。

这听起来像是个限制。是功能。探索器不能漂移,不能无意中做出改变,也不能用创意find -exec毁掉你的项目。它做了一件事,完成后,父 Agent会得到一个干净的答案,而不是中间文件读取的四十步。

学习成果

面向父 Agent的task工具会生成一个全新的ToolLoopAgent,只有readgrep,是廉价的模型,预算是五步。父 Agent可以委派研究工作,并获得文本摘要。

快速路径

  1. 定义一个task工具,其schema接受description子 Agent
  2. execute 内,用 readgrep 新实例化一个新的 ToolLoopAgent
  3. 使用claude-haiku-4-5stopWhen: stepCountIs(5)
  4. 将子 Agent的文本回复返回父 Agent,并以尝试/接住的形式包裹

动手练习 6.2

从父 Agent加上一个分组接缝,变成探索器子 Agent。

要求:

  1. 在你的工具注册表中添加一个task工具
  2. schema会用description: string,父 Agent用来告诉子 Agent调查什么
  3. execute里,创建一个新ToolLoopAgent,用readgrep(不bash,不askUser
  4. 选一个快的模型(claude-haiku-4-5),把步数限制在5
  5. 返回探索器的文本回复,错误被捕捉并以字符串形式返回

实现提示:

  • 探索器是按每个调用实例化的。不要重复使用。每个代表团都会获得新的一上下文窗口
  • 重复使用readgrep已有的工具。它们封闭在父 Agent使用的沙箱上,这正是你想要的
  • 用尝试/捕捉和返回"Subagent error: ${e.message}"包裹explorer.generate(...),而不是让异常传播。父 Agent期望任何工具都能回线

task 工具

ts
import { ToolLoopAgent, stepCountIs, tool } from "ai";
import { z } from "zod";
import type { Sandbox } from "./sandbox";

export function createTaskTool(sandbox: Sandbox, parentTools: {
  read: ReturnType<typeof createReadTool>;
  grep: ReturnType<typeof createGrepTool>;
}) {
  return tool({
    description: `Delegate research to a read-only subagent.
WHEN TO USE: investigating a codebase, finding patterns, gathering context
  across many files.
WHEN NOT TO USE: making changes (the subagent cannot write or run commands).
DO NOT USE FOR: tasks that need decisions or askUser interactions.`,
    inputSchema: z.object({
      description: z.string().describe("What the subagent should investigate"),
    }),
    execute: async ({ description }) => {
      const explorer = new ToolLoopAgent({
        model: "anthropic/claude-haiku-4-5",
        instructions: `You are an explorer agent. Investigate and report back concisely.
Working directory: ${sandbox.workingDirectory}`,
        tools: { read: parentTools.read, grep: parentTools.grep },
        stopWhen: stepCountIs(5),
      });

      try {
        const { text, steps } = await explorer.generate({ prompt: description });
        return text
          ? `[Explorer: ${steps.length} steps]\n${text}`
          : "(no response from subagent)";
      } catch (e: any) {
        return `Subagent error: ${e.message}`;
      }
    },
  });
}

以下是一些值得指出的设计选择:

  • Fresh Agent 根据调用。 探索器无法跨越调用存活。每个任务都有自己的上下文窗口,这正是委派工作的全部意义所在
  • **No bash,没有,askUser.**探索器可以读取和搜索。它无法修改项目,也无法暂停用户输入。父 Agent继续掌控决策权
  • **Haiku,探索Sonnet.**不是阅读和总结,而不是深度推理。更快、更便宜的模型才是合适的选择
  • **Five steps.**足够看几份文件并反馈。如果探索器需要更多,父 Agent应该把任务拆成更小的部分
  • **Errors返回为strings.**工具返回字符串到模型。未接住的例外打破了工具循环。返回错误文本让父 Agent决定下一步

接入父 Agent

ts
const tools = {
  read: createReadTool(sandbox),
  grep: createGrepTool(sandbox),
  bash: createBashTool(sandbox, createApproval({ mode: "interactive" })),
};

const tools_with_task = {
  ...tools,
  task: createTaskTool(sandbox, { read: tools.read, grep: tools.grep }),
};

const agent = new ToolLoopAgent({
  // ...
  tools: tools_with_task,
});

父 Agent现在有四个工具:readgrepbashtask。前三个是直接的。第四位代表。

**Warning:边做边添加日志debug**

当子 Agent什么都不返回或送错东西时,你根本不知道它运行过程中发生了什么。开发过程中,在任务工具中记录子 Agent的步数和文本长度。没有这些,你会看到混乱的父 Agent输出,不知道子 Agent是跑了一级还是五级,发现了什么,还是悄悄失败了。

动手试试

请父 Agent探索器适合的相关内容:

bash
bun run index.ts . "Delegate to a subagent: find every place this project uses zod and tell me which files import from it."

父 Agent应该调用 task这个描述。探索器应该运行,找到导入数据并返回摘要。父 Agent应该把这个摘要传回给你。

作为对比,运行同一个任务,但没有显式委派指令:

bash
bun run index.ts . "Find every place this project uses zod and tell me which files import from it."

父 Agent可能会授权,也可能不会。如果有强有力的工具描述,可能会直接影响调用 grep。没关系。当搜索需要浏览大量文件,而父 Agent不希望所有文本都包含在上下文中时,委托工具才真正发挥作用。

bash
npx tsc --noEmit

提交

bash
git add src/tools.ts index.ts
git commit -m "feat(subagents): add explorer via task tool"

完成标准

  • [ ] createTaskTool存在并返回一个task工具
  • [ ] 任务工具每调用生成一个新的ToolLoopAgent
  • [ ] 探索器只有readgrep
  • [ ] 探索器使用claude-haiku-4-5,停在5步处
  • [ ] 错误以字符串形式返回,而非例外
  • [ ] 父 Agent可以委派研究,并获得一个干净的总结
  • [ ] npx tsc --noEmit

**注意:平行探险者**

单一的探索器是协程。从父 Agent的工具循环并行生成两个机器人才是真正的并行。试着更改任务工具的schema,让它接受一系列描述并用 Promise.all 运行。现在父 Agent可以同时研究代码库的三个不同部分并综合结果。为了利用这一点,父 Agent提示词发生了哪些变化?

参考实现

ts
import { ToolLoopAgent, stepCountIs, tool } from "ai";
import { z } from "zod";
import type { Sandbox } from "./sandbox";

export function createTaskTool(
  sandbox: Sandbox,
  parentTools: { read: any; grep: any },
) {
  return tool({
    description: `Delegate research to a read-only subagent.
WHEN TO USE: investigating a codebase, finding patterns, gathering context.
WHEN NOT TO USE: making changes (the subagent cannot write or run commands).
DO NOT USE FOR: tasks that need decisions or askUser interactions.`,
    inputSchema: z.object({
      description: z.string().describe("What the subagent should investigate"),
    }),
    execute: async ({ description }) => {
      const explorer = new ToolLoopAgent({
        model: "anthropic/claude-haiku-4-5",
        instructions: `You are an explorer agent. Investigate and report back concisely.
Working directory: ${sandbox.workingDirectory}`,
        tools: { read: parentTools.read, grep: parentTools.grep },
        stopWhen: stepCountIs(5),
      });

      try {
        const { text, steps } = await explorer.generate({ prompt: description });
        return text
          ? `[Explorer: ${steps.length} steps]\n${text}`
          : "(no response from subagent)";
      } catch (e: any) {
        return `Subagent error: ${e.message}`;
      }
    },
  });
}

非官方简体中文翻译 · 原课程来自 Vercel Academy