Qwen Code ACP 重复工具调用失败保护(Repeated Tool-Call Protection)设计与实现指南
发布时间:2026/9/12 18:31:46 锦皓数字建站
设计与实现指南`)
Qwen Code ACP 重复工具调用失败保护Repeated Tool-Call Protection设计与实现指南【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读当 AI 编码 Agent 在 ACPAgent Client Protocol交互式前台提示循环中反复调用同一个工具、并因同一结构化原因持续执行失败时模型会浪费大量轮次、工具延迟与 Token最终才被总调用上限兜底拦住。Qwen Code 为此实现了ACP Repeated Tool-Call Protection一个保守的、每提示per-prompt粒度的内存态守卫通过双轴执行结果契约 阈值状态机 分阶段灰度的方式在工具执行边界上识别真正的重复失败并在给出一次纠正性提醒后仍重复失败时主动停止自动循环。本文基于设计文档 acp-repeated-tool-call-protection.md结合 repeated-tool-failure-guard.ts、Session.ts 等仓库源码完整讲解其设计动机、结果契约、状态机、四种运行模式、遥测隐私边界与分阶段灰度流程。背景三个已有断路器为何不够在引入本守卫之前ACP 路径上已有三个防护但都不完整重复的 provider call ID 去重——只能拦截传输层/服务商层面的重放重复非法工具参数——仅对executionStatus not_started的 schema 预执行失败三次后停止每轮提示的总工具调用上限——是绝对的兜底但触发时已经消耗了大量模型轮次、工具延迟与 Token。它们共同抓不住最常见的语义循环模型持续为同一个确实被进入执行的工具签发全新 call ID且每次都因同一结构化原因失败。总调用上限最终会拦住失控但在此之前成本已经产生。为什么不能只看终态error设计文档明确警告终态error单独不足以作为信号。它当前既包含从未执行的调用校验失败、权限拒绝也包含执行成功之后发生的失败后处理失败。把所有这些都当作重复工具失败会产生大量误报尤其会把用户取消变成产品稳定性问题——文档提到历史上曾有 468 条权限取消被错误记录为 error 的实例见 acp-repeated-tool-call-protection.md。因此本守卫的判定必须建立在两个独立轴之上终态status与独立的执行状态executionStatus。前置契约PR #8176 与 PR #8180 的双轴结果契约本守卫依赖两个前置遥测/契约变更的整合结果不得从旧的success字段、错误字符串、UI 帧或 span 中重建执行状态PR #8176让终态status成为权威信号并规范化取消cancelled与错误error字段PR #8180新增独立的executionStatus轴并修正 ACP 权限取消的分类问题。整合后的判定表是守卫一切决策的根基终态statusexecutionStatus对本守卫的含义successsuccess重置同一已解析工具的候选successnot_started重置同一工具候选协议级合成结果errornot_started重置校验、权限拒绝、hook 拦截或查找失败errorerror仅当带有可信的冻结executionErrorType时才可计入errorsuccess重置执行成功但后续处理失败cancelled任意重置任意cancelled重置任意缺失或unknown重置并将该提示剩余部分降级为至多warnPR #8180 定义的两个非法组合success/error、success/cancelled被视为契约违反重置守卫、发出诊断、不执行强制。取消仲裁以status为准如果执行错误与用户/父级取消竞态且终态为cancelled该调用不计入失败。文档还强调一个关键细节终态errorType不是重复失败键。后处理 hook、图像桥接等收尾步骤可能在其后替换errorType即使executionStatus仍是error。因此 ACP 内部批次回执batch receipt在执行落定时就复制冻结的executionErrorType。缺失或ToolErrorType.UNKNOWN的执行分类不具备候选资格——这一点在 repeated-tool-failure-guard.ts 中通过executionErrorType ToolErrorType.UNKNOWN的显式排除得到印证。候选资格与失败键一个合格的失败eligible failure必须同时满足终态status errorexecutionStatus error非空的、且不等于ToolErrorType.UNKNOWN的结构化executionErrorType来自工具注册表的已解析内置native或 MCP 工具身份由完全落定的交互式 ACP 前台批次产生的最终结果。失败键定义为(policyToolName, executionErrorType)其中policyToolName是 ACP 做权限检查时使用的已解析tool.name不是模型提供的显示名MCP 注册名本身已包含 server 限定的身份。executionErrorType在执行落定时冻结独立于终态调用错误。参数被刻意排除在键之外这样既能捕获针对同一执行边界的参数抖动parameter thrashing又依靠阈值与两个批次要求提供误报缓冲。原始参数、输出、路径与错误文本一律不得存入守卫或上报到中心遥测。未知工具、空身份、未分类错误、以及结果字段不完整的第三方事件都不具备候选资格。状态机idle → tracking → warned → latched每个 Session 为每个交互式前台 ACP 提示持有一个守卫实例。其状态类型定义如下与文档一致且已在源码 repeated-tool-failure-guard.ts 中落地为RepeatedToolFailureGuardPhase与RepeatedToolFailureGuardStatetype RepeatedToolFailureState | { phase: idle } | { phase: tracking; key: FailureKey; failureCount: number; batchCount: number; } | { phase: warned; key: FailureKey; failureCount: number; batchCount: number; } | { phase: latched; key: FailureKey; failureCount: number; batchCount: number; };阈值是代码常量不是用户设置8 次合格失败且必须横跨至少 2 个完整模型批次源码中为REPEATED_TOOL_FAILURE_THRESHOLD 8与REPEATED_TOOL_FAILURE_BATCH_THRESHOLD 2见 repeated-tool-failure-guard.ts。每个批次完全落定后的归约规则忽略已由 call-ID 去重器处理的重复 provider 事件——它们既不推进也不重置语义状态先排干drain已接受的 mid-turn 输入若观察到新的外部输入或排队中的完整提示queued full prompt重置为idle并保持既有输入与 FIFO 行为权威若排干不可靠重置并在该提示剩余部分禁用强制批次不完整、违反结果契约、或包含取消/未知/未启动/后执行失败时重置为idle收集批次内的合格失败键若存在多于一个唯一键重置为idle同一已解析工具的成功观察只重置该工具候选其他工具的成功既不推进也不重置成功没有执行错误类型因此会在剩余唯一失败键被选中前清除该工具的所有失败分类若键与当前追踪键不同从本批次开始新的一次连击streak否则累加本批次合格失败数并递增批次计数只包含无关成功工具的完整批次不改变当前连击当failureCount 8且batchCount 2时转入warned并请求模式对应的提醒动作若后续完整批次仍含同一合格键且无重置条件先完整执行并记录整批再转入latched。中途来自其他工具的成功不会掩盖重复失败但候选工具自身的成功会重置它。随后是否再发一次模型请求由配置的模式决定。从源码 reduceRepeatedToolFailureGuard 可以看出该流程的精确实现providerDuplicate先被过滤terminalStatus cancelled、executionStatus缺失/unknown、not_started、post_execution_failure、契约违反分别收集到resetReasons按INELIGIBLE_RESET_REASON_PRECEDENCE优先级contract_violationcancelledunknownnot_startedpost_execution_failure取最高优先级重置mixed多个唯一键直接重置warned阶段再遇匹配批次则进入latched并产出stop或would_stop。状态转换与控制动作分离模式阈值达到时下一个匹配批次时shadow记录would_warn不注入任何内容记录would_stop、锁定latch继续warn注入一次提醒并记录warned记录would_stop、锁定继续enforce注入一次提醒并记录warned记录stopped、锁定并在再次发送前停止一旦锁定latched该提示内守卫不再产生任何决策——避免 shadow/warn 模式下重复提醒与遥测放大。一次提醒保证是按候选连击per candidate streak而非按顶层提示候选被重置后追踪到不同失败键可以产生新的提醒。首版刻意只保留一个活跃候选而非多个独立连击的映射同工具成功被清除后若仍有多个失败键则重置因为哪个并发失败应拥有提醒或停止存在歧义。这保持了保守性同时仍能捕获主导的交替形态——一个工具反复失败而 read/edit/inspection 等工具在尝试之间成功。提醒与停止文案固定系统上下文提醒reminderSystem: the same tool execution has failed repeatedly for the same classified reason. Do not repeat the same approach. Inspect the returned result, change the approach or required preconditions, or explain the blocker.停止消息stop messageSystem: Automatic continuation stopped because the same tool execution failure continued after a corrective reminder. New user input is required to continue.两段文案在源码中分别定义为REPEATED_TOOL_FAILURE_REMINDER与REPEATED_TOOL_FAILURE_STOP_MESSAGE见 repeated-tool-failure-guard.ts。它们不含任何原始参数或原始错误文本是固定的系统上下文而非伪造的用户输入。批次与并发规则以完整批次为归约单元归约的单元是一个已完成的模型工具批次而不是单个流式事件——因为 Agent 调用可能并发执行终态帧到达的顺序可能与模型 function call 的顺序不同。runToolCalls只在所有被接纳的调用全部落定后返回一个窄回执narrow batch receipt。每个回执条目包含callId、policyToolName、终态status、冻结的executionStatus、冻结的executionErrorType、以及是否为 provider 重复——不包含参数、结果或原始错误。在 Session.ts 中回执由finalizeRunToolResult在排序后的记录上构造其complete字段要求记录数与去重后的 function call 数一致且序号集合完整——这正对应文档批次完整性必须可证明的要求。#buildNextMessageAfterToolRun先排干 mid-turn 输入再把回执与排干得到的parts、hasQueuedPrompt、reliable状态一起交给 reducer之后才构造下一条模型消息从而保留既有外部输入优先语义。结果按模型原始调用顺序保留尽管归约本身与顺序无关。守卫从不在批次中途停止在某个调用达到阈值后取消其兄弟调用把被跳过的兄弟调用变成执行失败把迟到的重复终态帧当作新观察。若 Session 无法证明批次完整就重置且不强制。PR #8180 的冻结执行状态是必要条件但单独不足以证明批次完成——reducer 只在落定的runToolCalls边界被调用。与既有保护的协作次序四层保护按从最具体到最宽泛的顺序保持Provider call-ID 去重——处理传输/服务商重放既有非法参数守卫——处理重复的预执行 schema 失败executionStatus not_started本守卫——处理重复的、带类型的执行失败每轮总工具调用上限——仍是绝对兜底。只有第一个停止该轮的守卫记录终态循环原因。本设计新增了独立的LoopType.REPEATED_TOOL_EXECUTION_FAILURE使运维可以将其与非法参数、重复 ID、总上限区分开。当本守卫停止时ACP 必须在聊天历史中保留全部已落定的 function 响应向历史追加固定停止上下文并通过既有可重放的 ACP agent-message 更新路径发送一次为该提示挂起 Todo Stop Guard及其他自动续跑机制保持排队中的外部输入原封不动结束当前 ACP 请求且不再开启新的模型流。源码中stop分支的执行正符合上述要求this.todoStopGuard.suspend()挂起自动续跑#preserveUnsentMessageHistory保留历史recordDaemonLoopDetected以recordToQwenLogger: false记录LoopType.REPEATED_TOOL_EXECUTION_FAILURE并通过messageEmitter.emitAgentMessage发送停止消息后返回stoppedByRepeatedToolFailure: true见 Session.ts。守卫保持锁定直到当前提示结束之后的顶层提示包括显式重试或继续请求在既有 Session 生命周期下创建全新守卫。作用域与四种运行模式首版仅应用于被选中的 live Session 所有者处理的交互式前台 ACP 提示。两个 channel 桥接实现会显式标记自己的提示Session 将这些被标记的提示强制为off——即使进程配置为 enforce。它不是进程全局的工作区所有权未知时不得回退到 legacy 或 primary 运行时。标记是客户端断言的 ACP 元数据因此另一客户端也可将自己的提示退出此保守保护它只是路由提示不是信任信号或授权边界。四种模式off无 reducer、无遥测shadow计算决策但不注入、不停止warn注入提醒但永不停止enforce按状态机注入并停止。默认是shadow。部署控制面不得将enforce分配给未知所有权、不受信任的生产者或混合部署版本运行时不推断这些部署属性。缺少executionStatus或出现不支持的结果组合时重置连击并将该提示剩余部分降级为至多warn。Cron、通知与后台路由在首版保持off。环境变量配置模式是运维控制的部署策略不是面向用户的设置。环境变量为QWEN_CODE_ACP_REPEATED_TOOL_FAILURE_GUARD源码常量见 shared-env-keys.ts取值off、shadow、warn、enforce在 Session 启动时解析缺失或非法值解析为shadow非空非法值会记录一条运维诊断日志见 Session.ts 中parseRepeatedToolFailureGuardMode与默认回退逻辑解析函数本身在 repeated-tool-failure-guard.ts。# 在启动 Session 前设置示例 export QWEN_CODE_ACP_REPEATED_TOOL_FAILURE_GUARDshadow # off | shadow | warn | enforce项目.env、项目.qwen/.env、工作区settings.env等来源不允许设置此策略导出的进程值或用户级环境文件仍有效。部署控制面只应在指定的、版本固定的队列version-pinned cohort上将模式提升到shadow以上。该特性不引入第二个 rollout 或所有者分配服务。输入边界依赖守卫依赖 ACP host 实现craft/drainMidTurnQueue且带布尔hasQueuedPrompt。较旧或第三方 host 若拒绝、超时或返回不完整的 drain 响应会产生一次unreliable_input重置诊断、在该提示禁用强制并在输入边界可靠前无法积累候选。这种fail-open行为兼容性安全但必须与受支持 host 的 shadow 基线分开统计。源码中#buildNextMessageAfterToolRun仅在repeatedToolFailureMode ! off时设置watchQueuedPrompt且把drained.reliable直接作为 reducer 的inputReliable输入见 Session.ts。遥测与隐私边界每次 reducer 转换发出低基数计数器与一条数据最小化的结构化诊断日志部署环境与服务版本来自既有 OpenTelemetry resource而非新守卫标签routeinteractive ACP foregroundmode转换前后的 phasedecisionreset、tracked、would_warn、warned、would_stop、stopped当批次只有一个合格键时记录候选终态状态、执行状态与工具类型冻结的执行错误类型只保留在结构化诊断日志中不作为指标标签否则仅记录低基数重置原因如success、cancelled、not_started、unknown、mixed、incomplete、external_input、contract_violation源码中这些枚举见 repeated-tool-failure-guard.ts失败计数分桶0、1-2、3-4、5-7、8批次计数分桶0、1、2、3同一原始 ACPprompt_id工具调用遥测已发出的仅出现在诊断日志中以便授权的 rollout 分析将守卫转换与落定的工具批次关联而不引入第二套标识仅诊断日志中的提示内候选序号candidate ordinalreducer 在同一个私有键活跃时复用该序号键变化时分配新序号。prompt ID 与候选序号永不作指标标签。序号无法跨提示关联工具也不会泄露工具身份。idle到idle的观察不发出任何内容。终态repeated_tool_execution_failure循环事件使用同一 OpenTelemetry prompt ID并在 Session 调用点显式绕过 QwenLogger/RUM对应源码recordToQwenLogger: false。共享 Core logger 不根据循环类型推断目的地其他循环类型保持既有遥测行为。不得在守卫专用字段中发出工具参数、结果、原始错误消息、堆栈、路径、MCP server 名、用户 ID 或私有失败键。取消被排除在所有失败率分子之外。主执行 SLI 使用 PR #8180 的契约execution_status error ──────────────────────────────────────── execution_status in {success, error}旧success字段与 PR 前部署数据不得用于验证本守卫。灰度与 rolloutshadow → warn → 限量 enforcePhase 0整合结果契约合并 PR #8176在其上 rebase PR #8180同时保留 #8176 的终态status维度与 #8180 的独立执行计数器在 ACP 批次回执中保留 #8180 的内部executionErrorType不要事后从终态errorType推断优先将 #8180 拆分为可审查的小改动执行契约、ACP producer 修复、遥测/spans、MCP 取消/超时仲裁先部署整合后的契约再收集新基线。那 468 条历史权限取消记录是旧 producer bug 的证据不是合格的守卫失败。七天基线只针对包含整合契约的部署版本重新计算内部与公有云分别进行不得混用新旧版本。Phase 1shadow影子模式实现纯 reducer 并接入落定的 ACP 批次边界以shadow模式运行至少 7 个完整日两个环境各自。shadow 只推进虚拟 warned 状态而不注入提醒因此would_warn与would_stop只估算体量无法证明模型看到提醒后的行为。基线开始前每个部署必须配置稳定的 OpenTelemetry Resource 属性如deployment.environment以区分内部与公有云。SDK 总是提供service.version但刻意不发明部署环境。缺少该 Resource 维度Phase 1 无法得出按环境结论rollout 不得推进。shadow 模式不改变模型续跑或注入消息但会向既有 mid-turn drain 请求添加todoStopGuardWatchQueuedPrompt: true使 reducer 能证明没有完整提示在排队。不具备该响应契约的 host 通过unreliable_input计数并排除在受支持 host 的 shadow 结论之外。必需不变量必须保持零违反零条取消调用被计为合格失败零条not_started、未知执行分类或后执行失败被计入零条基于不完整批次的决策零次在不可靠输入排干后的强制遥测中零个原始参数/结果/路径/错误文本字段每个would_stop前都有同一候选序号与提示的would_warn既有总工具调用与重复 ID 保护保持不变。人工审查隐私安全的 would-stop 会话样本授权本地 trace 访问分类未修改的续跑是否取得有效进展——这用于拒绝明显不安全的阈值而不是批准 enforce。Phase 2warn提醒模式对内部交互式 ACP 前台提示启用warn保持 7 天确认提醒注入不增加取消、重连、延迟、Token 或轮次回归。公有云保持 shadow。只有 warn 模式提示能展示模型在真实纠正提醒后是否仍重复失败该队列为 enforce 提供语义证据。Phase 3限量 enforcement对最多 5%的稳定、版本固定的内部交互式 ACP 前台所有者启用强制。按所有者确定性分配同一提示运行中不能切换处理方式其余 95% 保持warn——因此处理组与对照组收到相同的纠正提醒唯一差别是提醒后匹配批次是否停止。每波保持 7 天。晋升条件所有正确性不变量保持零违反warn 模式审查未发现匹配的提醒后失败中有明显有效进展强制的停止具有预期键、提醒、完整批次回执与保留的历史完成率不劣于对照组超过 1 个百分点十分钟内断线或重试不劣于对照组超过 0.5 个百分点p95 延迟、平均 Token、平均轮次各不劣于对照组的 1.10 倍停止循环率与节省调用估算与 warn 模式观察一致。使用所有者级分块分析与置信区间——同一所有者的调用不是独立样本。任何契约违反或取消误分类都会使环境立即回到shadow。5% 以上不得加码除非通过投影全流量下的容量测试确认了更高分配级别下的处理饱和若无法排除干扰则保留永久对照组并将强制封顶在 5%。公有云在内部闸门通过后独立重复 shadow、warn 与限量 enforce绝不继承内部的通过。实现形态与交付顺序代码改动刻意保持小在 ACP Session 代码旁新增纯repeated-tool-failure-guard.tsreducerSession 将落定的批次记录翻译成 reducer 的窄回执、排干外部输入并应用返回的动作通过既有低基数日志路径新增循环类型与遥测事件字段在两个桥接边界标记 channel 提示并使 rollout 变量远离项目控制的环境来源不改变任何工具实现也不新增执行调度器。因为改动触及 Core 遥测类型与 ACP Session 编排需要在仓库的 core-infrastructure gate 下由维护者负责。建议交付顺序结果契约整合与修正后的七天基线reducer、单元测试与 shadow 遥测提醒注入与 warn 模式 E2E 覆盖停止接线、生命周期重置与休眠的 enforce 模式受控 rollout——推进模式无需代码变更。验证体系纯 reducer 单元测试repeated-tool-failure-guard.test.ts 覆盖完整终态/执行判定表单批次 8 次失败不触发警告跨两个批次 8 次失败触发警告下一个匹配批次落定后才停止同工具成功、取消、not_started、unknown、后处理失败、混合失败键、不完整批次、不可靠排干、排队提示与新输入均重置其他工具的成功在匹配失败批次内部与间隔成功批次中均被忽略重复 provider 事件被忽略而非计数或重置新键开启新连击警告与停止文案固定且不含工具数据不支持的结果组合永不强制。ACP Session 与 channel 测试内部回执保留冻结的执行失败类型而非之后的终态错误类型off保持 legacy drain 请求形态shadow 观察而不改变轮次warn 对候选恰好注入一次提醒enforce 在历史中保留已落定的提醒后结果且不开启额外模型流排队输入与取消优先于强制不支持 host 通过unreliable_input降级两个 channel 桥接路径都标记提示且 Session 强制其为 off。遥测测试低基数属性终态循环事件显式排除 QwenLogger/RUM同时保留标准 OpenTelemetry 关联字段守卫专用遥测排除敏感工具字段。手工 E2E 与发布前检查行为变更将使用.qwen/e2e-tests/下的本地 E2E 计划。准备该计划并完成手工 ACP fixture 运行仍在进行中且是合并前的必要条件。Ready for review只表示维护者评审可以进行不代表手工 ACP fixture 验证已完成。fixture 必须覆盖一个带类型的失败工具、权限取消、提醒后成功恢复、重复失败停止、并发兄弟调用、不支持 host、channel 排除、重连与重启行为、历史重放、停止后的新提示、以及 shadow 模式不干扰。合并并部署后分别开始内部与公有云各 7 天的 shadow 基线它们门控后续晋升到 warn/enforce而非门控合并。交付前从各自包目录运行针对性的 Core 与 CLI Vitest 文件然后执行npm run build、npm run typecheck与npm run lint。失败处理与边界声明遥测发送失败永不改变工具或模型控制流缺失或畸形的结果数据会重置并降级强制携带提醒的模型请求若失败该提示在后续停止决策前退出守卫永不在未先构造并发送提醒轮次的情况下停止停止历史持久化失败时返回既有 ACP 内部错误不假装停止已被持久记录进程重启按设计丢失语义连击基于历史的 provider call-ID 去重仍防止已应答 provider 调用的重放总工具调用上限约束新启动的提示。总结ACP Repeated Tool-Call Protection 通过双轴结果契约 8 次/2 批次阈值状态机 每候选一次提醒 四模式分阶段灰度的组合在不依赖自由文本错误解析、不破坏既有去重/非法参数/总上限保护的前提下精准识别工具执行边界的重复语义失败。它的保守性体现在每一层只观察完全落定的批次、取消/未知/未启动/后执行失败一律重置、一次只追踪一个候选、默认 shadow 且公有云独立灰度。首版明确将跨重启精确一次执行、传输层重放、全局 rollout 编排排除在范围之外——除非生产证据表明有界的内存守卫不够用。如需深入可继续阅读repeated-tool-failure-guard.ts、repeated-tool-failure-guard.test.ts、Session.ts、shared-env-keys.ts以及设计文档 acp-repeated-tool-call-protection.md。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。