DeepAgents Interpreters 与 Dynamic Subagents:从原理到实战

目录


一、先用一句话理解它们

1. Interpreter 是什么

Interpreter 给 Deep Agent 增加了一个位于 Agent 循环内部的轻量 JavaScript 工作台

模型不再只能这样工作:

text 复制代码
思考 → 调一个工具 → 读取全部结果 → 再思考 → 再调工具

它还可以先写一小段 JavaScript,再一次性完成循环、分支、并行、过滤和汇总:

text 复制代码
思考 → 写 JavaScript → 解释器执行多步工作 → 只把精简结果交还模型

可以把它理解为给 Agent 配了一名"会执行流程的秘书"。模型负责决定做什么,解释器负责准确地重复执行。

2. Dynamic subagents 是什么

Dynamic subagents 是 Interpreter 上的一种子智能体编排能力。

解释器中会出现一个内置的 task() 函数。Agent 可以在 JavaScript 的循环、条件和 Promise.all() 中调用它,从而批量或并行派发子智能体:

javascript 复制代码
const results = await Promise.all(
  files.map((file) =>
    task({
      description: `审查文件 ${file}`,
      subagentType: "reviewer",
    }),
  ),
);

因此,两者的关系是:

text 复制代码
Interpreter
├── 普通 JavaScript:计算、排序、聚合、保存中间变量
├── PTC:通过 tools.xxx() 调用允许的工具
└── Dynamic subagents:通过 task() 调度子智能体

二、为什么需要 Interpreter

2.1 普通工具调用的局限

假设要分析 100 条工单。普通 Agent 可能需要反复经历:

  1. 模型决定调用工具;
  2. 工具结果回到模型上下文;
  3. 模型阅读结果并决定下一步;
  4. 再次调用工具。

这会出现几个问题:

  • 每一批中间结果都进入模型上下文,Token 消耗很快;
  • 一批工具调用在生成时已经固定,不能根据其中一个结果立即循环或分支;
  • 让模型自己"记住已经处理到第几条"不够稳定;
  • 面对大量项目时,模型有时只抽样处理,不能保证每一项都覆盖。

2.2 Interpreter 的改进

Interpreter 把机械流程交给代码:

javascript 复制代码
const passed = [];

for (const item of items) {
  const score = calculateScore(item);
  if (score >= 80) passed.push(item);
}

passed.sort((a, b) => b.score - a.score);
passed.slice(0, 10);

循环一定会遍历每一项,排序规则也是确定的。中间变量留在解释器内,最后只有前 10 条结果回到模型。

2.3 适合与不适合的场景

需求 推荐方式
只调用一两个简单工具 普通工具调用
循环、分支、重试、过滤、聚合 Interpreter
从代码中批量调用指定工具 Interpreter + PTC
大量独立任务、多角度分析、递归处理 Interpreter + Dynamic subagents
执行 Shell、安装包、跑测试、操作操作系统 Sandbox

三、安装与最小示例

3.1 环境要求

根据官方文档,当前要求:

  • Python >= 3.11
  • langchain-quickjs >= 0.1.0
  • Interpreter 目前是 Beta,升级依赖时应关注 API 变化

安装:

bash 复制代码
pip install -U "deepagents[quickjs]"

3.2 最小 Python 配置

python 复制代码
from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware

agent = create_deep_agent(
    model="openai:gpt-5.5",
    middleware=[
        CodeInterpreterMiddleware(),
    ],
)

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": (
                    "请使用解释器计算 1 到 1000 中所有 3 的倍数之和,"
                    "并说明最终结果。"
                ),
            }
        ]
    }
)

print(result["messages"][-1].content)

关键代码只有一行:

python 复制代码
CodeInterpreterMiddleware()

中间件会给主智能体增加一个默认名为 eval 的工具。模型需要计算或编排时,会自己生成 JavaScript 并调用 eval。应用代码通常不需要直接调用解释器

