踩坑笔记:DeepSeek 默认开了思考模式,首字延迟 15 秒触发前端降级
这个 bug 出现在我把对话模型切到 deepseek-v4-flash 之后。改动本身只有模型名一个字段,改完本地随手测了一下能用,就接着干别的了。
第二天验收,聊天窗的表现是:提问之后转圈 15 秒,然后显示「AI 助手暂时休息中」。每一个问题都这样。
先排除自己人
前端有个兜底:发出请求后 15 秒没收到第一个 token,判定失败,走降级文案。所以「15 秒后降级」有两种可能:后端真没吐字,或者吐了但前端没认出来。当时两个 bug 同时在场,这篇讲前者——「吐了但前端没认出来」那个是 SSE 换行符 的故事。
在服务器上 curl 接口验证:请求发出后,整整 15 秒没有任何输出,然后开始流式吐回答,内容完全正常。后端是好的,只是慢——慢在第一个字。
根因:思考模式默认开启
翻 DeepSeek 的更新文档才确认:V4 系列默认开启 thinking 模式,模型先在内部跑一段推理过程,跑完才开始输出正式回答。问答场景的问题一般十几秒推理就够了,但「够了」的意思是 10 到 20 秒之间——正好压在 15 秒的降级阈值上跳舞。
升级模型版本,行为默认值跟着变了。这是我没料到的:我以为换模型名只是换个权重,实际上换了一整套默认参数。
修复是显式关掉思考模式:
# blog-agent/app/core/llm.py
return ChatOpenAI(
model=settings.llm_model,
api_key=settings.deepseek_api_key,
base_url=settings.deepseek_base_url,
temperature=temperature,
streaming=True,
timeout=30,
extra_body={"thinking": {"type": "disabled"}},
)
改完首字延迟从 15 秒以上降到 2 秒上下。顺带还有个收益:思考内容计入输出 token,关掉之后账单也便宜了一截。对站内问答这种场景,思考模式的质量收益几乎感知不到,成本和延迟却是实打实的。
TTFT 是流式 UX 的核心指标
这件事之后我给「流式体验」立了一个明确的指标:TTFT(Time To First Token,首 token 延迟)。用户感知到的「快不快」,90% 取决于第一个字多快出现,而不是整个回答生成完要多久。逐字流出本身就是遮羞布——只要第一个字出现得够快,后面生成慢一点用户完全不在意。
所以流式接口的监控也该盯 TTFT,而不是盯总耗时。我目前的土办法是在日志里记一笔「请求到首帧的间隔」,超过 5 秒的捞出来看。将来如果有量,值得按模型版本做个分桶统计——因为这次事故的本质就是:模型方的默认值变更是一个需要被监控的外部依赖事件,和 API 改价、接口废弃是同一类风险。
复盘
教训两条:
- 模型版本升级 = 行为变更,哪怕接口签名一模一样。升级模型后第一件事是翻 release notes 找「默认」两个字,第二件事是实测 TTFT 和输出格式,别只验证「能回答」。
- 兜底阈值和外部延迟要留够余量。15 秒超时对普通问答很宽裕,但对「默认开思考」的模型就是刚刚好不够。阈值设计时要想清楚它防的是什么——我防的是「后端挂了」,结果先撞上的是「后端在想」。
后来我把这两件事都写进了上线检查清单的降级链路验证项里:故意改错 API Key 触发降级之外,还要单独测一次首字延迟。毕竟用户看不到思考过程,他们只看到一个转圈的窗口。