一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Agent 请求失败以后,别再反复点击重新生成了

时间:2026-07-29 12:32:04 编辑:袖梨 来源:一聚教程网

假设 Agent 正在替你处理一次测试失败。

Agent 查看报错日志并沿着线索定位到相关配置文件后,模型开始生成修改方案,回答却在半个代码块处停止。疑惑之下,你点击重新生成,可能会得到完整结果,但更多时候依旧卡住。还有一种情况更直接,接口会立即返回 529 overloaded,当前这一轮甚至没有半句话。

如果 Agent 面对任何失败都采取同一种做法,例如不加判断地重试,后续很容易产生问题。

当回答被截断后,程序必须判断是否应该保存已有的半段内容。若问题是上下文过长,无论把原请求重新发送多少次,都会继续超限;如果服务处于过载状态,连续点击重试也同样无法发出请求。

s11_error_recovery 需要做的是由 Agent Loop 依据失败发生的位置选择相应恢复动作。虽然它无法确保模型永不中断,却可以避免部分可恢复错误直接终止整轮任务。

程序可以采取什么动作,取决于失败发生的位置

本章将处理三种情况:

现象程序获得的信息恢复方式
模型输出触及长度上限响应内的停止原因扩大输出上限,必要时继续续写
请求的上下文过长接口调用期间抛出异常紧急精简消息后再次请求
接口受到限流或服务过载接口调用时抛出 429、529 类错误等待间隔逐步增加后重试

接口异常与输出截断存在很大差异。

模型输出遭到截断时,程序其实已经取得响应,只是内容尚未完成。上下文超限、429 和 529 则出现在请求模型期间,程序无法获得正常响应,只能转入异常处理分支。

将三种恢复路线放在同一张图中,更容易辨别它们分别调整的是输出空间、消息历史,还是请求节奏。

回答中途停止时,先在第一次扩大输出空间

本章首先假定模型可以输出 8000 token。

当模型因输出长度不足而停止,程序不会在第一次把这段回答写进消息历史,而是先将上限提升到 64000,再使用原任务和原消息记录重新请求模型。

if response.stop_reason == "max_tokens":if not state.has_escalated:max_tokens = ESCALATED_MAX_TOKENSstate.has_escalated = Truecontinue

这部分逻辑位于回复写入消息历史之前。

这种安排并不等同于普通重试。程序此时保持同一份输入不变,只为模型扩大输出空间,使其有机会完整回答当前任务,也不会因为读到旧回复的半截内容而重新补充一段背景说明。

只有当 64000 token 依旧不够时,程序才保存当前输出,并添加一条续写要求:

Output token limit hit. Resume directly — no apology, no recap. Pick up mid-thought.

这条提示只要求继续写,不允许模型重新解释已经说过的内容。续写最多执行三次,超过次数后循环结束。

设置这种限制十分必要。模型若连续多次无法完成输出,通常意味着任务粒度过大,或者回答方式不恰当。不断追加续写请求,最终可能只会得到篇幅惊人、阅读体验却没有改善的回答。

上下文过长时,仅保留最近五条消息只是应急手段

一旦上下文超限,模型接口会直接拒绝本次请求。

教学代码进行紧急压缩时没有生成摘要,仅留下消息历史中的最后五条,这里还要补充一项说明:

def reactive_compact(messages: list) -> list:tail = messages[-5:]return [{"role": "user","content": "[Reactive compact] Earlier conversation trimmed. " "Continue from where you left off.",}, *tail]

压缩后的消息会替换原来的消息历史,循环随后重新调用模型。

这一路径与 s08 的常规上下文压缩并非同一层级。s08 会尽可能保留任务目标、工具结果和既有结论,s11 的紧急压缩则只负责尽快降低请求长度。

假设 Agent 此前读取过十几个文件,而最近五条消息刚好仅包含工具结果和模型的一句中间判断,那么更早的用户要求、工具调用来源以及项目约束可能已从消息历史中消失。模型虽然还能继续生成内容,却不一定明白自己为何来到当前步骤。

