# AI 助手流式 fetch 排坑（2026-09-08）

前端把 `/api/chat` 改成流式输出（SSE）时踩的坑，SKILL.md 已达字数上限故存为 reference。

## 核心 bug：非 200 响应静默丢弃

**症状**：点发送后永远卡「正在连接…」，不报错也不出答案。

**根因**：fetch 链非 200 分支写成 `return r.json().then(...).catch(...)` 返回 `{_err}` 对象，但外层链没有 `.then` 接住——错误被静默丢弃，既不降级也不 settle，promise 链直接结束。

**修复**：非 200 直接降级，不要返回对象：
```js
if (!r.ok) {
  if (timer) clearTimeout(timer);
  console.error('[AI助手] HTTP ' + r.status + ': ' + url);
  tryNext();   // 降级下一端点
  return;
}
```

## 流式读取

- 读响应用 `r.body.getReader()` + `TextDecoder('utf-8')` 逐块解析，SSE 帧格式 `data: {json}\n\n`，`data: [DONE]` 结束。
- 后端用 Python `http.server` 逐事件 `wfile.write('data: '+json.dumps(evt,ensure_ascii=False)+'\n\n')` + `flush()`；`Content-Type: text/event-stream`。

## 超时设计：空闲超时 + 重置，不用硬超时

思考模型（首 token 前）可 90s+ 无数据输出。硬超时（如 240s 总时长）会把慢请求误杀；应改成「**空闲 240s 无数据才 abort，收到任意 chunk 就 resetTimeout**」。

## 症状辨识

- 「正在连接…」重复 N 次 = N 次 tryNext 降级（对应 CHAT_APIS 的 N 个端点），全失败后弹「回答超时」。重复次数直接反映轮询过多少端点。
- 前端卡「正在连接」且后端日志无新请求 = fetch 没到达后端（网络/代理/DNS 层），不是后端慢。

## 三层流式链路

`前端 fetch(ReadableStream)` ← `8765 销售工作台服务.py(_stream_sse 逐事件透传)` ← `ai_chat.py(_call_hermes_stream 从 8642 读 SSE)` ← 网关。每层都要流式透传，任一层缓冲都会破坏流式。
