菜鸟AI - 让提示词生成更简单! 全站导航 全站导航
AI工具安装 新手教程 进阶教程 辅助资源 AI提示词 热点资讯 技术资讯 产业资讯 内容生成 模型技术 AI信息库

已有账号?

首页 > AI创作与模型 > DeepAgents多Agent架构实战测评:深度调研助手搭建指南
模型技术 深度调研助手搭建

DeepAgents多Agent架构实战测评:深度调研助手搭建指南

2026-06-01
阅读 0
热度 0
作者 菜鸟AI编辑部
摘要

摘要

基于DeepAgents构建多Agent深度调研助手,主Agent负责任务规划与编排,子Agent分工协作:调研

好的,作为一位长期深耕 Agent 开发的从业者,看到你用 DeepAgents 搭建出这个调研助手,确实觉得亲切。很多新手容易陷入一个困境:每个能力单独理解并不难,但真正拼成一个可运行的工程却总觉得缺了临门一脚。这篇文章将从零开始,手把手带你完成这个深度调研助手的搭建过程,帮你把零散的知识点串成体系。

前面几篇文章我们依次拆解了 DeepAgents 的各个能力模块:Todo List 让 Agent 先规划再执行,Summarization 在上下文拉长时自动压缩,Tool Selector 在工具众多时辅助模型筛选,FileSystem 则把中间产物持久化到文件里。

DeepAgents 实战:用多 Agent 架构搭一个深度调研助手

这些能力单个拎出来都不算复杂。但一旦动手做一个能实际交付的 Agent 项目,你就会撞上现实:如果每个能力都要自己手动拼 middleware、写 prompt、接工具、管文件,代码会变得支离破碎,主 Agent 也容易变成“万能选手”,什么都要亲自干,最终既不可控,也难以调试。

那么,有没有一个更完整的 API,把多 Agent、skill、memory、filesystem、todo、上下文管理等常见能力一次性整合好?

答案就是 DeepAgents 提供的 createDeepAgent

这一期我们基于 DeepAgents 实现一个多 Agent 架构的“深度调研助手”。用户只需输入一个调研主题,主 Agent 会自动规划任务、生成 todo 列表、委派不同的子 Agent 执行联网搜索和数据计算,最后输出一份中文调研报告。

一、为什么需要多 Agent 深度调研助手?

平时让大模型做“深度调研”时,通常会遇到几个明显的痛点。

第一,任务链条太长。比如让 Agent 调研一个行业、一个框架或者一组经济数据,它不能只搜索一次就下结论,而是需要把问题拆开,再分别搜资料、交叉验证、整理来源,最后形成结构化报告。

第二,能力种类太杂。调研需要联网搜索,分析需要计算,报告需要写作,定稿前还需要审阅。如果让一个 Agent 在同一个上下文里包揽所有这些工作,它既要搜又要算,既要写又要自查,很容易出现任务漂移,写着写着就跑偏。

第三,中间结果不应全部塞在对话历史里。复杂任务通常会生成调研计划、搜索结果、分析数据、报告草稿、最终报告。如果这些东西都堆在 messages 里,后续步骤会越来越难读,也更容易被上下文压缩影响。

因此,一种更稳健的做法是:让主 Agent 只负责规划和编排,把专业工作交给不同的子 Agent,并通过文件系统传递中间产物。

在这个项目里,我们把调研助手拆成 4 个角色:

  • 主 Agent:整个系统的编排中心,负责把用户输入拆成可执行流程,并按“规划 → 调研 → 分析 → 起草 → 审阅 → 定稿”推进任务。
  • 调研员子 Agent(researcher):每次只负责一个聚焦的子主题,使用联网搜索收集资料,并把结果写入 findings_*.md
  • 分析师子 Agent(analyst):当任务涉及数字计算、排名、增长率、占比时启用,通过 QuickJS 沙箱执行 JavaScript,避免模型凭感觉估算。
  • 编辑子 Agent(editor):报告草稿完成后介入,只做审阅和反馈,不直接改写报告,实现“写作”与“审稿”职责分离。

这种架构的核心价值在于,把复杂任务拆解成多个职责清晰的小任务。主 Agent 不需要亲自完成所有细节,而是像项目经理一样协调资源;子 Agent 也不需要理解全局,只要把自己负责的部分做好,并把结果写入约定的文件。

二、整体架构:主 Agent 编排,子 Agent 分工

项目目录结构如下:

deep-research-assistant
├── AGENTS.md
├── package.json
├── skills
│   ├── report-writer
│   │   └── SKILL.md
│   └── web-research
│       └── SKILL.md
├── src
│   ├── agent.mjs
│   ├── cli.mjs
│   ├── max-input-tokens-test.mjs
│   ├── todo-middleware-test.mjs
│   └── tools
│       └── search.mjs
└── workspace
    ├── reports
    └── sources

