夜雨聆风学习资料网

ARTICLE · 1191029

AI编程学习10.16|第25课:退避重试与澄清回灌闭环

AI编程学习10.16|第25课:退避重试与澄清回灌闭环

第 25 课:退避重试与澄清回灌闭环

第 24 课给多工具路由加了「失败回退次选」和「低置信先反问」两道安全网。这一课把它们接成一条完整闭环:工具挂了不要只试一次就放弃,要按指数退避多试几次;用户没说清时反问了,下一轮得把答案接回来,而不是又从头问一遍。

先认词

  • Exponential Backoff(指数退避):每次重试前等的时间翻倍(0.1s、0.2s、0.4s…),避免对故障服务「猛锤」,也给它恢复窗口。
  • Clarification Loop(澄清回灌闭环):把用户对反问的回答写回上下文,重新判定意图,再决定调哪个工具。
  • Idempotent Rerun(幂等重跑):同一任务跑两次结果一致,重跑才安全——调度系统(本系列长文讲过)靠这个保证可重入。

一、为什么需要完整闭环

第 24 课的 fallback 只「重试一次」:首选失败就立刻跳次选。但真实故障常常是瞬时的——网络抖动、限流、依赖方刚重启。一次失败不代表永久失败,立刻放弃会丢掉本可成功的请求。

另一头,澄清反问之后如果不回灌,agent 下一轮又是「冷启动」:用户刚说过「我是会员」,它又问「您是会员还是普通用户?」。体验从「聪明」退回「失忆」。第 14 课就埋过伏笔:澄清函数支持把答案传回去。

二、核心思路:两个函数

文件 ai-learning/code/route_clarify_loop.py,复用第 22-24 课的零件:function_calling(注册表+分发)、multi_tool(选型)、clarification(澄清)、route_safety(兜底)。

import time
import route_safety as rs
import clarification as cl

def route_with_backoff(query, primary, max_retries=3, base=0.1):
    """首选失败按指数退避重试;耗尽仍失败则回退次选(复用第24课)。"""
    for attempt in range(max_retries):
        r = rs.fc.dispatch(rs._call(primary))
        if "error" not in r:
            return {"used": primary, "result": r["result"], "attempt": attempt + 1, "fell_back": False}
        print(f"attempt={attempt + 1} 失败,退避 {base * 2**attempt:.2f}s")
        time.sleep(base * 2**attempt)
    for name, _ in rs.mt.select_tools(query, top_k=5):
        if name == primary:
            continue
        rr = rs.fc.dispatch(rs._call(name))
        if "error" not in rr:
            return {"used": name, "result": rr["result"], "attempt": max_retries, "fell_back": True}
    return {"used": None, "result": None, "attempt": max_retries, "fell_back": True}

def clarify_then_act(query, simulated_answer):
    """先澄清拿问题;把用户回答回灌,闭合意图再行动。"""
    first = rs.route_with_clarification(query)
    if first.get("action") == "call":
        return first
    question = first.get("question", "")
    resolved = cl.clarify(query, cl.DOCS, answer=simulated_answer)
    if resolved.get("need_clarify"):
        return {"action": "clarify", "question": question}
    return {"action": "resolved", "answer": resolved["answer"], "question": question}

三、真实跑出来的输出

python route_clarify_loop.py --selftest:

route_clarify_loop selftest: 4/4 PASS

逐个看:route_with_backoff("上海今天天气怎么样", "get_weather") 首选一次成功,返回 used=get_weather、fell_back=False;把首选换成不存在的 ghost_tool,它先退避重试 3 次(打印 attempt=1/2/3),耗尽后自动回退次选 get_weather,fell_back=True。clarify_then_act("我的退款多久到账", "我是会员") 先反问,回灌「我是会员」后直接解析出 answer=D4(会员优先通道 3 工作日);换成意图清楚的「上海今天天气怎么样」则跳过澄清直接 call 天气工具。

四、为什么这么做

为什么退避而不是立即重试。 瞬时故障大多在几百毫秒内自愈。立刻重试等于在故障尖峰上叠加流量,既救不活又压垮对方;指数退避把重试请求「摊」到时间上,给下游恢复窗口,本地也只是多等零点几秒。

为什么回灌答案而不是重问。 第 14 课的澄清函数 clarify(query, docs, answer=...) 就是把用户的回答并回分析,从而把 need_clarify 从 True 翻成 False。把这一步接在路由层,agent 就记住了「用户刚说过的话」,闭环成立。

为什么两条安全网要合在一起。 单独的 fallback 救不了「意图不明」;单独的澄清救不了「服务抽风」。真实 agent 两种情形并存,缺一个就断路。

五、常见误区

  • 重试不设上限、不退避——把瞬时故障打成雪崩。
  • 退避重试时复用失败工具的坏参数,却没按次选 schema 重抽——次选照样挂。
  • 澄清反问后不回灌,下一轮又从头问——用户感知是「失忆」。
  • 把 backoff 的 base 设太大(如 1s)还 max_retries=5,一次失败就卡 31 秒——演示用 0.1s 足够。

15 分钟小练习

给 clarify_then_act 加「澄清后直接行动」:当用户回答能定位到工具时(如回灌「查上海天气」),用 rs.mt.select_tools(answer) 选出工具并 rs.fc.dispatch 真调一次,让闭环从「解析出答案」升级成「解析出答案并顺手把事办了」。再给 route_with_backoff 加一个 on_fail 回调参数,失败时打印 used=None 并触发告警,体会「退避耗尽」的边界处理。

相关学习资料