测试 agent 编制的 S1/S2 验收用例 + TL §10 实测记录, 此前一直以未跟踪状态存在,本轮随 r4 修复链一并入库。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
36 KiB
验收用例: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存档。 - 所有「<Ns」阈值测量用
curl -w 'time_total: %{time_total}s';±10% 记 PASS(备注实测值),超阈值记 FAIL。 - 会话/消息接口基础约定:创建会话
POST /api/sessions {"title":"r4-xx"}返回id即SID,下文引用。
文案事实源(修复前后均以源码为准,断言文案若与修复实现有出入,以修复后 i18n 键为准并在缺陷单注明):
ui/src/i18n/labels.js:searchNoAnswer: '没有检索到此问题的有效回答'(已有,缺陷 #28 修复不动文案)、sourceEmpty: '本次检索未命中文献'、kgEmpty: '本次检索未产生图谱关系,可使用下方搜索浏览知识库'、kgSearchPlaceholder、kgDown。缺陷 #32 的空结果提示文案为新增键(修复后以KgPane.vue实际键为准,如kgNoResult: '未找到相关实体')。ui/src/i18n/docgen.js:文档 TAB「文档」、docPaneClose(关闭文档)、docPaneLibraryView(资料库)、docPaneBackToDoc(返回文档)、docPaneEmpty、docPaneExportDocx: '导出 DOCX'。- 端点契约(修复目标):
GET /api/kg/entities?q=水稻&limit=20空结果 → 200 +results: [](非超时);POST /api/qa/search无关问题 → 200 +sources: []+degraded: false(正常空结果,非 TIMEOUT);POST /api/qa/private→{ok, hit, mode, answer, items[]};提取 job 失败 →message中文可读。
1. 缺陷 #27 会话级聚合后右侧文献来源「显示串行」
| 项 | 值 |
|---|---|
| 环境 | S1;S2 终验一次(待部署后执行) |
| 用户原话 | 「多轮问答后右侧文献列表混乱」 |
缺陷描述 / 根因摘要
会话级聚合本身已正常;message 级 sources items 的 title 字段为空 → SourceRow.vue 的 ft-name 渲染出不可读条目(正文片段开头如 "RESEARCH ARTICLE..."),多轮后视觉上「串行/混乱」。
修复点三处:
- 后端
sdk/app/services/normalize.pynormalize_source:title 为空时从 metadata 回填(metadata 中可用的标题类字段,如 source_entity / 论文元数据)。 - 前端
ui/src/components/bench/SourceRow.vue:title 渲染空值 fallback(如 paper_id),不得渲染空文本。 - 前端
ui/src/stores/bench.jsloadSessionSources:新回答完成后缓存失效,强制重拉(force重拉)。当前实现if (!force && this.sessionSources) return this.sessionSources有缓存,修复需在 chat 流 onComplete 后触发失效。
前置数据
- 新建会话 SID。
- 准备两个可命中文献的检索问题(如「水稻抗病基因有哪些」「水稻抗病育种研究进展」),使两轮各产生若干文献来源。
操作步骤与断言
- API 层 title 可读(S1,curl):
curl -s -X POST $BFF/api/qa/search -H 'Content-Type: application/json' -d '{"question":"水稻抗病基因有哪些","session_id":"<SID>"}'(请求体以实际契约为准;或走/api/qa/chat/stream取 evidence 帧)。- 断言:200;
sources[]每个条目的title为空串/null 的个数 = 0——title 缺失时已从 metadata 回填;回填后 title 为可读文本(如实体名/文献标题),而非正文片段。 - 断言:每个条目
paper_id/id/score字段仍在(回填不得破坏原字段)。
- UI 列表每条可读标题(S1,浏览器):
- 发送上述问题,切「文献来源」TAB。
- 断言:每条
.ft-name显示非空可读文本;不存在以正文片段开头的条目(如 "RESEARCH ARTICLE…");悬停 tooltip(:title属性)与显示文本一致或为完整标题。
- paper_id 一致、无串行(多轮):
- 同一会话连续两轮问答后看聚合列表。
- 断言:条目徽标 id 与 API
/api/sessions/<SID>/sources的paper_id(无 paper_id 按 id)一一对应,无错位、无「+1」偏移;两轮都出现的文献合并为一条,count ≥ 2。
- 新回答完成后强制刷新(S1):
- 停在「文献来源」TAB,记录当前列表条数与最后一条。
- 发送新问题(第三轮,可命中新文献),等答案完成。
- 断言:Network 在回答完成后出现新的
GET /api/sessions/<SID>/sources请求(无需手动切 TAB/刷新页面);列表更新为新会话聚合结果(新增文献出现、count 更新)。
- S2 终验(待部署后执行):176 真机复跑 2~4,多轮问答(≥3 轮)后列表无串行、每条 title 可读。
通过标准:1~4 全过(S1);5 部署后过。
证据:/api/qa/search 与 /api/sessions/<SID>/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 即显示「没有检索到此问题的有效回答」)。
修复点:
_select_graph_balanced加相关性下限(建议 0.25,常量配置化,如模块常量或 env 可覆盖),低于下限不入选;全部低于下限 → 返回空 sources。- Qdrant 查询加同一下限
score_threshold双保险。 - 修复后
/api/qa/search对无关问题的sources为空数组;degraded保持 false(正常空结果,不得与 TIMEOUT 降级混淆)。
前置数据
- 准备无关问题:「景甜是谁」;备选冷门问题(建议用不存在实体,如「请问 ZXCVQWER12345 这种字符串与植物有什么关系」)。
- 准备正常领域问题(回归用):与 arginine(精氨酸)相关的植物学问题,确保可命中文献。
操作步骤与断言
- API 空 sources(S1,curl):
curl -s -X POST $BFF/api/qa/search -H 'Content-Type: application/json' -d '{"question":"景甜是谁","session_id":"<SID>","use_llm":false}'(字段以实际契约为准,参考 r3 附录 A)。- 断言:200;响应
sources为空数组[];degraded为 false(或不存在该字段);不是 TIMEOUT 降级体。
- UI 空态文案(S1,浏览器):
- 检索模式(输入框模式 pill 选「检索」)发送「景甜是谁」。
- 断言:消息区显示「没有检索到此问题的有效回答」(
T.searchNoAnswer);无 pipeline 卡死、无错误信封样式。 - 「检索过程」TAB 显示「本次检索未命中文献」(
T.sourceEmpty)。 - 右侧「文献来源」TAB 列表为空,不出现 30 条无关文献(旧缺陷)。
- 正常领域问题回归(S1):
- 发送 arginine 相关问题。
- 断言:
sources非空、右侧文献列表正常、答案正常生成——下限不得误伤正常植物学问题。
- 常量配置化(代码级,dev 自测):
- 断言:下限为具名常量(非魔法数字),阈值与 Qdrant
score_threshold同源或同值;文档/注释注明可配置方式(env 或配置项)。 - 建议 dev 自测加桩:构造 30 条全部低于下限的 items → 选入 0 条;构造 5 条高于+25 条低于 → 仅选入 5 条(或按 graph 配额逻辑)。
- 断言:下限为具名常量(非魔法数字),阈值与 Qdrant
- S2 终验(待部署后执行):176 复跑 1
3;另选真实冷门问题 23 个(由 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 空态区分)。正常实体搜索路径不得回归。
前置数据
- 知识图谱 TAB 可打开、
GET /api/kg/overview200。 - 已知命中实体:arginine(英文库)。
操作步骤与断言
- 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 并排查)。
- UI 空结果提示(S1,浏览器):
- 知识图谱 TAB 搜索框输入「水稻」→ 回车/点搜索。
- 断言:搜索完成后界面出现明确提示「未找到相关实体」类文案(以修复后 i18n 键为准);不再空白/无反应;搜索按钮恢复可用,可继续搜索。
- 正常实体回归(S1):
- 搜索「arginine」。
- 断言:结果列表渲染命中实体(名称/类型/support_count);点击实体 → 详情面板正常;无「未找到」提示误现。
- 状态区分(S1):
- 断言:空结果提示、
kgDown(服务不可用/超时)、kgEmpty(初始空态)三者文案不同、触发条件互斥;搜索进行中 loading 态正常显示与消失。
- 断言:空结果提示、
- 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,两处同改):
- env 注入
PYTHONUNBUFFERED=1(子进程 stdout 不块缓冲,真因即写即落)。 - returncode≠0 且 job.log 无
doc_analyze_streaming行(成功任务实测必有此行)= 判定启动期失败 → 自动重试 1 次。 - 重试仍失败且仍无 pipeline 行 → status.json 写入可读中文错误(如「提取启动阶段失败,可能受主机 GPU/显示重配置干扰,请重试」)。
前置数据
- 样例 PDF(小文件即可,如 wang2019.pdf 或任意文字型 PDF)放 175。
- 启动期失败 stub:
/bin/false或一个 exit 1、无 stdout 输出的脚本(如#!/bin/sh\nexit 1)。 - 模拟配置纪律(重要):制造启动期失败需把 dev 抽取上游(8000 methodology 服务)的
MINERU_CMD临时指向 stub 并重启该服务。按环境纪律,复测 agent 不执行服务启停与配置修改;此模拟由 TL 授权人员执行、验后还原并重启。复测 agent 只执行上传与断言。175 有真实 GPU mineru,正常路径用例 5 无需模拟。
操作步骤与断言
- PYTHONUNBUFFERED 注入(代码级,dev 自测):
- 断言:两处
build_mineru_runtime返回的 env 均含PYTHONUNBUFFERED=1(含 legacy 文件);子进程调用使用该 env。
- 断言:两处
- 启动期失败 → 自动重试 1 次(S1,需 stub 模拟):
- stub 就位后,上传 PDF:
curl -s -X POST $BFF/api/extraction/upload -F "file=@<pdf路径>" -F "paper_id=r4-38-test"(BFF 代理到 8000/api/v1/methodology/extract-upload;返回 job_id)。 - 轮询
curl -s "$BFF/api/extraction/jobs/<job_id>"至终态(≤120s)。 - 断言:job 目录
job.log中出现两次 mineru 命令启动行(两次$ ... mineru ...),即发生一次自动重试;日志/状态中有可识别的重试标记(修复实现的自有标记,以实际字段为准,如 message 或 log 行含 retry 字样)。
- stub 就位后,上传 PDF:
- 重试仍失败 → 可读错误 + 日志未丢失(S1,同上模拟):
- 断言:终态 status 为 failed;
GET $BFF/api/extraction/jobs/<job_id>返回的message为中文可读、含「启动阶段失败」类提示(非空串、非裸 "task(s) failed");GET $BFF/api/extraction/run-log与上游GET /api/v1/methodology/run-log内容非空(修复前真因被块缓冲丢失,修复后日志可见)。
- 断言:终态 status 为 failed;
- pipeline 行判定不误伤(代码级 + 正常路径回归):
- 断言:正常成功任务的 job.log 确实含
doc_analyze_streaming(抽样历史成功任务日志验证);启动期判定仅当「无 pipeline 行 + returncode≠0」同时成立才触发重试——mineru 中途失败(已有 pipeline 行)不得触发重试。
- 断言:正常成功任务的 job.log 确实含
- 正常路径回归(S1,175 真实 mineru):
- 还原 stub 后上传正常 PDF:job queued → running(mineru) → llm → completed;运行日志可见 pipeline 输出;无异常重试发生。
- S2 终验(待部署后执行):176 真实 CPU MinerU 上传含图 PDF 走通全链;日志无 GPU 探测崩溃;如遇启动期失败,应看到自动重试 + 中文可读错误而非瞬间失败。
通过标准:1~4 全过;5 过(正常路径);6 部署后过。
证据:job.log 全文、/api/extraction/jobs/<job_id> 响应全文、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。
前置数据
- 生成一份「通用公文(红头)」文档(模板 official;模板 CSS 事实源
sdk/app/services/docgen/templates/official.css:红头#c0000030px bold 居中、h1.doc-title22px 黑体居中、h2 16px、h3 15px)。 - 另一份非红头模板文档(如 report)用于回归。
操作步骤与断言
- 导出 DOCX 与预览一致(S1,浏览器):
- 打开红头文档 → 预览确认红头红色居中、标题层级清晰(截图留证)。
- 点「导出 DOCX」→ 下载后本地打开(Word/LibreOffice)。
- 断言:红头文字为红色(色值 #c00000 系,非 FF0000 亮红)、加粗、居中;字号与 30px 对应(≈22.5pt,以修复实现解析结果为准,允许 ±10%);h1 标题 22px≈16.5pt 居中黑体;h2/h3 层级与预览一致;表格/图片不回归(r3 已修内容塌缩)。
- 与「导出 PDF」或 HTML 预览逐项对照:红头/标题的色值、字号、对齐一致。
- 样式来源(代码级,dev 自测):
- 断言:html2docx 中红头/标题样式不再硬编码 FF0000/22pt 常量,而是从 render_html 产物解析;解析失败时回退常量(回退分支有单元测试覆盖)。
- 非红头模板回归(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,修复需覆盖关闭→返回→再关闭及反复切换资料库/文档视图的路径。
前置数据
- 已有至少一个生成完成的文档(红头文档即可)。
操作步骤与断言
- 关闭→空态→再打开(S1,浏览器):
- 打开文档 A(文档 TAB 显示 A)→ 点「关闭文档」。
- 断言:文档区回到空态(资料库面板或
docPaneEmpty文案),无 A 残留。 - 重新打开文档 A(从资料库/历史/或重新生成)。
- 断言:A 正常显示(shadow 宿主重挂后内容完整渲染,无空白)。
- 再关闭不残留(S1):
- 再点「关闭文档」。
- 断言:空态正常,无空白区/无半渲染状态;反复 3 次均正常。
- 资料库往返(S1):
- 打开文档 → 点「资料库」→ 断言显示「我的资料」列表。
- 点「返回文档」→ 断言 A 内容正常显示;反复切换 3 次无空白、无残留。
- 状态残留断言(DevTools 辅助):
- close() 后(不刷新页面)检查:
docgen.viewMode === 'doc'、docgen.activeArtifact === null(可用 Vue DevTools 或 console 读取 store 状态)。 - 之后上传新资料/打开另一文档,断言新文档正常挂载、旧文档不闪现。
- close() 后(不刷新页面)检查:
- 会话切换回归(S1):会话 A 有打开的文档 → 切会话 B → 文档 TAB 空态;切回 A → 文档恢复显示(或按修复语义:仅新会话生成后才挂文档,以修复说明为准,断言不出现 A 文档错挂到 B)。
- S2 终验(待部署后执行):176 复跑 1~3 一轮。
通过标准:1~5 全过;6 部署后过。 证据:各步截图(关闭/空态/重开/资料库往返)、Console 无报错日志、store 状态检查输出。
6. 缺陷 #52 上传资料后可以问答但无法「总结论文」
| 项 | 值 |
|---|---|
| 环境 | S1;S2 终验一次(待部署后执行) |
| 用户原话 | 「上传资料后可以问答,但无法『总结论文』」 |
缺陷描述 / 根因摘要(三层)
ui/src/intent.js的WRITE_VERB(L19)缺「总结/概括/归纳」→「帮我总结论文」+附件被路由到 QA 而非 docgen。- QA 公共检索(Qdrant+Neo4j)与私有上传库(vstore 本地 FAISS)物理分离;前端有 FR-PRIV 私有桥(
ui/src/api/private.jsaskPrivate,在 chat.js_firePrivate并行触发)会联查私有库,但该文档被重复上传 4 次(4 个 doc_id × 200 chunk = 800 条)挤爆检索预算。 vstore/vault.pyadd()无内容去重、sdk/app/services/privateqa.py_pick_items只按 score 取 top_k 再每 doc ≤3 去重(PRIVATE_QA_PER_DOC_MAX=3),导致作者页重复块占满 top-8、正文块从未进入上下文。
修复点:
WRITE_VERB补「总结/概括/归纳」(带附件的写类请求进 docgen;无附件纯问答不受影响)。注意后端同词典sdk/app/services/docgen/intent.py与用例表docs/team/cases_intent.md需同步(dev 自测核对)。vault.add()按内容 SHA256 去重:同内容重复上传覆盖旧 doc_id 并清理旧 chunk。_pick_items改为先按 doc_id 聚合(doc 级 max score 排序取 top 文档)再取各 doc 内最相关块(每 doc ≤3)。
前置数据
- 一份论文 PDF(含作者页+正文,如 wang2019.pdf)。
- 干净的测试环境:如库中已有该论文的旧重复上传,复测前由 TL 确认清理方式(或换用新文件规避)。
操作步骤与断言
- 意图路由(前端,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 裁决结果断言。
- 重复上传去重(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 已清理,不再出现在结果中)。
- 上传同一 PDF 两次(浏览器拖拽或
- 私有检索块多样性(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 非空且非自谦语)。
- 端到端(S1,浏览器):
- 「帮我总结论文」+ 该 PDF → docgen 任务完成,文档内容引用论文正文(打开文档核对内容与论文相关,非空泛)。
- 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. 复测流程约定
- 完成门禁:六项缺陷(#27/#28/#32/#38/#47a/#47b/#52)全部 S1 项通过,缺陷单清零,才算修复完成;S1 失败项回归修复后重测该项 + 其回归矩阵关联行。
- S2 全部标「待部署后执行」:本轮不执行。176 部署新版本后按本文各「S2 终验」项执行;执行前提:176 部署完成(TL/部署 agent 通知)、真实 QA/Neo4j/MinerU 可用。
- 模拟配置纪律:#38 启动期失败模拟需临时改 dev 抽取上游(8000)的
MINERU_CMD并重启该服务——由 TL 授权执行并负责用后即还原+重启;复测 agent 不执行任何服务启停与配置修改。#28 若需调阈值模拟(如临时把下限调高验证过滤),同样按「TL 授权改常量/配置 → 重启 dev 相关服务 → 验后还原」执行。 - 复测环境纪律:S1 只用 175 dev(vite + dev BFF 9090),绝不碰生产 8090、部署树与 176。vstore(8094)与 methodology(8000)只读查询或按第 3 条授权执行。
- 执行顺序:先 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)
BFF=http://127.0.0.1:9090 # dev BFF,复测时以 TL 给的地址为准
# #27 会话来源聚合(title 可读性 / paper_id / count)
curl -s $BFF/api/sessions/<SID>/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":"<SID>"}' | 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":"<SID>","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=@<pdf路径>" -F "paper_id=r4-38-test"
curl -s "$BFF/api/extraction/jobs/<job_id>" | jq '{status, stage, message}'
curl -s "$BFF/api/extraction/run-log"
# 直连上游(175 本机,只读):job.log 重试行数
ssh xzq@<175> "cat data/evaluation/methodology_upload_jobs/<job_id>/job.log" 2>/dev/null
curl -s "http://127.0.0.1:8000/api/v1/methodology/extract-jobs/<job_id>"
# #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