所以,本章只准许紧急压缩一次。如果压缩后仍然超限,程序便直接返回错误。继续删除消息确实可以缩短上下文,但也可能把任务删到只剩一个标题。

还需注意一个实现细节:此处直接截取最后五条消息,并未像 s08 那样检查工具调用与工具结果是否成对保留。它可以用来说明恢复思路,却不宜直接移入必须严格维护消息格式的系统。

处理 429 和 529 时,重点在于调节重试节奏

429 代表限流,529 代表服务暂时过载。由于任务内容未发生改变,无须裁剪消息历史,程序只需间隔一段时间后重新尝试。

本章采用指数退避,等待时间将依次延长:

失败次数基础等待时长
第 1 次0.5 秒
第 2 次1 秒
第 3 次2 秒
第 4 次4 秒
后续上限为 32 秒

每轮等待还会叠加少量随机时间。

如果多个 Agent 同时遇到 529,并且都严格等待 0.5 秒后重试,那么服务刚恢复便会再次迎来一批请求。加入随机抖动可以稍微错开重试时间,避免压力重新集中在某个时间点。

代码将 429 和 529 放到 with_retry() 中处理,其他异常则会再次抛给外层循环。这样的职责划分使恢复逻辑更清晰:临时服务故障由重试函数处理,长度故障交由消息压缩逻辑处理,无法识别的错误在记录后结束任务。

教学代码中的备用模型其实尚未接通

连续发生三次 529 后,如果已经配置备用模型,代码将更新当前模型名称:

state.current_model = FALLBACK_MODEL

仅从这一行来看,备用模型似乎已经完成切换。

但接口调用时存在问题:请求函数已将当前模型保存为 lambda 的默认参数:

lambda mt=max_tokens, mdl=state.current_model:client.messages.create(model=mdl, ...)

后续重试仍会反复调用同一个 lambda。即使恢复状态中的模型名称已经改变,mdl 仍会保留 lambda 建立时使用的旧值。

因此,教学代码现在虽然会输出切换备用模型的日志,之后的重试却仍采用原模型。这是 s11 中需要单独记录的一处实现缺口。

若要让切换立即生效,请求函数必须在每次调用时重新读取恢复状态:

def request():return client.messages.create(model=state.current_model,system=system,messages=messages,tools=TOOLS,max_tokens=max_tokens,)

恢复逻辑里有一个常见误区:状态字段已经修改,不代表下一次操作一定会读取这个字段。中间如果缓存了参数、闭包保存了旧值,状态变化就只停留在日志里。

这份教学代码还省略了哪些内容

位置现有处理方式实际系统仍需补充的部分
紧急压缩留下最近五条消息维护工具调用与工具结果之间的关联
服务端建议的等待时间延迟函数能够接收该参数目前调用位置没有读取接口响应提供的等待信息
重试次数循环共发起 10 次请求清楚区分总尝试次数与额外重试次数
备用模型刷新当前模型状态确保下一次请求取得最新模型
输出续写固定上限为三次依据新生成的内容判断继续是否仍有价值

这些简化不会妨碍理解本章主线。s11 所要展示的是,错误恢复应当调整不同对象:有时需要改变输出空间,有时需要处理消息历史,也有时只需调整请求节奏。

本章总结

本章为 Agent Loop 增加了三类恢复动作。

模型输出空间不足时,程序会先扩大空间,再决定是否续写;消息过长时,程序压缩消息并重试一次;若接口只是暂时不可用,程序则按照逐渐增加的等待时间重试。每条路线都设置了次数上限,防止循环持续困在失败状态。

这套教学实现仍有若干部分需要完善,其中尤其包括备用模型切换,以及紧急压缩后的消息完整性。这说明恢复逻辑也必须接受测试,不能只检查状态是否发生更新。

下一章将从单次任务如何结束,转入对任务本身的管理。待办列表可以记录眼前的几个步骤,但要处理跨会话恢复、依赖关系和长期执行,还需要一套更完整的任务系统。

热门栏目