这里面最重要的有 4 类文件。

src/agent.mjs 是 Agent 的核心定义,主 Agent、子 Agent、模型、文件系统、skills、memory 全在这里组装。

src/cli.mjs 是命令行入口,负责读取用户输入、启动 Agent stream,并把关键步骤打印出来,方便我们观察主 Agent 和子 Agent 的运行过程。

skills/ 目录存放的是技能说明。它并不是子 Agent,而是主 Agent 在执行某类任务时需要遵循的流程指南。比如 web-research 告诉主 Agent 如何做联网调研,report-writer 告诉主 Agent 如何把 findings 写成报告。

workspace/ 是任务工作区。调研计划、搜索结果、分析结果、草稿、终稿都写到这里。这样,各个 Agent 之间不是靠“记住对话”协作,而是靠文件协作。

三、快速上手:创建项目并安装依赖

先创建项目:

mkdir deep-research-assistant
cd deep-research-assistant
npm init -y

安装依赖:

pnpm install @langchain/core @langchain/langgraph @langchain/openai @langchain/quickjs dedent deepagents dotenv langchain zod

这个项目里用到的关键依赖主要包括:

  • deepagents:提供 createDeepAgentFilesystemBackend 等核心能力。
  • @langchain/openai:用 OpenAI 兼容接口接入大模型。
  • @langchain/quickjs:给分析师子 Agent 提供 eval 代码执行工具。
  • langchain:提供 tool、createAgent、todoListMiddleware 等基础能力。
  • zod:定义工具入参 schema。
  • dedent:让多行 prompt 在代码里保持可读缩进,同时去掉多余空白。

然后配置 .env

OPENAI_API_KEY=sk-xx
OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
OPENAI_MODEL="qwen-plus"# 用于身份验证,实现链路上报
LANGCHAIN_API_KEY=xxx
# 指定 LangSmith 中的项目,追踪结果会归类到该项目下
LANGCHAIN_PROJECT=deep-research-assistant
# 开启 LangSmith 追踪功能
LANGCHAIN_TRACING_V2=trueBOCHA_API_KEY=sk-xx

这里使用的是 OpenAI 兼容接口,所以只要服务商兼容 OpenAI SDK,就可以通过 OPENAI_BASE_URLOPENAI_MODEL 切换模型。

联网搜索使用 Bocha API,因此还需要配置 BOCHA_API_KEY。LangSmith 不是必须的,但强烈建议开启,因为多 Agent 调用时,如果没有 trace,很难看清模型到底调用了哪些工具、哪个子 Agent 写了哪些文件。

四、联网搜索工具:把搜索能力包装成 tool

先看 src/tools/search.mjs

import { tool } from "langchain"
import { z } from "zod"const BOCHA_API_URL = "https://api.bochaai.com/v1/web-search"function formatWebPages(webpages) {
  return webpages
    .map(
      (page, idx) =>
        `引用: ${idx + 1}
标题: ${page.name ?? ""}
URL: ${page.url ?? ""}
摘要: ${page.summary ?? ""}
网站名称: ${page.siteName ?? ""}
网站图标: ${page.siteIcon ?? ""}
发布时间: ${page.dateLastCrawled ?? ""}`
    )
    .join("nn")
}

这里先把 Bocha 返回的网页结果格式化成适合模型阅读的文本。每条结果都保留标题、URL、摘要、网站名称和发布时间。对于调研类 Agent 来说,URL 很重要,因为最终报告里必须能追溯来源。

真正调用 API 的函数如下:

async function bochaWebSearch(query, count) {
  const apiKey = process.env.BOCHA_API_KEY?.trim()
  if (!apiKey) {
    return "Bocha 联网搜索的 API Key 未配置(环境变量 BOCHA_API_KEY),请先在 .env 中配置后再重试。"
  }  const response = await fetch(BOCHA_API_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query,
      freshness: "noLimit",
      summary: true,
      count,
    }),
  })  if (!response.ok) {
    const errorText = await response.text()
    return `搜索 API 请求失败,状态码: ${response.status},错误信息: ${errorText}`
  }  let json
  try {
    json = await response.json()
  } catch (e) {
    return `搜索 API 请求失败,原因是:搜索结果解析失败 ${e.message}`
  }  try {
    if (json.code !== 200 || !json.data) {
      return `搜索 API 请求失败,原因是: ${json.msg ?? "未知错误"}`
    }    const webpages = json.data.webPages?.value ?? []
    if (!webpages.length) {
      return `未找到与「${query}」相关的结果。`
    }    return formatWebPages(webpages)
  } catch (e) {
    return `搜索 API 请求失败,原因是:搜索结果解析失败 ${e.message}`
  }
}

