← 全部日志

聊天全链路:四场景验证矩阵,和一个教科书级的 Node 坑

2026-06-11约 50 分钟(其中排查 mock 挂起约 15 分钟)AI SDK v6降级设计验证踩坑

目标

打通核心链路:/api/chat 限流 → 校验裁剪 → 配置检查 → 真 AI 流式转发,任何一步失败都优雅降级到演示模式;并用「不花一分钱」的方式完成验证。

结果

写了一个本地 mock 的 OpenAI 兼容服务器(scripts/mock-openai.mjs),把四场景验证矩阵全部跑绿:

场景结果
① 无 key演示模式,元数据 mode: demo, reason: not_configured,FAQ 关键词命中正确
② 指向 mock 的「真 AI」mode: live, model: mock-model,流式逐字输出端到端打通
③ 故意用坏 keyHTTP 200(无 5xx),自动降级演示模式 reason: upstream_error,响应中零密钥/上游错误泄漏
④ 连发 12 次第 9 次起 429,文案友好;5000 字超长输入 200 不崩

服务端结构化日志可见每次对话的模式、问题摘要与耗时,例如: {"evt":"chat","mode":"demo","reason":"upstream_error","question":"你是真人吗?","ms":585}

踩坑与纠正

坑一(AI 的训练数据过期)convertToModelMessages 在 v6 变成了异步函数(v5 同步),AI 按旧习惯写漏了 await。tsc 直接抓住——这正是「每一步都过类型检查」的价值。

坑二(教科书级):场景②首测时,请求挂起 20 秒一个字节都不返回。逐层排查:先最小复现确认 http 模块基本用法没问题,再对比差异——元凶是 mock 里的连接清理代码 req.on("close", () => clearInterval(timer))。Node 15+ 里 IncomingMessageclose请求体读完时就触发,不代表连接断开;于是定时器在写出第一个字节前就被清掉,响应永远悬空。改成挂在 res.on("close") 上,立刻通了。

意外收获:排查期间 mock 被 kill 掉,反而无意中验证了 ECONNREFUSED 场景——服务端日志显示链路正确落入演示模式,访客无感知。顺手给 streamText 补了 maxRetries: 1 和超时配置,防止上游挂死时拖着访客陪等。

用时与备注

约 50 分钟。验证全程零 API 费用——mock 厂商这个办法本身值得复用。