模型可能生成类似代码:

javascript 复制代码
let total = 0;
for (let i = 3; i <= 1000; i += 3) {
  total += i;
}
total;

最后一个表达式 total 就是本次执行的返回值。

注意:解释器运行的是 JavaScript ,不是 Python。项目主体仍然用 Python 配置 Agent,只有 eval 内部的编排代码是 JavaScript。


四、Interpreter 到底怎样工作

4.1 一次调用的完整流程

text 复制代码
用户提出任务
    ↓
主模型判断适合用解释器
    ↓
主模型生成 JavaScript,并调用 eval
    ↓
QuickJS 在内存中执行代码
    ↓
捕获 console.log 和最后一个表达式
    ↓
精简结果返回主模型
    ↓
主模型组织最终回答

4.2 默认能做什么

默认情况下,解释器可以:

  • 执行轻量 JavaScript;
  • 使用顶层 await
  • 使用变量、数组、对象、循环和分支;
  • 排序、分组、解析、校验和聚合数据;
  • 捕获 console.logconsole.warnconsole.error
  • 在同一次 Agent 运行的多个 eval 调用之间保留状态;
  • 默认在同一线程的多轮对话之间保存可序列化状态。

例如:

javascript 复制代码
const rows = [
  { team: "A", score: 8 },
  { team: "B", score: 13 },
  { team: "A", score: 21 },
];

const totals = rows.reduce((result, row) => {
  result[row.team] = (result[row.team] ?? 0) + row.score;
  console.log(`${row.team} 当前总分:${result[row.team]}`);
  return result;
}, {});

totals;

4.3 默认不能做什么

QuickJS 默认不能直接访问:

  • 宿主机文件系统;
  • 网络;
  • Shell;
  • pipnpm 等包管理器;
  • 系统时间;
  • 宿主机环境变量。

这是一条很重要的边界:

Interpreter 是 Agent 循环内的轻量执行层,不是拥有完整操作系统的 Python/Shell 环境。

需要调用外部能力时,应显式开放 PTC 工具;需要 Shell、依赖安装和操作系统级隔离时,应使用 Sandbox。


五、使用 PTC 在解释器中调用工具

PTC 是 Programmatic Tool Calling,可译为"程序化工具调用"。

它允许解释器中的 JavaScript 通过 tools.xxx() 调用指定工具。PTC 默认关闭,必须配置白名单。

5.1 完整示例

python 复制代码
from deepagents import create_deep_agent
from langchain_core.tools import tool
from langchain_quickjs import CodeInterpreterMiddleware


@tool
def search_docs(query: str) -> str:
    """在示例知识库中搜索资料。"""
    knowledge = {
        "retrieval": "检索系统应关注召回率、重排质量与引用准确性。",
        "memory": "记忆应区分当前线程状态与跨线程长期存储。",
        "evaluation": "评估应同时覆盖正确性、完整性、延迟与成本。",
    }
    return knowledge.get(query, "未找到相关资料")


agent = create_deep_agent(
    model="openai:gpt-5.5",
    tools=[search_docs],
    middleware=[
        CodeInterpreterMiddleware(
            ptc=["search_docs"],
        )
    ],
)

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": (
                    "请使用解释器工作流,分别查询 retrieval、memory、evaluation,"
                    "并把结果整理成三个要点。"
                ),
            }
        ]
    }
)

print(result["messages"][-1].content)

模型在解释器中可能写出:

javascript 复制代码
const topics = ["retrieval", "memory", "evaluation"];

const results = await Promise.all(
  topics.map((topic) =>
    tools.searchDocs({ query: topic }),
  ),
);

topics.map((topic, index) => ({
  topic,
  finding: results[index],
}));

5.2 三个必须注意的细节

第一,工具需要在 PTC 白名单中:

python 复制代码
CodeInterpreterMiddleware(ptc=["search_docs"])

没有加入白名单的工具不会跨过 QuickJS 边界。

