目录
- 一、先用一句话理解它们
- [二、为什么需要 Interpreter](#二、为什么需要 Interpreter)
- 三、安装与最小示例
- [四、Interpreter 到底怎样工作](#四、Interpreter 到底怎样工作)
- [五、使用 PTC 在解释器中调用工具](#五、使用 PTC 在解释器中调用工具)
- [六、Interpreter 状态持久化](#六、Interpreter 状态持久化)
- [七、Dynamic subagents 是什么](#七、Dynamic subagents 是什么)
- [八、Dynamic subagents 最小示例](#八、Dynamic subagents 最小示例)
- [九、task() 的参数与结构化输出](#九、task() 的参数与结构化输出)
- 十、五种常见编排模式
- 十一、完整实战:批量工单分类与处理
- 十二、容易混淆的概念对比
- 十三、安全、限制与避坑
- 十四、配置参数速查
- 十五、如何选择
一、先用一句话理解它们
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 可能需要反复经历:
- 模型决定调用工具;
- 工具结果回到模型上下文;
- 模型阅读结果并决定下一步;
- 再次调用工具。
这会出现几个问题:
- 每一批中间结果都进入模型上下文,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.log、console.warn、console.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;
pip、npm等包管理器;- 系统时间;
- 宿主机环境变量。
这是一条很重要的边界:
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 目标
对一批工单执行:
- 判断是 Bug、功能需求还是咨询;
- 根据类别选择不同子智能体;
- 并行处理所有工单;
- 最后按类别汇总。
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 依赖 name 和 description 选择角色。
不清楚:
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
最后记住四句话:
- Interpreter 是 Agent 循环内的 JavaScript 工作台。
- PTC 让这个工作台只能调用你明确允许的工具。
- Dynamic subagents 让工作台通过
task()批量编排已配置的子智能体。 - 凡是高风险外部能力,都要把白名单、审批和隔离边界设计清楚。