这段代码没有直接抛错,而是把错误转换成中文文本返回给模型。这样做的好处是:工具失败时,Agent 仍然能读懂失败原因,并决定是换关键词、减少结果数量,还是直接把失败原因反馈出来。

最后用 tool 包装成 LangChain 工具:

export const webSearch = tool(
  async input => {
    const count = input.count ?? 10
    console.log(`   搜索: ${input.query}${count} 条)`)
    return bochaWebSearch(input.query, count)
  },
  {
    name: "web_search",
    description:
      "使用 Bocha 联网搜索 API 检索互联网网页。输入中文或中英结合的搜索关键词,可选 count 指定结果数量。返回标题、URL、摘要、网站名称、图标和发布时间。",
    schema: z.object({
      query: z
        .string()
        .min(1)
        .describe(
          "搜索关键词,优先使用中文,例如:2026年 AI Agent 框架对比、LangGraph 最新动态"
        ),
      count: z
        .number()
        .int()
        .min(1)
        .max(20)
        .optional()
        .describe("返回的搜索结果数量,默认 10 条"),
    }),
  }
)

工具描述要写得足够具体,因为模型会根据 namedescriptionschema 判断何时调用工具、传什么参数。这里还特别强调“优先使用中文关键词”,因为我们的调研目标主要是中文资料源。

五、子 Agent 设计:每个 Agent 只做一类事

接下来进入 src/agent.mjs。先准备项目路径和依赖:

import path from "node:path"
import { fileURLToPath } from "node:url"
import dedent from "dedent"
import { ChatOpenAI } from "@langchain/openai"
import { createCodeInterpreterMiddleware } from "@langchain/quickjs"
import { createDeepAgent, FilesystemBackend } from "deepagents"import { webSearch } from "./tools/search.mjs"const projectDir = path.resolve(
  path.dirname(fileURLToPath(import.meta.url)),
  ".."
)

这里 projectDir 指向项目根目录,后面 FilesystemBackend 会用它作为实际文件系统根目录。

5.1 researcher:只调研一个子主题

调研员子 Agent 的定义如下:

const researcherSubAgent = {
  name: "researcher",
  description:
    "通过联网搜索调研单一子主题。每次只分配一个子主题;多个独立子主题可并行启动多个调研员。",
  systemPrompt: dedent`
    你是一名专业调研员,负责调研**一个**分配给你的子主题,并写入**一份**调研结果文件。    ## 工作流程(严格遵守,禁止空转循环)    1. **可选**:用 write_todos 列出最多 3 条中文执行步骤(例如「搜索官方文档」「搜索社区评价」「整理并写入 findings」),然后按步骤执行
    2. 最多调用 3 次 web_search(硬性上限,绝不超过)
    3. 将搜索结果整理为结构化摘要,包含关键事实与来源 URL
    4. 调用 write_file **一次**,保存到任务指定的路径(必须在 /workspace/sources/findings_*.md 下,禁止写到其他目录)
    5. 用一句话确认已完成,然后**立即停止**,不要再搜索、写文件或更新 todo    ## write_todos 使用规则(若使用)    - 最多 3 条,每条 content 必须用中文
    - 仅用于拆解本子的调研步骤,不要重复主 Agent 已完成的总体规划
    - 最后一条 todo 必须是「写入 findings 文件」;该步骤完成后将所有 todo 标为 completed 并结束    ## 其他规则    - 不要重复相同的搜索关键词
    - write_file 完成后禁止再次搜索——你的任务已结束
    - 其他人只能看到你写入的文件,内容必须完整、自洽
    - **所有输出必须使用中文**(专有名词如 LangGraph 可保留英文)
    - 搜索关键词优先使用中文;若主题本身是英文专有名词,可中英结合
  `,
  tools: [webSearch],
}

这里有几个细节很关键。

第一,调研员每次只做一个子主题。这样主 Agent 可以把大问题拆成多个互不重叠的小问题,并行启动多个 researcher。

第二,搜索次数要限制。Agent 如果没有边界,很容易“再搜一下”“再确认一下”,最后陷入空转循环。所以这里明确最多调用 3 次 web_search

第三,调研员必须把结果写入 /workspace/sources/findings_*.md。这意味着主 Agent 后续写报告时,不需要从子 Agent 的对话历史里找信息,只要读取文件即可。

5.2 analyst:所有计算都交给代码执行

分析师子 Agent 的定义如下:

const analystSubAgent = {
  name: "analyst",
  description:
    "使用 eval REPL 进行数值计算与结构化数据分析。适用于计算、排名、同比对比或 JSON/CSV 分析。",
  systemPrompt: dedent`
    你是一名数据分析师,所有计算必须通过 eval REPL 完成——**禁止**猜测数字。    ## 工作流程    1. 从 /workspace/sources/ 读取数据文件(或从调研结果中提取数字)
    2. 在 REPL 中编写并运行 JavaScript,计算总和、均值、排名、增长率等
    3. 将分析结果保存到 /workspace/sources/analysis_*.md,包含计算逻辑与结论    必须展示计算过程,结论可从 REPL 输出复现。所有输出使用中文。
  `,
  middleware: [createCodeInterpreterMiddleware()],
}