第二,工具名会从 snake_case 转为 camelCase:

text 复制代码
Python 工具名:search_docs
JavaScript 调用:tools.searchDocs(...)

第三,参数仍然遵循原工具的输入 Schema:

javascript 复制代码
await tools.searchDocs({ query: "memory" });

不能随意改成:

javascript 复制代码
await tools.searchDocs("memory"); // 通常不符合工具输入 Schema

5.3 为什么 PTC 节省上下文

三次工具调用的原始结果先回到 JavaScript 变量 results,解释器可以过滤、合并后再返回。主模型不必逐次阅读每个工具的完整输出。

例如只保留成功项:

javascript 复制代码
const results = await Promise.all(
  topics.map((topic) => tools.searchDocs({ query: topic })),
);

results
  .filter((text) => !text.includes("未找到"))
  .map((text) => text.slice(0, 200));

5.4 PTC 的审批边界

PTC 调用发生在 eval 内部,不走普通工具调用路径。因此,父 Agent 为普通工具配置的 interrupt_on,不会自动对每一次 tools.xxx() 调用逐个审批。

如果某个工具能:

  • 发邮件;
  • 删除或修改数据;
  • 支付或产生费用;
  • 访问敏感系统;
  • 进行无限制网络请求;

就不要轻易把它加入 PTC 白名单。确需人工审批时,可以把审批放在整个 eval 调用之前,或者不要通过 PTC 暴露该高风险工具。


六、Interpreter 状态持久化

6.1 同一次运行内

同一次 Agent 运行中,多次 eval 使用同一个实时解释器上下文。

第一次:

javascript 复制代码
globalThis.processedIds = ["T-001", "T-002"];
processedIds;

后续 eval 可以继续读取:

javascript 复制代码
processedIds.push("T-003");
processedIds;

6.2 多轮对话之间

snapshot_between_turns=True 是默认值。每轮结束时,中间件会序列化解释器状态;同一线程下一轮开始时再恢复。

python 复制代码
from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware
from langgraph.checkpoint.memory import MemorySaver

agent = create_deep_agent(
    model="openai:gpt-5.5",
    checkpointer=MemorySaver(),
    middleware=[
        CodeInterpreterMiddleware(
            snapshot_between_turns=True,
        )
    ],
)

config = {
    "configurable": {
        "thread_id": "interpreter-demo-001",
    }
}

agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "使用解释器保存数组 [10, 20, 30],变量名为 scores。",
            }
        ]
    },
    config=config,
)

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "使用解释器读取刚才的 scores,并计算平均值。",
            }
        ]
    },
    config=config,
)

print(result["messages"][-1].content)

要实现"同一线程",调用时必须保持相同的 thread_id

6.3 什么状态适合保存

适合保存:

  • 字符串、数字、布尔值;
  • 普通数组和对象;
  • 已处理 ID;
  • 统计结果;
  • 下一轮还要用的轻量中间数据。

不适合依赖跨轮恢复:

  • 函数和类;
  • Promise;
  • 打开的文件句柄;
  • 工具连接或其他实时运行时对象。

函数等不可序列化值恢复后会成为不可访问对象,使用时会报错。简单说:

跨轮快照适合保存"数据",不要用它保存"活着的对象"。

关闭跨轮快照:

python 复制代码
CodeInterpreterMiddleware(
    snapshot_between_turns=False,
)

七、Dynamic subagents 是什么

7.1 普通子智能体委派

普通子智能体模式中,主模型直接调用 task 工具:

text 复制代码
主模型 → task 工具 → 一个子智能体 → 结果回到主模型

这种方式适合单次、直接的委派,例如:"让研究员调查这个主题。"

7.2 动态子智能体委派

Dynamic subagents 让主模型先写 JavaScript,再从代码中多次调用 task()

text 复制代码
主模型
  ↓ 生成编排代码
