tongyi-deepresearch — 本课题摘录
读了哪几篇: 01-react-loop(主循环)、03-context-and-robustness(上下文管理与容错)。
其余几篇本轮没读。
这一家是本课题最朴素的一份完整实现:一个循环、四种标签、三道闸。适合当我们最小原型的骨架参照。
它对本课题回答了什么
决定二:四种标签,而且分工写清楚了谁写哪个
| 标签 | 谁写 | 含义 |
|---|---|---|
| 思考 | 模型 | 内心推理 |
| 要调工具 | 模型 | 内容是一段结构化参数 |
| 工具结果 | 外层代码 | 工具执行结果,回填给模型 |
| 答案 | 模型 | 最终答案,出现即收尾 |
这套约定写死在系统提示里,开头一句就是"当你准备好给出最终回答时,必须把整个答案包进答案标签"。
(依据: shelf=agent/tongyi-deepresearch#01-react-loop @f72f75d8c3eb 事实=模型与外界只通过
"谁写哪个标签"这一列很重要:四个标签里有一个是外层写的。 模型不该写那一个,但它会写。 所以才有下面这条。
一个必须有的防护:调完模型后,先把"工具结果标签"之后的内容切掉——防模型自己幻觉出观察。
这跟 deepanalyze 的"停在收尾标签"是同一个问题的两种解法:
- deepanalyze:用停止词让它根本没机会写;
- tongyi:让它写,然后切掉。
前者更省(不生成就不花钱),后者更简单(不依赖接口支持停止词)。 两条都要知道:自定义格式方案必须处理"模型自己把结果编出来"。
决定四:一个答案标签 + 三道硬闸
| 退出原因 | 触发 |
|---|---|
| 正常交卷 | 内容里出现答案标签 |
| 超时 | 单题运行超过 150 分钟 |
| 轮数耗尽 | 可用的模型调用次数归零(默认 100) |
| 上下文超长 | token 数超过 11 万,强制收尾 |
这是本课题"什么时候停"最完整的一份清单,而且四条互不重叠: 一条是"做完了",三条是"不能再做了"——分别对应时间、次数、空间。
对我们的最小原型,这四条就够了。
决定一最妙的一条:快撑爆时不截断历史,而是逼它现在就交卷
每轮结束都数一次用量,超过阈值就触发一次"最后通牒":把最后一条消息改写成一句强指令,再逼模型立刻用现有信息给出答案。
它的巧妙处:不是粗暴截断历史,而是改写最后一条消息。这样答案仍基于完整证据,只是不再允许继续搜。 (依据:Agent 库 · Tongyi DeepResearch · 上下文管理与容错 —— 每轮数一次 token,超 110K 就把 messages 最后一条内容改写成「已达上下文上限,停止调工具,现在给出答案」的强指令再调一次模型,而非截断历史;收尾后按有没有 answer 标签区分 termination 是「因 token 上限而生成答案」还是「格式错」)
这条是"压缩"之外的第三条路,前面没见过:
- 压缩:把历史变小,继续跑;
- 截断:丢掉旧的,继续跑;
- 交卷:不再跑了,用现有的答。
对我们的最小原型,第三条最省事而且不丢信息。 mirothinker 的"超窗前主动退一步收尾"是同一条。
阈值的选法有讲究:定在 11 万而不是窗口上限 12.8 万,留了约 1.8 万余量给"最后一次回答"本身要占的输入加输出。
这条要记:任何"快满了就收尾"的阈值,都必须给收尾动作本身留出预算。 不留的话,你会在触发收尾的那一刻超窗。
收尾后还区分了两种结果:"因为到顶而生成的答案"和"格式错"。
又一次印证 hermes 那条:停止原因要能说出名字。
决定三:结果以"用户"角色回填
无论哪个工具,结果都套上工具结果标签再作为一条用户消息追加。
拆解文档明确标注:这是这套实现的取舍。
跟 agenticseek 一样的选择,理由也一样——不依赖接口支持专门的工具结果角色。 本课题第三个决定的这个分岔,现在有三家实例了。
循环骨架(可以直接当我们的起点)
消息 = [系统提示, 用户问题]
剩余次数 = 100
while 剩余次数 > 0:
剩余次数 -= 1
内容 = 调模型(消息)
内容 = 切掉「工具结果标签」之后的部分 ← 防幻觉
消息.追加(助手, 内容)
如果 内容里有「要调工具」:
调用 = 抠出标签之间的部分
结果 = 派发执行(调 用)
消息.追加(用户, 包成工具结果标签的结果)
如果 内容里有「答案」:
记下「正常交卷」,跳出
这十几行就是本课题的最小骨架。 前面读的三十多家,骨架都可以还原成这十几行; 差别全在这十几行外面挂了什么。
它没回答什么
- 并发——它一轮只调一个工具。
- 人机协同——没有审批、没有插话。
- 状态持久化——跑完即结束,不可续。
坑与代价
- 每轮都重新加载分词器来数 token。 拆解文档指出这是可优化点——功能正确但重复加载。
这个细节值得留意:"数 token"这件事在朴素实现里很容易变成每轮的固定开销。 goose 的"用厂商回报的真实用量"能绕开它。
- 一个常量被定义了两次,后者覆盖前者。 小仓库里也会出现的配置混乱。
- 150 分钟、100 轮、11 万 token 都是硬编码的经验值。
- 结果以用户角色回填的代价:模型看到的历史里,"用户"说了很多它自己没说过的话。
判断(无锚): 对短任务无所谓;长任务里这会让模型对"用户到底要什么"的认知变模糊。 如果错,会错在: 如果工具结果都用标签明确包起来(它确实包了),模型能分清哪些是真用户说的,那这个担心不成立。