大模型最不适合直接做精确计算。比如 GDP 占比、同比增速排名、总和、平均值,看起来都很简单,但只要数字多一点,模型就可能算错。

所以这里给 analyst 加了 createCodeInterpreterMiddleware()。这个 middleware 会提供 eval 工具,让模型生成 JavaScript 代码,并在 QuickJS 沙箱里执行。这样数字结论不是模型“想出来”的,而是代码算出来的。

这个设计在数据调研里非常有用。主 Agent 只需要判断“这个任务涉及计算”,然后委派 analyst;analyst 负责读取数据、执行代码、写分析结果。

5.3 editor:只审阅,不改写

编辑子 Agent 的定义如下:

const editorSubAgent = {
  name: "editor",
  description:
    "审阅报告草稿的准确性、结构与完整性。在 /workspace/reports/draft_*.md 写好后使用。",
  systemPrompt: dedent`
    你是一名资深情报编辑,负责**审阅**报告草稿——**不要**亲自改写报告。    ## 阅读材料    - 原始问题:/workspace/sources/question.txt
    - 待审草稿:任务中指定的路径
    - 支撑材料:/workspace/sources/ 下的调研文件(如需要)    ## 审阅要点    - 报告是否直接回答了原始问题?
    - 章节结构是否清晰,段落是否充实(而非只有 bullet 列表)?
    - 是否引用了来源,并在「参考资料」章节列出?
    - 是否有遗漏、无依据的断言或缺失的视角?
    - 语言是否为中文,表述是否专业?    ## 输出    返回简洁的审阅意见和具体、可操作的修改建议。
    **不要**写入报告文件,只提供反馈。所有输出使用中文。
  `,
}

这里故意让 editor “不要亲自改写报告”。原因很简单:报告的整体结构和最终表达应该由主 Agent 统一控制,编辑只负责提出问题和修改建议。

这相当于把“写”和“审”分开。主 Agent 写草稿,editor 负责挑问题,主 Agent 根据反馈修订一次并保存终稿。这样流程更接近真实写作协作,也更容易调试。

六、主 Agent:把流程写进 orchestratorPrompt

子 Agent 负责专业任务,主 Agent 负责流程编排。这个流程主要写在 orchestratorPrompt 里。

const orchestratorPrompt = dedent`
  你是「深度调研助手」的主 Agent,负责协调调研、分析与编辑,产出高质量调研简报。  ## 语言要求  - **所有输出必须使用中文**:对话回复、write_todos 任务列表、文件内容、搜索关键词
  - write_todos 中每条 todo 的 content 必须用中文描述,例如「撰写调研计划」「委派调研员调研 LangGraph」
  - 搜索时优先使用中文关键词;英文专有名词(如 LangGraph、AutoGen)可保留
  - 报告、调研笔记、计划文件全部用中文撰写  ## 你的职责  协调调研员、分析师和编辑完成报告。不要亲自完成所有调研——将专业工作委派给子 Agent。  ## 标准流程  1. **规划** — 用 write_todos 拆解任务(中文)。将用户问题保存到 /workspace/sources/question.txt
  2. **调研** — 按 web-research 技能:写 research_plan.md,委派调研员子 Agent(可并行)
  3. **分析** — 若涉及数字对比或数据表,委派分析师子 Agent
  4. **起草** — **由你亲自**按 report-writer 技能撰写,用 write_file 写入 /workspace/reports/draft_[主题].md
  5. **审阅** — 委派编辑子 Agent 审稿,根据反馈修订一次
  6. **定稿** — 保存最终报告到 /workspace/reports/report_[主题]_[日期].md
`

这个 prompt 不是简单告诉模型“你要做调研”,而是把工作流拆成明确阶段。复杂 Agent 的 prompt 一定要写流程,否则模型很容易想到哪做到哪。

后面还要明确哪些是子 Agent,哪些是 skill:

  ## task 工具(子 Agent 委派)  **仅**以下 subagent_type 合法:researcher、analyst、editor、general-purpose。  - web-research、report-writer 是**技能**(写作指南),**不是**子 Agent,禁止作为 subagent_type 调用
  - 报告起草、修订、定稿由**主 Agent 自己**用 write_file / edit_file 完成,不要委派 task

这一段非常重要。

DeepAgents 里既有 subagents,也有 skills。它们都能影响 Agent 行为,但不是一回事。