eval / QuickJS
  ├── task() → 子智能体 1
  ├── task() → 子智能体 2
  ├── task() → 子智能体 3
  └── JavaScript 聚合结果
  ↓
主模型只读取聚合结果

它适合:

  • 一批独立项目需要逐项处理;
  • 同一个问题需要多个独立视角;
  • 第一轮发现需要第二轮验证;
  • 需要循环搜索,直到没有新发现;
  • 需要用确定的代码保证所有项目都被处理。

7.3 为什么叫"Dynamic"

"动态"不是指运行时临时创建全新子智能体配置,而是指:

  • 子智能体角色先在 Python 中配置;
  • 调用数量、调用顺序、并行方式和下一步分支,由运行时生成的 JavaScript 动态决定。

例如第一轮发现 7 个问题,第二轮就自动启动 7 次验证;若只发现 2 个问题,就只启动 2 次。这就是动态编排。


八、Dynamic subagents 最小示例

python 复制代码
import asyncio

from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware


agent = create_deep_agent(
    model="openai:gpt-5.5",
    subagents=[
        {
            "name": "reviewer",
            "description": "逐项审查输入内容,找出风险并说明理由",
            "system_prompt": (
                "你是一名严谨的审查员。"
                "每次只审查交给你的一个项目,返回风险等级、问题和建议。"
            ),
        }
    ],
    middleware=[
        CodeInterpreterMiddleware(),
    ],
)


async def main() -> None:
    result = await agent.ainvoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": """
请运行一个 workflow,并行审查下面每一条操作:
1. 管理员登录不启用二次验证
2. 用户头像允许上传任意扩展名
3. 数据库每天加密备份
4. 日志永久记录完整访问令牌

