deepagents × QuickJS:系统提示词快照深度解析——eval REPL 语义与 task 子代理派发规范
发布时间:2026/9/10 12:56:41 作者:尧图编辑部 阅读量:1,286

deepagents × QuickJS系统提示词快照深度解析——eval REPL 语义与 task 子代理派发规范【免费下载链接】deepagentsThe batteries-included agent harness.项目地址: https://gitcode.com/GitHub_Trending/de/deepagents本文以langchain-quickjs中间件在“无工具no-tools / 无 PTC场景”下注入 agent 系统提示词的快照为线索逐条拆解CodeInterpreterMiddleware赋予模型的JavaScript REPL 沙箱执行语义与task()子代理派发规范并结合仓库源码提示词渲染、中间件注入、子代理桥接还原每段提示词的来源与工程考量。读完本文你将清楚理解快照文件在测试体系中扮演什么角色、REPL 在modeturn下的状态与沙箱边界、以及模型应如何用task()完成有界并发、多阶段编排的“分配型”工作负载。快照文件从何而来不是文档是契约libs/partners/quickjs/tests/unit_tests/smoke_tests/snapshots/quickjs_system_prompt_no_tools_turn.md是 smoke_tests/test_system_prompt.py 生成并提交的系统提示词快照。测试通过GenericFakeChatModel捕获create_deep_agent实际发给模型的首条 SystemMessage 全文再与快照逐字比对。这套快照的职责是捕捉deepagents SDK 侧的漂移quickjs 中间件组合出的提示词依赖 SDK 的脚手架write_todos提示、工具列表格式、harness 框架文本等当 SDK 变更而 quickjs 未变时CI 会以专用test-quickjs-sdk-smoke任务运行该文件防止漂移悄悄落地见 test_system_prompt.py 模块文档。快照家族覆盖三种持久化模式与两种 PTC 形态命名即参数快照文件modePTCquickjs_system_prompt_no_tools.mdthread默认关闭quickjs_system_prompt_no_tools_turn.mdturn关闭quickjs_system_prompt_no_tools_call.mdcall关闭quickjs_system_prompt_mixed_foreign_functions.mdthread5 个混合工具quickjs_system_prompt_mixed_foreign_functions_turn.md/_call.mdturn/call5 个混合工具被测逻辑位于 test_system_prompt.pymode由pytest参数化驱动_snapshot_name_for_mode按模式决定文件名。快照更新方式是pytest ... --update-snapshots选项在 conftest.py 中注册。本文所解读的..._no_tools_turn.md快照对应配置CodeInterpreterMiddleware(modeturn)且ptcNone——即不暴露任何宿主工具、仅在 turn 内保持 REPL 状态的最简形态。提示词是怎么拼出来的渲染管线快照文本并非手写而是由 langchain_quickjs/_prompt.py 中的模板函数在运行时渲染render_repl_system_prompt()渲染### Interpreter段。ptc_attachedFalse时使用“纯计算”表述ptc_attachedTrue时替换为“仅能通过tools.*命名空间产生外部副作用”mode决定 intro 行与状态持久化行源码render_subagent_system_prompt()渲染### Dispatching Subagents with task段将模板中的{tool_name}替换为实际工具名默认eval源码。注入时机在中间件的wrap_model_call/awrap_model_call_prepare_for_call读取本轮工具列表若宿主存在 Deep Agentstask工具且subagentsTrue就把子代理段落一并拼进 SystemMessagemiddleware.py。同时中间件保证每个 LangGraphthread_id持有独立的 QuickJSRuntime/槽位会话间全局变量互不可见middleware.py。### Interpretereval 工具的沙箱与状态语义快照首段定义了eval工具可配置改名默认eval的全部运行契约逐条对应实现持久化 REPL 与 turn 级状态。快照原文State (variables, functions) persists across tool calls within a single turn of conversation. They DO NOT persist across multiple turns.即modeturn下let/const/function在同一轮对话内跨多次eval调用存活跨轮清空。对比同族快照thread模式“跨调用且跨 turn 持久”call模式“每次调用全新环境”。三种表述分别由 render_repl_system_prompt 的三个分支生成mode参数解析在 middleware.py 的 _resolve_mode非法值直接ValueError。thread模式的跨轮持久依赖快照机制after_agent把 REPL 内存序列化进 checkpointerbefore_agent恢复——超出max_snapshot_bytes的快照会被丢弃middleware.py还可用snapshot_signing_key对持久化快照做 HMAC 签名防篡改middleware.py。顶层 await。异步路径上顶层await可用Promise 在调用返回前落定。eval工具同时注册了同步与异步实现sync_eval/async_evalmiddleware.pyREADME 特别提示 PTC 桥是异步 QuickJS 宿主函数带异步桥的 REPL 上同步invoke会抛ConcurrentEvalError因此官方用法是ainvoke。沙箱边界。快照明确没有内建文件系统、网络、stdlib 或墙上时钟 APIfetch、require、fs、process、真实的Date.now()均不可用或被 stub。这是 QuickJS 上下文的默认能力面——能力只能通过显式配置的 PTC 白名单补入对应 README 的 Sandbox 章节。纯计算原则无 PTC 时的副作用声明。快照原文The REPL has no access to host tools, files, or the network: it is pure computation. Return values to communicate results.当ptc_attachedFalse时该行由模板注入一旦启用 PTC这行会替换为指向tools.*API 参考的指引见 mixed 快照第 8 行。资源预算。快照原文Timeout: 5.0s per call. Memory: 64 MB total.对应timeout5.0与memory_limit64 * 1024 * 1024两个默认值middleware.py。超时表现为error typeTimeout内存共享于同一 Runtime 下所有上下文OOM 表现为error typeOutOfMemory。注意一个实现细节超时预算计的是 QuickJS VM 执行时间而非 Python 墙钟时间等待tools.*宿主调用Python 协程的时间不计入慢宿主调用可能让一次 eval 超过timeout秒middleware.py。console 捕获。console.log输出被捕获随结果一并返回独立的stdout块独立截断默认max_result_chars4000。可capture_consoleFalse彻底不装console。### Dispatching Subagents with task把编排写进 JavaScript这是快照的第二个大段也是模型在 REPL 内“派发工作”的完整行动指南。它只在宿主 agent 具备 Deep Agentstask工具且subagentsTrue时出现middleware.pyfind_subagent_task_tool通过“工具名为task且输入字段包含description、subagent_type”识别该工具_subagent.py。原语签名await task({ description, // full autonomous task prompt subagentType, // configured subagent name label, // optional short UI label for this dispatch responseSchema, // optional JSON Schema for structured output }); // - Promiseunknowntask对选定子代理跑完整的 agentic loop子代理可用自身配置的工具、迭代、检查上下文、返回最终结果。subagentType必填description是该次派发子代理收到的唯一提示词必须自包含目标、约束、要检查什么、期望返回的形状/详细度。description 规范给 locator别给 payload快照反复强调一条原则上下文以“定位符”传递——文件路径和符号名而不是粘贴的文件内容。子代理有自己的文件工具任何存在于文件里的东西要审查、重写、审计的文件都应传路径让它自己读不要先读整个文件再把内容粘贴进 description——那会让每次派发都膨胀并在多次派发间重复搬运同一份文件。内联内容只保留给没有路径的小型/派生数据单条解析记录、拆分出的大块数据先写成自己的文件再传路径。从调用链看这正对应call_subagent_task_tool把description原样交给task_tool.arun_subagent.py没有二次拼装。responseSchema让流程可组合responseSchema可选但结果要喂给后续代码的派发必须设置确定性的类型化形状才能可靠支撑下一阶段索引、排序、比较字段、分支、合并而不是解析自由文本。提供后解析值是已匹配 schema 的类型化 JS 值除非子代理故意返回 JSON 字符串否则不要再JSON.parse。动态 schema 对声明式子代理有效runnable 后端子代理拒绝动态 schema其 runnable 已编译。实现侧schema 在上桥前要过三重校验_subagent.py序列化后 ≤ 4096 字节、嵌套深度 ≤ 5、总属性数 ≤ 32超限抛ValueError缺失顶层title时会注入默认subagent_response以满足把 JSON Schema 当函数用的结构化输出后端如 OpenAI function-calling 路径_subagent.py。审批模型eval 内部派发不经父级 HITLtask从已运行的eval调用内派发不走父 agentToolNode管理的task工具不触发父级interrupt_on/HITL 逐派发审批。声明式子代理仍遵守其自身 spec 内配置的审批中间件。若父级要求派发前审批在 JavaScript 之外用普通task工具或让eval调用本身被审批门控或设subagentsFalse。README 与中间件 docstring 用几乎相同的警告措辞强调此点middleware.py。心智模型数组进、数组出Hold your work in JS: an array of items in, an array of results out.把每次派发结果合并回对应条目多阶段分析 跑一轮 → 在 JS 里过滤/重组 → 对幸存者再跑一轮。整条工作流可塞进一次eval也可拆成多次想在各阶段间检查结果时但别跨调用重做工作——复用已在作用域内的结果。有界并发 fan-outPromise.all并行派发独立工作但用约 10 的显式批次推进避免一次性拉起数百个子代理桥在每个 REPL 上硬性限制 32 个并发子代理调用。快照给出的完整示例原文完整继承const files [/src/a.ts, /src/b.ts, /src/c.ts]; // found while exploring const batchSize 10; const reviewed []; for (let i 0; i files.length; i batchSize) { const batch files.slice(i, i batchSize); reviewed.push(...(await Promise.all(batch.map(async (file) { const result await task({ description: Read file and review it for SQL injection. Cite line numbers., subagentType: reviewer, responseSchema: { type: object, properties: { vulnerabilities: { type: array, items: { type: object, properties: { type: { type: string }, line: { type: number }, evidence: { type: string }, }, required: [type, line, evidence], }, }, }, required: [vulnerabilities], }, }); return { file, ...result }; })))); }先用自己的工具探索再分发模型在写编排脚本之前应先用常规工具读、列目录、glob、grep理解任务——这些是与eval平级的普通工具调用。快照明确禁止写eval代码只为让子代理读文件/解析文件/列目录——那是确定步骤自己一个直接工具调用就做完为它消耗一整条 agentic loop 是浪费。拆分的自由发挥空间包括每文件/每记录一次派发、自己分块大输入必要时每块写个小文件逐块派发、先做廉价分类只对值得的条目深度派发。多阶段组合在轮次之间用 JS 过滤数组。快照示例原文完整继承const tagged await Promise.all(files.map((file) task({ description: Read file and classify it as handler, util, test, or config., subagentType: reviewer, responseSchema: { type: object, properties: { kind: { type: string }, risky: { type: boolean } }, required: [kind, risky], }, }).then((tag) ({ file, ...tag })) )); const riskyHandlers tagged.filter((it) it.kind handler it.risky); const deepReviews await Promise.all(riskyHandlers.map((it) task({ description: Deep security review of it.file . Cite line numbers., subagentType: reviewer, }).then((review) ({ ...it, review })) ));用最后一个表达式返回结果而不是 console.logeval调用的最后一个表达式或已 resolve 的顶层 await的值就是返回给模型的结果。console.log仅供临时调试其输出被截断而返回值不截断所以真正的结果永远别console.log。大中间集保存在 JS 变量里只返回紧凑摘要或小切片完整输出要持久化就让子代理写文件或在自己的文件工具里eval之外写。复用之前 eval 留在作用域里的东西turn 内 REPL 持久每个顶层变量、函数、类都保留都被提升到全局作用域下一个eval调用可直接按名引用。快照给出“反例警示”// An earlier eval bound this: // const auditResults await Promise.all(files.map(/* ...audit... */)); // A later eval — reference it; do NOT paste the findings back in as a literal: const findings auditResults.flatMap((r) r.findings.map((f) ({ ...f, file: r.file })) ); const verified await Promise.all(findings.map((f) task({ description: Verify this finding: f.evidence, subagentType: verifier, }).then((v) ({ ...f, ...v })) ));判据很直白当你发现自己把上次调用产生的大数组/对象重新敲成字面量说明变量仍在作用域——用它。重打旧结果浪费 token 且偏离实际运行过的东西。“workflow”触发词用户请求中出现 “workflow”或用该词指代流程时先用自有工具探索然后写eval内的 JavaScript用task()并行分发、汇总结果——把重活分发出去而不是一次工具调用一个地硬磨。三个 mode 的提示词差异速查同一段模板按 mode 切换表述形成快照间的三处差异可逐行 diff 同族快照验证维度threadturncallintro 行persistent REPLpersistent REPLfresh sandboxed REPL for each invocation状态行persists across callsand across turnspersists within a single turn,not across turnsdoes not persist; each invocation blank实现落点快照持久化到 checkpointer跨调用保留、turn 结束 evict每次 eval 后registry.reset_repl其中call模式下sync_eval/async_eval在finally中重置 REPLmiddleware.pyturn模式则在after_agent/aafter_agent中registry.evictmiddleware.py。如何复用这套规范配置要点与验证把中间件接进 deepagents agent 的最简形态READMEfrom deepagents import create_deep_agent from langchain_quickjs import CodeInterpreterMiddleware agent create_deep_agent( modelclaude-sonnet-4-6, middleware[CodeInterpreterMiddleware()], ) # 使用 ainvoke —— PTC 桥注册为异步 QuickJS 宿主函数 # 带异步桥的 REPL 上同步 invoke 会抛 ConcurrentEvalError。 result await agent.ainvoke({messages: [{role: user, content: ...}]})与快照直接相关的配置项modethread | turn | call决定提示词中状态段文案与真实持久化行为subagentsTrue默认决定### Dispatching Subagents with task段是否出现——仅在检测到宿主task工具时注入tool_nameeval替换提示词中的工具名与{tool_name}占位符timeout5.0/memory_limit64MB直接渲染进### Interpreter的预算行ptcNone本快照正是ptcNone形态开启后该段首行会替换为tools.*指引并追加### API Reference — tools命名空间段见 mixed 快照。有意调整提示词后用pytest tests/unit_tests/smoke_tests/test_system_prompt.py --update-snapshots重生成快照再重新比对。整套快照测试的价值在于提示词是模型行为契约的一部分任何上游 SDK 造成的措辞漂移都能被逐字捕获而这正是quickjs_system_prompt_no_tools_turn.md这类文件存在的意义——它不是普通文档而是一份可机器校验、可回归的“模型指令契约”。【免费下载链接】deepagentsThe batteries-included agent harness.项目地址: https://gitcode.com/GitHub_Trending/de/deepagents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考