subagents 是可以通过 task 工具委派的执行角色,比如 researcheranalysteditor

skills 是能力说明文档,主 Agent 会读取它们来知道“某类任务应该怎么做”,但不能把 skill 当成 subagent_type

如果不在 prompt 里说清楚,模型可能会尝试调用一个不存在的 web-research 子 Agent,导致任务失败。

再看委派规则:

  ## 委派规则  - 每个调研员只负责一个聚焦的子主题
  - **每份报告最多 3 个调研员**——只选最相关的子主题
  - 框架对比类任务:优先调研用户明确点名的框架;否则选最重要的 3 个
  - 最多并行启动 3 个调研员,已有 3 份 findings 文件后不再新增调研员
  - 仅在确实需要数值计算时使用分析师
  - 每份报告只调用编辑一次(草稿完成后)
  - 调研完成后直接进入起草 → 审阅 → 定稿,不要额外开调研轮次

这里的限制不是为了“少做事”,而是为了让 Agent 可控。多 Agent 系统最怕的就是无限派生任务,尤其是调研类任务,如果不限制轮次和子 Agent 数量,很容易搜索很多轮却迟迟不写报告。

七、createDeepAgent:把模型、文件系统、memory、skills、subagents 串起来

最后看核心创建函数:

export function createIntelligenceDeskAgent() {
  const apiKey = process.env.OPENAI_API_KEY?.trim()
  if (!apiKey) {
    throw new Error("未设置 OPENAI_API_KEY 环境变量")
  }  const model =
    process.env.MODEL_NAME?.trim() ||
    process.env.OPENAI_MODEL?.trim() ||
    "gpt-4o"
  const baseURL = process.env.OPENAI_BASE_URL?.trim() || undefined  const backend = new FilesystemBackend({
    rootDir: projectDir,
    virtualMode: true,
  })  const chatModel = new ChatOpenAI({
    model,
    temperature: 0,
    apiKey,
    ...(baseURL
      ? {
          configuration: {
            baseURL,
          },
        }
      : {}),
  })  return createDeepAgent({
    model: chatModel,
    systemPrompt: orchestratorPrompt,
    backend,
    memory: [path.join(projectDir, "AGENTS.md")],
    skills: ["/skills/"],
    subagents: [researcherSubAgent, editorSubAgent, analystSubAgent],
  })
}

这段代码就是整个项目的核心。

model 负责模型调用。这里通过 ChatOpenAI 接 OpenAI 兼容接口,temperature 设置为 0,是为了让调研和报告生成尽量稳定。

backend 负责文件系统。FilesystemBackendrootDir 指向项目根目录,virtualMode: true 表示 Agent 看到的是虚拟路径,比如 /workspace/sources/question.txt,但实际文件会落到项目目录下。

memory 加载长期记忆文件。这里加载的是 AGENTS.md,里面写了报告偏好、调研标准和工作区目录。

skills 指向 /skills/,DeepAgents 会读取技能目录下的 SKILL.md,让主 Agent 在需要联网调研和写报告时遵循对应流程。

subagents 注册 3 个子 Agent。后续主 Agent 可以通过 task 工具委派 researcheranalysteditor

相比自己手动创建 LangGraph 节点、边、中间件和状态,createDeepAgent 的好处就是:它把复杂 Agent 常见的能力都包装好了,我们只需要把模型、prompt、文件系统、skills 和子 Agent 配进去。

八、skills:把流程指南沉淀成可复用能力

这个项目里有两个 skill。

第一个是 skills/web-research/SKILL.md

---
name: web-research
description: 结构化多来源联网调研,支持并行委派调研员子 Agent
---# 联网调研技能当用户要求调研、调查、对比或深度分析某个主题时使用本技能。> **注意**:本技能是主 Agent 的流程指南,不是子 Agent。联网搜索请委派 `researcher` 子 Agent,**不要**将 `web-research` 作为 subagent_type 调用。

它规定了联网调研流程:先写 question.txt,再写 research_plan.md,然后最多并行委派 3 个 researcher,最后读取 findings 做综合。

第二个是 skills/report-writer/SKILL.md

---
name: report-writer
description: 将调研结果整理为结构清晰、专业的中文情报报告
---# 报告撰写技能将调研 findings 综合为最终交付物时使用本技能。> **注意**:本技能是主 Agent 的写作指南,不是子 Agent。请主 Agent 亲自用 `write_file` 撰写报告,**不要**通过 `task` 工具委派 `report-writer`。

它规定了报告结构:标题、执行摘要、背景、核心发现、分析、结论、参考资料。

这就是 skill 的价值:把某类任务的执行规范从主 prompt 里拆出去。主 prompt 只保留总体编排,具体任务规范放到 skill。以后如果想调整报告格式,只需要改 report-writer skill,不用重写主 Agent。

