DeepAgents(二):DeepAgents 的 harness 做了什么

2026-09-30 01:360 条评论18 次阅读

DeepAgents 的 harness 做了什么

DeepAgents 的 harness 干了两件事:装配和分发。装配是把工具、上下文管理、行为规范一层层挂到 Agent 循环上,分发是按模型名决定给哪一套行为规范。你调一次 create_deep_agent,这两件事同时发生。

不传 system_prompt 时,模型收到 8 个工具定义共 2729 token,同时收到一条长度 0 字符的 system 消息。这不是漏传,是 0.7.0 主动删的,弃用信息原文写着「Deep Agents no longer provides an authored base prompt」。

这篇的中心主题就一句:harness 把「怎么装配」全做完了,「Agent 怎么说话」留给你自己写。 那 0 个字符就是这条分工留下的口子。

装配不是写死在函数里的,靠的是一层层中间件,第四节量给你看。

本机跑了 12 次三组对照,结论先给:动作不受影响,措辞从零开始长。同一句请求,空提示词下回你 59 字的道歉加反问,补 80 字提示词后回你 24 字的结果,工具调用序列一次没变。

读完能得到 6 个答案:空是怎么来的、分界线在哪、装配那头给了多少、靠什么挂上去的、空着跑会怎样、要补就补多少。全程只需一台装好 deepagents 的机器,读完约 5 分钟。

一、先把结论摆出来

问题 实测结论
提示词真的空吗 是。不传就是 0 字符,14 个内置 profile 一个都没补 BASE 位
是官方漏了吗 不是漏,是改成按模型键分发。模型名不在那 14 个里,就是 0 字符
那装配那头给了多少 8 个工具定义 2729 token,是旧提示词 569 token 的 4.8 倍
这些装配靠什么挂上去 中间件。默认挂 11 个实例,工具与上下文管理都由它们提供
空着跑会崩吗 不会。12 次运行全部走完 4 轮循环,动作三组一致
补多少字够 80 字。与官方那 2258 字,在「别客套」这一项上效果相同

deepagents 的默认装配

工具那一头
8 个 · 2729 token
调度次序写在描述里

说话那一头
按模型键分发
不在名单就是 0 字符

后面 5 节逐条回答上面这 6 个问题,最后收口。

二、那 0 个字符是怎么来的

提示词按三段拼:USER、BASE、SUFFIX,段与段之间空一行。

USER 是你传的 system_prompt。BASE 取自当前生效的 harness profile 的 base_system_prompt,SUFFIX 取自同一个 profile 的 system_prompt_suffix。

官方文档把结论写得很直白。不传 system_prompt 且 profile 这两项也没写时,「the model receives an empty authored system prompt」。

我注册了一个自己的 profile 验证这段拼接。三种写法各跑一遍,模型收到的是 USER、USER + BASE、USER + SUFFIX,顺序与文档一致,两次运行结果相同。

import deepagents as da

prof = da.HarnessProfile(
    base_system_prompt="基准位",
    system_prompt_suffix="补充位")

da.register_harness_profile(
    "openai:my-model", prof)

顺带踩到一个点:同一个键重复注册是叠加合并,不是覆盖。我注册第二次时,第一次写的内容还留在里面,两段都被拼进了提示词。

三段里 USER 是你的,BASE 与 SUFFIX 归框架。所以问题收窄成一句话:框架往那两位里写了什么。

三、官方没放弃写提示词,它改成了按模型分发

接着上一节那两个位置查。包里注册了 14 个 harness profile,key 形如 anthropic:claude-sonnet-4-6。逐个读出来的结果是:14 个都写了 system_prompt_suffix,写了 base_system_prompt 的是 0 个。

模型 suffix 长度 覆盖几个 key
nemotron-3-ultra 2962 字符 8
claude-opus-4-7 2148 字符 1
claude-haiku-4-5 与 sonnet-4-6 1455 字符 2
gpt-5.x-codex 1166 字符 3

这些 suffix 写得相当具体,内容是行为规范,不是模型介绍。nemotron 那段拆成 5 个标签,其中 <loop_control> 交代的是:工具调用失败时先读错误、改调用再重试,「never re-issue the same failing call unchanged」。codex 那段更直接:「Do not communicate an upfront plan or status preamble before acting. Just act.」

在

不在

你的模型

模型键在那 14 个里吗

拿到一段 suffix
1166 到 2962 字符

base 与 suffix 都空
system 消息 0 字符

所以真正的分界线在模型键。模型名不在那 14 个里,profile 匹配不上,BASE 与 SUFFIX 同时是空,落到你头上的就是 0 字符。本机跑的正是这个情况,那条 system 消息的内容就是空字符串。

官方以前不是这么写的。包内留着一个 _LEGACY_BASE_AGENT_PROMPT 常量,2258 字符、约 569 token,当年是一篇通用的基础提示词。

它有 5 个小节:Core Behavior、Professional Objectivity、Doing Tasks、Clarifying Requests、Progress Updates。内容全是行为规范,一句工具说明都没有。摘几句原话:

小节 原话
Core Behavior NEVER add unnecessary preamble
Core Behavior If the request is underspecified, ask only the minimum followup needed
Doing Tasks Verify — check your work against what was asked, not against your own output
Doing Tasks If something fails repeatedly, stop and analyze why

去读 BASE_AGENT_PROMPT 会触发弃用提示,原文写着它在 0.7.0 弃用、0.9.0 移除。所以这个常量只剩兼容访问的用处,不参与装配。

