5.0 KiB
5.0 KiB
二次对话必现中断问题排查记录
时间范围:2025-10-14 ~ 2025-10-15
调试参与者:Codex(AI 助手)
目标:定位并解决「在同一聊天中第二次发送消息必定失败」的问题
一、现象与初始假设
-
复现特征
- 第一次对话正常生成页面与 Section,并成功保存。
- 紧接着第二次对话必定失败,前端提示
net::ERR_INCOMPLETE_CHUNKED_ENCODING或返回 502。 - 服务端日志显示
request.signal被中止,随后触发/api/project保存失败并抛出P2003。
-
初步假设
- 可能是浏览器主动 abort 导致 SSE 连接中断。
- 也可能是结构化摘要(structuredPageSnapshot)等辅助调用耗时太长,引发代理或服务器超时。
/api/project写库失败属于连锁反应,需要隔离观测。
二、针对性尝试与结果
1. 前端日志打点
- 为
fetch、AbortController.abort、useChat状态变更以及stream-data增加详尽日志。 - 结果:确认
ERR_INCOMPLETE_CHUNKED_ENCODING发生时,浏览器确实收到 200 响应头后被动断流;AbortController.abort堆栈来自 Remix fetcher 的submit(revalidation),并非我们主动调用。
2. 阻断 /api/project 的连锁影响
- 在
useChatMessage.onFinish与useProject.saveProject中加入skipNextProjectSaveRef与getAborted()检查,避免失败时继续提交/api/project,消除P2003噪声。 - 验证:失败后不再触发保存请求,日志干净。
3. 结构化摘要调用的中止处理
- 在
structuredPageSnapshot周围增加AbortController,尝试在前端 abort 时终止重试。 - 初始实现将自定义
AbortError抛到外层,导致 Node 进程崩溃。修复后改用retry.AbortError并吞掉异常,确保只记录 warning,不崩溃。
4. 服务端额外日志与防护
- 捕获
chatStreamText、structuredPageSnapshot的耗时及异常堆栈,判断是否是上游 LLM 调用超时。 - 在
streamExecutor与chatStreamText内部检测request.signal.aborted后立即返回,防止中止后继续执行后续逻辑。 - 处理
trust proxy设置,避免限流中间件报错阻塞服务启动。
5. 观察结果
- 中止场景下服务器不再崩溃,也不会触发
/api/project,但「第二次请求仍然被客户端中止」这一现象未根治。 - 前端控制台依旧会在第二轮输出
ERR_INCOMPLETE_CHUNKED_ENCODING,说明连接被关闭但没有崩溃日志。
三、现阶段确认的结论
-
崩溃原因已排除
- 之前 Node 进程重启是因为我们抛出的
Error("客户端已中止…")浮出到最外层,现在已改为retry.AbortError并在 catch 内消化,服务端不会再因此崩溃。
- 之前 Node 进程重启是因为我们抛出的
-
保存数据的连锁反应已阻断
- 二次对话失败不会再触发
/api/project的 500 与P2003,数据库层面不受影响。
- 二次对话失败不会再触发
-
核心断流仍存在
- 浏览器在第二次流式请求开始不久就断开连接,
AbortController.abort的堆栈指向 Remix fetcher(revalidation)。 structuredPageSnapshot(辅助模型调用)在每次中止时都会被打断,并记录 warning;但即便完全不保存也会早早中止,说明问题不在写库阶段,而是在请求处理链更早的位置。
- 浏览器在第二次流式请求开始不久就断开连接,
四、待验证与下一步计划
-
验证结构化阶段是否必要条件
- 临时跳过
structuredPageSnapshot和selectContext,仅保留主模型回复,看二次对话是否仍断流。 - 若断流消失,则将进一步评估结构化内容的超时策略或输入长度限制。
- 临时跳过
-
获取 Abort 堆栈
- 在浏览器控制台记录
[AbortController.abort]的堆栈(我们已注入补丁),用于确认到底是 Remix fetcher 还是其他逻辑主动取消请求。 - 若堆栈指向 Remix revalidation,可考虑调整 Remix 的
shouldRevalidate或 fetcher 调用时机。
- 在浏览器控制台记录
-
查看上游网络/代理
- 目前日志没有出现 502,但仍报
ERR_INCOMPLETE_CHUNKED_ENCODING,需要确认 Docker/宿主机是否存在连接重置(本地端口、代理等)。 - 可以通过
curl -N直接请求/api/chat进行 CLI 流式验证,排除浏览器因素。
- 目前日志没有出现 502,但仍报
-
针对 HTML 解析警告
- 第二轮响应中模型返回了不完整的
<script>与<section>,导致前端解析失败。虽与断线无直接关系,但可能触发某些保护性中止,需在后续排查中一并关注。
- 第二轮响应中模型返回了不完整的
五、总结
目前的改动保证了服务端在异常情况下不会崩溃,也不会误写数据库。但核心问题是客户端在第二轮请求时仍旧提前关闭连接(表现为 ERR_INCOMPLETE_CHUNKED_ENCODING)。
下一步的核心工作是找出“谁”触发了 AbortController.abort(),并验证是否与结构化辅助步骤相关。待上述验证完成后,再恢复辅助功能并优化网络策略,才能真正实现「第二次对话不再失败」的目标。*** End Patch