九、AGENTS.md:给 Agent 加长期记忆

项目根目录还有一个 AGENTS.md

# 深度调研助手 — 用户偏好本文件在 Agent 启动时作为长期记忆加载。## 报告偏好- **所有报告、笔记、任务列表均使用中文**
- 报告顶部包含「执行摘要」(3–5 条要点)
- Markdown 标题层级:`#` 标题、`##` 章节、`###` 小节
- 每份报告末尾必须有「参考资料」章节,列出所有引用 URL
- 正文以段落叙述为主;仅在摘要或对比表时使用 bullet 列表## 调研标准- 重要结论尽量用两个以上独立来源交叉验证
- 明确标注信息冲突,不要掩盖分歧
- 区分事实与推测/预测
- 时效性信息需标注日期

这个文件通过 memory: [path.join(projectDir, "AGENTS.md")] 注入到 Agent。它适合放稳定偏好,比如报告语言、标题层级、引用规范、工作区目录。

长期记忆和 skill 的区别在于:memory 更像全局偏好,skill 更像某类任务的操作手册。

十、CLI:观察多 Agent 执行过程

src/cli.mjs 负责启动 Agent,并把关键过程打印出来。核心逻辑是:

const agent = createIntelligenceDeskAgent()for await (const [namespace, chunk] of await agent.stream(
  { messages: [new HumanMessage(query)] },
  { streamMode: "updates", subgraphs: true, recursionLimit }
)) {
  for (const [node, data] of Object.entries(chunk)) {
    if (node === "model_request") {
      trackFileCalls(data, pending)
      trackEvalCalls(data, pendingEval)
      console.log(stepLabel(namespace, node))
    } else if (node === "tools") {
      logToolResults(data, pending, pendingEval)
    } else if (node === "todoListMiddleware.after_model") {
      console.log(stepLabel(namespace, node))
    }
  }
}

这里用了 streamMode: "updates"subgraphs: true

updates 可以看到每个节点的更新,subgraphs: true 可以看到子 Agent 内部的执行过程。对于多 Agent 项目来说,这个配置很有用,因为你可以区分当前是主 Agent 在调用工具,还是某个子 Agent 在执行自己的子图。

另外,CLI 里还专门追踪了文件工具和 eval 工具:

const FILE_TOOLS = new Set([
  "write_file",
  "edit_file",
  "read_file",
  "ls",
  "glob",
  "grep",
])const EVAL_TOOL = "eval"

这样运行时可以看到类似:

[主 Agent] model_request
  write_file: workspace/sources/question.txt
  write_file: workspace/sources/research_plan.md
[subagent:researcher] model_request
  web_search ...
  write_file: workspace/sources/findings_nbs_official.md
[subagent:analyst] model_request
   eval: ...
  write_file: workspace/sources/analysis_results.md

这类日志比直接打印完整 messages 更容易读,也更适合调试多 Agent 流程。

十一、跑一个真实任务:调研 2023 年省级 GDP 前 6 名

我们用这个命令启动:

node src/cli.mjs "调研国家统计局公开的2023年省级地区生产总值(GDP)数据:提取GDP总量前6名省份的具体数值及同比增速,计算六省GDP总和、各省占全国GDP的比重,并按增速从高到低排名"

这是一个很适合测试的任务,因为它同时包含:

  • 官方数据调研
  • 多来源验证
  • GDP 总量提取
  • 占全国 GDP 比重计算
  • 增速排序
  • 最终报告撰写

主 Agent 首先会写入原始问题:

workspace/sources/question.txt

内容就是用户输入的调研目标。

然后写调研计划:

# 国家统计局2023年省级GDP数据调研计划## 主调研问题
获取国家统计局官方发布的2023年各省级行政区GDP数据,提取GDP总量前6名省份的具体数值及同比增速,计算六省GDP总和、各省占全国GDP的比重,并按增速从高到低排名。## 子主题划分
1. **国家统计局官方数据源**:查找国家统计局官网发布的2023年国民经济和社会发展统计公报中省级GDP数据
2. **权威媒体汇总报道**:查找新华社、人民日报等权威媒体对2023年省级GDP数据的汇总报道和解读
3. **专业经济数据库**:查找中国统计年鉴2024或专业经济数据库(如CEIC、Wind)中的2023年省级GDP数据

这个计划体现了主 Agent 的职责:先拆分问题,而不是直接搜索。

后续 researcher 会生成多个 findings 文件,例如:

workspace/sources/findings_nbs_official.md
workspace/sources/findings_economic_databases.md

如果涉及计算,主 Agent 会委派 analyst。分析结果写入:

workspace/sources/analysis_results.md

其中包含了基础数据和计算结果:

