4.3 错误反馈设计:把失败变成信息


4.3 错误反馈设计:把失败变成信息

本节摘要:工具一定会失败:文件不存在、命令非零退出、网络超时、权限被拒。失败本身不可怕,可怕的是失败被翻译成"无信息"的消息——Error: something went wrong 对模型是一堵墙,它只能瞎猜重试;而一条好错误是写给模型的接口,能让它下一步就走上正确的路。本节给出错误反馈的四要素(类型、原因、线索、下一步)与结构化返回格式;一张错误分类表区分用户错、环境错、权限拒、系统错各自该反馈什么、要不要自动重试;以及重试纪律(哪些可重试、指数退避、上限),并接回 3.2 节的熔断器——错误反馈与熔断是同一套免疫系统的两半:前者治单次失败,后者治失败复利。

学习目标

阅读完本节,你应当能够:

  1. 用四要素改写一条"无信息"的错误返回。
  2. 按四类错误(用户错 / 环境错 / 权限拒 / 系统错)决定反馈内容与重试策略。
  3. 说明"权限拒绝也是一种错误反馈"及其写法。
  4. 实现带退避与上限的可重试包装。

一、错误消息是写给模型的接口

对比同一个场景(读一个不存在的文件)的两种返回:

// 坏:一堵墙——模型只能原样重试或放弃 {"error": "something went wrong"} // 好:一条路——模型知道发生了什么、为什么、下一步怎么走 {"error": { "type": "file_not_found", "message": "文件不存在:src/util.py", "hint": "相近路径:src/utils.py、lib/util.py(本仓库存在)", "next": "用 glob 工具确认路径,或改读 src/utils.py" }}

四要素拆解:类型(机器可读的稳定标识,模型能跨场景学习"这类错误意味着什么");原因(人读的一句诊断);线索(本环境里与修复相关的上下文——相近路径、退出码含义、超时阈值);下一步(一个明确建议动作)。不是每次都凑满四条,但类型 + 原因是底线,线索是拉开差距的那条。

💡 判断标准只有一句话:**模型读完你的错误消息后,能说出"我下一步该做什么"吗?**能,就是好反馈;不能,就是墙。

二、错误分类:四类各自怎么反馈

例子 反馈要点 自动重试
用户错(调用方错) 参数缺字段、路径不存在、类型不对 指出具体字段 + 纠正线索 否(重试同样必败)
环境错 网络超时、磁盘满、进程被杀 说明环境性质 + 是否暂时 视情况(暂时性可重试)
权限拒 allowlist 未命中、审批被拒、被护栏拦截 拒绝原因 + 合法替代路径 否(须改变请求或请求授权)
系统错(工具自身 bug) 注册器异常、未捕获崩溃 坦承工具故障 + 建议换路 有限次

权限拒尤其值得单说:第 5 章的权限门拦截一次工具调用时,拦截结果将以错误反馈的形式回填给模型——这时反馈的写法直接决定模型是"绕路"还是"绕过":

// 坏:诱导绕过——模型会尝试绕开权限(换个写法再干一次) {"error": "command blocked"} // 好:说明边界 + 给合法出路 {"error": { "type": "permission_denied", "message": "rm 被策略禁止:本 harness 不允许删除工作区外文件", "hint": "若要清理构建产物,可用受控工具 clean_build,其作用域已限定", "next": "改用 clean_build,或向用户申请一次性授权" }}

"被拦截也是一种错误反馈"——第 5.3 节的多闸门护栏会沿用这个约定,让每一次拦截都带着出路。

三、结构化返回:成功与失败同构

让成功与失败走同一种信封,模型学会一种解读规则就能处理全部工具(registry.pydispatch 已按此实现):

# envelope.py —— 统一返回信封(写法示意,以实际工程为准) import json, time def ok(result) -> str: return json.dumps({"ok": True, "result": result}, ensure_ascii=False, default=str) def err(etype: str, message: str, hint: str = "", next_: str = "") -> str: return json.dumps({"error": {"type": etype, "message": message, "hint": hint, "next": next_}}, ensure_ascii=False) def retryable(call, attempts: int = 3, base: float = 0.5) -> str: """环境错的自动重试:指数退避 + 上限;耗尽后返回结构化错误。""" for i in range(attempts): try: return ok(call()) except TransientError as exc: # 只重试"暂时性"错误 time.sleep(base * 2 ** i) except Exception as exc: # 其余立即返回错误 return err(type(exc).__name__, str(exc)) return err("retries_exhausted", f"重试 {attempts} 次后仍失败(暂时性错误)", next_="可将此失败报告用户,或换一种方法")

四、重试纪律与熔断的衔接

重试的纪律四条:

  1. 只重试暂时性错误(超时、限流、瞬时网络);确定性错误(参数错、权限拒)重试必败,且每次都在污染上下文。
  2. 指数退避:0.5s → 1s → 2s;对限流类错误,退避是对服务方的礼貌,也是对自己的保护。
  3. 上限三次左右(示意值),且重试耗尽要返回结构化错误,不能静默吞掉。
  4. 与熔断器串联:工具级重试(本节,单次失败内消化)失败后,把失败记入 3.2 节的 Breaker;连续失败触发熔断时,回填的是元反馈("连续失败,请复述目标换方法")而不是又一条原始错误。单次失败→工具级重试;失败复利→循环级熔断——两级配合,错误风暴才真正防得住。

⚠️ 最危险的错误是被吞掉的错误except: pass 让模型以为操作成功,带着假事实继续推进——错误风暴烧的是钱,吞掉的错误烧的是正确性。宁可把失败大声说出来,也别让它沉默。

本节要点回顾

  1. 错误是写给模型的接口:四要素(类型、原因、线索、下一步);标准是"模型读完知道下一步做什么"。
  2. 四类错误:用户错给纠正线索、环境错标暂时性、权限拒给合法出路(防绕过式反馈)、系统错坦承故障;权限拒的反馈写法决定模型绕路还是绕过。
  3. 统一信封:成功失败同构,模型一招解读全部工具;暂时性错误才重试,退避加三次上限。
  4. 两级免疫:工具级重试治单次,循环级熔断治复利;最危险的是被吞掉的错误。

工具系统完整了:契约、选择、执行、失败反馈。但 4.3 节里反复出现的"权限拒"到底怎么判定?哪些操作能自动做,哪些必须问人?下一章进入 harness 的价值观所在——权限与审批门。

延伸阅读:同站教程库《strix》。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U