ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WeClaw_90|给 LLM 快路径上保险:6 秒最坏情况是怎么被设计出来的

WeClaw_90|给 LLM 快路径上保险:6 秒最坏情况是怎么被设计出来的 Hi带娃的我热爱AI 大模型应用落地、意识解码与 AI 开发工具链。 创业路上用技术换时间一起把 AI 变成生产力 WeClaw_90给 LLM 快路径上保险6 秒最坏情况是怎么被设计出来的系列文章第 90 篇- LLM 意图分类 · 超时预算 · 快失败重试 · 熔断器 · 双引擎降级 专栏信息《从零到一构建跨平台 AI 助手WeClaw 实战指南》专栏专栏定位面向开发者和技术决策者的实战专栏用真实案例和完整代码带你理解如何构建生产级 AI 应用本文记录给一条 LLM 辅助路径设计「最坏情况」的完整过程。WeClaw 的意图识别默认走 0.1ms 的规则引擎但支持用 LLM 增强分类精度。增强路径一旦接入网络就有了无限延迟的可能。这篇讲我们如何用 6 秒超时、一次快失败重试、3 次失败熔断 60 秒三件套把这条路径的最坏耗时从「不可控」收敛到「6 秒必降级」以及一个关键的架构决策LLM 永远只是增强规则引擎永远在场。‍ 作者与项目作者简介翁勇刚 WENG YONGGANG新概念龙虾-WeClaw 开发团队负责人一群专注于跨平台 AI 应用的实践者理念“再复杂的技术也能用代码讲清楚” 项目地址https://github.com/wyg5208/weclaw.git 官网地址https://weclaw.link 作者 CSDNhttps://blog.csdn.net/yweng18⭐ 欢迎 Star⭐、Fork、贡献代码 摘要本文结构概览为什么意图识别的 LLM 路径不能复用主对话的重试策略场景差异→ 6 秒超时预算的推导用户感知阈值与级联效应→ 快失败重试为什么只给 1 次成本曲线→ 熔断器从「每次请求都试一次」到「坏了就别试了」→ 纯函数化解析层带来的可测试性 → 实测数据正常 1.51s、慢响应 6.01s 精确降级。核心结论交互式路径里的 LLM 调用超时不是可选项而是第一设计约束——没有超时的异步等待等于把响应时间交给第三方重试次数是成本决策不是可靠性决策快路径重试 1 次的边际收益已经吃掉大部分网络抖动第 2 次重试的收益趋近于零而延迟成本翻倍熔断器的真正价值是保护正常时段——故障期间停止无效的探测请求让系统以最便宜的方式规则引擎维持 100% 可用。一、同样是 LLM 调用为什么这条路需要完全不同的保险条款WeClaw 有两条截然不同的 LLM 对话路径它们的容错诉求完全相反维度主对话路径意图识别 LLM 路径用户预期「AI 在思考」等 10~30 秒可接受「秒回」是默认预期失败后果直接可见的错误用户会重发静默降级用户无感有无兜底没有LLM 就是答案本身有0.1ms 的规则引擎随时接管重试策略指数退避多次重试值得等重试越多越糟延迟累加主对话的重试策略是「为了成功值得多等」意图路径的正确策略是「为了不拖慢主流程宁可放弃」。如果把主对话的重试策略原样搬过来一次 API 抖动会让用户盯着转圈的输入框等 20 秒——而这段时间里一个 0.1 毫秒就能给出答案的规则引擎就在隔壁待命。这是整个设计的第一原则这条 LLM 路径的唯一产出是「更准的意图」而不是「意图本身」。意图永远有保底方案。二、三件套之一6 秒超时预算2.1 数字是怎么定的超时值不是拍的是三段约束夹出来的下界正常 LLM 分类的实测耗时约 1.5s输出一个 JSON几十个 token超时值必须显著大于它否则正常请求被误杀上界意图识别在流式对话里位于首 token 之前串行执行——它的耗时直接加在用户等待首字的时间上。超过 5~6 秒用户会明显感知「卡了」可配置intent_llm_timeout 6.0写入 config/default.toml不同模型/网络环境可自行调节。2.2 实现asyncio.wait_for 的边界行为try:rawawaitasyncio.wait_for(self._call_intent_llm(user_input),timeoutself._intent_llm_timeout,# 6.0s配置驱动)exceptasyncio.TimeoutError:logger.warning(意图 LLM 分类超时%.1fs降级规则引擎,self._intent_llm_timeout)returnNone# None 是统一降级信号两个容易被忽略的细节超时后任务必须被取消。wait_for会取消被等待的协程但如果下游 client 没有正确响应 cancel比如同步 HTTP 库包在线程里泄漏的请求会继续占着连接池。我们用的是支持取消的异步 client并在压测中确认超时后无连接堆积。降级信号要简单。统一返回None调用方只需一行result llm_result or rule_result——降级逻辑越薄越不容易在异常路径上再出 bug。三、三件套之二max_retries 1 的成本推导重试几次我们推导过收益曲线重试 0 次网络瞬时抖动DNS 解析失败、连接重置直接降级误伤率最高 重试 1 次吃掉绝大部分瞬时故障延迟成本 最多再等一个超时周期 重试 2 次边际收益已很小——连续两次瞬时故障的概率本来就低 而最坏延迟从 6s 涨到 12s用户感知从「卡一下」变「卡死了」于是定max_retries1且重试间隔为 0快失败立即重试——这条路径等不起退避。配合超时单次请求的最坏延迟被精确锁定最坏 第一次尝试 6s超时 重试 1 次 6s超时但我们做了更激进的决定重试与超时共享总预算而不是各自独立计时。实际最坏情况就是 6 秒不是 12 秒。宁可少一次重试机会也不让最坏情况翻倍——因为少重试一次的损失只是「这次用规则引擎」而多等 6 秒的损失是「用户以为程序死了」。四、三件套之三熔断器——故障期间别再做无效探测重试解决的是「偶发抖动」但如果 LLM 服务彻底不可用配额耗尽、模型下线、区域性故障每个请求都要白白烧掉 6 秒再降级——故障本身不该被放大成延迟灾难。熔断器的状态机极简class_IntentLLMCircuit:FAILURE_THRESHOLD3# 连续失败 3 次 → 打开COOLDOWN60.0# 熔断 60 秒内直接跳过 LLMdefrecord_failure(self):# 超时/解析失败/空响应都计为失败self._failures1ifself._failuresself.FAILURE_THRESHOLD:self._opened_attime.monotonic()defallow(self)-bool:ifself._opened_atisNone:returnTrueiftime.monotonic()-self._opened_atself.COOLDOWN:self.reset()# 半开60 秒后放一次探测returnTruereturnFalse几个设计取舍阈值 3 次1 次就熔断会把偶发抖动误判为故障5 次以上则故障前几个请求已经各被拖了 6 秒。3 是「确认故障」与「止损速度」的平衡点。冷却 60 秒短了起不到保护作用频繁半开探测长了恢复迟钝。60 秒恰好覆盖绝大多数短时故障又不至于让用户长时间享受不到 LLM 增强的精度。失败的定义要宽超时、网络错误、JSON 解析失败、返回空意图全部计入。模型「响应了但答非所问」在效果上等同于故障。五、把解析层做成纯函数治理的隐藏收益LLM 返回的 JSON 千奇百怪带 markdown 代码围栏、带前后缀解释文字、键名大小写漂移、意图名不在 25 类白名单里。最初的解析代码内嵌在 async 调用链里每种异常都要构造一次真实 LLM 响应才能测——测不了就等于没治理。重构把它抽成纯函数def_parse_llm_classify_json(raw:str)-dict|None:解析 LLM 意图分类 JSON。纯函数输入字符串输出结构化结果或 None。text_strip_code_fences(raw)# 剥掉 json ... start,end_find_json_bounds(text)# 定位第一个完整 JSON 对象ifstartisNone:returnNonedatajson.loads(text[start:end])# 只解析 JSON 段容忍前后噪音intentstr(data.get(intent,)).strip().lower()ifintentnotinINTENT_CATEGORIES:# 白名单校验25 类之外一律不信任returnNoneconfidence_clamp_float(data.get(confidence),0.0,1.0)return{intent:intent,confidence:confidence}收益立竿见影8 种畸形输入空串、纯围栏、截断 JSON、非法意图名、confidence 越界、字符串数字……全部用字符串字面量构造测试用例不需要 mock 任何网络。纯函数是「可测试性」最便宜的实现——把 IO 挡在边界外边界内全是可用字面量驱动的逻辑。六、统一入口三条链路的门控分级治理完成后顺手解决了另一个结构性问题chat、chat_stream、deferred 三条调用链各自写了一遍「要不要走 LLM」的判断逻辑。统一收口到一个入口asyncdef_route_intent_detection(self,user_input:str,*,allow_llm:boolTrue):意图检测统一入口。allow_llm 由调用链门控决定。ifallow_llmandself._intent_modellmandself._circuit.allow():llm_resultawaitself._detect_intent_with_llm(user_input)ifllm_resultisnotNone:returnllm_resultreturndetect_intent_with_confidence(user_input)# 规则引擎永远在场三条链路的门控值各不相同且都是显式声明而非默认推断链路allow_llm理由chat非流式定时任务/远程降级True后台场景无首 token 压力chat_stream日常交互self._intent_llm_in_stream配置子开关默认 Falsedeferred延迟补发False字面量永久排除评审决议写死deferred链路用字面量 False而非变量是刻意的未来任何人重构这个函数签名这个调用点都不会被误开。门控策略写在调用点而不是藏在配置里代码即文档。七、实测三组场景三组数字基准脚本覆盖三种典型工况n30 取均值场景耗时行为A. 规则引擎对照组0.1ms无网络纯内存匹配B. LLM 正常响应1.51s在 6s 预算内LLM 增强生效C. 慢响应模拟服务抖动6.01s精确触发超时降级规则引擎场景 C 的 6.01s 值得多看一眼它证明超时不是理论值wait_for在真实慢响应下精确掐断且降级路径返回的意图结果完全可用——用户最终拿到的答案只慢了 6 秒而不是挂死。熔断器也做过故障注入验证连续 3 次失败后第 4 个请求直接走规则引擎0ms LLM 耗时60 秒冷却后第一次探测成功即恢复正常路径。八、可复用的方法论先问「失败了怎么办」再问「成功了多准」。有兜底方案的增强路径设计重心永远是降级质量而不是增强质量。超时预算从用户感知倒推。意图识别在首 token 之前串行它的时间就是用户的时间——6 秒上界不是技术偏好是体验红线。重试次数要算账。交互式路径上每多一次重试最坏延迟线性翻倍快失败立即重试 1 次是收益拐点。熔断保护的是正常时段。它的价值不在故障期间省几个请求而在让故障不转化为延迟、让恢复自动发生。把解析变成纯函数。LLM 输出的所有畸形形态都应该能用字符串字面量测试覆盖——做不到就说明边界没切对。门控显式化。每条链路的策略写在调用点字面量优于变量代码即决议记录。九、总结6 秒、1 次重试、3 次熔断、60 秒冷却——四个数字背后不是经验口诀而是同一个问题的四次回答「这条路径失败时用户的体验损失上界是多少」当答案从「不可控」收敛到「最多慢 6 秒且 60 秒内自动止损」LLM 增强才敢真正接到交互式路径上。可靠性工程的本质不是消灭故障而是给故障标好价格。下期预告《WeClaw_91双引擎意图识别为什么我们既写了规则引擎又接了 LLM》一次「配置开了但没生效」的排查揭开 chat 与 chat_stream 的链路分工真相0.1ms 与 1.5s四个数量级的差距如何决定了架构形态子开关灰度让增强能力先躺在配置里等数据说话敬请期待版权声明本文为 CSDN 博主「翁勇刚」的原创文章遵循 CC 4.0 BY-SA 版权协议转载请附上原文出处链接及本声明。
返回列表