确保四项都被审查,最后按风险从高到低汇总。
""",
                }
            ]
        }
    )
    print(result["messages"][-1].content)


if __name__ == "__main__":
    asyncio.run(main())

这里没有在 Python 中手写 task() 循环。主模型识别到"workflow"和批量任务后,会在解释器中生成类似代码:

javascript 复制代码
const items = [
  "管理员登录不启用二次验证",
  "用户头像允许上传任意扩展名",
  "数据库每天加密备份",
  "日志永久记录完整访问令牌",
];

const reviews = await Promise.all(
  items.map((item) =>
    task({
      description: `审查这项操作:${item}`,
      subagentType: "reviewer",
    }),
  ),
);

reviews;

官方文档建议在提示词中使用单词 workflow,把它作为选择动态编排的明确触发信号。若只想进行一次普通委派,则用自然语言直接描述任务即可。

Deep Agents 内置了一个通用子智能体,因此简单的批量分派可以不配置自定义角色;但生产任务更建议定义名称、描述、系统提示词和最小工具集都清晰的专用子智能体。


九、task() 的参数与结构化输出

解释器中的 task() 接收三个核心字段:

字段 是否必填 作用
description 交给子智能体的具体任务提示
subagentType 要调用的已配置子智能体名称
responseSchema 要求子智能体返回符合 JSON Schema 的结构化结果

注意 JavaScript 中使用的是 camelCase:

javascript 复制代码
subagentType
responseSchema

9.1 结构化输出示例

javascript 复制代码
const issueSchema = {
  type: "object",
  properties: {
    riskLevel: {
      type: "string",
      enum: ["high", "medium", "low", "none"],
    },
    problem: { type: "string" },
    suggestion: { type: "string" },
  },
  required: ["riskLevel", "problem", "suggestion"],
};

const review = await task({
  description: "审查:日志永久记录完整访问令牌",
  subagentType: "reviewer",
  responseSchema: issueSchema,
});

review.riskLevel;

传入 responseSchema 后,task() 的结果已经是 JavaScript 对象,可以直接:

javascript 复制代码
review.riskLevel

不需要:

javascript 复制代码
JSON.parse(review) // 多余,甚至可能报错

只有当子智能体故意返回"JSON 字符串"而不是结构化结果时,才需要 JSON.parse()

9.2 为什么推荐结构化输出

自由文本适合直接阅读,但不适合稳定编排。下面的判断很脆弱:

javascript 复制代码
if (review.includes("高风险")) {
  // ...
}

结构化结果更可靠:

javascript 复制代码
if (review.riskLevel === "high") {
  // ...
}

只要后续还要过滤、排序、投票、去重或二次验证,就优先使用 responseSchema


十、五种常见编排模式

10.1 扇出再汇总(Fan-out and synthesize)

同一种任务分发给多个独立项目,然后合并结果。

javascript 复制代码
const results = await Promise.all(
  documents.map((doc) =>
    task({
      description: `总结文档:${doc}`,
      subagentType: "summarizer",
      responseSchema: summarySchema,
    }),
  ),
);

results;

适合批量文档、批量文件、批量服务检查。

10.2 先分类,再交给不同专家(Classify and act)

javascript 复制代码
const specialistMap = {
  bug: "bug-fixer",
  feature: "feature-analyst",
  question: "support-agent",
};

const handled = await Promise.all(
  tickets.map((ticket) =>
    task({
      description: `处理这条 ${ticket.category}:${ticket.text}`,
      subagentType: specialistMap[ticket.category],
    }),
  ),
);

handled;

适合工单、日志、用户反馈等混合输入。

10.3 对抗式验证(Adversarial verification)

第一轮发现问题,第二轮让独立验证者反驳或确认:

javascript 复制代码
const audit = await task({
  description: "审计支付模块并列出疑似漏洞",
  subagentType: "auditor",
  responseSchema: findingsSchema,
});

const verdicts = await Promise.all(
  audit.findings.map((finding) =>
    task({
      description: `独立验证这个问题是否真实:${JSON.stringify(finding)}`,
      subagentType: "verifier",
      responseSchema: verdictSchema,
    }),
  ),
);

const confirmed = audit.findings.filter(
  (_, index) => verdicts[index].confirmed,
);

confirmed;

适合安全审计、合规检查等"不希望误报"的场景。

10.4 多方案生成与筛选(Generate and filter)

javascript 复制代码
const proposals = await Promise.all(
  [1, 2, 3].map((number) =>
    task({
      description: `提出第 ${number} 套数据库重构方案,并说明取舍`,
      subagentType: "architect",
      responseSchema: proposalSchema,
    }),
  ),
);

const best = proposals
  .map((proposal) => ({
    ...proposal,
    score: scoreProposal(proposal),
  }))
  .sort((a, b) => b.score - a.score)[0];

best;

适合架构方案、重构策略、内容创意和设计方案。

10.5 循环直到没有新结果(Loop until done)

javascript 复制代码
const seen = new Set();
const allItems = [];

while (true) {
  const result = await task({
    description: `继续查找遗漏项。已找到:${[...seen].join(", ") || "无"}`,
    subagentType: "analyzer",
    responseSchema: itemsSchema,
  });

  const fresh = result.items.filter((item) => !seen.has(item.id));
  if (fresh.length === 0) break;

  for (const item of fresh) {
    seen.add(item.id);
    allItems.push(item);
  }
}

allItems;

适合范围事先未知、追求尽可能完整的发现任务。

必须给循环设计停止条件。实际项目还应增加最大轮数,防止模型或数据异常导致无限探索:

javascript 复制代码
const MAX_ROUNDS = 10;

for (let round = 0; round < MAX_ROUNDS; round++) {
  // 查找新结果
  // 如果没有新结果就 break
}

十一、完整实战:批量工单分类与处理

下面把 Interpreter、Dynamic subagents 和结构化输出放到一个完整场景中。

11.1 目标

对一批工单执行:

  1. 判断是 Bug、功能需求还是咨询;
  2. 根据类别选择不同子智能体;
  3. 并行处理所有工单;
  4. 最后按类别汇总。

11.2 完整 Python 代码

python 复制代码
import asyncio

from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware


subagents = [
    {
        "name": "bug-fixer",
        "description": "处理 Bug 报告,分析可能原因并给出复现和排查步骤",
        "system_prompt": (
            "你是 Bug 分诊专家。一次只处理一条 Bug 工单。"
            "返回问题摘要、可能原因、复现步骤和排查优先级。"
        ),
    },
    {
        "name": "feature-analyst",
        "description": "分析功能需求的价值、可行性、工作量和风险",
        "system_prompt": (
            "你是产品与技术分析师。一次只处理一条功能需求。"
            "返回用户价值、技术可行性、粗略工作量和主要风险。"
        ),
    },
    {
        "name": "support-agent",
        "description": "回答产品使用咨询,给出清晰、可执行的操作步骤",
        "system_prompt": (
            "你是客服专家。一次只处理一条咨询。"
            "直接回答问题,并给出简洁的操作步骤。"
        ),
    },
]


agent = create_deep_agent(
    model="openai:gpt-5.5",
    system_prompt=(
        "你是工单协调员。遇到批量工单时,使用解释器 workflow:"
        "先分类,再通过动态子智能体逐条处理,不得遗漏任何一条。"
        "最后按类别汇总,并保留原工单编号。"
    ),
    subagents=subagents,
    middleware=[
        CodeInterpreterMiddleware(
            timeout=20.0,
            max_result_chars=12000,
        )
    ],
)


async def main() -> None:
    tickets = """