## 基础数据
- 全国GDP总量:1260583亿元(126.06万亿元)
- GDP前6名省份及数据:
  - 广东:135673.2亿元,增速4.8%
  - 江苏:128204.7亿元,增速5.8%
  - 山东:92069.0亿元,增速5.2%
  - 浙江:82553.2亿元,增速5.2%
  - 河南:61345.1亿元,增速4.1%
  - 四川:60132.9亿元,增速6.0%## 计算结果### 六省GDP总和
559978.1亿元### 各省占全国GDP比重
- 广东:10.76%
- 江苏:10.17%
- 山东:7.30%
- 浙江:6.55%
- 河南:4.87%
- 四川:4.77%

最后主 Agent 会生成报告:

workspace/reports/draft_gdp_2023.md
workspace/reports/report_gdp_2023_20240527.md

报告中会把前 6 名省份整理成表格,并给出核心发现:

| 排名 | 省份 | GDP总量(亿元) | 同比增速 |
|------|------|------------------|----------|
| 1 | 广东 | 135673.2 | 4.8% |
| 2 | 江苏 | 128204.7 | 5.8% |
| 3 | 山东 | 92069.0 | 5.2% |
| 4 | 浙江 | 82553.2 | 5.2% |
| 5 | 河南 | 61345.1 | 4.1% |
| 6 | 四川 | 60132.9 | 6.0% |

从这个例子可以看到,主 Agent 没有把所有事情都塞进一次模型调用里,而是把任务拆成了多个阶段:计划、调研、计算、写草稿、审阅、定稿。每个阶段都有文件产物,后续步骤可以明确读取。

十二、Todo List:复杂任务不能走一步想一步

复杂 Agent 最重要的习惯之一,就是先列 todo,再执行。

DeepAgents 里已经集成了 todo 相关能力。主 Agent 在收到复杂任务后,会生成中文 todo 列表,并随着执行推进更新状态。

todo 的状态通常有 3 种:

  • pending:尚未执行。
  • in_progress:正在执行。
  • completed:已经完成。

子 Agent 也可以使用 todo。比如 researcher 在调研单一子主题时,可以列出最多 3 条步骤:

1. 搜索官方数据源
2. 整理关键事实和来源 URL
3. 写入 findings 文件

这个机制的意义不是让输出更好看,而是让模型在长任务里保持执行轨迹。没有 todo 时,Agent 很容易在搜索、阅读、写文件之间反复横跳;有 todo 后,它更容易按阶段推进。

如果想单独理解 LangChain 的 todoListMiddleware,可以看项目里的 src/todo-middleware-test.mjs

import dotenv from "dotenv"
dotenv.config({ override: true })
import { ChatOpenAI } from "@langchain/openai"
import { createAgent, HumanMessage, todoListMiddleware } from "langchain"const model = new ChatOpenAI({
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  temperature: 0,
  configuration: {
    baseURL: process.env.OPENAI_BASE_URL,
  },
})const agent = createAgent({
  model,
  tools: [],
  systemPrompt:
    "你是生活规划助手。收到需要多步完成的请求时,先用 write_todos 列出中文执行步骤,然后简要说明你的计划。",
  middleware: [todoListMiddleware()],
})const query =
  "我下周末想带女朋友去珠海玩两天,帮我规划一下:交通怎么选、住哪里方便、必去景点和吃什么,预算控制在人均 1500 元左右。"const result = await agent.invoke({
  messages: [new HumanMessage(query)],
})console.log("todos:", JSON.stringify(result.todos, null, 2))
console.log("─".repeat(50))
console.log("回复:", result.messages.at(-1)?.content)

这里没有使用 DeepAgents,而是直接用 createAgent + todoListMiddleware()。运行后可以看到,middleware 会给 Agent 注入 write_todos 工具,并把 todo 写到 graph state 里。

DeepAgents 的好处是,这些常见能力已经被集成到更完整的 Agent 框架里,不需要我们每次从零拼。

十三、QuickJS:让模型生成代码,让沙箱负责计算

前面提到 analyst 使用了 createCodeInterpreterMiddleware()

middleware: [createCodeInterpreterMiddleware()]

它会给 Agent 提供一个 eval 工具,底层通过 QuickJS 执行 JavaScript。

为什么需要这个?

因为“会写解释”和“会精确计算”是两回事。大模型可以解释 GDP 占比怎么算,但它未必每次都能把一组数字算对。只要涉及总和、占比、排序、增长率,最好都让模型生成代码,再由沙箱执行。

比如 GDP 示例里,六省 GDP 总和、各省占全国 GDP 比重、增速排序,都应该由代码计算。这样报告里的结论可以复现,也方便后续排查。

这个思路可以扩展到很多场景:

  • JSON 数据分析
  • CSV 表格计算
  • 排名和聚合
  • 环比、同比、占比计算
  • 简单模拟和规则校验

