三国杀武将觉醒马超怎么样 三国杀武将觉醒手游安卓手机版马超角色详解
2026-07-29 3433719
2026-07-29 0
设想一下,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代表服务暂时过载。由于任务内容没有改变,无需裁剪消息历史,程序只要等待一段时间后重新尝试。
本章采用指数退避,等待时间将按以下方式逐步增加:
| 失败次数 | 基础等待时间 |
|---|---|
| 第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增加了三类恢复动作。
模型输出空间不足时,程序会先扩大空间,之后才考虑续写。若消息过长,程序压缩消息并重试一次;若接口只是暂时不可用,则按照递增等待时间进行重试。每条路径都设有次数上限,以免循环始终困在失败状态。
这套教学实现仍有若干方面需要完善,其中尤其包括备用模型切换,以及紧急压缩后消息能否保持完整。这说明恢复逻辑本身也必须接受测试,不能只检查状态是否已经更新。
下一章将从单次任务的结束转向对任务本身的管理。待办列表可以记录眼前的几个步骤,但要实现跨会话恢复、依赖关系和长期执行,仍然需要更加完整的任务系统。