diff --git a/docs/acceptance-r4-20260911.md b/docs/acceptance-r4-20260911.md new file mode 100644 index 0000000..d9b0215 --- /dev/null +++ b/docs/acceptance-r4-20260911.md @@ -0,0 +1,485 @@ +# 验收用例:r4 六项缺陷(2026-09-11) + +- 状态:修复前落库(测试设计),dev 自测与复测在修复完成后按 TL 通知执行。 +- 范围:176 手工测试 Excel 缺陷 #27(会话级聚合显示串行)、#28(空态:无关问题出现 30 条无关文献)、#32(知识图谱「水稻」无响应)、#38(MinerU 上传提取瞬间打断)、#47(DOCX 导出格式不一致 + 关闭文档后再返回看不到)、#52(上传资料后可问答但无法「总结论文」)。 +- 修复分工:Dev-F 前端(ui/)、Dev-B1 后端(sdk/)、Dev-B2 后端(agent/ + vstore/)。本文件是修复目标、dev 自测依据与复测依据。 +- 对应修复基准:仓库 `feature/bioflow-conversation-orchestration` @ 7840527(编制时干净)。修复落在 175 各 debug worktree,合入主仓库后复测。 + +## 0. 环境与执行约定 + +| 标识 | 环境 | 用途 | +|---|---|---| +| S1 | 175 dev 实例(vite dev server + 独立 dev BFF 9090) | 功能正确性复测。绝不可打生产 8090、绝不碰部署树与 176。浏览器实测用 vite dev server + Mac 隧道,端口由 TL 复测时提供。 | +| S2 | 176 终验(生产拓扑 80 端口,真实 QA/Neo4j/MinerU,CPU 无 GPU) | 本轮全部标「待部署后执行」:部署 agent 部署新版本后按本文执行。 | + +编制时环境快照(复测前请 TL 确认): +- dev BFF 9090 进程在(uvicorn app.main:app --host 127.0.0.1 --port 9090);`app/` 树不在主仓库内,dev BFF 的可达地址与启动方式以 TL 为准。 +- vite dev server(5180)编制时未起,TL 复测时确认已起并提供隧道方式。 +- 抽取上游:dev BFF → methodology 主 8000(回落 8093),即 agent/methodology 的 `/api/v1/methodology/*`。 +- vstore 服务:127.0.0.1:8094(175 本机可直连,供 #52 断言)。 +- dev BFF env 已见 `VSTORE_BASE_URL=http://127.0.0.1:8094`。 + +通用约定: + +- 浏览器证据:DevTools → Console(错误)、Network(请求/响应体)、关键帧截图存 `~/acceptance-r4/缺陷号-步骤.png`(175 或本机均可,缺陷单里写路径)。 +- API 证据:curl 响应全文粘贴进缺陷单,或 `-o /tmp/acceptance-r4/xx.json` 存档。 +- 所有「"}'`(请求体以实际契约为准;或走 `/api/qa/chat/stream` 取 evidence 帧)。 + - 断言:200;`sources[]` 每个条目的 `title` 为空串/null 的个数 = 0——title 缺失时已从 metadata 回填;回填后 title 为可读文本(如实体名/文献标题),而非正文片段。 + - 断言:每个条目 `paper_id`/`id`/`score` 字段仍在(回填不得破坏原字段)。 +2. **UI 列表每条可读标题(S1,浏览器)**: + - 发送上述问题,切「文献来源」TAB。 + - 断言:每条 `.ft-name` 显示非空可读文本;不存在以正文片段开头的条目(如 "RESEARCH ARTICLE…");悬停 tooltip(`:title` 属性)与显示文本一致或为完整标题。 +3. **paper_id 一致、无串行(多轮)**: + - 同一会话连续两轮问答后看聚合列表。 + - 断言:条目徽标 id 与 API `/api/sessions//sources` 的 `paper_id`(无 paper_id 按 id)一一对应,无错位、无「+1」偏移;两轮都出现的文献合并为一条,count ≥ 2。 +4. **新回答完成后强制刷新(S1)**: + - 停在「文献来源」TAB,记录当前列表条数与最后一条。 + - 发送新问题(第三轮,可命中新文献),等答案完成。 + - 断言:Network 在回答完成后出现新的 `GET /api/sessions//sources` 请求(无需手动切 TAB/刷新页面);列表更新为新会话聚合结果(新增文献出现、count 更新)。 +5. **S2 终验(待部署后执行)**:176 真机复跑 2~4,多轮问答(≥3 轮)后列表无串行、每条 title 可读。 + +**通过标准**:1~4 全过(S1);5 部署后过。 +**证据**:`/api/qa/search` 与 `/api/sessions//sources` 响应全文、文献来源 TAB 前后截图、Network 中 sources 请求时间线。 + +--- + +## 2. 缺陷 #28 检索空态:无关问题返回 30 条无关文献 + +| 项 | 值 | +|---|---| +| 环境 | S1(功能);S2 终验一次(待部署后执行) | +| 用户原话 | 「输入『景甜是谁』应显示未命中,右侧不应出现 30 条无关文献」 | + +**缺陷描述 / 根因摘要** + +QA 检索链无相关性下限:`agent/qa/src/rag/qa_v1_1_73/pipeline.py` `_select_graph_balanced`(L3313)无条件取 top-30;实测「景甜是谁」30 条得分全部 0.19-0.20(纯植物学无关文献);Qdrant 查询(`v16_retriever.py` `query_points`)无 score_threshold。前端空态文案已存在(`sources.length===0` 即显示「没有检索到此问题的有效回答」)。 + +修复点: +1. `_select_graph_balanced` 加相关性下限(建议 0.25,常量配置化,如模块常量或 env 可覆盖),低于下限不入选;全部低于下限 → 返回空 sources。 +2. Qdrant 查询加同一下限 `score_threshold` 双保险。 +3. 修复后 `/api/qa/search` 对无关问题的 `sources` 为空数组;`degraded` 保持 false(正常空结果,不得与 TIMEOUT 降级混淆)。 + +**前置数据** +1. 准备无关问题:「景甜是谁」;备选冷门问题(建议用不存在实体,如「请问 ZXCVQWER12345 这种字符串与植物有什么关系」)。 +2. 准备正常领域问题(回归用):与 arginine(精氨酸)相关的植物学问题,确保可命中文献。 + +**操作步骤与断言** + +1. **API 空 sources(S1,curl)**: + - `curl -s -X POST $BFF/api/qa/search -H 'Content-Type: application/json' -d '{"question":"景甜是谁","session_id":"","use_llm":false}'`(字段以实际契约为准,参考 r3 附录 A)。 + - 断言:200;响应 `sources` 为空数组 `[]`;`degraded` 为 false(或不存在该字段);不是 TIMEOUT 降级体。 +2. **UI 空态文案(S1,浏览器)**: + - 检索模式(输入框模式 pill 选「检索」)发送「景甜是谁」。 + - 断言:消息区显示「没有检索到此问题的有效回答」(`T.searchNoAnswer`);无 pipeline 卡死、无错误信封样式。 + - 「检索过程」TAB 显示「本次检索未命中文献」(`T.sourceEmpty`)。 + - 右侧「文献来源」TAB 列表为空,不出现 30 条无关文献(旧缺陷)。 +3. **正常领域问题回归(S1)**: + - 发送 arginine 相关问题。 + - 断言:`sources` 非空、右侧文献列表正常、答案正常生成——下限不得误伤正常植物学问题。 +4. **常量配置化(代码级,dev 自测)**: + - 断言:下限为具名常量(非魔法数字),阈值与 Qdrant `score_threshold` 同源或同值;文档/注释注明可配置方式(env 或配置项)。 + - 建议 dev 自测加桩:构造 30 条全部低于下限的 items → 选入 0 条;构造 5 条高于+25 条低于 → 仅选入 5 条(或按 graph 配额逻辑)。 +5. **S2 终验(待部署后执行)**:176 复跑 1~3;另选真实冷门问题 2~3 个(由 TL 指定),全部为空 sources + 空态文案。 + +**通过标准**:1~4 全过;5 部署后过。 +**证据**:`/api/qa/search` 响应全文(含 sources 长度)、消息区空态文案截图、文献来源 TAB 空列表截图、arginine 正常问题对照截图。 + +--- + +## 3. 缺陷 #32 知识图谱 TAB 搜索「水稻」无响应 + +| 项 | 值 | +|---|---| +| 环境 | S1(功能);S2 终验一次(待部署后执行) | +| 用户原话 | 「搜索『水稻』响应超时无结果」 | + +**缺陷描述 / 根因摘要** + +非超时:nginx 日志证明 7 次查询全部 200 返回 31 字节空 JSON,直测 0.022s;「水稻」在库中无命中属正常(库为英文数据)。缺陷是前端 `ui/src/components/bench/KgPane.vue` 成功空结果(`results.length === 0` 且无 error)时界面无任何提示,被误读为超时。 + +修复点:纯前端,空结果时显示「未找到相关实体」类提示(建议新增 i18n 键,如 `kgNoResult`;与 `kgDown` 离线提示、`kgEmpty` 空态区分)。正常实体搜索路径不得回归。 + +**前置数据** +1. 知识图谱 TAB 可打开、`GET /api/kg/overview` 200。 +2. 已知命中实体:arginine(英文库)。 + +**操作步骤与断言** + +1. **API 空结果 200(S1,curl)**: + - `curl -s -w '\ntime_total:%{time_total}s\n' "$BFF/api/kg/entities?q=水稻&limit=20"`。 + - 断言:200;`results` 为空数组(或 count=0);time_total 记录实测值(预期亚秒级——旧缺陷实为 0.022s,若 >3s 记 WARN 并排查)。 +2. **UI 空结果提示(S1,浏览器)**: + - 知识图谱 TAB 搜索框输入「水稻」→ 回车/点搜索。 + - 断言:搜索完成后界面出现明确提示「未找到相关实体」类文案(以修复后 i18n 键为准);不再空白/无反应;搜索按钮恢复可用,可继续搜索。 +3. **正常实体回归(S1)**: + - 搜索「arginine」。 + - 断言:结果列表渲染命中实体(名称/类型/support_count);点击实体 → 详情面板正常;无「未找到」提示误现。 +4. **状态区分(S1)**: + - 断言:空结果提示、`kgDown`(服务不可用/超时)、`kgEmpty`(初始空态)三者文案不同、触发条件互斥;搜索进行中 loading 态正常显示与消失。 +5. **S2 终验(待部署后执行)**:176 复跑 1~3,时延 <1s;「水稻」空结果提示明确。 + +**通过标准**:1~4 全过;5 部署后过。 +**证据**:curl 输出(含 time_total)全文、空结果提示截图、arginine 命中截图。 + +--- + +## 4. 缺陷 #38 MinerU 上传提取任务瞬间被打断(exit 1,无失败原因) + +| 项 | 值 | +|---|---| +| 环境 | S1(模拟启动期失败);S2 终验一次(待部署后执行,真实 CPU MinerU) | +| 用户原话 | 「上传 wang2019.pdf 失败,exit 1,无失败原因」 | + +**缺陷描述 / 根因摘要(机制级)** + +任务在最早阶段(任何 pipeline 日志出现前)抛出无消息异常 → API 侧 task.error 为空串 → CLI 拼出空 message 的 "task(s) failed" → API 异常日志打向 stdout 被块缓冲、CLI 失败后立即杀 API 进程导致真因丢失。触发因素:失败窗口 74 秒前 176 主机 GPU 显示器重配置(GNOME 会话重注册、NVIDIA DFP 重连),mineru 启动期有设备探测(GPU Memory 日志行)。 + +修复点(`agent/methodology/api/methodology_routes.py` 与 `agent/legacy-rag/api/methodology_routes.py` 的 `build_mineru_runtime` 及 `run_uploaded_methodology_job`,两处同改): +1. env 注入 `PYTHONUNBUFFERED=1`(子进程 stdout 不块缓冲,真因即写即落)。 +2. returncode≠0 且 job.log 无 `doc_analyze_streaming` 行(成功任务实测必有此行)= 判定启动期失败 → 自动重试 1 次。 +3. 重试仍失败且仍无 pipeline 行 → status.json 写入可读中文错误(如「提取启动阶段失败,可能受主机 GPU/显示重配置干扰,请重试」)。 + +**前置数据** +1. 样例 PDF(小文件即可,如 wang2019.pdf 或任意文字型 PDF)放 175。 +2. 启动期失败 stub:`/bin/false` 或一个 exit 1、无 stdout 输出的脚本(如 `#!/bin/sh\nexit 1`)。 +3. **模拟配置纪律(重要)**:制造启动期失败需把 dev 抽取上游(8000 methodology 服务)的 `MINERU_CMD` 临时指向 stub 并重启该服务。按环境纪律,复测 agent **不执行**服务启停与配置修改;此模拟由 TL 授权人员执行、验后还原并重启。复测 agent 只执行上传与断言。175 有真实 GPU mineru,正常路径用例 5 无需模拟。 + +**操作步骤与断言** + +1. **PYTHONUNBUFFERED 注入(代码级,dev 自测)**: + - 断言:两处 `build_mineru_runtime` 返回的 env 均含 `PYTHONUNBUFFERED=1`(含 legacy 文件);子进程调用使用该 env。 +2. **启动期失败 → 自动重试 1 次(S1,需 stub 模拟)**: + - stub 就位后,上传 PDF:`curl -s -X POST $BFF/api/extraction/upload -F "file=@" -F "paper_id=r4-38-test"`(BFF 代理到 8000 `/api/v1/methodology/extract-upload`;返回 job_id)。 + - 轮询 `curl -s "$BFF/api/extraction/jobs/"` 至终态(≤120s)。 + - 断言:job 目录 `job.log` 中出现**两次** mineru 命令启动行(两次 `$ ... mineru ...`),即发生一次自动重试;日志/状态中有可识别的重试标记(修复实现的自有标记,以实际字段为准,如 message 或 log 行含 retry 字样)。 +3. **重试仍失败 → 可读错误 + 日志未丢失(S1,同上模拟)**: + - 断言:终态 status 为 failed;`GET $BFF/api/extraction/jobs/` 返回的 `message` 为中文可读、含「启动阶段失败」类提示(非空串、非裸 "task(s) failed");`GET $BFF/api/extraction/run-log` 与上游 `GET /api/v1/methodology/run-log` 内容非空(修复前真因被块缓冲丢失,修复后日志可见)。 +4. **pipeline 行判定不误伤(代码级 + 正常路径回归)**: + - 断言:正常成功任务的 job.log 确实含 `doc_analyze_streaming`(抽样历史成功任务日志验证);启动期判定仅当「无 pipeline 行 + returncode≠0」同时成立才触发重试——mineru 中途失败(已有 pipeline 行)不得触发重试。 +5. **正常路径回归(S1,175 真实 mineru)**: + - 还原 stub 后上传正常 PDF:job queued → running(mineru) → llm → completed;运行日志可见 pipeline 输出;无异常重试发生。 +6. **S2 终验(待部署后执行)**:176 真实 CPU MinerU 上传含图 PDF 走通全链;日志无 GPU 探测崩溃;如遇启动期失败,应看到自动重试 + 中文可读错误而非瞬间失败。 + +**通过标准**:1~4 全过;5 过(正常路径);6 部署后过。 +**证据**:job.log 全文、`/api/extraction/jobs/` 响应全文、run-log 截图、正常任务对照日志。 + +--- + +## 5. 缺陷 #47 DOCX 导出格式不一致 + 关闭文档后再返回看不到文档 + +| 项 | 值 | +|---|---| +| 环境 | S1;S2 终验一次(待部署后执行) | +| 用户原话 | 「导出 DOCX 格式不一致(与预览红头样式不同);关闭文档后再返回看不到文档」 | + +### 5a. DOCX 导出样式不一致 + +**缺陷描述 / 根因摘要** + +导出 DOCX 走 `sdk/app/services/docgen/html2docx.py`,红头样式硬编码 22pt `RGBColor(255,0,0)`(FF0000);而 PDF/预览走模板 CSS(`official.css`:红头 30px、`#c00000`,h1 22px,h2 16px)。两条路径不一致。 + +修复点:html2docx 的标题/红头样式改从 `render_html` 产物解析(优先内联样式/模板 CSS 声明,失败回退现有常量)。具体解析策略由 Dev-B1 读产物结构后确定;注意 html2docx 输入是渲染后 HTML(含模板 css 注入或内联样式),而非原始 markdown。 + +**前置数据** +1. 生成一份「通用公文(红头)」文档(模板 official;模板 CSS 事实源 `sdk/app/services/docgen/templates/official.css`:红头 `#c00000` 30px bold 居中、`h1.doc-title` 22px 黑体居中、h2 16px、h3 15px)。 +2. 另一份非红头模板文档(如 report)用于回归。 + +**操作步骤与断言** + +1. **导出 DOCX 与预览一致(S1,浏览器)**: + - 打开红头文档 → 预览确认红头红色居中、标题层级清晰(截图留证)。 + - 点「导出 DOCX」→ 下载后本地打开(Word/LibreOffice)。 + - 断言:红头文字为红色(**色值 #c00000 系**,非 FF0000 亮红)、加粗、居中;字号与 30px 对应(≈22.5pt,以修复实现解析结果为准,允许 ±10%);h1 标题 22px≈16.5pt 居中黑体;h2/h3 层级与预览一致;表格/图片不回归(r3 已修内容塌缩)。 + - 与「导出 PDF」或 HTML 预览逐项对照:红头/标题的色值、字号、对齐一致。 +2. **样式来源(代码级,dev 自测)**: + - 断言:html2docx 中红头/标题样式不再硬编码 FF0000/22pt 常量,而是从 render_html 产物解析;解析失败时回退常量(回退分支有单元测试覆盖)。 +3. **非红头模板回归(S1)**:report 模板导出 DOCX,断言样式与预览一致、无红头误渲染。 + +**通过标准**:1~3 全过。 +**证据**:预览截图、DOCX 打开截图(红头色值/字号/对齐)、PDF 对照截图。 + +### 5b. 关闭文档后再返回看不到文档(视图状态机) + +**缺陷描述 / 根因摘要** + +前端 DocPane 关闭文档 → 返回 → 再关闭 的状态机问题:`ui/src/components/bench/DocPane.vue` 的 viewMode('doc'|'library')切换与 shadow DOM 宿主重挂、`ui/src/stores/docgen.js` `close()` 后 activeArtifact/viewMode 残留。当前 close() 实现:`viewMode='doc'; activeArtifact=null`,修复需覆盖关闭→返回→再关闭及反复切换资料库/文档视图的路径。 + +**前置数据** +1. 已有至少一个生成完成的文档(红头文档即可)。 + +**操作步骤与断言** + +1. **关闭→空态→再打开(S1,浏览器)**: + - 打开文档 A(文档 TAB 显示 A)→ 点「关闭文档」。 + - 断言:文档区回到空态(资料库面板或 `docPaneEmpty` 文案),无 A 残留。 + - 重新打开文档 A(从资料库/历史/或重新生成)。 + - 断言:A 正常显示(shadow 宿主重挂后内容完整渲染,无空白)。 +2. **再关闭不残留(S1)**: + - 再点「关闭文档」。 + - 断言:空态正常,无空白区/无半渲染状态;反复 3 次均正常。 +3. **资料库往返(S1)**: + - 打开文档 → 点「资料库」→ 断言显示「我的资料」列表。 + - 点「返回文档」→ 断言 A 内容正常显示;反复切换 3 次无空白、无残留。 +4. **状态残留断言(DevTools 辅助)**: + - close() 后(不刷新页面)检查:`docgen.viewMode === 'doc'`、`docgen.activeArtifact === null`(可用 Vue DevTools 或 console 读取 store 状态)。 + - 之后上传新资料/打开另一文档,断言新文档正常挂载、旧文档不闪现。 +5. **会话切换回归(S1)**:会话 A 有打开的文档 → 切会话 B → 文档 TAB 空态;切回 A → 文档恢复显示(或按修复语义:仅新会话生成后才挂文档,以修复说明为准,断言不出现 A 文档错挂到 B)。 +6. **S2 终验(待部署后执行)**:176 复跑 1~3 一轮。 + +**通过标准**:1~5 全过;6 部署后过。 +**证据**:各步截图(关闭/空态/重开/资料库往返)、Console 无报错日志、store 状态检查输出。 + +--- + +## 6. 缺陷 #52 上传资料后可以问答但无法「总结论文」 + +| 项 | 值 | +|---|---| +| 环境 | S1;S2 终验一次(待部署后执行) | +| 用户原话 | 「上传资料后可以问答,但无法『总结论文』」 | + +**缺陷描述 / 根因摘要(三层)** + +1. `ui/src/intent.js` 的 `WRITE_VERB`(L19)缺「总结/概括/归纳」→「帮我总结论文」+附件被路由到 QA 而非 docgen。 +2. QA 公共检索(Qdrant+Neo4j)与私有上传库(vstore 本地 FAISS)物理分离;前端有 FR-PRIV 私有桥(`ui/src/api/private.js` `askPrivate`,在 chat.js `_firePrivate` 并行触发)会联查私有库,但该文档被重复上传 4 次(4 个 doc_id × 200 chunk = 800 条)挤爆检索预算。 +3. `vstore/vault.py` `add()` 无内容去重、`sdk/app/services/privateqa.py` `_pick_items` 只按 score 取 top_k 再每 doc ≤3 去重(PRIVATE_QA_PER_DOC_MAX=3),导致作者页重复块占满 top-8、正文块从未进入上下文。 + +修复点: +1. `WRITE_VERB` 补「总结/概括/归纳」(带附件的写类请求进 docgen;无附件纯问答不受影响)。注意后端同词典 `sdk/app/services/docgen/intent.py` 与用例表 `docs/team/cases_intent.md` 需同步(dev 自测核对)。 +2. `vault.add()` 按内容 SHA256 去重:同内容重复上传覆盖旧 doc_id 并清理旧 chunk。 +3. `_pick_items` 改为先按 doc_id 聚合(doc 级 max score 排序取 top 文档)再取各 doc 内最相关块(每 doc ≤3)。 + +**前置数据** +1. 一份论文 PDF(含作者页+正文,如 wang2019.pdf)。 +2. 干净的测试环境:如库中已有该论文的旧重复上传,复测前由 TL 确认清理方式(或换用新文件规避)。 + +**操作步骤与断言** + +1. **意图路由(前端,dev 自测 + S1 浏览器)**: + - 「帮我总结论文」+ 附件发送。 + - 断言:路由到 docgen——Network 出现 `POST /api/docgen/generate`(或 docgen 骨架块),不出现 `/api/qa/search` 主请求;生成任务开始(queued/running)。 + - 「总结/概括/归纳」类动词补入 `WRITE_VERB`(代码级断言,前端 intent.js;后端 intent.py 同步检查)。 + - 边界回归:无附件「总结一下植物光合作用的过程」→ 不进 docgen(无 DOC_TYPE 命中,仍 qa);「写一篇总结」仍进 docgen(DOC_TYPE 含「总结」)。**注意**:加入 WRITE_VERB 后,无附件「总结这篇论文」因 DOC_TYPE 含「论文」也会命中 docgen 规则 3——此行为变化点由 TL 裁决是否可接受,复测按 TL 裁决结果断言。 +2. **重复上传去重(S1,curl + 175 本机 vstore)**: + - 上传同一 PDF 两次(浏览器拖拽或 `POST /api/docgen/library` 上传两次)。 + - 断言:`GET /api/docgen/library` 中该文件的条目行为以修复实现为准(覆盖旧 doc_id 或仅 1 条);关键断言在 vstore 层: + - `curl -s "http://127.0.0.1:8094/api/vstore/search-chunks?q=<论文正文关键词>&top_k=20&min_score=0"`(175 本机执行)。 + - 断言:返回 items 中该文件的 `doc_id` 去重后仅 1 个;chunk 总数不因重复上传翻倍(旧 doc_id 的 chunk 已清理,不再出现在结果中)。 +3. **私有检索块多样性(S1,curl)**: + - `curl -s -X POST $BFF/api/qa/private -H 'Content-Type: application/json' -d '{"question":"总结这篇论文的主要方法和结论","top_k":8}'`(字段以实际契约为准)。 + - 断言:`items[]` 的 doc_id 多样性改善——不再由单一文档(作者页)占满 top-8;items 中含正文块(以 snippet 内容判断:出现正文/方法/结果语义,而非清一色作者列表);`mode:"hit"` 且 answer 能引用正文内容(可断言 answer 非空且非自谦语)。 +4. **端到端(S1,浏览器)**: + - 「帮我总结论文」+ 该 PDF → docgen 任务完成,文档内容引用论文正文(打开文档核对内容与论文相关,非空泛)。 +5. **S2 终验(待部署后执行)**:176 复跑 1、3、4。 + +**通过标准**:1~4 全过;5 部署后过。 +**证据**:Network generate 请求、`/api/vstore/search-chunks` 响应全文(doc_id 去重清单)、`/api/qa/private` 响应全文、生成文档内容截图。 + +--- + +## 7. 回归矩阵 + +修复波及面 → 逐条「做什么 + 看什么」。执行于 S1,任一条失败登记缺陷单并标注关联修复项。 + +### G1. 检索相关性下限(关联 #28,防误伤) + +| 回归项 | 操作 | 看什么 | +|---|---|---| +| G1-1 正常问题检索 | arginine/水稻抗病类正常问题 2 个 | sources 非空、条数合理,下限未把正常问题全部过滤 | +| G1-2 检索过程 TAB | 正常问题提问 | 检索过程时间线正常,无 pipeline 卡死 | +| G1-3 得分边界 | 中低相关度问题(由 TL 指定一个 0.2~0.3 边缘问题) | 行为与阈值语义一致(低于下限空、高于下限入),记录实测 | + +### G2. 意图路由(关联 #52-1) + +| 回归项 | 操作 | 看什么 | +|---|---|---| +| G2-1 普通问答 | 「你好,介绍一下你自己」 | 走 QA,不误进 docgen | +| G2-2 无附件总结类 | 「总结一下植物光合作用的过程」 | 走 QA(无 DOC_TYPE) | +| G2-3 写文档 | 「帮我写一份水稻研究报告」 | 仍进 docgen | +| G2-4 生信分流 | 「分析水稻基因家族」 | 仍进生信流,不被新动词干扰 | +| G2-5 意图单测 | 跑 intent.js/intent.py 词典用例表 | 全绿,前后端词典一致 | + +### G3. vstore 去重(关联 #52-2) + +| 回归项 | 操作 | 看什么 | +|---|---|---| +| G3-1 不同内容上传 | 上传两个不同 PDF | 两个 doc_id 各自独立,互不覆盖 | +| G3-2 同名不同内容 | 同名但内容不同的两个 PDF | 按 SHA256 不误判为重复(若实现按内容哈希,同名不同内容应保留两个 doc_id) | +| G3-3 资料删除 | 删除被覆盖前的引用(如有) | 无悬空引用、无 500 | + +### G4. source 归一化(关联 #27-1) + +| 回归项 | 操作 | 看什么 | +|---|---|---| +| G4-1 normalize 单测 | sdk 单测(normalize 用例) | 全绿;title 回填新增用例覆盖 | +| G4-2 来源字段 | 正常检索问题的 sources | paper_id/score/snippet/url 字段不回归 | +| G4-3 图谱来源 | 图链命中的来源条目 | title 回填后实体名/文献标题仍正确(不被错误字段覆盖) | + +### G5. DocPane/docgen 状态机(关联 #47b) + +| 回归项 | 操作 | 看什么 | +|---|---|---| +| G5-1 生成后自动打开 | 完成一次文档生成 | 文档自动挂载显示正常(viewMode/activeArtifact 状态正确) | +| G5-2 编辑/预览/导出 | 打开文档后编辑→预览→导出 | 三态正常,切换不丢内容 | +| G5-3 二次预览 | 开文档 A 预览后再开文档 B | B 正常渲染无空白(r3 回归) | +| G5-4 修改文档 | 编辑模式输入修改指令 | 修改生效无「修改失败」 | + +### G6. MinerU 重试(关联 #38) + +| 回归项 | 操作 | 看什么 | +|---|---|---| +| G6-1 正常任务不重试 | 175 上传正常 PDF | 全程仅 1 次 mineru 启动,无多余重试 | +| G6-2 中途失败不重试 | (可选,代码级)构造已有 pipeline 行后失败 | 不触发「启动期重试」,错误路径为原有可读错误 | +| G6-3 提取 TAB 轮询 | 上传任务 running | 轮询/日志刷新正常(r3 回归) | + +--- + +## 8. 复测流程约定 + +1. **完成门禁**:六项缺陷(#27/#28/#32/#38/#47a/#47b/#52)全部 S1 项通过,缺陷单清零,才算修复完成;S1 失败项回归修复后重测该项 + 其回归矩阵关联行。 +2. **S2 全部标「待部署后执行」**:本轮不执行。176 部署新版本后按本文各「S2 终验」项执行;执行前提:176 部署完成(TL/部署 agent 通知)、真实 QA/Neo4j/MinerU 可用。 +3. **模拟配置纪律**:#38 启动期失败模拟需临时改 dev 抽取上游(8000)的 `MINERU_CMD` 并重启该服务——由 TL 授权执行并负责**用后即还原+重启**;复测 agent 不执行任何服务启停与配置修改。#28 若需调阈值模拟(如临时把下限调高验证过滤),同样按「TL 授权改常量/配置 → 重启 dev 相关服务 → 验后还原」执行。 +4. **复测环境纪律**:S1 只用 175 dev(vite + dev BFF 9090),绝不碰生产 8090、部署树与 176。vstore(8094)与 methodology(8000)只读查询或按第 3 条授权执行。 +5. **执行顺序**:先 API(curl 层)后 UI;#38 的 stub 模拟放在其他用例完成后进行,验后立刻通知还原。 + +## 9. 缺陷单模板 + +复测发现缺陷按此登记(记入复测报告,由 TL 归档): + +``` +缺陷编号: r4-<原始缺陷号>-<序号>(如 r4-27-01) +关联修复项: #27(Dev-B1 normalize_source / Dev-F SourceRow+bench store) +复现步骤: 1. … 2. … +实际结果: … +期望结果: … +证据: 截图路径 / API 响应全文 / 日志路径(dev BFF /tmp/r3-dev-bff*.log、上游 8000 日志、vstore 日志) +严重级: P0(阻断) / P1(功能错误) / P2(体验) / P3(建议) +复测环境: S1(175 dev) / S2(176,待部署后) +``` + +## 附录 A:快速 curl 断言清单(S1) + +```bash +BFF=http://127.0.0.1:9090 # dev BFF,复测时以 TL 给的地址为准 + +# #27 会话来源聚合(title 可读性 / paper_id / count) +curl -s $BFF/api/sessions//sources | jq '.sources[] | {id, paper_id, count, title}' +curl -s -X POST $BFF/api/qa/search -H 'Content-Type: application/json' \ + -d '{"question":"水稻抗病基因有哪些","session_id":""}' | jq '.sources[] | {paper_id, title, score}' + +# #28 空态(无关问题 → 空 sources;degraded 必须 false) +curl -s -w '\ntime_total:%{time_total}s\n' -X POST $BFF/api/qa/search \ + -H 'Content-Type: application/json' \ + -d '{"question":"景甜是谁","session_id":"","use_llm":false}' + +# #32 图谱空结果(200 + 空 results;记录 time_total) +curl -s -w '\ntime_total:%{time_total}s\n' "$BFF/api/kg/entities?q=水稻&limit=20" + +# #38 提取(上传 + 轮询 job;message 中文可读断言) +curl -s -X POST $BFF/api/extraction/upload -F "file=@" -F "paper_id=r4-38-test" +curl -s "$BFF/api/extraction/jobs/" | jq '{status, stage, message}' +curl -s "$BFF/api/extraction/run-log" +# 直连上游(175 本机,只读):job.log 重试行数 +ssh xzq@<175> "cat data/evaluation/methodology_upload_jobs//job.log" 2>/dev/null +curl -s "http://127.0.0.1:8000/api/v1/methodology/extract-jobs/" + +# #52 私有检索(块多样性:doc_id 去重计数) +curl -s -X POST $BFF/api/qa/private -H 'Content-Type: application/json' \ + -d '{"question":"总结这篇论文的主要方法和结论","top_k":8}' \ + | jq '{hit, mode, doc_ids: [.items[].doc_id] | unique | length, items: [.items[] | {doc_id, score, snippet}]}' +# vstore 层查 chunk(175 本机执行;重复上传后 doc_id 应仅 1 个) +curl -s "http://127.0.0.1:8094/api/vstore/search-chunks?q=<论文关键词>&top_k=20&min_score=0" \ + | jq '{count, doc_ids: [.items[].doc_id] | unique}' +curl -s "$BFF/api/docgen/library" | jq '.items[] | {id, name}' +``` + +## 附录 B:修复目标契约速查(断言按此) + +``` +POST /api/qa/search 无关问题 → 200, sources:[], degraded:false(正常空结果) + (修复目标:QA 检索链相关性下限建议 0.25 常量配置化;Qdrant 同下限 score_threshold) +GET /api/kg/entities?q=水稻&limit=20 → 200, results:[](空结果;非超时) + (修复目标:KgPane 空结果显示「未找到相关实体」类新 i18n 提示) +GET /api/extraction/jobs/{id} 启动期失败 → status:failed + message 中文可读 + (修复目标:PYTHONUNBUFFERED=1;无 doc_analyze_streaming 行且 returncode≠0 → 自动重试 1 次; + 重试仍失败 → status.json 写「提取启动阶段失败,可能受主机 GPU/显示重配置干扰,请重试」) +GET /api/sessions/{SID}/sources → 每条目 title 非空(metadata 回填)、paper_id 与 UI 徽标一致 + (修复目标:normalize_source 回填 + SourceRow fallback + 新回答完成后强制重拉) +POST /api/docgen/export DOCX → 红头样式与模板 CSS 一致(#c00000 系,30px≈22.5pt,居中加粗) + (修复目标:html2docx 从 render_html 产物解析样式,回退常量) +POST /api/qa/private → items doc 级多样性(先按 doc_id 聚合再取块,每 doc ≤3) + (修复目标:_pick_items 重排;vault.add 内容 SHA256 去重覆盖旧 doc_id) +「总结/概括/归纳」入前端 WRITE_VERB(后端 intent.py 词典同步);带附件+写动词 → docgen +``` + +--- + +## 10. TL 实测记录(r4 修复回改后,dev 9271/9090/5180) + +- 日期:2026-09-11 晚;环境:175 dev 栈(9271 QA/9272 methodology/9273 legacy/9274 vstore/9090 BFF/5180 vite,全部新代码)。 +- **#28 回改三轮(v2/v3)后实测**:景甜 0.86s sources=[];arginine search 0.82s sources=30;chat 回归中(rerank 候选腐烂,Dev-B1 修复后复测)。阈值定稿:**vector 域 0.45(env QA_MIN_VECTOR_SCORE,仅 retrieval_only 的 Qdrant score_threshold 与 select 层)、final 域 0.25(env QA_MIN_SOURCE_SCORE,chat 模式 rerank 后过滤)**。r4-28 原版「双保险 0.25」作废。 +- **#38 实测**:mineru 启动期失败(缺 accelerate)→ 自动重试 1 次 + 中文可读错误 + 原始日志尾部全部落地;装 accelerate 后全链 completed 100%(MinerU 10 页 + LLM 方法学提取)。175 minerupy312 环境已补 `accelerate 1.15.0`。 +- **#52b 实测**:同 PDF 上传 2 次,dev vstore 9274 chunk 索引仅 1 个 doc_id(34 条 chunk),不翻倍。 +- **#52c 实测**:私有问答「supplemental lighting 对植物生长响应的影响」→ hit:true、3 条引用块、答案引正文。 +- **#52a 实测**:BFF「帮我总结论文」→ docgen 202 queued(前后端词典同步)。 +- **#47a 实测**:红头 DOCX #C00000/22.5pt 居中,h1 16.5pt(模板 30px/22px 一致)。 +- **#27 实测**:BFF arginine search sources 30 条 title 全非空(实体名/论文标题回填),score 0.694 保留。 + +### 10.1 chat 回归发现与定位(TL 实测) + +- **r3 基线对照(176 生产,只读)**:同问题 chat/stream 62.3s,终帧 mode=chat source_count=29、sources=29(完整 title/pmid/pmcid/doi)。 +- **v3 dev(9271)异常**:同问题 chat sources=0、rerank_audit 前 10 bge≈0.020-0.047、rrf_score 全 0.0、graph_quota below_min_source_score=136。 +- **TL 探针定界**:reranker 模型+transformers 5.12.1 正常(有正文 logit=+6.73 / 空正文 -3.07);审计 bge 0.047=sigmoid(-3.07) → 进入 rerank 的条目 evidence 全空;rrf=0.0 连 method_bonus 都没有 → 条目未经过正常检索流水线(_pre_rerank 必给 qdrant 条目 vector+0.10≥0.7)。 +- **结论**:chat 空 sources 是 r4 引入的回归(r3 基线正常),0.25 final 门只是把已腐烂候选滤掉,病灶在候选来源。Dev-B1 定位修复中。 + +### 10.2 chat 回归修复实测(v4,dev 9271) + +- **根因(r3 既有隐患,175 环境暴露)**:175 未配 paper metadata registry → Qdrant 条目无 pmid 被 public_citation 过滤(244→136);剩 136 条归一化图断言 evidence 全空 → rerank 全垃圾分(bge 0.02-0.05)→ 0.25 终门全拒 → sources 空。176 配了 registry 故 r3 正常。 +- **修复(d54419f)**:item_from_payload 引用字段回填链 payload > registry > paper_id 派生(doi/pmcid),三种 paper_id 形态实测通过。 +- **实测(9271)**:chat 同问题 sources=16、bge 0.993-1.0、answer 6039 字、54.7s;景甜 search 0.87s 空态;arginine search 0.73s 30 条——三件套全过。 + +### 10.3 TL 终验(2026-09-11 晚,dev 栈第三次重建后,跳板机公网接入) + +- #28:景甜 1.47s sources=0 degraded=None(非降级);arginine 2.16s 30 条——过 +- #32:水稻 KG 0.38s 200 空 results——过 +- #27:arginine 30 条 empty_titles=0,scores 0.694/0.677/0.646——过 +- #38:缺 accelerate 真实启动期失败→自动重试+中文可读错误全链路实测;装 accelerate 后 completed 100%——过 +- #47a:DOCX #C00000/22.5pt 居中/h1 16.5pt(模板 30px/22px 一致)——过 +- #52a:帮我总结论文→docgen 202——过;#52b:vstore 1 doc_id 不翻倍——过;#52c:hit:true 3 块引正文——过 +- chat 全链:非流式 55.7s 20 sources 6414 字;流式 68.2s 16 sources 6107 字——过(此前一次流式卡「开始生成答案」系 QA 进程陈旧状态,重启后不复现) +- 单测账:vitest 373 + r3debug 68(含 r4fixes 31)+ sdk 306 + vstore 106 + qa score_gate 9 + mineru retry 16 + html2docx 12 + privateqa 8 + intent 59——全绿 +- 结论:六项缺陷修复全部验收通过,待用户命令后 push + 部署 176