如果你希望执行 Python、SQL 或其他语言,也可以换成对应的代码执行工具。关键不是 QuickJS 本身,而是让“计算”从模型输出里分离出来,交给可执行环境。

十四、LangSmith:用 filter 看清多 Agent 链路

多 Agent 系统如果不开 trace,调试体验会比较痛苦。

这个项目建议开启:

LANGCHAIN_API_KEY=xxx
LANGCHAIN_PROJECT=deep-research-assistant
LANGCHAIN_TRACING_V2=true

开启后,可以在 LangSmith 里查看每次模型调用、每个 tool 调用、每个子 Agent 的输入输出。

这里有个小技巧:在 LangSmith 里通过 filter 过滤所有 tool 调用,可以更快理清流程。比如你可以重点看:

  • task 调用了哪些子 Agent
  • web_search 搜了哪些关键词
  • write_file 写了哪些文件
  • read_file 读取了哪些中间结果
  • eval 执行了什么计算代码

对于多 Agent 项目,调试的关键不是只看最终答案,而是看每一步有没有按预期推进。尤其是当结果不稳定时,LangSmith trace 往往能直接暴露问题:是调研员没有写文件,还是主 Agent 没读 findings,还是 analyst 没有被触发。

十五、上下文压缩:什么时候触发 Summarization?

DeepAgents 还内置了上下文压缩能力。它会根据模型的输入上下文限制判断是否需要总结历史消息。默认逻辑可以理解为:当上下文接近模型输入上限时,触发摘要,只保留一部分最新上下文。

但这里有一个实践问题:不是所有 OpenAI 兼容模型都会正确暴露 profile.maxInputTokens。比如使用某些 Qwen 兼容接口时,模型对象里可能拿不到准确的上下文长度。

项目里用 src/max-input-tokens-test.mjs 演示了如何覆盖这个值:

import dotenv from "dotenv"
dotenv.config({ override: true })
import { ChatOpenAI } from "@langchain/openai"const model = new ChatOpenAI({
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  temperature: 0,
  configuration: {
    baseURL: process.env.OPENAI_BASE_URL,
  },
})console.log(model.profile.maxInputTokens)Object.defineProperty(model, "profile", {
  get: () => ({ maxInputTokens: 1_024 }),
})console.log(model.profile.maxInputTokens)

真实项目里可以按模型实际上下文长度设置,比如 32K、128K 等。设置得太小,会过早触发摘要;设置得太大,又可能在模型真实上限前没有及时压缩。

上下文压缩不是越早越好。对于调研任务来说,关键事实最好写入文件,而不是依赖对话历史。Summarization 可以作为兜底能力,但稳定的多 Agent 工作流应该优先通过文件系统保存中间结果。

十六、这个架构适合什么场景?

这个深度调研助手适合所有“需要多阶段、多角色、多文件产物”的任务。

比如:

  • 技术框架调研:对比 LangGraph、AutoGen、CrewAI 等框架。
  • 行业研究:调研某个产业链、市场规模、竞争格局。
  • 数据简报:搜索公开数据,计算指标,并生成分析报告。
  • 选型报告:调研多个工具或服务,整理优缺点和推荐结论。
  • 代码仓库研究:读取项目资料,生成架构说明和改造建议。

但它也不是万能的。

如果任务只是简单问答,没必要上多 Agent。多 Agent 会增加调用次数、成本和调试复杂度。只有当任务确实需要规划、搜索、计算、写作、审阅这些阶段时,多 Agent 才能体现价值。

另外,联网搜索结果质量取决于搜索 API 和关键词设计。Agent 可以整理资料,但不能保证所有来源天然可靠。因此重要结论仍然需要来源追踪和交叉验证。

十七、总结

这期我们基于 DeepAgents 的 createDeepAgent 实现了一个多 Agent 架构的深度调研助手。

这个项目里,主 Agent 负责编排流程:先用 todo 拆解任务,再写调研计划,随后委派 researcher 做联网搜索,必要时委派 analyst 用 QuickJS 执行计算,报告草稿完成后再交给 editor 审阅,最后由主 Agent 修订并保存终稿。

相比手动拼各种 middleware,createDeepAgent 把常见的复杂 Agent 能力整合到了一个更高层的 API 里。我们只需要配置模型、主 prompt、filesystem、memory、skills 和 subagents,就能搭出一个支持规划、文件协作、多 Agent 委派、沙箱计算、长期记忆和上下文压缩的完整系统。

这也是 DeepAgents 适合工程实战的地方:它不是只让 Agent “多调用几个工具”,而是把复杂任务执行中真正需要的流程能力都打包好了。对于深度调研、技术选型、数据分析、报告生成这类任务,用它来搭第一版系统会非常省力。

来源:互联网

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

同类文章推荐

相关文章推荐

更多