Home
avatar

.𝙃𝙖𝙣

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 这层,我后面会更重视两件事:

  1. 超时界限要明确
  2. 状态兜底要明确

不然系统很容易一直停在“好像还活着,但谁也不知道它到底做完没”这种状态里。

6. 我后来会把“超时”看成一个状态决策点

这是我挺强烈的一个体会。

一开始会自然觉得,超时无非就是等太久。

但在业务系统里,超时重要的地方在于:

到这个时间点以后,系统必须做出一个明确决定。

比如:

  • 继续等,还是判失败
  • 标记为异常,还是直接允许重试
  • 还保不保留原任务结果
  • 这次失败要不要影响整次创作状态

所以超时本质上不是“过了多久”这么简单。

它更像是一个状态切换点。

一旦到了这个点,后端不能再模糊地挂着,而是要选一种能继续往下走的处理方式。

这个认识对我后来做异常处理挺重要。

因为它让我开始把“超时”看成任务流转的一部分,而不是纯基础设施层的问题。

7. 降级这件事,当时给我最大的提醒是:系统不一定每次都能给满结果,但至少要给稳定结果

“降级”这个词,我一开始也会觉得有点大。

后来真做进去以后,反而觉得它非常务实。

因为很多时候,外部能力一抖,业务系统不可能总是做到“完整成功”。

那这时候先要解决的是:

至少别让系统整条链一起垮了。

这个思路一旦有了,降级就很好理解了。

对我当时来说,降级不一定是很重的机制,很多时候它只是:

  • 先停在上一步可用结果
  • 不继续推进后面高风险动作
  • 给前端一个可解释状态
  • 保留后续重试入口
  • 不让整批任务一起进入不可恢复状态

比如故事生成成功了,但分镜拆分这一轮不稳,那至少可以把故事结果先留下,而不是整次任务像从来没发起过。

再比如一批分镜生图里有部分成功,那至少要把成功结果先保住,而不是因为几张失败就把前面所有结果一起否掉。

我后来挺认同这种处理思路的。

因为业务系统很多时候追求的,不是“永不失败”。更现实的是,失败时别把用户和自己一起带崩。

8. 我当时处理这类问题时,重点其实一直围着三件事转

回头整理一下,那段时间虽然看起来问题很多,但我真正反复在做的,其实就是三件事。

第一,识别失败类型

先分清楚这次是:

  • 真异常
  • 超时
  • 结果结构不可用
  • 部分成功
  • 回调迟到

这一步很重要,因为不同失败类型后面的动作不一样。

第二,给失败一个明确状态

只要失败了,就别让任务继续停在模糊状态里。

至少要让系统内部和前端都知道:

  • 当前卡在哪
  • 还能不能继续
  • 该不该重试

第三,尽量保住已经可用的结果

如果前面某一层已经产出了能用的数据,就尽量别因为后面一层抖了就全部推翻。

这一点对用户体验和系统可恢复性都很重要。

9. 回头看,这一层真正让我补上的,是“外部能力不稳定”下的系统思维

如果只从表面看,这篇写的是:

  • 接口异常
  • 超时
  • 降级

但我现在回头看,真正补上的东西其实更深一点。

就是我开始比较明确地接受了一件事:

AI 业务系统的核心能力,有一部分天然就不完全受自己控制。

而后端要做的,不是去幻想这些外部点会永远稳定。它更该处理的是:

  • 它不稳时怎么识别
  • 它失败时怎么收口
  • 它卡住时怎么转状态
  • 它抖一下时怎么别把整条链一起带死

这个思路对我后来影响很大。

因为它让我看 AI 系统时,不再只盯着“能力强不强”,也会去看:

这个能力一旦不稳定,后面的业务还能不能接得住。

10. 这篇先记一个阶段结论

这段关于 LLM / ComfyUI 异常、超时和降级的处理,我现在先记这几条:

  1. AI 链路里“调用成功”和“结果可用”是两回事
  2. 超时不只是“等太久”,它往往也是系统必须做状态决策的节点
  3. LLM 侧更容易出结构不稳,ComfyUI 侧更容易出执行不稳
  4. 降级不用追求把结果补到完美,先保住系统还能继续解释、继续恢复更重要
  5. 外部能力不稳定时,后端最重要的职责是把失败收住,别让它在链路里继续放大

这篇先写到这里。

因为前面这些任务、异常、降级一旦全接进系统以后,后面最能决定系统长期能不能维护下去的,往往就是这些基础能力。

.NET LLM ComfyUI 异常处理 降级