SDA 深度优化:5 项 Bug 修复 + 功能升级实录

#SDA#RAG#SSE#Docker#LLM#后端#前端

背景

上次修完 chat.html 的 6 个渲染 bug 后,排查出一份 7 项优化清单。今天一口气处理了其中 5 项(去掉已存在的删除确认和够用的上传进度),涵盖字段对齐、容器修复、多轮对话、流式输出、健康检查五个维度。


🐛 Bug 1:引用来源从不显示

现象

每次提问后,回答正常,但下方的引用来源片段一直都是空的。

根因

后端 POST /documents/qa 返回的 JSON 字段名叫 snippets(数组),里面的对象字段是 textsource。但前端 chat.html 读取的是 data.references,以及 ref.contentref.filename——字段名全都不匹配。

数据层后端实际字段前端读取字段
数组名snippetsreferences
内容字段textcontent
来源字段sourcefilename

修复

三行映射对齐,data.referencesdata.snippetsref.contentref.textref.filenameref.source


🐛 Bug 2:容器无法启动(entrypoint.sh 换行符)

现象

docker compose up -d 后容器不断重启,日志显示 Restarting (255)

根因

scripts/entrypoint.sh 第一行 #!/bin/bash 在 Windows Git 检出时被自动转为 CRLF(\r\n),容器内 Linux 看到的是 #!/bin/bash^M,找不到解释器。

修复

cat scripts/entrypoint.sh | tr -d '\r' > tmp && mv tmp scripts/entrypoint.sh

然后重建镜像(docker compose up -d --build),容器正常启动。

教训:Windows 开发环境下,shell 脚本统一用 .gitattributes 锁定 LF。


⚡ 功能 3:多轮对话上下文记忆

需求

之前每次提问都是独立的——问完”审批节点有几个”,再问”详细说明一下”,AI 不知道在说什么。

方案

保留最近 10 轮对话历史(20 条消息),每次请求时一起发给 LLM。开销极小:多 2500 tokens 左右,在 128K 上下文里只占 2%。

实现(4 个文件)

文件改动
schemas/api.pySearchRequest 加 `history: list[dict]
core/llm_client.pygenerate_with_context 接收 history,_build_messages() 统一拼接
api/rag.pyQA 端点传 req.history 给 LLM
chat.html维护 conversationHistory,请求时 slice(-20) 发送,回答后 push,错误时 pop 回滚

额外加了 /reset 命令——输入框里打 /reset 回车即可清空历史,重新开始。


⚡ 功能 4:流式输出(SSE)

需求

替代”等 30 秒 → 一次性出结果”的加载圈体验,改成 ChatGPT 那样逐字冒出来。

方案

传统方案:后端等 LLM 返回全部文本 → 打包 JSON → 前端一次性渲染。

流式方案:后端向 Ollama/DeepSeek 请求时设置 stream: true,拿到 token 就通过 SSE 推给前端,前端用 ReadableStream 逐字追加到 DOM。

[前端 fetch] ──────────────────────────────────→ [后端 SSE]
  reader.read() ← data: {"type":"token","content":"审批"}
  reader.read() ← data: {"type":"token","content":"节点"}
  reader.read() ← data: {"type":"token","content":"包含"}
  ...
  reader.read() ← data: [DONE]
  → 渲染完成,展示引用来源

实现(3 个文件)

文件改动
llm_client.py新增 generate_with_context_stream()_ollama_chat_stream()_deepseek_chat_stream()
rag.py新增 POST /documents/qa/stream SSE 端点,先推 snippets 再逐 token 推
chat.htmlask() 改用 fetch + reader,创建空 bot 气泡并用 textSpan.textContent 实时追加

关键技术细节

  • Ollama 流式:NDJSON 格式,每行一个 {"message":{"content":"token"}}
  • DeepSeek 流式:SSE 格式,data: {"choices":[{"delta":{"content":"token"}}]},以 data: [DONE] 结尾
  • 前端 buffer 处理TextDecoder + 行分割,最后一条不完整行保留到下次循环

⚡ 功能 5:Docker 健康检查

需求

docker ps 能看到容器到底健康不健康,而不是盲猜”应该跑着吧”。

实现

服务探测方式端口
apppython -c "import urllib.request; urlopen('http://localhost:8000/health')"8000
qdrantbash /dev/tcp 发 HTTP GET,检查 2006333

配置参数:interval: 30stimeout: 10sretries: 3、app start_period: 60s(留够模型加载时间)。

最终 docker ps 输出:

smart-doc-analyzer-app-1      Up xx seconds (healthy)
smart-doc-analyzer-qdrant-1   Up xx seconds (healthy)

总结

今天处理的 5 项覆盖了 SDA 的稳定性(容器启动、健康检查)、准确性(引用来源)和体验(多轮对话、流式输出)三个维度。核心改动集中在 chat.htmlllm_client.pyrag.py 三个文件。

下一阶段计划:迁至台式机(RTX 5090D 32GB),彻底解决笔记本内存瓶颈。