LLM / ComfyUI 接口异常、超时和降级是怎么处理的
这篇继续接 vchoo 这条主线,专门记外部能力接进业务系统以后最容易把链路打断的几件事:接口异常、超时和降级。
前面写了全链路,也拆了生图任务调度。真正把系统往线上放以后,很快就会碰到一个更现实的问题:链路里的每个外部能力都不一定稳定。故事生成会超时,分镜拆分可能返回结构不稳,ComfyUI 也可能卡住、回调慢、结果不回。这个阶段后端最重要的工作之一,就是别让整条业务链因为某一个外部点抖一下就直接散掉。
1. 先把问题说透:AI 业务系统和普通 CRUD 最大的区别之一,就是核心能力不在自己手里
做普通后台或者中后台时,很多核心逻辑都在自己系统内部。
比如:
- 查数据库
- 改状态
- 写日志
- 返回结果
这些动作虽然也会有 bug,也会有慢查询,也会有异常,但至少大部分行为边界是自己可控的。
到了 vchoo 这里,情况很不一样。
因为这条链里最关键的几步,很多都依赖外部能力:
- 故事生成依赖 LLM
- 分镜拆分依赖 LLM
- 生图执行依赖 ComfyUI 工作流
- 最终结果回流依赖回调链路
这意味着一件很现实的事:
你能控制业务流程,但你不能保证外部能力每次都按你希望的方式稳定返回。
所以从这个阶段开始,后端做的很多事,其实都围着一个目标打转:
外部能力不稳的时候,系统还能不能继续维持可解释、可恢复、可交代。
2. 我最开始对“异常处理”的理解还比较浅,觉得无非就是 try-catch
刚开始写这类接口时,我对异常处理的理解还比较偏代码层。
会更容易想到这些:
- 某一行报错了就 catch
- 返回个失败信息
- 日志记一下
这当然不算错,但放到 vchoo 这种链路里就明显不够了。
因为这里碰到的很多异常,都不是那种代码直接报错的场景。更多时候,是外部能力自己会冒出各种非理想情况:
- 返回慢
- 超时
- 返回结构不完整
- 当前节点工作流跑崩
- 回调迟到
- 某一张图成功,另一张图失败
这些问题并不总是传统意义上的异常抛出。
很多时候它只是:
- 不按预期返回
- 不在预期时间返回
- 返回了,但结果不可用
所以我后来对“异常处理”这件事的理解就换了一层。
我开始更在意:
这一步外部能力没按预期工作时,系统后面要怎么继续。
3. 先说 LLM 这一层:最容易卡人的,其实是返回结构不稳
LLM 这块一开始最容易给人的错觉是:
- 能返回内容
- 故事也写出来了
- 分镜也拆了
- 看起来挺顺
但真放进业务以后,问题很快就会暴露出来。
因为后端光拿到一段文本还不够,还得拿到:
- 能继续存储的结果
- 能继续拆分的结果
- 能继续推进下一步任务的结果
这个时候就会发现,问题已经不只在内容质量上了,结构稳定性同样关键。
比如:
- 段落数量和预期不一致
- 分镜字段缺失
- 输出格式不规整
- 某些内容语义还行,但后面很难继续程序化处理
这类问题最麻烦的地方在于,它不像接口 500 那样直接、明确。
很多时候它是“看起来返回成功了”,但你一往下接,就会发现这个结果没法安全推进下一层。
所以我后来处理 LLM 结果时,脑子里会多一个判断:
这次调用成功,不代表这次结果可用。
这个区别在 AI 系统里很重要。
4. LLM 一旦超时,整条文本链就会被卡住
故事生成和分镜拆分这几步,如果只是人工玩一下,等待感可能还没那么强。
但一旦它被放进业务链里,超时影响就会放大。
因为后面的步骤都在等它:
- 故事没出来,分镜就拆不了
- 分镜没出来,生图就启动不了
- 前面一层一直卡,后面所有状态都跟着悬空
所以放到业务里,LLM 一超时,整次创作任务就可能卡在某一个不上不下的状态里。
这个阶段我开始比较明确地意识到:
- 超时本身就是一种失败
- 不能无限等
- 等得越久,用户体验越差
- 如果没有超时后的状态处理,系统会越来越乱
所以从后端角度看,光把等待时间拉长解决不了问题。
它还要求你补上后面的动作:
- 这次任务要不要标失败
- 还能不能重试
- 前端看到什么提示
- 这层失败以后后面的步骤是不是全停
5. ComfyUI 这一层的问题更“硬”,因为它不仅会超时,还会卡在执行态里
如果说 LLM 这边很多问题偏结果结构和响应延迟,那 ComfyUI 这边的问题会更偏执行链。
比较常见的几类情况大概是:
- 工作流没真正跑起来
- 某个节点执行失败
- 任务已经发出去了,但回调没回来
- 回调回来得很慢
- 某一批分镜里只有部分成功
- 任务长时间停在“执行中”
这类问题比文本侧更麻烦一点,因为它和前面那篇任务调度是直接绑在一起的。
尤其是“执行中卡住”这种情况,很容易把系统带进一个很尴尬的状态:
- 前端看到还在转
- 后端没有明确失败信号
- 用户不知道该继续等还是重试
- 运维和开发也很难第一时间判断到底是慢,还是真的挂了
所以到了 ComfyUI 这层,我后面会更重视两件事:
- 超时界限要明确
- 状态兜底要明确
不然系统很容易一直停在“好像还活着,但谁也不知道它到底做完没”这种状态里。
6. 我后来会把“超时”看成一个状态决策点
这是我挺强烈的一个体会。
一开始会自然觉得,超时无非就是等太久。
但在业务系统里,超时重要的地方在于:
到这个时间点以后,系统必须做出一个明确决定。
比如:
- 继续等,还是判失败
- 标记为异常,还是直接允许重试
- 还保不保留原任务结果
- 这次失败要不要影响整次创作状态
所以超时本质上不是“过了多久”这么简单。
它更像是一个状态切换点。
一旦到了这个点,后端不能再模糊地挂着,而是要选一种能继续往下走的处理方式。
这个认识对我后来做异常处理挺重要。
因为它让我开始把“超时”看成任务流转的一部分,而不是纯基础设施层的问题。
7. 降级这件事,当时给我最大的提醒是:系统不一定每次都能给满结果,但至少要给稳定结果
“降级”这个词,我一开始也会觉得有点大。
后来真做进去以后,反而觉得它非常务实。
因为很多时候,外部能力一抖,业务系统不可能总是做到“完整成功”。
那这时候先要解决的是:
至少别让系统整条链一起垮了。
这个思路一旦有了,降级就很好理解了。
对我当时来说,降级不一定是很重的机制,很多时候它只是:
- 先停在上一步可用结果
- 不继续推进后面高风险动作
- 给前端一个可解释状态
- 保留后续重试入口
- 不让整批任务一起进入不可恢复状态
比如故事生成成功了,但分镜拆分这一轮不稳,那至少可以把故事结果先留下,而不是整次任务像从来没发起过。
再比如一批分镜生图里有部分成功,那至少要把成功结果先保住,而不是因为几张失败就把前面所有结果一起否掉。
我后来挺认同这种处理思路的。
因为业务系统很多时候追求的,不是“永不失败”。更现实的是,失败时别把用户和自己一起带崩。
8. 我当时处理这类问题时,重点其实一直围着三件事转
回头整理一下,那段时间虽然看起来问题很多,但我真正反复在做的,其实就是三件事。
第一,识别失败类型
先分清楚这次是:
- 真异常
- 超时
- 结果结构不可用
- 部分成功
- 回调迟到
这一步很重要,因为不同失败类型后面的动作不一样。
第二,给失败一个明确状态
只要失败了,就别让任务继续停在模糊状态里。
至少要让系统内部和前端都知道:
- 当前卡在哪
- 还能不能继续
- 该不该重试
第三,尽量保住已经可用的结果
如果前面某一层已经产出了能用的数据,就尽量别因为后面一层抖了就全部推翻。
这一点对用户体验和系统可恢复性都很重要。
9. 回头看,这一层真正让我补上的,是“外部能力不稳定”下的系统思维
如果只从表面看,这篇写的是:
- 接口异常
- 超时
- 降级
但我现在回头看,真正补上的东西其实更深一点。
就是我开始比较明确地接受了一件事:
AI 业务系统的核心能力,有一部分天然就不完全受自己控制。
而后端要做的,不是去幻想这些外部点会永远稳定。它更该处理的是:
- 它不稳时怎么识别
- 它失败时怎么收口
- 它卡住时怎么转状态
- 它抖一下时怎么别把整条链一起带死
这个思路对我后来影响很大。
因为它让我看 AI 系统时,不再只盯着“能力强不强”,也会去看:
这个能力一旦不稳定,后面的业务还能不能接得住。
10. 这篇先记一个阶段结论
这段关于 LLM / ComfyUI 异常、超时和降级的处理,我现在先记这几条:
- AI 链路里“调用成功”和“结果可用”是两回事
- 超时不只是“等太久”,它往往也是系统必须做状态决策的节点
- LLM 侧更容易出结构不稳,ComfyUI 侧更容易出执行不稳
- 降级不用追求把结果补到完美,先保住系统还能继续解释、继续恢复更重要
- 外部能力不稳定时,后端最重要的职责是把失败收住,别让它在链路里继续放大
这篇先写到这里。
因为前面这些任务、异常、降级一旦全接进系统以后,后面最能决定系统长期能不能维护下去的,往往就是这些基础能力。
