# CHANGELOG.md —— 进度与决策记录(PM 独占写入) ## 2026-09-02(能力集成 S0–S7) - **S0 基线**:7920 旧容器停(不删);deploy-unified 全量备份;金标 30 题固化 (benchmark 20 + 水稻 SPL 补充 10,断言级判定器 qa-golden/run_golden.py); 冷启动计时 151.3s(热加载硬要求实证);tag capabilities-baseline。 - **S1 机制地基**:capabilities 五模块 + tool_runner + capabilities.yaml 热加载面 + compile_qa.sh 扩展;mcp 2.1.1 入 venv 与 requirements.lock;llm_client tools 参数; /capabilities/install loopback;自检工具 tooling/cap/(cap_selfcheck 4 项 PASS)。 - **S2 多轮记忆**:SDK services/history.py(3 轮/16k 预算)+ qa.py session_id 剥离注入; QA answer_composer Dialogue Context 段(检索零回归);BFF 端到端指代消解验证通过。 - **S3 联网搜索**:web_search.py(PubMed+Crossref,中文问题自动转英文查询); pipeline 确定性门控(去重公开引用数 < min_evidence;selected 恒 30 缺陷已修); sse_relay STAGE +2;四象限门控验证(覆盖好不触发/覆盖弱触发/请求关/env 关)。 - **S4 SDK+UI**:GET /api/capabilities + POST install 透传 + SessionMeta.capabilities PATCH 白名单合并;Humanizer 9 类规则(块级保护);设置面板(展开态入口 + Drawer 360px)+ AppShell 接线 + chat.js session_id;labels 文案组。 - **S5 安装闭环验收**:聊天内安装 → 热加载 diff → 面板可见 → 卸载,全程 QA PID 不变。 - **S6 Superpowers**:prompts/ 3 个 SKILL.md + prompt_pack.py 注入点(env 总闸 QA_V1_ENABLE_SUPERPOWERS=0);compile_qa.sh prompts 目录自动拷贝。 - **金标判定器防抖**:FAIL 复跑一次;GI-005 配对采样实证上游 LLM 漂移,标 flaky。 - 测试:QA 单测 24、SDK 146、UI vitest 172 全绿;金标对照 regressions=0。 ## 2026-08-25 - **计划批准**:新前端 + 薄适配层方案获批。端口 8090(BFF)/ 5180(Vite dev),并行部署不切换。 - **M0 启动**:远端目录树建立;8090/5180 端口复核空闲;本地 staging 目录 `/Users/jerry/haiguji/PlantAI-rag-new/` 建立。 - **契约文档定稿**:`PRD.md`(81 个 FR)、`API.md`、`UI_SPEC.md`、`ARCHITECTURE.md`、`TEST_PLAN.md` 完成并锁定。 - **Mock 层完成**:`sdk/mock/mock_server.py` + 21 个 fixtures(含 SSE 回放序列)。 - **共享前端文件完成**:`ui/src/i18n/labels.js`、`ui/src/config/modes.js`。 - **环境就绪**:`plantai-bff` conda 环境(3.11);npm 依赖 187 包(Vite 5.4.11/Vue 3.5.13);Playwright + Chromium headless shell 131 下载完成;mock server 在 8099 运行并全接口验证通过。 - **D0 探测完成**(`DISCOVERY.md`,345 行)。**契约偏差修正**(基于真实上游): - Neo4j 当选 `neo4j-plant-srt-direct-v1-full`(bolt 18788,QA 7920 实际使用)。schema 为 **canonical**(`SRT2CanonicalEntity`/`SRT2CanonicalAssertion`),非假设的 `:Entity`;**无全文索引**,名字检索走 `name_norm` 前缀索引(0.09s)。 - 抽取任务真实状态枚举 `queued/running/completed/failed`,**无 progress 数值**——BFF 按 stage 派生 `progress_pct`。 - QA source 真实分数字段为 `final_score`;检索计划在 `query_plan` 键;`native_graph_chains` 为列表、`graph_knowledge_chains` 为 dict 双形态。归一化规则已更新。 - 实测 search ≈92s(读超时调至 180s)、stream ≈37s。 - `processing-status/progress/summary/run-log` 当前空数据(上游批次目录未配置),前端按空态处理。 - **已知问题**:QA 上游 DeepSeek LLM 余额不足(402 Insufficient Balance)——检索链路正常,chat 当前只出证据列表 fallback,无自然语言答案。**需用户充值 DeepSeek 账户**。开发不受阻(mock 覆盖);M2 验收对答案质量相应调整。 - **上游重启记录**:无。 ## M0 完成 → M1 启动(2026-08-25 晚) - **M0 验收达成**:契约文档、mock(8099 运行中且已按真实上游对齐:canonical schema / queued-runing 派生进度 / run-log JSON)、conda 环境、npm 依赖、Playwright、本地 staging + 远端副本、BFF 骨架 import 通过。 - **本地开发环境补齐**:`.venv-backend`(3.12,供后端 agent 本地自测)+ 本地 node_modules;`deploy/sync.sh` 排除规则修正(`.venv-backend`)。 - **跨归属占位脚手架**:sdk/app 全部模块 + ui main.js/App.vue 占位,由各 agent 在 M1 以真实实现替换。 - **M1 发射**:A1/A2/A3(后端三线)+ B1/B2/B3(前端三线)+ T(测试用例)七个 agent 并行。前端三入口占位组件已由 PM 提供;文件归属含测试目录拆分(`tests/ui/unit/b1|b2|b3`,后端单测随代码 `sdk/app/**/test_*.py`)。 - **A1 完成**(15 文件,29 单测全过):config/db/schemas/normalize/upstream/health/sessions/main + pyproject + run.sh + .env.example。7 条偏差全部 PM 裁决通过并回填 API.md(hop 中间态字段、来源聚合取首条、search answer 空串、PATCH 空 body 幂等、title_manual 保护、凭据仅入 BFF 配置、新增 python-multipart)。 - **待办**:A1 报告 A3 的 17 个测试失败(`test_kg.py` 用 ASGITransport 直挂裸 router 与 fastapi 0.141 不兼容;`test_extraction.py` 两处 respx 失败)——待 A3 汇报后统一派发。 - **B3 完成**(工作台四 tab,54 单测全过,`vite build` 通过):BenchPanel/RetrievalPane/SourcesPane/SourceRow/ChainGroup/ExtractionPane/ProgressItem/KgPane/KgGraphView(手写 SVG 力导向,无第三方库)/KgEntityDetail/graphLayout/bench store/两 API 模块。15 条工作台文案已由 PM 合并进 labels.js,benchLabels.js 改为薄别名。 - **集成对齐点(B3 提出,待各线就绪后统一处理)**:① chat store getter 名(`currentEvents/currentMessage/messages`,B2 确认);② bench 折叠由 B1 AppShell 控制(emit/props 接缝);③ `pa-edge-grad` SVG defs id 前缀;④ ExtractionPane 卸载时 `stopPolling`(BenchPanel onBeforeUnmount);⑤ kg 邻域深度 2 的 13s 提示已做。 - **A2 完成**(QA 代理 + SSE 中继,36 单测全过):四端点 + STAGE_PHASE/STAGE_LABEL + 帧解析/重发/看门狗/重试。SSE 读超时采用 `read=None` + 应用层看门狗(producer task + Event.wait,规避 anyio.fail_after 毒化 generator 链的工程陷阱)。`degraded` 语义已 PM 裁决:仅 UPSTREAM_DOWN 为 true(API.md 已回填)。 - **A2 集成对齐点**:A1 就绪后删除 qa.py/sse_relay.py 内本地等价实现(`_fallback_normalize_qa` 等),归一化以 A1 为准;真实上游冒烟(1.78MB search 响应、SSE 全流程)在远端做(本地 7920 不可达)。 - **A3 完成**(图谱直连 + 抽取代理,88 单测全过):Neo4jService(实例回退 18788→19788→17687、TCP 预检、depth=1 绝不执行第二跳)+ 五 kg 端点 + ExtractionService(8000 主/7900 回落、progress_pct 派生、plan 409→null、spool 上传)+ 全套 respx/FakeDriver 单测。**A1 报告的 17 个测试失败已被 A3 自行解决**(测试切换到兼容 pytest-asyncio 的纯 async def)。5 条偏差全部 PM 裁决通过(count 语义、422 暂留原生格式待 M2 全局处理器收敛、plan 失败置 null 等,API.md 已回填)。 - **M2 集成前待办(PM 执行)**:① 补 pyproject dev 依赖 `respx`;② main.py 加全局 RequestValidationError 处理器(VALIDATION 信封);③ 删除 A2 本地等价实现切换 A1 normalize;④ 验证 A1 health 的 `probe_neo4j` 接缝;⑤ 远端真实 Neo4j/QA 冒烟。 - **后端集成预检(PM,2026-08-25)**:三线代码合并后全量 `pytest .` = **119 passed**(2.34s),仅有 5 个 deprecation warning(A1 main.py 的 `on_event` 弃用,建议转 lifespan——列入 M3 收尾清单,不影响功能)。后端三条线无测试冲突,接缝(config/upstream/envelope)互相兼容。 - **B1 完成**(样式层 + 外壳 + 响应式 + 主题/健康 store,25 单测全过,build 通过 285KB/81KB):tokens 逐字复刻、四档断点、抽屉、覆盖层、visualViewport 键盘适配、http.js 统一错误。 - **PM 集成修复(B1 偏差裁决 + 接缝接线)**:① laptop 档折叠策略裁决为「首次进入无偏好时自动折叠,把手保留,用户显式展开被尊重」(CSS 改为响应 side-collapsed 状态 + AppShell watch tier);② AppShell 顶栏 title/status 接上 B2 sessions store(原返回 null);③ 移动端全屏覆盖层 tab 条接上 B3 bench store 的 setActiveTab(原点击无效);④ 验证:B1 25 测 + B3 54 测全过、build 通过。 - **B2 完成**(会话与对话流式状态机,58 单测全过):chat store 全流程(落库→骨架→SSE→检查点→终态)、stop/retry/刷新恢复/揭示动画、sessions store、SseParser(多行 data/注释/CRLF/UTF-8 跨 chunk)、16 组件、markdown 消毒。手动冒烟:mock SSE 12 帧消费 + 18 模块 transform 全 200。 - **B2 偏差裁决 + PM 集成补丁**:① labels 补 4 键(toolParams/toolResult/toolLog/graphSummary);② 消息内 tool 块无 API 产出路径——保留渲染器待数据源;③ checkpoint 时序(揭示完成才落终态)裁决通过;④ interrupted 为前端本地标注(后端枚举不变)裁决通过;⑤ 重试同位置替换、旧 DB 行不更新裁决通过;⑥ **FR-CHAT-015 高亮链补全**(bench store 加 highlightSource + setHighlightSource 2s 自动清除、SourcesPane 传 highlighted、SourceRow 滚动定位 + 2s 渐隐高亮)。验证:B3 54 测 + B2 58 测全绿。 ## M2 集成(2026-08-25 夜) - **真实栈部署**:远端 BFF 8090(plantai-bff + python-multipart 补齐)、Vite dev 5180(代理→8090)、集成检查脚本 11/11 通过(健康/qa info/会话/Neo4j canonical/实体搜索/抽取/真实 search ≈90s)。 - **修复两个集成接缝 bug**:① A2 的 `get_qa_client` 误用 A1 的 async `get_client()` 返回 coroutine → 裁决为「QA 流式路径独立持有 read=None 的 client,不复用 A1 单例」(语义不同:SSE 长静默会被 300s 读超时误杀);② A3 `_tcp_reachable` 未解包 `open_connection` 的 (reader, writer) 元组。 - **PM 集成单点修改**:main.py 转 lifespan + 全局 RequestValidationError→VALIDATION 信封;pyproject 补 respx;远端 @playwright/test 安装 + 根 node_modules symlink(T 方案 b)。 - **SSH 隧道三连坑(PM 记录,供后续复用)**:pkill 自匹配杀 shell(用 `[x]` 正则技巧);`ExitOnForwardFailure` 被配置里坏掉的 RemoteForward 15721 触发退出;`ClearAllForwardings=yes` 会清掉命令行自己的 `-L`。 - **响应式断链缺陷(M2 核心 bug)**:真实浏览器中流水线相位卡冻结。六轮探针定位:fetch 层 12 个 progress + 3MB complete 全到达 → store 状态正确(探针直读 pinia)→ 唯一 PipelineCard props 已是 done 但 DOM textContent 冻结。**根因**:`send()`/`retry()` 里 `const aMsg = newSkeletonMessage(); this.messages.push(aMsg)` 后闭包持有 **raw 对象**,所有 SSE 相位突变作用在 raw target 上——Pinia reactive 代理读值正确但**突变不触发渲染**,子组件因 props 引用相等被 Vue 跳过 patch。B2 的 vitest 只断言 store 状态未测真实渲染链,故未暴露。**修复**:三处 push 后改持 `this.messages[this.messages.length - 1]`(代理)。修复后 M2 验收 **12/12 全过**(相位推进 4 快照/47s、答案 690 字、来源、图谱、SQLite 刷新恢复、无 JS 错误)。 - **诊断探针存档**:`deploy/m2_probe{1..6}.js`(chunk 级/网络层/store 直读/组件 props/卡片枚举),保留备查。 ## M3 功能面补齐(2026-08-25 深夜) - **真实 KG 数据缺陷修复(PM)**:① 邻域 0 边——真实库边属性**无** `source_canonical_id/target_canonical_id`(端点在两端节点上),A3 组装代码从边读这两个键恒 None;改为 Cypher 投影时用 `startNode(r)/endNode(r)` 注入;② overview 0/0——1644 万边 COUNT 聚合冷缓存超过 10s 查询超时,新增 `OVERVIEW_QUERY_TIMEOUT=60s` 用于 overview 两查询;③ 自环边(真实库 `gene_structure` 等实体自身关系)在组装层过滤。验证:overview 200/200(gene 35 万)、邻域 21 节点 8 边 0 自环。 - **部署工具**:`deploy/bff_restart.sh`(setsid 脱离 SSH + `[u]` pkill 技巧,解决 SSH 后台挂起坑)。 - **M3 响应式接线修复(PM)**:① 平板档汉堡/工作台按钮不可见——responsive.css 规则写 `.top-bar.has-tools` 但该类挂在 `.app-shell` 上,选择器永不匹配;② 手机档工作台按钮开的是右抽屉而非全屏覆盖层——`openBench()` 未按 tier 分派;③ 覆盖层内两套 tab 条重复——隐藏 BenchPanel 内部那套保留外壳 tab 条(含关闭按钮)。 - **M3 验收 16/16 通过**:真实 KG(实体搜索/邻域图谱/抽取任务列表)、平板抽屉(打开/Esc 关闭)、手机档(底栏隐藏/覆盖层打开/tab 切换)、主题切换(浅色 canvas 精确 #F6F8FC)。三档视口截图存 `artifacts/m3/`。 ## M4 测试轮次(2026-08-25 凌晨) - **R1 全量结果**:L1+L2 后端 119/119(行覆盖 87% 达标);L3+L4 前端 137/137(81.54% 达标);L5 Playwright 三档 19 pass/17 fail/12 skip → 收敛 8 缺陷;L2s 上游 smoke 7/7;**FR 追溯主门禁 81/81 双向零失配**。 - **R1 缺陷 8 项已批量派发**:BUG-R1-07 Blocker(http.js 30s 读超时 < search 92s)→ B1;BUG-R1-01 Major(kg 搜索丢 q 参数)→ B1;BUG-R1-02/03/04/05/06 Major(会话高亮/删除死代码/QA 降级未禁用/错误信封误显示/markdown 零调用)→ B2;BUG-R1-08 Minor(health 索引误报)→ A3。 - **T 侧环境说明**:远端补装 pytest-cov;曾误 `--save-dev` 动 package.json 已还原(md5 一致);证据归档 `~/5090/teamWorkspace/zh/r1_test_evidence/`。 - **R1 修复进展**:A3 已交付 BUG-R1-08(probe 列表全等比较误报索引缺失——真实实例另有 canonical_id 约束索引;改为成员判断 + 2 锁定单测,23/90 过,真实冒烟 detail:None)。B1 已交付 BUG-R1-01/07(http.js 默认超时 600s 覆盖 API.md 全表、get 增 query 序列化、FormData 走 multipart;32 测过;隧道实测 KG 搜索 17 条含 q 参数、search 47.7s 不自断)。B2 已交付 BUG-R1-02~06(selectSession 同步 activeId;SessionItem 删除按钮+右键+确认弹层 Teleport;ChatPanel 接 health store 降级禁用;err.code 优先映射 + errText 透传;qaToBlocks/_finalizeReveal 走 renderMarkdown;70 测过,全前端 156 测)。 - **R1 修复集成(PM)**:本地全量回归后端 121 测/前端 156 测全绿;mock 本地远端 md5 一致;BFF 重启后 health 无索引误报;R2 全量回归已派发 T。 - **R2 全量回归通过(T)**:后端 121/121、前端 156/156、Playwright 48 条(35 pass/12 skip 依赖充值/1 flake 复跑通过)、上游 smoke 7/7、行覆盖 87%/80.59%、FR 追溯 81/81。**R1 八缺陷全确认修复**(含 E2E-01 刷新恢复补验三档全通)。无新缺陷。唯一失败为环境性 flake(tablet 主流程 300s 超时,单独复跑 52.3s 通过,上游 QA 串行排队所致)。T 清理了全部测试污染会话数据。 - **M4 判定(PM)**:主/辅门禁全达标、产品用例全 pass → 按 TEST_PLAN 纪律收尾。收尾前采纳两处加固:① E2E 真实流超时 480s/终态等待 360s(T 测试代码);② Vite 忽略 coverage 目录(测试期覆盖率文件触发 198 次页面重载,干扰 E2E 与浏览器访问)。 - **M5 最终验收通过(PM,2026-08-25)**:`deploy/m5_acceptance.js` 15/15 全过。生产形态打通(BFF 8090 单进程静态托管,修复 dist 路径解析:sdk/ui/dist → 项目根;sync 排除 dist 需单独同步)。真实 SQLite 历史恢复(相位卡 5/5、引用 chip 高亮定位)、真实问答全链路(答案 698 字 + 4 引用)、三档视口档位全正确。`docs/ACCEPTANCE.md` 归档。**项目收官**——遗留唯一外部依赖:DeepSeek 余额(充值即恢复,零代码改动)。 ## DeepSeek 恢复与 QA 修复(2026-08-25 下午) - **诊断**:用户充值后 QA 仍 402。实测三个 DeepSeek key:QA 旧 key(sk-9d70bc)与 PlantAI-rag key(sk-9de0a3)余额均不足;**PlantAI-dev 的 NEW_API_KEY(sk-9c0235)余额充足**。用户指示用该 key。 - **手工重启的弯路(PM 记录,教训)**:① 整行 shlex.quote 导致 source 时变量全失效;② 保留旧进程 CONDA_*/PATH 污染新 shell 致 conda activate 崩溃;③ source 缺 `set -a` 导致变量不 export 进 uvicorn;④ runtime env 文件缺 `V15_QDRANT_URL` 等进程内联覆盖项。最终用户提供官方管理脚本解决。 - **正确路径**:`~/5090/PlanAI-cooperate/RAG/rag-develop/start_plantAI_RAG.sh` 统一管理 QA(7920)/Neo4j(18788)/Qdrant(26333)/方法学(8000)/Ollama。换 key = 更新 `runtime/qa_v1_1_73.env`(已完成,备份 `.bak_oldkey`)+ 官方脚本 `start`。脚本显式 `QA_V1_LLM_MODEL=deepseek-chat`(flash 模型在新 key 下输出截断)、嵌入服务 `QA_V1_EMBED_URL=Ollama bge-m3`、BGE CUDA 等全套配置。 - **验证结果**:PlantAI-rag-new 问答——**自然语言答案 1379 字 + 15 条来源**(degraded_execution=False);PlantAI-dev 知识问答——**success:True + 2671 字答案 + 14 来源**(需把 `QA_V1_1_73_TIMEOUT` 从 300 提到 600,完整链路实测 216~273s;已写入 .env 并重启 9011,备份 `.env.bak_qa_timeout`)。 - **PlantAI-dev 9011**:仍用手动方式重启(官方 manage_plantai.sh 默认管 9000 端口),`source .env` + `set -a` 后 uvicorn 拉起,已验证 env 含新超时与 7920 地址。 - **运维提示**:QA 服务重启一律走官方脚本 `bash start_plantAI_RAG.sh restart|start|verify`,不要手工起进程。 ## QA 提速三项(2026-08-26,用户批准方案 2/3/4,否决 1 不改跳数) - **方案 2 证据量下调**:探索发现上游 `QA_V1_MAX_EVIDENCE` 环境变量是**死代码**(pipeline 无消费者),真正决定 LLM 证据量的是请求级 `ablation` 透传(默认 top_k_for_llm=30)。已在 modes.js 的 ask/plan 加 `ablation:{top_k_for_llm:20, graph_min_evidence:10}`(配套下调防图证据挤压),`evidence_first:true`。API.md 契约已同步。 - **方案 4 Neo4j 扩容**:`deploy/neo4j_recreate.sh` 固化重建命令(原容器手工 docker run 创建、命令已丢失)。已执行:heap 4G→8G/12G→24G、pagecache 保持 24G(数据仅 14G)、`dbms.memory.transaction.total.max` 显式 16G。实体 3,820,996 / 断言 8,246,875 校验通过,QA 已恢复。踩坑:Neo4j env 映射规则(单下划线→点、双下划线→下划线),`total__max` 写成 `total_max` 才正确。 - **方案 3 两级体验**:BFF `relay_stream` 加 `evidence_first` 并发打上游 /search,归一化后作为非终态 `event: evidence` 帧在 progress 与 complete 之间先推(search 失败静默降级、complete 后不再注入);qa.py 剥离 evidence_first 防上游 422;前端 api/qa.js 加 evidence dispatch、chat.js 加 onEvidence(_evidenceOnce 守卫,挂 sources/graph 块 + 生成相位 desc 更新,complete 整体替换自动覆盖保证引用编号)。测试:后端 125 测 / 前端 158 测全过。 - **集成验证(2026-08-26)**:接口级 SSE 实测帧序列 = 20 progress + **1 evidence(第 5 帧)+ 1 complete(第 22 帧)**——证据先于终态约 3 分钟到达。M5 回归 12/13(唯一失败项是历史会话数据相关的引用 chip 条件检查,非产品回归;真实问答三项全过:答案 616 字 + 相位全 done + 3 引用 chip)。 - **运维备注**:远端 sync.sh 的 `--delete` 偶尔会把新文件漏掉(scp 直传兜底);公网 SSH 间歇性 Connection reset,长任务一律 `setsid ... > /tmp/xxx.log & disown` 后台化再轮询。 ## 多流重构:切换会话不再中断(2026-08-26 用户反馈) - **行为变更**:原 FR-SESSION-004「切会话即中断」废止。新行为:切换会话时流在**后台继续**(`sessionMsgs` 按会话缓存消息数组,流回调持续写入;`streamCtx` 每会话独立持有 AbortController/检查点/事件/揭示状态),切回时直接拿实时数组。stop/重试只作用于当前会话的流。busy/currentMessage/currentEvents/reveal 改为从当前会话 ctx 派生的 getter。 - **踩坑(Pinia reactive 代理陷阱)**:`_finishCtx` 用 `streamCtx[sessionId] === ctx` 判断清理——读出的代理对象与闭包持有的 raw ctx 恒不相等,清理永远失败、busy 永远 true。修复为按 key 删除。 - **currentEvents 保留**:终态后 ctx 被清理,事件快照保留到 `lastEvents`,bench 阶段卡在完成后持续可看。 - 测试:chat.store 19 测全过(重写「切会话中断」为「切走后台继续」),全前端 158 测全过。已部署(index-DTp5vppj.js)。 ## 图文并茂长篇回复(2026-08-28,用户要求 5000 字以上) - **上游镜像改造**(用户批准 docker commit 方案):快照 `:20260828-orig` 回滚锚点 → 修改 answer_composer.py(删 short paragraphs 约束、加「至少 5000 中文字」目标 + `[[FIG:PMCID]]` 图片位置标记指令、max_tokens 3500→12000)→ 提交 `:20260828-longanswer` → 同名重建。**踩坑**:原部署的 `./models` 挂载目录是空的(reranker 模型从来不在里面),导致 /chat 500("Repo id must be in the form...")——重建时补挂真实模型 `/home/xzq/5090/PlantAI-rag/bge-reranker-base:/models/bge-reranker-base:ro`。QA_V1_LLM_TIMEOUT=300 经环境变量注入。 - **BFF 图片服务**:`services/figures.py`(本地 MinerU 索引 + Europe PMC fullTextXML 解析 + license 查询 resultType=CORE)+ `routers/figures.py`(`/api/figures/{pmcid}` 清单 + `/api/figures/proxy` 图片代理带磁盘缓存)。**踩坑**:graphic 正则误抓许可链接/PDF(需 content-type="image" 过滤);`/proxy` 路由被 `/{pmcid}` 捕获(声明顺序修正)。 - **注入逻辑**:`normalize.inject_figure_markdown`(纯同步,figures_map 注入)+ `sse_relay._inject_figures_async`(异步预取)+ qa.py `/chat` 同步端点同样注入。LLM 输出 `[[FIG:PMCID]]` 标记 → BFF 解析替换为 `![]()` 图注块;无标记时按 `[PMID: xxx]` 引用位置兜底注入;总图数上限 8。 - **前端**:markdown.js image 规则 → `
` 图注块(caption 带许可);AssistantMessage 点击放大遮罩(Teleport + Esc/点击关闭)+ capture 委托的加载失败隐藏。 - **看门狗适配长文**:STREAM_IDLE_TIMEOUT 240s → 1200s——长文时代上游图检索静默期可超 240s(高负载下更久),原值会误判超时掐断正常流。 - **最终端到端验证**(真实 QA + 真实图片):答案 **9938 字 / 17 小节 / 8 张图片注入 / 无 FIG 残留**,final_answer 43.9s。m5 回归 8/8 通过(真实问答项在负载 62+ 时超过旧 360s 超时,属测试阈值问题已调 900s;功能经独立验证通过)。 - **已知权衡**:高负载下(服务器被同事 iqtree/MinerU 任务挤占时)单次完整问答可能 5-15 分钟——这是上游图检索是 CPU 密集串行链路的固有特性,错峰使用体验最佳。