从「一篇通用」改成「按模型键分发」,就是那 0 个字符的直接来路。

四、看另一头:装配给得有多足

提示词那一头是 0,工具那一头是完全另一种待遇。

最小装配给 8 个工具:ls、read_file、write_file、edit_file、delete、glob、grep、task。这 8 个工具的定义加起来 2729 token,拆开看,工具描述 1349 token、参数 schema 1380 token,两半几乎平分。

工具 合计 描述 参数 schema
grep 597 144 461
task 499 359 146
read_file 456 309 154
glob 414 207 216
edit_file 293 94 209
write_file 191 85 116
delete 161 95 75
ls 118 56 70

这 8 个工具不是手写进 create_deep_agent 的。harness 把每件事拆成一个中间件,装配的时候一层层挂上去。本机量了一遍默认装配:主 Agent 挂 5 个,子 Agent 挂 5 个,再加一个把 task 桥过去的 SubAgentMiddleware,一共 11 个实例。

工具的来路只有 2 类:

工具 谁给的
ls、read_file、write_file、edit_file、delete、glob、grep FilesystemMiddleware
task SubAgentMiddleware

包里这类中间件一共 27 个,默认只挂 5 类。要加能力也是同一条路:传 skills= 会挂上 SkillsMiddleware,传 memory= 会挂上 MemoryMiddleware。干活的东西都挂在中间件上,提示词那一头就能是空的。

把三样东西放一起量:工具定义 2729 token,官方删掉的那篇提示词 569 token,当前默认提示词 0 token。花在「工具怎么用」上的字,是它当年花在「行为规范」上的 4.8 倍。

那 8 段描述也不只是参数说明,里面写着调度次序。摘几句原话:

工具 描述里的原话
ls You should almost ALWAYS use this tool before using the read_file or edit_file tools
read_file Always read a file before editing it
edit_file You must read the file before editing; this tool errors otherwise
write_file Prefer to edit existing files over creating new ones when possible

拿旧提示词那 5 条规范逐条去工具描述里找,只接走 1 条。

旧提示词里的规范 工具描述里找得到吗
先读文件再动手 找得到
不要必要之外的前言 找不到
做完要核对,不看自己的输出 找不到
能力受限时别开头就长篇解释 找不到
长任务给进度更新 找不到

这条对照把主题的另一半补上了:工具描述能接走「怎么调工具」,接不走「怎么说话、怎么收尾」。 框架在装配那一头把工具调用的次序写满了,说话方式一个字没留。

五、空着跑会怎样:三组 12 次对照

上一节说清了工具有描述、说话没规范,那就把缺的那部分补回来测。

三组配置是:不传提示词、补 4 条短提示词(80 字符)、贴回官方那篇 2258 字符的旧提示词。2 个请求各打一个点。第一个给明确路径但文件不存在,看失败之后怎么说话;第二个不给路径,只问目录里有什么,看它先调哪个工具。

第一个请求,三组都是 4 轮、都只调 read_file。措辞分成三层:

组 提示词 回答
不传 0 字符 抱歉,我没有找到这个文件。请问您是否想检查其他文件,或者需要我为您创建这个文件呢?
补 4 条 80 字符 文件未找到。
官方旧提示词 2258 字符 文件未找到。请确认文件路径是否正确。

第二个请求,三组都是 4 轮、都先调 ls,回答分别是 20 字、14 字、16 字,差距远小于第一个请求。

一句请求 · 跑 12 次

动作
三组一致
4 轮 · read_file

措辞
三组分岔
59 / 24 / 36 字

12 次运行里,工具调用序列三组完全一致,没有一次重复调用同一个失败动作。动作那一栏一次都没分岔,分岔全在措辞那一栏。

有一条得说清楚:温度 0 让重复运行相当稳,但也出现过同一个请求跑出 22 字与 20 字这样的小差异。单次结果不能当结论,这是我留着 12 次的原因。

六、要补就补多少、补在哪

动作层面不用补,措辞层面补 80 字就够。

补 4 条那组与官方那篇 2258 字,在「别客套、直接给结果」这一项上效果相同,都从 59 字收到 24 字与 36 字。补提示词不必补全篇。

补的位置有两种。传 system_prompt 最直接,它落在 USER 位、排在整段最前。要按模型区分,就注册一个自己的 harness profile,写进 base_system_prompt 或 system_prompt_suffix。

动手之前先自查一下。上面那个假模型的做法可以搬走:继承 BaseChatModel、在 _generate 里量一次 system 消息的长度,跑一遍就知道自己拿到的是几位数。

代价也说一下。补上的规范靠模型自己遵守,没有强制性,它不像工具描述那样会在出错时硬拦一次。

第一个请求里,三组都只调一次 read_file 就把结论交回来了,没有一组回头核对路径对不对。旧提示词里那条「做完要核对」,工具描述没接走,我补的 4 条也没写进去,它就真的没发生。

七、结论

harness 替你决定的是装配,提示词它留了口子。

装配那一头做得相当到位:8 个工具、2729 token 的定义、默认 backend、11 个中间件实例,全都装好了,你一句话不写就能跑通完整循环。

提示词那一头按模型键分发。你用它认得的那 14 个模型,白拿一段上千字符的规范;不在名单里,就是 0 字符。

那 0 字符不会让你的 Agent 崩。它会让你的 Agent 用模型自己的习气说话,而小模型的默认习气,是道歉加反问。

所以要写,就写那 80 字。