一个简单任务为什么跑了 50 轮?DeepSeek V4.1 Flash × DSH 源码分析

作者
DeepSeekAgent.io 编辑部
发布
更新

一个原本很简单的任务,在 DeepSeek Harness 中运行了 33–50 轮,产生 32–70 次工具调用。模型很早就找到了答案,文件修改只占 1–3 轮,剩余动作集中在修改前确认和修改后验证。任务完成后,Agent 仍继续调用工具,导致上下文和 Token 消耗增加。

本文从一组 V4.1 Flash 社区运行记录出发,对照 DSH 固定版本源码,再用论文 When Agents Do Not Stop: Uncovering Infinite Agentic Loops in LLM Agents 的分析框架解释:Agent 为什么会持续做合理的小步骤,却始终无法判断“已经够了”。

故障现场:一个简单任务跑了 33–50 轮

社区报告使用同一条提示词运行四个全新 Session,任务是给现有模型配置增加 highmax 两档推理强度,并把 high 设为默认值。运行覆盖 PTC 与 Standard,其中三次没有加载规则文件或 Skill。

运行模式规则文件Assistant 轮数工具调用
1PTC39,随后由用户中止未记录
2PTC5059
3PTC3332
4Standard4370

三次完整记录显示,模型在第 8–12 步已经理解字段含义。修改文件用了 1–3 步,修改前的确认占 14–28 步,修改后的验证与收尾占 4–18 步。

其中一轮记录了 2,946,619 个输入 Token:探查阶段约 160 万,写入阶段约 9 万,写入后的验证约 125 万。文件修改占输入 Token 的 3%,其余消耗来自探查、确认和验证。

这是不是普通的“工具卡住”

排查时先区分四种现象:

现象典型特征处理方向
单个工具卡住同一次命令长时间不返回工具超时、进程终止、沙箱检查
完全重复调用工具名称与参数连续相同重复调用检测、参数去重
长推理无动作长时间没有工具事件或文本输出推理强度、输出上限、模型状态
无界确认工具都成功,参数不断变化,但一直补充检查Turn 步数、进展判断与停止条件

33–50 轮案例属于第四种。每次读取、搜索和验证单独看都有理由,现有的单工具超时不会触发;参数持续变化时,精确重复调用检测也不会触发。系统缺少的是整轮累计视角。

DSH 源码里的循环怎样继续

社区后续分析基于 DSH 提交 c291e7961a515f6d7af9304e7fd1d257929aef26。在该提交的 packages/core/agent-loop/src/agent.ts 中,Turn 控制器从 while (true) 开始。每个 Step 调用模型、解析输出,并在存在 Tool Call 时执行工具。

关键路径可以简化为:

模型生成 Tool Call
  → DSH 执行工具
  → 工具结果写入 next-step
  → 下一步重新构造 messages
  → 模型继续判断和调用工具

当模型没有继续发出 Tool Call,或工具明确结束当前 Turn 时,Step 才返回完成状态。工具结果进入 next-step 后,又为下一次循环提供了新输入。因此,只要模型持续发现“还可以再确认一件事”,循环就能继续。

同一文件中的 buildRequest() 会在每一步调用 session.deriveMessages(),把当前派生出的消息历史作为下一次模型请求的 messages。越靠后的验证步骤携带的历史越长,多一次动作带来的成本也会逐步变大。案例中的修改后验证阶段消耗了约 125 万输入 Token。

Infinite Agentic Loops 研究提供了什么视角

这组 33–50 轮记录展示的是过长的 Agent Loop:三次运行最终结束,一次由用户中止。论文 When Agents Do Not Stop 研究的是更进一步的工程风险——当 Agent 的反馈路径能反复到达模型调用、工具执行、状态增长或 Agent 转交,同时整条路径缺少有效上限,就可能演化成 Infinite Agentic Loops(IAL)。这里借用论文的检查框架,分析 DSH 为什么能运行这么多轮。

它与普通代码死循环的区别在于,循环由多层行为共同形成:

  • Agent 控制器允许继续执行;
  • 模型每一步都能提出新的合理动作;
  • 工具结果更新状态并重新进入上下文;
  • 退出依赖模型决定不再调用工具;
  • 局部超时或局部退出条件没有覆盖整条反馈路径。

当前退出依赖 finish_reason、工具调用和路由结果。固定提交中没有 Turn 级步数、时间或费用上限。

研究者用 IAL-Scan 扫描了 6,549 个 LLM Agent 仓库。工具报告 74 个候选问题,人工确认其中 68 个 IAL,分布在 47 个项目中,精确率为 91.9%。相关风险包括成本耗尽、上下文增长、服务不可用和重复触发外部操作。

用论文的检查项分析这次 DSH 案例

论文检查项DSH 案例中的对应位置
循环控制器turn() 内的 while (true)
高成本操作每一步的模型请求与工具调用
增长状态工具结果、Session 事件与消息历史
反馈边工具结果进入 next-step,触发下一步
模型依赖退出模型停止调用工具,或工具宣告 Turn 完成
整轮上限固定提交中没有面向 Turn 的总步数预算

模型无需重复同一句话,也无需调用完全相同的工具,反馈路径仍可运行很久。搜索文件 A、读取配置 B、检查解析器 C、再回头验证 A,每一步都不同,累计 Step、上下文和 Token 会持续增长。这个案例缺少整轮上限,并出现了循环和成本放大。

用户现在怎么止损

发现 Agent 长时间验证时,可以按下面的顺序处理:

  1. 查看最近 10 个 Step,确认验收目标是否已经完成。
  2. 直接 Steer:停止新增检查,只汇报“已完成、尚未完成、下一步”三项。
  3. 如果继续调用工具,取消当前 Turn,保留 Session 与 Trajectory。
  4. 从已经完成修改的检查点启动新 Session,只运行明确的验收命令。
  5. 对比工具调用数和输入 Token;后段输入快速增长时,优先减少 Step,而非只缩短单次输出。