T-001:保存个人资料后页面一直转圈,刷新后修改也没有生效。
T-002:希望支持把月度报告导出为 Excel。
T-003:在哪里修改登录密码?
T-004:上传 8MB 图片时提示成功,但头像仍然是旧图片。
T-005:希望管理员可以批量停用长期未登录账号。
T-006:怎样查看本月 API 调用量?
"""

    result = await agent.ainvoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": (
                        "运行一个 workflow,处理下面所有工单。"
                        "先分类,再交给对应专家并行处理,最后形成分组报告。\n\n"
                        f"{tickets}"
                    ),
                }
            ]
        }
    )

    print(result["messages"][-1].content)


if __name__ == "__main__":
    asyncio.run(main())

11.3 模型在解释器里可能生成的核心逻辑

下面是为了帮助理解而展示的等价编排代码,实际代码由模型生成:

javascript 复制代码
const tickets = [
  { id: "T-001", category: "bug", text: "保存个人资料后页面一直转圈......" },
  { id: "T-002", category: "feature", text: "希望支持导出 Excel" },
  { id: "T-003", category: "question", text: "在哪里修改登录密码?" },
  { id: "T-004", category: "bug", text: "上传头像后仍显示旧图片" },
  { id: "T-005", category: "feature", text: "批量停用长期未登录账号" },
  { id: "T-006", category: "question", text: "怎样查看 API 调用量?" },
];

const specialistMap = {
  bug: "bug-fixer",
  feature: "feature-analyst",
  question: "support-agent",
};

const resultSchema = {
  type: "object",
  properties: {
    summary: { type: "string" },
    action: { type: "string" },
    priority: {
      type: "string",
      enum: ["high", "medium", "low"],
    },
  },
  required: ["summary", "action", "priority"],
};

const handled = await Promise.all(
  tickets.map(async (ticket) => {
    const result = await task({
      description: `工单 ${ticket.id}:${ticket.text}`,
      subagentType: specialistMap[ticket.category],
      responseSchema: resultSchema,
    });

    return {
      id: ticket.id,
      category: ticket.category,
      ...result,
    };
  }),
);

const grouped = Object.groupBy(
  handled,
  (item) => item.category,
);

grouped;

如果担心运行时不支持较新的 Object.groupBy(),可使用兼容性更好的 reduce()

javascript 复制代码
const grouped = handled.reduce((groups, item) => {
  (groups[item.category] ??= []).push(item);
  return groups;
}, {});

11.4 这个示例体现了什么

  • Python 负责定义角色、权限和运行参数;
  • 主模型负责理解需求、分类并生成编排代码;
  • QuickJS 负责确定性遍历和并行调度;
  • 子智能体负责各自的专业判断;
  • 结构化输出让 JavaScript 能可靠地分组和排序;
  • 主模型最终只需要阅读整理后的结果。

十二、容易混淆的概念对比

12.1 Interpreter 与 Sandbox

对比项 Interpreter Sandbox
主要目标 在 Agent 循环内编排工具和子智能体 在隔离环境中执行系统级代码
典型语言 QuickJS / JavaScript Python、Shell 等
文件系统 默认无 通常有
网络 默认无 由 Sandbox 配置决定
安装依赖 不支持 支持
运行测试 不适合 适合
中间状态 JavaScript 变量与快照 Sandbox 文件和进程状态

记忆口诀:

text 复制代码
Interpreter 管"Agent 内部流程";
Sandbox 管"外部执行环境"。

12.2 普通 Subagent 与 Dynamic subagents

对比项 普通 Subagent Dynamic subagents
谁发起调用 主模型直接调 task 工具 JavaScript 调内置 task()
适合任务数 一个或少量 批量、多轮、多阶段
循环与分支 依赖多次模型决策 直接写在代码中
并行批处理 能力有限且由模型决定 可用 Promise.all() 明确表达
中间结果处理 常回到模型 可留在解释器变量中

12.3 Dynamic subagents 与 Async subagents

这是最容易混淆的一组。

对比项 Dynamic subagents Async subagents
核心目的 用代码动态编排一批子智能体 把长任务放到后台持续运行
主智能体是否等待 await task() 时会等待该任务结果 启动后立即获得任务 ID
典型能力 循环、分支、并行、验证、聚合 查询进度、追加指令、取消任务
适合场景 当前请求内的批量分析 跨交互的长时间后台任务
关键概念 QuickJS 中的 task() 异步任务生命周期

一句话区分:

text 复制代码
Dynamic 解决"这一轮怎么聪明地派很多活";
Async 解决"这个活很久,让它在后台慢慢跑"。

Dynamic subagents 中的 Promise.all() 虽然是并发调度,但主流程仍要等待结果才能继续聚合;它不等于可跨多轮管理的后台异步任务。


十三、安全、限制与避坑

13.1 Interpreter 不是完整安全沙箱

QuickJS 默认隔离了文件、网络和 Shell,但它运行在嵌入式、同进程环境中。应把它看作能力受限的解释器运行时,而不是强隔离的生产 Sandbox。

面对不可信或半可信代码时,建议:

  • 在隔离的工作进程或容器中运行 Agent;
  • 缩小 PTC 白名单;
  • 不向解释器暴露读取密钥、付款、删库等宽泛工具;
  • 为耗时和内存设置合理上限。

13.2 不要开放"万能工具"

不推荐:

python 复制代码
CodeInterpreterMiddleware(
    ptc=["run_any_shell", "request_any_url", "execute_any_sql"],
)

更好的做法是开放参数和能力都受限的专用工具:

python 复制代码
CodeInterpreterMiddleware(
    ptc=["search_public_docs", "read_project_report"],
)

13.3 interrupt_on 不会逐个拦截

以下两种调用都发生在 eval 内:

javascript 复制代码
await tools.someTool(...);
await task(...);

它们不走普通工具调用路径,因此父 Agent 的 interrupt_on 不会自动逐次审批。若需要人工确认整个动态流程,应审批 eval 本身;若需要每一步都审批,则不要把相应能力放进解释器桥接层。

13.4 并行不是越多越好

下面的写法可能瞬间启动大量子智能体:

javascript 复制代码
await Promise.all(
  tenThousandItems.map((item) => task({ /* ... */ })),
);

实际项目应分批执行:

javascript 复制代码
const BATCH_SIZE = 10;
const allResults = [];

for (let start = 0; start < items.length; start += BATCH_SIZE) {
  const batch = items.slice(start, start + BATCH_SIZE);
  const results = await Promise.all(
    batch.map((item) =>
      task({
        description: `处理:${item}`,
        subagentType: "worker",
      }),
    ),
  );
  allResults.push(...results);
}

allResults;

这样更容易控制模型并发、限流、成本与失败范围。

13.5 给循环设置双重停止条件

不要只依赖"没有新结果":

javascript 复制代码
for (let round = 0; round < 10; round++) {
  const result = await task(...);
  if (result.items.length === 0) break;
}

推荐同时设置:

  • 业务停止条件:没有新结果;
  • 技术停止条件:最多 N 轮。

13.6 max_result_chars 不是越大越好

返回字符过小会截断重要结果,过大又会把中间数据塞回模型上下文。更好的办法不是无限调大,而是让 JavaScript 在返回前完成:

  • 去重;
  • 排序;
  • 取 Top N;
  • 删除无用字段;
  • 汇总统计。

13.7 子智能体描述必须清楚

主 Agent 依赖 namedescription 选择角色。

不清楚:

python 复制代码
{
    "name": "helper",
    "description": "帮助处理问题",
}

更清楚:

python 复制代码
{
    "name": "bug-fixer",
    "description": "处理软件 Bug 报告,提供可能原因、复现步骤和排查优先级",
}

13.8 Dynamic subagents 可关闭

只想让子智能体通过普通 task 工具调用时:

python 复制代码
CodeInterpreterMiddleware(
    subagents=False,
)

这不会删除子智能体,只是不再把解释器内置的 task() 暴露给 JavaScript。


十四、配置参数速查

CodeInterpreterMiddleware 的主要参数如下:

参数 默认值 作用
memory_limit 64 * 1024 * 1024 QuickJS 堆内存上限,单位字节
timeout 5.0 每次 eval 的超时时间,单位秒
max_ptc_calls 256 每次 eval 最多允许的 tools.* 调用次数
tool_name "eval" 暴露给模型的解释器工具名称
max_result_chars 4000 返回结果和控制台输出的最大字符数
capture_console True 是否捕获 console.log/warn/error
subagents True 有子智能体时,是否开放内置 task()
ptc None 可从解释器调用的工具白名单
snapshot_between_turns True 是否跨轮保存解释器状态
max_snapshot_bytes None 快照最大字节数,默认跟随内存上限

一个偏保守的配置示例:

python 复制代码
middleware = [
    CodeInterpreterMiddleware(
        memory_limit=32 * 1024 * 1024,
        timeout=15.0,
        max_ptc_calls=50,
        max_result_chars=8000,
        capture_console=True,
        subagents=True,
        ptc=["search_public_docs"],
        snapshot_between_turns=True,
        max_snapshot_bytes=4 * 1024 * 1024,
    )
]

max_ptc_calls=None 只应在可信环境中使用。


十五、如何选择

遇到任务时,可以按下面顺序判断:

text 复制代码
只是一次简单调用?
├── 是:普通工具或普通 Subagent
└── 否
    ├── 需要循环、分支、过滤或聚合?
    │   └── Interpreter
    ├── 需要从代码中批量调用工具?
    │   └── Interpreter + PTC
    ├── 需要批量、多角色、验证或递归分析?
    │   └── Interpreter + Dynamic subagents
    ├── 需要跨对话运行很久、查进度或取消?
    │   └── Async subagents
    └── 需要 Shell、安装依赖或跑测试?
        └── Sandbox

最后记住四句话:

  1. Interpreter 是 Agent 循环内的 JavaScript 工作台。
  2. PTC 让这个工作台只能调用你明确允许的工具。
  3. Dynamic subagents 让工作台通过 task() 批量编排已配置的子智能体。
  4. 凡是高风险外部能力,都要把白名单、审批和隔离边界设计清楚。