切换 Standard、PTC 或增删 Skill 不能替代停止条件。社区记录在 PTC 和 Standard 中都出现了同类形状。模式切换仍可作为对照实验,但先给当前 Turn 建立边界。

如何从一开始节省 Token

这类任务的大量消耗发生在答案已经出现之后。可以通过明确完成条件,并缩短每一步重新携带的历史来减少 Token 消耗。

1. 把停止条件写进任务

不要只写“帮我改好并检查一下”。同时给出允许修改的范围、唯一验收命令和验证通过后的动作:

任务:完成下面这一项修改。
范围:只检查并修改指定目录,不做额外重构。
完成条件:目标行为出现,且指定验证命令通过。
验证:只运行列出的命令;通过后立即结束 Turn 并汇报结果。
如果连续 3 个 Step 没有产生新结论、文件变化或测试变化,停止并说明阻塞点。

提示词里的停止条件能够减少模型继续搜索的理由,但它仍是模型依赖的约束。长任务还需要 Harness 层面的确定性预算。

2. 缩小读取和验证范围

明确目录、文件类型和验收命令,避免从仓库根目录反复搜索。修改一个配置时,只验证配置解析和相关测试;完整构建、全仓库检查或第二轮代码审阅应由任务风险决定,不要自动叠加。

3. 把实现和扩展检查拆开

第一轮完成修改和必要验收后结束 Turn。确实需要全面回归时,再开启一个新 Session,并只提供变更摘要与验证目标。新 Session 不必在每一步重新携带前一轮的全部探索历史。

4. 盯住“修改完成后的 Step”

记录第几步产生了最终文件变化。如果之后连续 5–8 个 Step 没有新的文件变化、测试结论或交付物,可以先 Steer 要求收尾;仍未结束再取消 Turn。这个范围是排障起点,应按任务复杂度调整,不是通用硬阈值。

5. 在 Harness 层设置硬预算

提示词可以改善行为,步数、时间、Token 和费用上限可以提供确定性边界。达到软上限时先让 Agent 汇报剩余工作;超过宽限后停止当前 Turn,并保留 Session 与 Trajectory,避免为已经完成的任务继续支付上下文成本。

Harness 应该增加哪些边界

1. Turn 级步数预算

在整轮 Step 达到阈值时,先要求 Agent 汇报剩余缺口;超过宽限步数仍未结束,再停止当前 Turn。阈值要覆盖完整反馈路径,不能只限制单个工具。

2. 时间与费用双预算

Step 数相同,历史长度不同,成本也会不同。生产环境应同时记录每轮耗时、累计输入 Token、累计输出 Token 和实际费用,并设置可观察的软上限。

3. 基于进展的检测

精确重复参数只能发现最简单的循环。更有效的信号包括:验收项是否变化、修改文件集合是否变化、测试结果是否变化,以及连续多少步没有新增可交付结果。

4. 压缩后的动作台账

上下文压缩后保留“已经执行过什么、结果是什么”的短台账,避免 Agent 丢失自己的操作历史,再次读取和验证同一组事实。

5. 明确且可审计的停止原因

Turn 因步数、时间、费用或人工取消而结束时,应把原因记录进 Trajectory。后续恢复才能区分正常完成、预算耗尽和外部中止。

社区 Turn Budget Guard 做了什么

讨论参与者随后发布了 @argszero/cordis-plugin-turn-budget-guard。默认配置在 20 个 Step 后注入一次收尾请求,再给 8 个宽限 Step;仍未收束时取消当前 Turn,并保留用户在运行期间发送的消息。

- insert:
    - id: turn-budget-guard
      name: '@argszero/cordis-plugin-turn-budget-guard'
      config:
        maxSteps: 20
        gracefulSteps: 8

这个插件展示了一种可挂载的止损方案。它由社区维护,安装前应检查源码,并先在独立 Profile 中验证阈值是否适合自己的长任务。未来如果 DSH Core 提供原生 Step Budget,应以原生机制统一记录停止原因和恢复行为。

一套可复现的排查记录

为了判断自己遇到的是模型偶发绕路,还是 Harness 反馈路径缺少边界,至少记录:

维度要记录的内容
环境DSH 版本、模型 ID、模式、推理强度、Endpoint
任务初始提示词、工作区状态、验收标准
动作Step 数、工具名称、关键参数与结果
进展第几步已经拿到答案、第几步完成修改
成本每阶段输入/输出 Token、耗时与费用
终止正常完成、用户取消、预算停止或错误

同一任务至少运行三次,并保留一次完整 Trajectory。关键指标包括:答案出现之后又执行了多少动作,以及这些动作是否改变最终交付物。单独记录“模型想了多久”无法描述这类故障。

相关阅读

常见问题

这是 DeepSeek V4.1 Flash 的模型 Bug 吗?

四次社区运行都使用 V4.1 Flash,说明它能触发这种行为;DSH 源码同时提供了循环持续扩张的机制。定位时应把它视为模型行为与 Harness 停止边界的组合问题,并用同题跨模型、跨版本测试继续拆分变量。

换成 PTC 就能解决吗?

不能依赖模式切换解决。案例在 PTC 与 Standard 中都出现了 33–50 轮运行。PTC 可以压缩适合批量执行的工具往返,但不会自动提供整轮 Step 上限。

为什么后面的几步特别贵?

DSH 会为下一步派生当前消息历史。Session 越长,后续请求携带的上下文通常越大,因此修改完成后的重复验证会反复付费处理已经累积的历史。