# Changelog ## [1.6.7] - 会话级状态与 `/kb` 命令 + 相关性地板 + References 判定重做 + 工具注册竞态修复 ### ⚠️ 升级须知(4 条破坏性变更,请先读这里) 1. **老库必须重灌**:`PARSER_REV` **4 → 5**(「References 判定重做」改了分块口径)。升级后整库会被标记为**分块陈旧**(`kb_stats` 报 `stale_kind=chunk`),需要 `kb_ingest(rebuild=true)` 重灌;`chunk` 级陈旧时插件**不再提供"只刷新元数据"**这个无效选项(刷元数据不重切块,选了也不生效)。316 篇实测 **219 秒**,期间自动转后台,用 `kb_status(job_id=…)` 轮询。**重灌只改分块边界、不丢结果,但排序会有小幅抖动**(同一批 20 条库内查询实测:17 条 top1 不变、0 条结果变少;同时 2 篇原本"整篇查不到"的文档恢复可检索)。 2. **同样的查询,结果会变**:新增**相关性地板**(库外问题不再返回 Top-3 垃圾,改为 `verdict` / `no_hit` / `closest`)、`filters` 归一化(连字符/下划线/连续空格/大小写)与 `authors` **分词 AND** 匹配。 3. **必须重装 npm 包**:包新增 `exports["./client"]` 与 `dsh.client`(客户端半边:来源卡片 + 会话栏指示条)。只改依赖版本号而不重装,客户端不会生效。 4. **状态作用域变化**:`scope` / `depth` / `strict` / `enabled` / `diligence` 由**全局单份**改为**按会话隔离**。旧实现里第二个会话起不再询问、且某个会话的改动会污染整个进程;升级后每个会话各管各的,工作区默认值(`/kb save`)才跨会话。 ### 修复:启动时 `kb_*` 工具一个都不注册(issue #1) - `inject` 补上 `subprocess`(`npm-package/lib/index.js`、`plugin/host.js`)。原因:cordis 只等待 `inject` 中声明的服务,而 loader 并发激活各 entry(`cordis-plugin-loader` 用 `Promise.allSettled`),本包体积小、往往在 `@deepseek-ai/dsh-subprocess-local` 注册服务之前就执行 `apply()`,此时 `ctx.get("subprocess")` 返回 `undefined`(未注册返回 `undefined` 而不抛错)→ 提前 `return`,10 个工具全部丢失。声明 inject 后 cordis 会 park 本插件直到该服务就绪 - 动态插件侧(`plugin/host.js`)同样受影响:沙箱的 `ctx.get(name)` 是"可选查询"、不要求声明,服务未就绪时同样是静默丢工具;属性访问 `ctx.subprocess` 才必须声明 - 保留 `ctx.get("subprocess") === undefined` 的兜底日志(措辞改为 "despite inject"),便于诊断注入被改坏的场景 ### 修复:嵌入/精排不可用时静默降级,用户看不到原因(issue #2) - `kb_ingest` 新增返回字段 `embedding_error` / `vectors_missing` / `retry_secs`;插件在「N 块 / 0 向量」旁直接给出原因与下一步动作。此前只有 `embedding: null`,用户看到的是一个"成功"却没有任何解释的入库结果 - `kb_stats` 新增 `embedding` / `embedding_error` / `vectors_missing`(只反映本进程已有的加载结果,不主动加载模型),插件渲染 `health.missing_vecs` / `health.orphan_chunks` - `kb_search`/`kb_rag` 的渲染层此前丢弃引擎的 `note`,降级原因(「向量索引缺失,降级为纯关键词」)到不了用户;现在透传含"降级"的 note。1.6.6 已按 `mode_used` 渲染检索方式,本次补齐原因 ### 修复:模型加载失败被永久缓存 + 修好环境后补不回向量(issue #2) - `get_embedder()` / `get_reranker()` 的失败状态此前在守护进程内永久短路:依赖装好、模型就位后同一进程依然返回 `None`,表现为"修了还是没用",必须杀掉守护进程或重启 DSH。新增 `KB_MODEL_RETRY_SECS`(默认 120 秒,`0` = 每次重试,负数 = 旧行为):失败过期后自动重试 - 新增引擎命令 `reload`:清除失败缓存并立刻重试(`drop_models=true` 连已加载模型一起释放;`rerank=true` 顺带探测精排模型)。已登记到 `kb_engine.py` 的两处命令表与 `plugin/kbrag.plugin.json` 的 `engine.commands` - 增量入库在内容未变时直接 `skipped` 返回、从不补向量,所以"修好环境后重跑 `kb_ingest`"过去补不回来(只能 `rebuild=true` 全量重灌,312 篇约 322 秒)。现在该分支会对已入库文档做一次"只补缺失向量",并把补出的数量计入 `totals.vectors`;向量齐全时只是一条 SELECT,代价可忽略 ### 新增:GPU 感知(设备可见、批大小可配、OOM 有兜底) - **设备可见**:`kb_stats` / `kb_ingest` 新增 `device` 字段(`requested` = `KB_DEVICE` 取值、`torch` 版本、`cuda_available`、`gpu` 名称、各模型实际 `device`、以及 OOM 回退记录)。此前"装了 CUDA 版 torch、模型却仍跑在 CPU"完全看不出来——与 issue #2 的可见性同源 - **设备可控**:`KB_DEVICE=auto`(默认,不改动 sentence-transformers 的自动选择)/ `cpu` / `cuda` / `mps`。默认路径下装上 CUDA 版 torch 即自动使用 GPU,无需改代码 - **批大小可配**:新增 `KB_EMBED_BATCH`(默认 CPU 32 / GPU 128)与 `KB_RERANK_BATCH`(默认 CPU 16 / GPU 64)。原来两处写死(32 / 16),GPU 上喂不饱算力 - **OOM 不再是死路**:`encode()` / `rerank()` 捕获显存不足 → 清 CUDA 缓存 + 缩到 batch 4 重试;仍 OOM 则把模型回退 CPU 重试,并把"已回退 CPU"记进 `device.note`。否则一次瞬时 OOM 就会变成"整个会话再也用不了向量"(issue #2 的永久缓存坑) - 引擎 `VERSION` 3.1.0 → 3.2.0(`PARSER_REV` **4 → 5**,见下面「References 判定重做」一节 —— **升级后老库会被标记为分块陈旧,需 `kb_ingest(rebuild=true)` 重灌**,见文首升级须知);搜索缓存 key 含引擎 token,升级后不会复用旧响应 ### 新增:GPU 自动配置与 CPU 兜底增强(探测 / 显存分级 / 折半梯次 / reload 可恢复) - **真实可用性探测**:`auto` 与显式 `cuda*` / `mps` 不再只信 `torch.cuda.is_available()`,而是先在目标设备上做一次 8×8 矩阵乘并同步(`_gpu_probe`)。原因:**驱动报告有卡 ≠ 能跑内核** —— 驱动与 CUDA runtime 版本不匹配、容器/WSL、显卡处于独占计算模式,这些在 `is_available()==True` 时照样失败;不探就会把失败推迟到模型加载期(代价更大、报错更难懂,三级加载链还会在 GPU 上连撞几次)。探测失败即粘性关闭 GPU 并把原因写进 `device.gpu_disabled_reason`。`KB_GPU_PROBE=0` 可跳过探测(保留"先试一次"的旧行为)。 - **显存分级批大小**:显存 <4 GB → 嵌入 16 / 精排 8,<8 GB → 64 / 32,≥8 GB 保持 128 / 64;显存查不到则沿用历史默认。用于保护共享显存的轻薄本。实测本机 32–256 之间吞吐无差别(167.6 → 164.7 块/s,噪声级,瓶颈在 CPU 侧分词),所以**降批不牺牲速度**。`KB_EMBED_BATCH` / `KB_RERANK_BATCH` 仍然最优先。 - **批大小跟着模型实际所在设备**:运行期回退 CPU 之后不再继续用 GPU 的 128,而是回到 CPU 默认(32 / 16)。 - **折半梯次 + 记忆**:设备类错误不再"一步掉到 batch 4",而是折半 → 再缩到 4 → 才回退 CPU;**能跑通的批大小会被记住**,同一进程后续调用直接从它开始(此前每次调用都要重新撞一遍 OOM)。 - **体检无副作用**:`kb_stats` / `device_report` 只回答"按当前已知信息能不能用",不触发探测、也不因此关闭 GPU —— 一次瞬时故障不该被一次只读查询固化下来。 - **`reload` 可恢复**:`reload` 现在重置设备判定(`_CUDA` / `_GPU_OK` / 探测结果 / 显存缓存)、清空批大小记忆与设备说明,并在响应里返回 `device` —— 修好驱动或装上 CUDA 版 torch 后**不必重启 DSH**(与 issue #2"模型失败不再永久缓存"同源)。模型若已落在 CPU,需要 `drop_models=true` 才会重新加载到 GPU。 - **报告补齐**:`device` 新增 `probe` / `vram_gb` / `batch`(含 `env` / `gpu_tier` / `effective` 与 CPU·GPU 默认值);`kb_stats` 补上此前缺失的 `reranker` 字段(精排模型名过去只在检索响应与 `reload` 响应里可见)。 - 设备类错误识别扩到 `mps` / `rocm` 特征串;刻意**不**加 `hip` / `metal`(会误伤 `chip` / `metallurgy` 这类普通错误文本),保持"非设备故障原样上抛、不被兜底掩盖"的不变量。 - 回归:`tests/suites/s_gpu.py` 19 → **52 条断言**(探测失败/成功、`KB_GPU_PROBE=0`、显存分级、梯次与记忆、回退后批大小、体检无副作用、MPS/ROCm 识别、`reload` 重置);全量 15 suite / **413 断言**。设备自动配置**不影响检索结果**:同库同查询 top1 与得分与改动前逐位一致。 ### 文档:写明 `filters.journal` 的可用范围(BACKLOG §2.8 的 ④,纯文档,无行为变更) - **问题**:`extract_meta()` 从不给 `journal` 赋值,只有 Zotero 迁移会写入。因此 `kb_ingest` 建起来的库里该列恒为 `NULL`,`filters.journal` 必然零命中——而工具描述此前只写"期刊子串匹配",等于给了模型一个静默失效的过滤器。 - **改动**:`filterSchema.journal` 的字段说明与 `kb_search` 的 filters 说明(`plugin/host.js` 与 `npm-package/lib/index.js` 两个副本、`mcp-server/server.py` 的 `kb_search`/`kb_rag` docstring)都写明"仅 Zotero 迁移填充,`kb_ingest` 入库的文档为 `NULL`,用它过滤通常零命中,请改用 authors/year/title";`README.md` / `README_CN.md` / `npm-package/README.md` / `mcp-server/README.md` 与 `docs/DESIGN.md`(§4、§5)同步说明。 - **未做**:字段本身的补全(Crossref 按 DOI 反查权威期刊名)与 §2.6 的 DOI 补全合并评估,仍留在 `docs/BACKLOG.md` §2.8 的 ③。 ### 发布前隐私清理(仅注释与文档,无行为变更) - `kb_engine.py`(及 `npm-package/kb_engine.py` 副本)中说明"PDF `/Author` 可能是排版/制作人员"的三处注释、`CHANGELOG.md` 的实测案例、`docs/BACKLOG.md` §2.8 与 §2.11 的实测记录里含**真实姓名与真实期刊名**(来自实测 PDF 的生产元数据与 Zotero 记录)。按 `AGENTS.md` 的发布约定统一换成中性占位(`Smith, John`、`Author A`、`Carbon`),具体无关命中文献名改为"三篇不同主题的文档"。 - 引擎文件因此哈希变化(`fde65ed6` → `ae1da639`),但**只动了注释**:功能与行为不变,`kb_engine.py` 与 `npm-package/kb_engine.py` 仍逐字节一致。 ### 修复:References 判定重做 —— 消除"整篇不可检索"(PARSER_REV 4 → 5) - **问题**:重灌后 3290 块(13.9%)被判 weight=0,其中 **2 篇文档整篇没有任何可检索分块**(用户既搜不到、也没有任何提示),4 篇 >50% 被吞。根因是"像不像条目编号"这条判据太松:作者单位行(`1,2,3,*`)、正文编号列表、末页图注数字都能触发。 - **条目判据改为按参考文献的版式特征**:① 编号在条目首位且全文递增(`1` / `1.` / `[1]`)② 通常落在文末 ③ **人名一定在首位**(`姓, 首字母` / `首字母. 姓` / 拼音姓名 / `姓 et al.`),其后依次是期刊缩写、年份、卷页。正文编号列表(`1. Introduction…`)在这一条上直接出局。中文文献等非英文版式回落到"逐条证据分"(DOI/卷页/年份)兜底 —— 只用人名判据会丢掉约 250 个引用块的标注。 - **位置口径统一为字符占比**(原为段落序号):同一篇里两种口径能差一倍,实测 11 篇真参考文献因此被整条漏掉。标题门 0.5、无标题引文链门 0.3。 - **总量上限改为字符占比**(0.6):段落占比会把"每条参考文献单独成段"的 PDF 误伤(实测 60% 段但仅 29% 字符)。超限即放弃判定,整篇按正文索引。 - **新增整篇兜底**:分块后若没有任何 weight>0 的分块,把最后一节(≥800 字符)按 weight 1.0 写回,section 标记 `<原节> (rescued)` —— 保证"任何文档都不会从检索里消失"。 - **可观测**:`kb_stats` 的 health 新增 `docs_without_retrievable_chunks` + `blind_sample`;插件渲染「⚠ N 篇文档没有任何可检索分块」。升级提示分型:新增 `stale_kind`(chunk / meta)与 `CHUNK_AFFECTING_REVS`,`stale_kind=chunk` 时**不再提供**"只刷新元数据"这个无效选项。 - **实测(同一 316 篇库上 rev4 与重灌后 rev5 逐项对比)**:整篇不可检索 2 → **0**(块口径与字符口径一致);被吞 >50% 的文档 4 → **3**(字符口径 7 → 4);weight=0 块占比 13.92% → **14.55%**(可检索分块 20345 → 20110,-1.2%)——被判为参考文献的块略有增加,但**查询级实测库内不丢结果**:20 条库内查询 0 条结果变少、0 条被判无命中,17/20 的 top1 不变。少数查询的 top1 会 ±1 位抖动(分块边界变化会连带改变精排分):其中一条 top1 换成了原本"整篇查不到"的文档(兑现本节承诺),另一条换成了相关性更弱的文档(正确那篇退到 #2)——重灌是净收益,但不是"排序完全不变"。 ### 修复:相关性地板 —— 库外问题不再返回 Top-3 垃圾 - **实测标定**(316 篇真实库,12 个库内问题 vs 8 个库外问题):裸精排分 库内 min 0.685 / 中位 0.996,库外 max 0.072 / 中位 0.020(空隙极大);**最高余弦 库内 min 0.691 vs 库外 max 0.688(基本重叠)**。 - 因此:`verdict`(相关/弱相关/无关)+ `no_hit` + `max_score` + `floor` + `closest`(无命中时给"最接近的 5 篇")。`KB_MIN_RERANK` 默认 0.10、`KB_MIN_RERANK_WEAK` 默认 0.35;**纯向量/纯关键词路不做"无关"判定**(余弦不可分离,硬判就是在刀尖上赌)。 - 候选从二元组改成三元组 `(下标, 加权分, 裸分)`:地板只能按裸分判(加权分被章节权重乘过 1.0–1.5,量纲会错位)。 - 插件两侧渲染:无命中给出理由与"库内最接近的 N 篇",并明确要求如实说明、不要再反复改写同一句话重试(深挖档改出"补库三步",见下面「显示层四处误报」一节的第 4 条)。 ### 修复:无命中/弱命中不入缓存 + 缓存 key 稳定性 - 缓存写入门槛去掉 `and results`:负结果同样入缓存(索引/元数据变化时 `cmd_ingest` 会清缓存,key 含解析器 token,不会陈旧)。 - 修掉一个真 bug:缓存 key 里原先是运行时的 `_RERANK_NAME`,模型加载前是 `None`、加载后变成模型名 → **某进程第一次 deep 检索写下的缓存行永远命中不了**。改用配置名 `RERANK_KEY_NAME`。 - 地板/精排参数并入 cache key:改了阈值不会复用旧结论。 ### 新增:会话级状态 + `/kb` 命令 + 三档关闭 + 深挖模式 - `scope/depth/strict/enabled/diligence` 改为**按会话隔离**(会话键取 `exec.agent.id`,即 SessionId)。旧实现是插件闭包里的单份变量:第二个会话起不再询问、某会话改动污染全 app。 - 状态落 `kb_scope` / `/kb`;工作区默认值存 `<工作区>/.kb-rag/state.json`(引擎新增 `state` 命令代读写,插件不直接碰文件系统)。`/kb save` 才持久化(第一版实现直接持久化,`/kb both` 会污染之后所有新会话)。 - `/kb` 命令(direct UI handler,不进模型):`status` / `kb|both|web` / `quick|deep` / `strict on|off` / `thorough|normal` / `off [hard|search]` / `on` / `save` / `policy`。 - 三档关闭:**软关闭**(工具在,调用即返回 `kb_rag_disabled`,不拉守护进程)/ **硬关闭**(运行时撤掉 10 个工具注册,无需重启)/ **半关闭**(只撤检索,保留入库与统计)。 - **深挖模式**:用户明确要求"仔细找/慢慢来/别省时间/把相关文献都找齐"时用 `kb_scope(diligence="thorough")` 或 `/kb thorough` —— 解除"≤3 次调用、无命中即停"的省成本纪律,改为「反复检索 → `kb_fetch(ingest=true)` 补库 → 引文关联 → 增量入库 → 再查」的循环;结果层的"叫停类"规则在该模式下自动让位给"下一步补库"的动作指令。配套:`kb_fetch` 新增 `ingest=true`(下载即入库),citations 条目新增 `doi` 字段(可直接据此补库)。 - `userQuestions` 改为**调用时**惰性读取(apply 期一次性捕获会在服务未注册时静默跳过所有询问);**不写进 `inject`** —— 可选服务未注册会让插件永远 park。 ### 修复:动态插件半边(`plugin/host.js`)与静态半边对齐 - 上一节的会话级状态 / `/kb` 命令 / 三档关闭 / 深挖模式此前**只落在 npm 包那一半**:动态插件侧仍是闭包里的单份 `scopePref/scopeDepth/scopeStrict`(第二会话起不再询问、改动污染全 app),没有 `/kb`,也没有 `diligence`。本次把两者对齐:会话级状态(含 `askedAt`/`netAsked`)、工作区默认值 `state.json` 读写、`/kb`(`status`/`kb|both|web`/`quick|deep`/`strict on|off`/`thorough|normal`/`off [hard|search]`/`on`/`save`/`policy`)、软/硬/半关闭与 `/kb on` 重新注册、`kb_scope` 的 `diligence`/`save`、`kb_fetch` 的 `ingest=true`、`presentationMeta`(来源卡片元数据)与 `presentCall`。 - 描述层与结果层改用镜像块:`plugin/host.js` 现在通过内嵌的 `KBG`(`tools/sync-host-guidance.mjs` 从 `npm-package/lib/guidance.js` 生成,`--check` 防漂移)调用 `guidedDescription` 与 `resultNotes`,删掉了宿主侧手写的 `kbResultNotes` 分支。两个半边的 10 个工具现在**逐字段一致**(描述、参数 JSON Schema、输出 schema、`timeoutMs`、呈现器)。 - **修掉一个只在动态半边出现的真 bug**:沙箱对工具 `execute` 的返回值做 lossless-JSON 校验,`undefined` 字段会直接报错 —— `kb_scope` 返回 `strict_note: undefined`(strict 关闭时的默认路径)因此**每次调用都失败**(`Error: … strict_note must be lossless JSON data …`)。现在返回前剔除 `undefined` 字段。静态半边不受此约束,但响应形状保持一致。 - **另一条沙箱约束(对齐时踩到、已加回归)**:动态半边的 `harness.registerTool` 只接受 `harness.defineTool` 返回的**同一个对象**(上面有个不可枚举的 Symbol 标记)—— 照搬静态半边那样 `Object.assign({}, spec, …)` 包一层 `execute` 会丢标记,注册时报 `dynamic tool registration must use a tool returned by harness.defineTool(...)`,后果是 10 个工具全不注册。动态半边改为**原地替换 `execute`**,并在包装函数上记住原始实现,`/kb on` 反复重注册也不会层层套娃。 - 顺手修掉 `kb_rag` 描述里两处引号与静态半边不一致(`"…"` → `「…」`),这是两半唯一的描述差异。 - 新增 node suite `host_half`:以函数体方式加载 `plugin/host.js`、harness 用真实 `sandboxDefineTool`(因此会真正校验 schema / 渲染块 / JSON 可克隆),覆盖 inject 不得含可选服务、10 个工具、描述与结果注入、`/kb` 三档关闭与会话隔离、深挖分档、`commands` 服务缺失兜底,并逐字段比对两半的工具定义。 - **客户端半边同样对齐**:`plugin/client.js` 此前只有"解析结果文本里的链接"这一条路,既读不到宿主刚补上的 `presentationMeta`(结构化 sources/verdict/closest),也没有无命中理由、弱相关提示与会话栏指示条。现在两个客户端半边跑同一套渲染逻辑(结构化 meta 优先 + 文本退回、作者/年份/章节、无命中理由与最接近的几篇、`/kb` 指示条),并声明 `inject: ['slots']`;`client_half` 套件把同一组断言对两个半边各跑一遍,并逐条比对两半的中文文案集合。 ### 修复:显示层四处误报(软关闭报「0 文档」/ 开关四档塌成两标签 / 重复绝对路径 / 深挖与「别换词」对撞) - **软关闭时 `kb_stats` 谎报空库**:软关闭的 wrapper 只回 `{ok, kb_rag_disabled, scope, note}`,没有 `docs/chunks/vectors`,渲染器却直接落到统计行,输出「0 文档 / 0 块 / 0 向量」——用户会以为库被清空了。现在渲染「**kb-rag 已关闭**(范围 kb)—— …当前处于关闭状态…」,并把 `note` 原样带出。两半同步。 - **`/kb status` 的开关四态塌成两标签**:状态卡只判 `st.enabled` 布尔,于是 `on` 与 `off search` 都写"开"、`off` 与 `off hard` 都写"关(软关闭)"——`off hard` 已经把 10 个工具全撤掉,卡片上完全看不出来(同一张卡的 `- 工具注册数:N / 10` 就在下一行,两行互相矛盾)。现在按 `enabled` + 实际注册数**无损推出**档位:`开` / `开(仅检索工具:写库类已卸载)` / `关(软关闭:工具仍在注册表里…)` / `关(硬关闭:工具已卸载,仅 /kb on 可恢复)`。不新增持久化字段,旧 `state.json` 同样适用。 - **每条来源重复打印一行绝对路径**:无 DOI 的证据先印「无 DOI · score … · 文件:`xxx.pdf`」(基名),紧接着又推一行 `C:\…\papers\xxx.pdf`。信息重复,且每篇来源泄露一次本机目录结构(`r.path` 与 `r.file` 引擎都会给)。删掉该行;定位文件用基名 + `kb_stats` 里的库根即可。 - **深挖档下两条指令自相矛盾**:规则层在深挖档会跳过"叫停类"规则、换成"补库循环"(`换术语 / kb_fetch / related`),但**渲染层两种档位都会跑**,曾无条件写「不要再换词反复重试」——用户在深挖档看到的就是同一块输出里既有"去补库"又有"别重试"。现在渲染层读 `__diligence` 分档:深挖档出补库三步,默认档出"如实说明 + 按 scope 转 `web_search`"。 - 顺手消掉"换词"这个词本身的自相矛盾:调用纪律第 1 条要求"每次必须换实质策略(中文→英文术语 / 放宽 filters / 换同义术语)",第 2 条与无命中规则却说"不要再换词连试/不要再换词重试";`kb_scope` 的 `diligence_note`(默认档)更是把两者写进同一句——「一次提问最多 3 次检索,**每次换实质策略**;无命中就如实说明,**不要换词穷举**」。统一改为"不要再反复连试/不要再反复重试"。`npm-package/lib/guidance.js` 是唯一源,`plugin/host.js` 的镜像块由 `tools/sync-host-guidance.mjs` 重新生成(`--check` 已通过);`diligence_note` 那处是两半各一份的活文案,逐字对齐。 - 回归:`s_host_half` 与 `s_plugin_harness` 各 +8 条(渲染层分档、绝对路径不得出现、`kb_stats` 关闭文案、状态卡三档区分、`kb_scope` 默认纪律文案),`s_guidance` +2 条(默认档措辞、纪律文本不再出现"换词");两半逐条对齐。全量 15 suite / **380 断言** / 0 失败。 ### 修复:filters 归一化 - `title/authors/journal` 两侧同时归一(连字符/下划线/连续空格 → 单空格、小写)。实测现场:`Electric-field control of local ferromagnetism` 用空格写法查过 3 遍(含 2 次零命中)。 - `authors` 改为**分词 AND**:`Smith J` 命中 `Smith, J. A.; Jones, B.`(整串子串匹配在标点/顺序不同时必然零命中,这是 agent 穷举 author filter 的直接原因之一)。 ### 修复:GPU 兜底做成硬保证 - **加载期**就撞 CUDA(驱动/内核不匹配、cuDNN/cuBLAS、加载期显存不足)时退回 CPU 再加载一次,并把本进程 GPU 标记为不可用(sticky)—— 旧实现会在同一条路上连撞三次(本地缓存 → 下载 → 镜像)全部失败,最终把模型判成不可用,而 CPU 明明能跑。 - 设备探测每进程最多一次(含失败结果);CUDA 不可用或已判定不可用时 `device_kwargs` 直接给 `device="cpu"`,不给上游自动选择再去碰 GPU 的机会。 - 运行期兜底从"仅 OOM"扩到整类 CUDA 错(内核不匹配 / device-side assert / 设备丢失):清缓存缩批 → 移到 CPU → 再失败才抛。非设备故障照常上抛,不被兜底掩盖。 - `KB_DEVICE` 现在对**精排**同样生效(此前精排三处构造都没传 device);`_move_model_to_cpu()` 兼容 `CrossEncoder`(本身不一定有 `.to()`,退到 `model.model.to()`);设备说明按链路记账、正常加载成功时清除。 - `device_report()` 新增 `gpu_usable` / `gpu_disabled_reason`(torch 未加载时不主动 import,保持 `kb_stats` 便宜)。 ### 新增:npm 包补上客户端半边 - `npm-package/lib/client.js`(按 DSH 客户端 bundle 的 lazy-CJS 形态:`window.__ModuleLoader__.load({ id, factory })`,导出 `apply` / `inject`)+ `package.json` 的 `exports["./client"]` 与 `dsh.client`。 - 内容:`kb_search` / `kb_rag` 的**来源卡片**(可点击 DOI、作者/年份/章节、弱相关提示、无命中时显示理由与最接近的几篇)与会话标题栏指示条。 - 宿主侧配套:`output.presentationMeta` 投影结构化 sources/verdict/closest(`ContentBlockMap` 只有 text/reasoning/image/tool-call/tool-result,没有可自造的通知块类型)。 - 修掉一个真 bug(两个客户端半边都有):退回路径的正则按「`[1] [标题](链接)`」写,而宿主渲染的是「`1. [标题](链接)`」→ 永远匹配不上、没有 meta 时卡片恒显示"无命中"。 ### 变更:精排成本可配、扫描可观测、提示镜像生成 - `KB_RERANK_CHARS`(默认 1800,截到 512 约 3× 快)、`KB_RERANK_POOL`(候选池下限,默认 20)。 - 检索响应新增 `scan`(参与检索的分块数、读取耗时、是否加载了向量、语料缓存是否命中、超阈值提示):库变大时这是第一个瓶颈,以前完全不可见。纯关键词路不再 JOIN `vecs`(省掉全部向量 BLOB)。 - **语料缓存**:按 (库路径, 是否要向量, `MAX(chunks.id)`, `COUNT(docs)`, WHERE) 记账,同会话里换查询词不再重复读取(实测 167 ms → 0–1 ms);签名变化(入库/清库/过滤变化)即失效,`KB_CORPUS_CACHE=0` 可关、`KB_CORPUS_CACHE_MAX`(默认 6 万块)限制常驻规模。 - **BM25 提速**:小写文本与平均长度按语料签名缓存,并把"df 一遍 + tf 一遍"合并成一遍 —— 实测 302 ms → 54–125 ms(2.2–3.1×),**8 个查询(含中文)逐位一致**(`_test_bm25_identical.py` 用 git 上一版实现做 A/B)。 - 检索侧阶段成本已实测留档(`docs/BACKLOG.md` §2.9):当前 20345 块下扫描占 deep 约 12%,精排才是绝对大头;架构级索引化(FTS5 等)明确推迟并写明重新评估的触发条件。 - MCP 侧对齐:`kb_fetch` 增加 `ingest` / `kb_root` 参数(下载即入库),与 DSH 侧一致。 - 新增 `tools/sync-host-guidance.mjs`:动态插件半边(`plugin/host.js`)的提示文本由 `lib/guidance.js` **生成**(`--check` 可检测漂移),根治"两半手抄必然漂移"。 - 提示层结果规则限定到检索类工具(`kb_ingest` / `kb_zotero` 也带 `embedding_error`,但那里的文案由 `renderIngest` 自己给;用检索口径会说成"本次检索已退化",误导)。 ### 修复:缓存一致性审计(提前退出与"缓存永不失效"专项) 一次针对性复查("乱七八糟的提前退出 + 缓存永远无法命中/失效"),共 6 处: - **内存语料缓存看不到"只改元数据"的写入**(本次新增缓存引入):`metadata_only` 刷新后 `title/authors/year/doi` 变了,但 `MAX(chunks.id)`/`COUNT(docs)`/`MAX(indexed_at)` 全都没变 → 同一守护进程里的检索继续返回旧元数据(而 `kb_stats` 走另一条查询显示的是新值,"一半新一半旧")。 修法两层:① 写库命令(`ingest`/`zotero`/`dedup`/`clear`)结束后显式 `_invalidate_caches()`; ② 语料缓存 key 增加**库文件指纹**(主库 + WAL 的 mtime/大小)与 `MAX(indexed_at)` —— 覆盖跨进程写入(另一个 CLI/后台子进程写同一个库时,进程内钩子根本不会被调用)。 - **缓存条数没有上限**(本次新增缓存引入):key 含过滤条件,每条又是整库分块副本 —— 多试几种 filters 就能把守护进程撑到几百 MB。新增 `KB_CORPUS_CACHE_ENTRIES`(默认 2)按插入顺序淘汰; BM25 缓存同样受约束。 - **查询响应缓存表无上界**:每个不同查询一行、只在入库/元数据变化时整体清空。新增 `KB_QUERY_CACHE_MAX`(默认 3000,每 200 次写入修剪一次,保留最近 N 条)。 - **`_REL_CENTROID`(关联文献的文档质心)只在部分写路径清理**:补进 `_invalidate_caches()`, 所有写库命令走同一条失效路径。 - **`_ingest_file` 的 `duplicate` 分支同样不补向量**:与 `skipped` 分支同理——内容已在别处入库 不代表那篇的向量是齐的(模型不可用时入的库同样是 0 向量)。现在也补一次,并把数量计入 `totals.vectors`。 - **`cmd_clear` 的 docstring 写在语句之后**(等于没有 docstring);插件侧 **`askScopeOnce` 与持久化默认值的读取竞态**:进程重启后的第一次检索会在 `persisted` 还是空对象时 就判断"没记住偏好",把已经答过的范围又问一遍 —— 现在先 `await` 读取再决定是否询问。 - 另外给插件的按会话表加了条数上限(100),避免 GUI 长跑后 `sessionStates`/节流表无限增长。 验证:新增 `_test_cache_staleness.py`(同进程写入 + **跨进程**写入两个场景都必须读到新值,且 `scan.corpus_cached` 如实反映是否重读)与 `_test_corpus_cache.py` 的条数上限断言;全量回归 12 组通过。 ### 修复:引文关联的回归(全库实测驱动) References 判定重做后必须复核**引文关联**是否还准 —— 即"文内 `[n]` 编号能否在文末找到对应条目"。 用同一批 316 篇真实 PDF、只解析一次、两套切块各跑一遍(脚本 `_cites_compare.py`): | | 改前 | 改后 | |---|---|---| | 文末能解析出的条目数 | 16479 | **16929** | | 文内引用编号(去重) | 15695 | 15813 | | 能对上的编号数 | 12541 | **12911** | | **命中率(按编号加权)** | **79.9%** | **81.6%** | | 逐篇 | — | 改善 25 / 持平 273 / 变差 10 | | 完全解析不出条目的文档 | 54 篇 | **38 篇**(新增可用 21 / 失效 5) | 四处修复(各有实测依据): - **紧贴式条目正则**改为 `(?=[A-Z](?:\.|[a-z]))`:既认 Wiley 的 `1J. Valasek` / `1Smith`,又排除 `2D materials` / `3D printing` / `4H-SiC`。上一版写成 `[A-Z][a-z]`,把 Wiley 式整表排除 (某篇综述 237 条只剩 5 条)。 - **巨段文献表通道**(阶段 3b/3c):整张文献表被 PDF 抽成一两个大段、条目还常在词中换行时, 行首锚定的链检测完全看不到(某篇 59 条、另一篇 54 条整表丢失)。现在既支持"任意位置的编号 + 递增链",也支持"按段判定"(`_ref_para_is_list`,不要求编号严格递增 —— 双栏抽取会让编号交叉)。 - **补充材料编号**:编号接受字母前缀(`S1.` / `[S1]` / `[S1–S3]`),条目与文内引用两侧都支持; 条目键统一走 `_ref_num_key()`(`S12` → 12),避免 `int()` 直接抛错。 - **位置口径按字符**(此前已改)。 **两条被实测否掉的路线**(已从代码里删除并在注释里留档,避免以后再走一遍): - `REF_MAX_CHAR_SHARE`("参考文献超过 60% 字符就整段作废"):把综述类大文献表一起丢掉 (某篇 190 条、某篇 237 条 → 条目归零),命中率被压到 **79.2%**、失效 11 篇。 - 逐段"散文退回"(把 References 区里明显散文的段落退回正文):依赖"这段没检出条目", 而条目样式认不全(`(1)`、`【1】`、作者-年份式都不认)→ 真文献表被误退, 命中率 **79.8%**、失效 13 篇。正文被吞的代价改由"整篇不可检索兜底"承担。 ### 未做 / 待验证(诚实记录) - **客户端半边需在真实 Web GUI 里确认**:官方没有给出第三方包手写该 bundle 的公开规范(是否接受手写、`require("react")` 的外部化规则)。加载失败不影响核心功能(工具与 markdown 来源链接由宿主渲染)。两个半边(npm bundle 与动态插件函数体)现在都已覆盖同一组渲染断言,但"在 GUI 里真的被 materialize"仍需人工看一眼。 - **十万块级的索引化改造未做**:`_search_core` 仍是 O(库规模) 扫描(已加 `scan` 观测与提示);方案(FTS5 / 倒排 + 向量分级)记在 `docs/BACKLOG.md`。 ("动态插件半边的会话级状态未对齐"已于本轮修复,见上面的"动态插件半边与静态半边对齐"。) ## [1.6.6] - 元数据刷新通道 + 陈旧数据检测 + 入库进度可见 + 检索语言归 AI 层 ### 新增:元数据刷新通道(`kb_ingest`) - **`metadata_only=true`**:只重跑解析与元数据抽取并 `UPDATE docs`(标题/作者/年份/期刊/DOI),**不重切块、不重嵌入**。实测 312 篇 **28–30 秒(约 90 ms/篇)**。存在的理由:增量入库按 sha256 跳过未变文件,所以引擎的解析改进**不会自动作用于老库**——实测一处抽取改动漏掉了 49 篇的 DOI,直到一次全量重灌(322 秒)才暴露 - **`rebuild=true`**:按库内现有路径**原地重灌全部文档**(引擎自己从库里取路径)。避免由调用方传目录:`force` 会绕过去重检测(`if dup is not None and not force`),传目录会把内容重复的文件当新文献重复入库(实测会多插 31 个重复文件 + 一个 Office 临时锁文件) - 元数据刷新走**只读首页**的快通道(新增 `read_first_page()`):标识符/年份/标题的判据全部落在 `page1` 与 PDF 元数据上,因此结果与全量解析一致、成本降到约 1/10(首页无文本层或非 PDF 时自动回退全量解析) ### 新增:DOI 与元数据的抽取改进(`PARSER_REV` 2 → 3) - **DOI 增加 XMP 来源**:部分出版商 PDF 正文里根本不印 DOI,只写在 XMP 元数据里(实测 Science Advances / RSC / Nature 系);**16 篇**因此恢复,全库 DOI 覆盖率 **46% → 66%**(191 → 207 篇) - **不再盲信 PDF 生产元数据**:`/Author` 形如单个「姓, 名」(排版/制作人员)且首页或文件名给出多作者信号时弃用、改走文件名回退;标题里的生产残片(如 `*.indd`)一律拒绝。实测某篇的 `title='manuscript-v3 Review.indd'`、`authors='Smith, John'` 被修正为真实标题与作者,并因此**恢复了「引文 → 库内匹配」**(`[库内]` 标记此前静默失效) ### 新增:陈旧数据检测(schema v3 → v4)与升级提示 - 每条 `docs` 记录写入 `indexed_with`(形如 `<引擎版本>/rev`);`PARSER_REV` **只在改动会写进库的解析逻辑时 +1**——判定只看 rev、不看引擎版本号,否则每次发版都会把整库标成陈旧,提示变成噪音 - `kb_stats` 新增 `stale_docs` / `stale_sample` / `parser_rev` / `indexed_with` - DSH 插件在**会话内首次检索时**询问一次(仅当 `stale_docs > 0`):暂不处理 / **只刷新元数据(秒级)** / 全量重灌(自动转后台) ### 新增:入库进度可见(第 10 个工具 `kb_status`) - DSH 侧此前是同步调用,30 分钟时限内**没有任何进度输出**;现在**由引擎自己**判断批量大小并转后台(`async_if_large`,阈值 `KB_ASYNC_THRESHOLD` 默认 25),立即返回 `job_id`,用新增的 `kb_status` 轮询:`running` 给出已处理/错误/分块数,`done` 给出 totals 与最近文件,`error`/`not_found` 如实说明 - 计数在引擎侧完成(不在 JS 宿主里引入文件系统访问);后台任务的 `progress_path` 保证不会二次 fork - 工具数 **9 → 10**;DSH 与 MCP 两侧现在都有 `kb_status`(此前只有 MCP 有,文档里"用 kb_status 替代 kb_scope"的表述一并订正) ### 改进:检索语言归 AI 层 - 工具描述与 README 明确:**query 用英文术语串**(3–12 词,结构「材料/体系 + 方法/工艺 + 性质/表征」),年份/期刊/作者放 `filters`,需要中文文献时用原话再发一条;**引擎按原样检索、不翻译** - 引擎新增零成本检测:query 含 CJK 且库内中文占比 < 10% 时,响应附 `lang_note` 说明(BM25 关键词路空转、命中主要由跨语言向量匹配决定),**不改写查询**;渲染层显示该提示 ### 实测 - 全库 312 篇:`force` 全量重灌 **322 s**(`updated=312 / errors=0 / duplicates=0`);`metadata_only` 刷新 **28–30 s**;DOI **46% → 66%**;页码锚点恢复 **99.7%**;`stale_docs` 归零 - 新增功能的回归验证见 `docs/BACKLOG.md` §3 ### 真实文档测试与审计修正(发布前抓出并修掉) - **同一论文的多份 PDF 会各占一个结果位**:两份 PDF 内容不同 → sha256 去重不合并,deep `top_k=3` 里同一篇综述出现两次(用户实际只拿到 2 篇)。检索层现按**归一化 DOI**(大小写不敏感)折叠,保留最高分那条并**从后续候选补齐 Top-K**,返回 `dup_collapsed`,界面提示"已折叠 N 份同论文副本" - **查询缓存 key 现包含引擎/解析器版本**:否则升级后旧缓存继续吐旧行为(实测:改了结果折叠规则后,缓存仍在返回没有新字段的旧响应) - **元数据刷新不再抹掉 Zotero 写入的字段**:`metadata_only` 曾用首页抽取结果无条件覆盖 `docs` 行,而 Zotero 迁移写入的 `doi`/`journal` 不在 PDF 首页里(实测 DOI 211→209)。现改为**绝不用空值覆盖非空值**,同时保留"好值替换脏值"(如 `.indd` 标题) - **作者 junk 过滤扩充**(`PARSER_REV` → 4):真实库命中 `user`、`Administrator`、`aipuser` 等出版商/系统账号,现按名单 + `*user` 账号模式 + 软件名拒绝,回退文件名解析 - **`kb_ingest(rebuild=true)` 可省略 `paths`**:此前 schema 把 `paths` 标为必填,而 rebuild 的路径由引擎从库内取,调用方必然撞 `missing required property paths`;`kb_scope` 同理改为可只查看(此前 `scope` 必填,"查看"调不通) - **渲染修正**:失败条目显示原因(`✗ 1.pdf · ValueError: no text extracted`,此前只有文件名);`metadata_only` 结果单独渲染(此前显示成"入库完成 新增 0…");`kb_zotero(dry_run)` 渲染为"Zotero 预演 · 候选 N 篇"(此前同样显示成"入库完成 0/0/0");检索头部按实际 `mode_used` 显示(此前 `mode=keyword/vector` 都写"混合检索");后台转交提示不再重复两遍;异步 `pending_files` 报真实篇数(此前是"阈值+1"的提前退出值,如 100 篇显示 26) ## [1.6.5] - 并发写锁容错 + zotero 逐文件提交 + 引文链后章节还原 ### 并发与后台任务(补上 1.6.2 未覆盖的两处) - **检索缓存写不再让整次检索失败**:`_search_core` 结尾的 `INSERT OR REPLACE INTO cache` 此前没有锁容错——异步入库子进程持有写锁时,已经算完的检索会以 `database is locked` 整次报错(DSH/MCP 侧表现为工具调用失败)。现按"缓存只是加速"处理:只吞 locked/busy,其他 `OperationalError` 照旧上抛 - **`kb_zotero` 改为逐文件 commit + 进度回写**:原实现整批单事务、且从不写 progress——异步整库迁移既看不到进度,任务被杀还会把已入库文件**全部回滚**(与 `cmd_ingest` 语义不一致),写锁窗口也覆盖整批。现与 `cmd_ingest` 对齐:每篇 `_ingest_file` 后 `db.commit()` + `_prog()` - **异步启动校验真正生效**:原实现 `Popen` 后**立刻** `poll()`,而 python 即使只是打开不存在的脚本也要数十毫秒才退出,最可能的启动失败(脚本路径/解释器错误)抓不到、留下长期 running 的任务。现改为 `wait(timeout=0.5)`,并在 Popen 前校验引擎脚本存在,失败即清掉 job 文件 - 订正 1.6.2 的两处过宽表述(见该节内注) ### 章节标注 - **引文链结束后还原被打断的章节**:原实现一律重置为 `Front matter`(权重 1.0)。Nature 式论文的正文 refs 与 Methods refs 是**两段离散链**,链后正文因此被标错章节——`filters.section` 精确过滤(如 Methods)会漏召回、hybrid 排序权重偏低、结果里的 §标签也是错的。现进入 References 时暂存 `(section, weight)`,链结束还原(实测:链后段落由 `Front matter/1.0` 变为 `Methods/1.2`) - **图注块之后的正文同样还原章节**(同类缺陷):`Fig./Table` 图注原实现处理完后把章节重置为 `Front matter`,Results 中插图之后的所有段落都丢章节与权重;现图注仍独立成 `Figure/Table` 块,但其后的正文回到图注前的章节 ### 卫生 - `kb_clear` 清理 `.kb-jobs/` 时连原子写的 `*.json.tmp` 一起清(原先 glob `*.json` 不匹配,进程在 rename 前被杀就会留下清不掉的残留) - `_apply_ref_spans` 在无页信息时返回 `None` 而非 `[]`,保持"无页 = None"语义(当前调用方都有 `len()` 守卫,属预防性修正) ### 实测 - 新增 16 项回归验证并全部通过:缓存撞锁时检索仍返回结果、无竞争时缓存照常命中、zotero 中途中断后**首篇已落盘且进度已回写 processed=1**、引文链后章节还原为 Methods/1.2、图注独立成块且其后正文还原、子进程立即退出被识别且不留 job、引擎脚本缺失时直接失败、`kb_clear` 连 `.tmp` 一起清 ### 文档 - 新增 [`docs/BACKLOG.md`](docs/BACKLOG.md):本版修复清单、**尚未修复**的问题(后台任务无自动失败终态、页码近似、引擎退出行为待验证、`run_job` 白名单待确认、未审计区域)、回归验证方法与记录约定;README 文档索引已挂链 ## [1.6.4] - kb_fetch 描述订正、文档与元数据同步 - **文档与元数据同步到 1.6.3**:`plugin/kbrag.plugin.json` 描述补全新能力(混合检索 + 交叉编码器精排 + 章节/页码级出处 + 快速/深度双模式);`plugin/host.js`、`plugin/client.js` 头部注释与工具注册日志的版本号 `v1.0.0` → `v1.6.3`;`npm-package/package.json` 的 description 与 keywords 同步(补 `dsh-plugin`、`mcp`) - **异步作业归属订正**:异步入库(`ingest_async` / `status` / `.kb-jobs/`)**只走 MCP 侧**(`KB_ASYNC_THRESHOLD` 自动转后台 + `kb_status` 轮询);DSH 插件是同步长任务(`plugin/host.js` 时限 30 分钟,无 job/status 处理)。此前 README 架构图、npm 页与 QUICKSTART 把异步说成通用行为,已按实现订正 - **npm 页(`npm-package/README.md`)补齐 1.6.x 能力**:新增「Engine capabilities」小节(章节感知分块、混合检索、精排、出处、引文关联、快速/深度、增量去重、查询缓存、常驻守护进程 + 异步入库);工具表补异步作业、出处(含 PDF 页码)与检索深度说明;示例改为石墨烯主题;文档统一英文(中文见 `README_CN.md`) - **过时文档订正**:`SECURITY.md` 与 `npm-package/SECURITY.md` 工具数 8 → 9;`docs/MIGRATION.md` 当前 schema 由 1.5.0 更新为 1.6.3 / `user_version = 3`(补 `chunks.para_start/para_end`、`page_start/page_end` 两列与 v1/v2/v3 三个迁移块),变更记录拆出 1.6.1 的 v3 行;`QUICKSTART.md` 补检索深度、引文补充与同步/异步入库边界说明;`UNINSTALL.md` 补 `.kb-jobs/` 说明并统一路径占位符;`docs/OUTPUT-FORMAT.md` 第 6 节的"待落地"表述改为已实施状态;`mcp-server/README.md`、`docs/DESIGN.md` 同步到 1.6.3(schema v3 / 检索链路 / 9 工具 / 异步作业 / depth 双模式 / `UNPAYWALL_EMAIL`) - **`kb_fetch` 行为描述订正**:实现一直是"出版商正式版优先"(先解析落地页 `citation_pdf_url`,**校园网/机构订阅网络下可直接取得订阅版 PDF**,无权限再回退 Unpaywall/Crossref 的开放获取),但工具描述却写成"只下载 OA 文献,不碰付费墙",与实际行为不符。现统一为完整顺序说明,并保留合规边界:只做常规抓取,不绕过付费墙、不访问 Sci-Hub、不伪造凭据。同步 `plugin/host.js`、`npm-package/lib/index.js`、`mcp-server/server.py` 三处工具描述与 README(中英)、npm/mcp 文档、QUICKSTART - **`doi_pdf.mjs` 尊重 `UNPAYWALL_EMAIL`**:Node 下载器的 Unpaywall 请求此前硬编码示例邮箱(`researcher@university.edu`),配置项只对 Python 回退路径生效;现与引擎一致读取该环境变量 - **`doi_pdf.mjs` 头部注释订正**:原注释写"优先 OA,其次校园网订阅",与实际实现顺序相反,已按实际顺序重写(arXiv → 出版商正式版 → 落地页 pdf 链接 → Unpaywall OA → Crossref) ## [1.6.3] - 引文关联深挖 + Nature 角标识别 + 快速/深度双模式 + 真·一键安装 ### 引文关联(citation linking) - **Nature 系上标角标识别**:PDF 文本层会把上标引用压平成 `graphene1,2`,引擎在 `read_document` 按**字体度量**检测(字号 ≤ 行内正文 80% + 基线抬高 ≥ 12%),转写为 `graphene[1,2]` 方括号形式入库;宁缺勿错:跳过作者行(≥3 个上标簇)、指数(锚点以数字结尾)、单位标记(`1*` 等非纯数字簇) - **References 三级检测**(原两级):③ 新增**递增条目链**——Nature 无标题 References(标题是图形,正文 refs 1–30 与 Methods refs 31–37 分两段离散出现)与 Science `1. Author` 行首风格;编号从 1 递增或续接上条链、过半条目带年份/et al 信号、链尾在 Acknowledgements/©/图注停止行截止,边界段落自动切分。阶段 ② 修复"文末连续段"被末页公式/坐标轴数字劫持(新增文献列表相似度门槛:过半条目带年份/et al) - **引文条目锚点修复**:References 长块改按**行边界**切分(`split_refs`)——原 `split_long` 句边界拼接把 2、3 号条目挤到行中间压平 `N.` 行首锚点,长引文列表只能解析第 1 条;`_parse_references` 多风格同时命中时取**编号链最完整**的模式(避免年被拆行的噪声风格劫持) - **实测**(11 篇各出版商 PDF):Wiley 综述 0→399 条、Nature Letter 8→37、中文期刊 0→90、Science 0→29,全部只增不减 - **被引文献库内匹配(引文关联深挖)**:`_match_cite_lib` 三规则——① 引文文本中 DOI 精确命中;② 库内标题(归一化 ≥30 字符)整串出现;③ 首作者姓(≥6 字符)+ 括号年份双命中。命中条目带 `lib` 字段(title/authors/year/journal/doi/zotero_key),三层渲染输出「[库内]」标记行(DOI 链接 + 元数据 + 即本证据的 Ref n + Zotero 打开) - **推荐输出格式(回答层三列制)**:`kb_rag` guidance 硬性要求答案末尾按来源分三列——①「库内可查(循引文找到)」必须带关系链《被引文献》被 [证据编号] 的引文 Ref n 引用;②「建议补库(循引文发现)」注明被 Ref n 引用尚不在库内;③「相关文献」(元数据相似)。不得混列,引文关联的必须带关系链 - **渲染优化**:引文编号 `[n]` → `[Ref n]`(与证据编号消歧);库内命中合并为单行(标题链接 + 元数据 + 关系 + Zotero 内联);命中条目(≤5)优先、库外仅展开 3 条 + 一行折叠汇总(Ref 区间压缩如 `5–8`);`score` 仅精排后显示(RRF 融合分无绝对含义);snippet 起止按词边界对齐 - 旧库需 `force` 重灌才有角标与全新 References 切分(入库时处理) ### 快速/深度双模式(depth) - `kb_search` 默认 `quick`(快速检索:混合召回直出、跳过精排/引文扩展/相关文献,亚秒级响应)、`kb_rag` 默认 `deep`(深度检索:重排序 + 引文关联 + 相关文献全链路)——按入口定位自动分流,显式传参永远优先;实测 34ms vs 2.8s - 会话级深度:`kb_scope` 新增 depth 参数;会话启动询问新增「检索深度」问题(快速检索 / 深度检索,默认推荐深度检索);选择解析双向显式(快速检索→quick) - 快速检索 guidance 反长思考:立即作答、一两句内直给、禁止背景铺垫/延伸分析/二次检索;渲染尾注提示 `depth=deep` 升级路径 - 深度检索 guidance:跨文献综合论述 + 三列推荐格式;快速检索渲染压缩(无引文链/相关文献、短 snippet),体积约 57% - 术语统一(五文件):快查→快速检索、详细→深度检索(会话询问/工具 description/guidance/渲染标签/尾注) - MCP `server.py`:`kb_search`/`kb_rag` 签名改 Optional,未传参不再以默认值覆盖引擎模式化缺省;请求剔除 null 值;引擎 `_depth_flag` null 语义兜底 ### 真·一键安装 - **新微包 `dsh-kb-rag-install`**(已发布 1.0.0):裸 `npx dsh-kb-rag-install` 直接可用,根治"包名与 bin 名不一致导致裸命令 E404"的老坑;微包零逻辑(定位依赖转发),安装逻辑仍在主包维护;npm 平铺/嵌套双布局验证通过 - **profile 自动检测**:未指定 `-Profile` 时扫 `~/.dsh/profiles/`(含 cordis.yml/package.json 才算,排除 node_modules 误报)——唯一 profile 直接用;多个时列出(交互可选、非交互走默认目录) - **模型预下载默认开**:`install.mjs` 默认注入 `--models`(配合直连失败自动切 hf-mirror.com 镜像重试,装完即全就绪),`--no-models` 可跳过(两平台脚本均支持) - **非交互默认**:`install.mjs` 默认注入 `--yes`(npx 场景不再被 pnpm 确认卡住);pnpm 全局安装失败自动回退 `corepack enable pnpm` - **中文用户名安装修复**:Windows PowerShell 5.1 的 `$OutputEncoding` 默认 ASCII,管道送 python 的中文路径变 `?` 报 WinError 123——脚本顶部强制 UTF-8(无 BOM)管道编码 - **安装前内存提示**:装前读物理内存并按阈值提示(≥8GB 正常 / 4–8 偏紧 / <4 可能不足) - 模型下载失败自动镜像重试(两平台);Python 缺失提示补 winget/brew/apt - 文档:QUICKSTART/README 换裸命令为推荐写法;npm README 排错表补 WinError 123 行 ### 其他 - `docs/OUTPUT-FORMAT.md`:新增 §2 双模式章节、三列推荐模板、实施记录与局限全面修订(角标/References 三级检测/库内匹配),章节重编号 - 引擎 `kb_engine.py` 运行期 HF 镜像回退真正生效:`HF_ENDPOINT` 在 huggingface_hub import 时固化,同进程后置 `os.environ.setdefault` 是空操作——新增 `_apply_hf_mirror()` 直接 patch `constants.ENDPOINT` + 派生的 URL 模板,embedder/reranker 直连失败自动切镜像重试 - 兼容:`install.mjs` 旧写法 `npx --yes --package dsh-kb-rag -c "dsh-kb-rag-install"` 等价不变 ### 文档重写与隐私清理 - **README 双语化重写**:`README.md`(英文)与 `README_CN.md`(中文)两份逐节对应,顶部互链。结构:定位段 → 输出示例 → Positioning(三条取舍 + 「适用范围与预期」callout)→ 三种形态 → 快速开始(`
` 折叠 Windows/受限网络/大批量)→ 升级 → 工具参考 → 架构 → 实测数据 → 文档表格 → 配置 → 仓库布局 → 已知限制 → 联系/相关项目;术语与边界表述保持技术文档语气 - **去除装饰性符号**:全仓库清理装饰性图标字符(README 与 docs 的表格图标列、状态标记等),渲染输出中的星形库内命中标记改为纯文本 `[库内]`——同步 `plugin/host.js`、`npm-package/lib/index.js`、`mcp-server/engine_client.py` 三处渲染器与 `kb_engine.py`/`server.py` 的 guidance 文本;`→ ↳ ✓ ✗` 等技术符号保留 - **升级路径修正**:DSH profile 是 pnpm 工作区(含 `pnpm-lock.yaml`,`dsh plugin` 内部即转发 pnpm),原文档让用户在 profile 里跑 `npm install dsh-kb-rag` 会与 pnpm 布局冲突。现统一为 `dsh plugin --profile add dsh-kb-rag[@版本]`(或重跑安装器),`npm install` 仅标注为手动部署场景(npm README 的 Option 3 加醒目警告 + 新增 Upgrading 章节) - **隐私清理**:`docs/OUTPUT-FORMAT.md` 的示例改为中性占位数据(原示例使用具体真实文献与 Zotero item key);全部文档/脚本描述中的具体 DOI 与 arXiv ID 统一换成占位符(`10.5555/…`、`arXiv:2401.00001`),作者/期刊改为 `Author A` / `J. Appl. Phys.` 形式;`tools/README.md` 的领域相关示例参数改为中性措辞 - 安装脚本侧修复(审查发现):HF 缓存目录探测在 sh 下用单连字符 `tr '/' '-'` 导致缓存恒判未命中(应为双连字符 `--`);未指定 profile 时 `dsh plugin add` 缺 `--profile` 必然失败却仍 `exit 0`(现改为:唯一 profile 自动用 / 多个非交互报错退出 / 无 profile 默认 web / 安装失败 `exit 1`;dry-run 下多 profile 不中断演练);内存探测的 CIM 非终止错误导致误报"0 GB 内存"(补 `-ErrorAction Stop` + 守卫);微包嵌套布局兜底路径修正 - 配置表订正:`KB_AUTO_PIP` 仅在 npm 静态包 `lib/index.js` 实现(动态插件 host 只提示不自动装);补充 `UNPAYWALL_EMAIL` ## [1.6.2] - MCP 超时加固 + 健壮性修复 - **MCP 大批量入库自动转后台(Kimi Work 60s 超时解药落地)**:`kb_ingest` 先轻量估算待处理文件数(目录递归/文件列表),超过 `KB_ASYNC_THRESHOLD`(默认 25)自动改用 async_mode,立即返回 `job_id` + `kb_status` 轮询指引——agent 无需知道 async_mode 存在,传整个文献库文件夹也不会超时;显式 `async_mode=true/false` 可强制 - **`kb_zotero` 支持 async_mode=true**:async 任务分发泛化(job 带 command 字段,`run_async_job` 按命令分发 ingest/zotero),整库迁移可后台执行 + kb_status 轮询 - **异步入库期间并发读不再锁死(高)**:`cmd_ingest` 由整批单事务改为**逐文件 commit**(写锁窗口从"整批"缩到"单文件+嵌入");`_migrate` 孤儿向量清理改 500ms 短超时探测、撞锁即跳过(读命令的 connect 不再干等 5s 或抛 database is locked) - *1.6.5 订正*:该断言当时过宽——只覆盖 `cmd_ingest` 的连接路径;检索末尾**写缓存**仍无锁容错、`cmd_zotero` 仍是整批单事务(两处均已在本版修复) - **迁移健壮性**:`_migrate` 的 ALTER 只吞 "duplicate column",锁冲突等其他 OperationalError 上抛(避免"版本号置新但列缺失"的静默不一致) - **后台任务卫生**:`cmd_status` 校验 job_id 为 12 位十六进制(阻断目录穿越);done 后自动清理 job/progress 残留(result 保留可重复读);超 1h 无进展提示可能卡死;`kb_clear` 一并清空 `.kb-jobs` - **启动即失败可感知**:`cmd_ingest_async` spawn 后短窗口 poll,子进程启动即退出时立即报错并清理,不再留"永久 running"的幽灵任务 - *1.6.5 订正*:Popen 后立刻 `poll()` 基本抓不到失败(python 打开不存在的脚本也要数十毫秒才退出),实际只剩 1h stale 提示兜底;已改为 `wait(timeout=0.5)` + 启动前脚本存在性校验 - **原子写**:progress/result 改为临时文件 + rename,轮询不会读到半截 JSON - **渲染修正**:DSH 宿主(`plugin/host.js` 与 `npm lib`)改用 `files_total` 显示真实文件数(引擎只回最近 20 条后不再误报"共 20 个文件");`kb_zotero` dry_run 返回完整候选清单(预览语义,不截断);MCP `render_status` 展示 error 详情、`result.ok=false` 如实呈现失败而非假"完成" - 引擎同步进 npm-package 副本 ## [1.6.1] - MCP 异步入库 + PDF 页码锚点(schema v3) - **异步入库(MCP 60s 超时解药)**:`kb_ingest` 支持 `async_mode=true`,fork 独立子进程跑 ingest 并立即返回 `job_id`;新增 `kb_status(job_id)` 轮询进度(`.kb-jobs/` 目录,与 kb.sqlite 同级),宿主超时不影响后台任务 - **PDF 页码锚点(schema v2→v3)**:`chunks` 表新增 `page_start/page_end`(PDF 物理页码,1 基);`read_document` 建立段落→页码映射(`meta['_paras']`);检索结果带 `page` 字段,渲染优先 `§章节 · p.N`,可配合 Zotero `?page=N` 一键跳页;段落号降级为辅助(两栏 PDF 段落合并时段号不可靠);txt/md/docx 与旧数据无页码(NULL)自动降级 - **响应体积压缩**:ingest/zotero 的 `files` 只回最近 20 条 + 新增 `files_total` 真实总数(针对 Kimi Work 等宿主的体积限制) - **渲染增强**:页码优先定位;证据引文关联前 5 条("↳ 引文补充",供补库/深读) - **解释器修复**:MCP 服务默认用 `sys.executable`(拉起服务的 Python)替代裸 `python`,避免命中错误解释器;`KB_RAG_PYTHON` 仍可覆盖 - **库迁移**:`_migrate()` 自动 v2→v3 ALTER 加页码列;旧数据页码为 NULL,`force` 重入库后恢复(详见 `docs/MIGRATION.md`) - 文档:`docs/OUTPUT-FORMAT.md` 页码版示例与 `docs/MIGRATION.md` v3 迁移行同步更新 ## [1.6.0] - 版本化迁移 + 元数据质量修复 + 段落定位与引文关联 - **库结构版本化迁移**:`PRAGMA user_version` 门控替代临时 ALTER(详见 `docs/MIGRATION.md`);首次建库一次建全表+索引并写版本号;旧库 v0→v1 自动补齐表/列并回填 `zotero_key`;**v1→v2 新增 `chunks.para_start/para_end`(段落定位,旧行 NULL)**;迁移显式 `commit()` - **段落定位(隐式元数据)**:`chunk_document`/`fallback_chunks` 记录全局段落号;检索结果带 `para` 字段,默认不渲染,供"这句在文献第几段"追问与点开文献定位(详见 `docs/OUTPUT-FORMAT.md`) - **References 保留 + 引文关联**:References(weight 0)入库供引文关联(检索按 `weight>0` 排除);References 检测两级(行首标题 / 文末连续序号段,支持 `n.` `[n]` `nAuthor` 风格);正文 `[n]` 引用 → 该文献引文条目,检索结果带 `citations` 字段(实测:结构化论文可解析;无标题栏排 PDF 部分解析,见 OUTPUT-FORMAT §6) - **迁移与健康提示**:连接时迁移日志走 stderr;`kb_stats` 返回 `schema_version` / `migration` / `health` - **元数据修复**:纯中文标题支持(≥4 汉字);年份级联(文件名 → ©/Copyright/Vol → 括号 → 裸年份 → creationDate 兜底 + <1990 修正);文件名命名习惯解析(作者-年份-标题 / Z-Library / (作者1,作者2) / 中文 作者-标题);短标题偏好 + 封面重复去重 - **发布包隐私**:移除 npm-package 与 tools/ 中的本地绝对路径与硬编码库路径;Unpaywall 邮箱改 `UNPAYWALL_EMAIL` 可配置 - **README 安装命令补全**:明确 `npx` 必须带 `--package dsh-kb-rag`(裸命令 E404)、`npm install` 在 profile 目录执行、新增 Troubleshooting 表 - 可选 `KB_SQLITE_WAL=1` 开启 WAL(默认关闭) ## [1.5.0] - Zotero 集成 + 文件路径显示 - **Zotero 直接打开 PDF**:`kb_zotero` 迁移时存储 Zotero itemKey,搜索结果渲染 `zotero://open-pdf/library/items/{key}` 链接,点击直接跳 Zotero 阅读器 - **文件路径显示**:所有搜索结果底行展示完整文件路径,方便复制后在文件管理器或引用管理器中打开 - 引擎:docs 表新增 `zotero_key` 列(含存量 DB 自动迁移),搜索/关联文献结果带回 `zotero_key` 字段 ## [1.4.0] - kb_fetch 下载增强 - **kb_fetch 首选 Node 下载器**(随包分发 `scripts/doi_pdf.mjs`):Node fetch 的 TLS 指纹更接近浏览器,手动重定向 + 全程 cookie jar 绕过 Nature `cookies_not_supported`;候选源比 Python 版多(Unpaywall / Crossref PDF link / `citation_pdf_url` meta / 页面 pdf 链接模式),Node 不可用或漏项时回退 Python urllib 路径 - **下载顺序改为「出版商正式版优先,OA 兜底」**:先落地页 `citation_pdf_url`(校园网/机构 IP 直接下订阅版 PDF,实测 Nature Materials 付费墙期刊成功),再 Unpaywall/Crossref OA - **arXiv 直连补全**:裸 ID / `arXiv:ID` / `10.48550/arXiv.ID` / abs URL 四种形式均直达 arxiv.org(原 doi_pdf.mjs 无 arXiv 分支) - **反爬识别**:Cloudflare "Just a moment" 与 Akamai `bm-verify` 挑战页明确报"需真实浏览器手动下载后入库"(Wiley / science.org / cambridge.org / MDPI 实测 403);MDPI 令牌跟随尝试保留(部分站点可过) - **快速失败**:`_download_bytes` 对 `text/html`(付费墙页)立即失败回退,不再整页下载后再判魔数 - **动态插件清单同步**:`plugin/kbrag.plugin.json` 版本号 1.0.0 → 1.4.0,`engine.commands` 补 `fetch`,`tools` 补 `kb_fetch`(此前清单长期未随发版更新) - 实测(2026-09,校园网):Nature Comms / Sci Reports / arXiv / Nature Materials(订阅) 均经 citation_pdf_url 或直连成功;Wiley/Science/Cambridge/MDPI 为 JS 反爬,需浏览器手动下载 ## [1.3.1] - 安装器体验 + 隐私修正 - **检测环境避免重复下载**:安装器先探测 embed/rerank 模型是否已在 HF 缓存,已缓存则打印"已缓存,跳过下载";未缓存且未加 `--models` 时提示"首次检索自动下载"并给出镜像指引 - **人类可读提醒**:模型下载前明示体积(embed ~95MB / rerank ~1.1GB)、Ctrl+C 可跳过、HF 镜像地址;未设 `HF_ENDPOINT` 时主动提醒国内镜像 - **隐私修正**:安装脚本与文档中的示例从「研究者领域专属示例」改为中性的「石墨烯化学气相沉积合成」,移除作者研究领域信息 - **编码修复**:install.ps1 恢复 UTF-8 BOM(Windows PowerShell 5.1 无 BOM 会把中文当 GBK 读导致脚本语法报错) - 文档:安装命令用 `web` 实值 profile(可直接复制,`dsh web` 启动即 `web`) ## [1.3.0] - 一键安装补全 - **npm bin 入口 `dsh-kb-rag-install`**:新增 `install.mjs`(37 行薄分发器,`"bin": {"dsh-kb-rag-install": "./install.mjs"}`),一行装环境:`npx --yes --package dsh-kb-rag -c "dsh-kb-rag-install --profile "`;按平台转发到 `scripts/install.ps1|sh`,Windows 自动把 bash 风格参数翻译成 PowerShell 风格(`--profile`→`-Profile`),用户全程只用一种参数写法;`engines: node>=18` - **一键安装脚本**:新增 `scripts/install.ps1`(Windows)与 `scripts/install.sh`(macOS/Linux/Git Bash)+ `install.cmd` 双击入口,一条链完成:Python ≥3.9 定位 → pip 依赖安装(`--mirror` 镜像、`--user` 回退、`--with-docx` 可选)→ 引擎 stats 冒烟测试 → Node/pnpm 检查(缺 pnpm 自动 `npm i -g`)→ `dsh plugin --profile add dsh-kb-rag` 安装并激活 → 可选 `--models` 预下载模型(尊重 `HF_ENDPOINT`/`KB_EMBED_MODEL`/`KB_RERANK_MODEL`);`--dry-run` 全流程演练,幂等可重跑 - **KB_AUTO_PIP=1 可选自动装依赖**:插件启动探测到缺失时默认仍只打印命令(安全默认不变);设 `KB_AUTO_PIP=1` 后自动执行 `python -m pip install`(固定 argv,不进 shell,尊重 `PIP_INDEX_URL`),装完二次探测确认 - **修复依赖探测缺陷**:裸 `import` 链在首个缺失模块即中断(最多报 1 个);改用 `importlib.util.find_spec` 一次性给出**完整缺失清单** - **可操作的工具错误**:依赖缺失且未自动安装时,工具调用直接返回中文修复指引(手动 pip / KB_AUTO_PIP / 安装脚本三条路径),不再让引擎子进程崩出裸 ImportError;首次工具调用先等探测/自动安装结束(一次性门控,后续零开销) - npm 包随包分发安装入口与脚本(`files` 清单含 `install.mjs` 与 `scripts/install.ps1|sh`),手动 `npm install` 用户可从 `node_modules/dsh-kb-rag/` 一键补环境 - SECURITY.md 更新:spawn 点清单 2→3(新增可选 pip 安装点)、网络节补 KB_AUTO_PIP/PIP_INDEX_URL/安装脚本行为、bin 为显式调用不随安装执行;`package.json` 仍声明零 lifecycle install scripts - 文档:QUICKSTART 以一键安装为第 0 节首选路径;README(Quick Start / Option 1 / 配置表 KB_AUTO_PIP / 目录结构)与 npm-package README 同步 ## [1.2.0] - 标识符阶梯 + 元数据增强 + 图注坐标 - **标识符阶梯**:首页限定 DOI(References 之前截断,避免抓参考文献的 DOI)+ arXiv ID 归一化为可解析 DOI(`10.48550/arXiv.xxxx`)+ 最大字号行提取真实标题 - 元数据本地增强(纯离线):清理 Word/PowerPoint 前缀、arXiv 头、投稿模板串、文件名式占位标题;回退首页标题启发式;作者占位符清理;年份合理性校验 - 图注坐标关联(保守精确匹配):命中正文段引用 `Fig. N`(仅同文档、编号完全一致)时附带"↳ 图注坐标: Fig. N — 图注原文",匹配不到不猜 - 无 DOI 命中附"搜索串"(标题+第一作者+年份,可复制到 Scholar 精确定位) - 不做:Crossref 联网回填(伤"零上传"承诺 + 错配 DOI 风险)、OCR ## [1.1.0] - 关联文献 - kb_search / kb_rag 新增 related 关联文献列表(同作者/同期刊/年份相近/主题相似,基于元数据 + 文档向量质心余弦,默认开启,可用 related=false 关闭) - 检索渲染新增"关联文献(可作补充建议)"区块;kb_rag 的补充建议优先引用 related 列表 - 文档质心缓存随入库/去重/清空/Zotero 变更自动失效 ## [1.0.7] - 仓库更名 dsh-kb-rag - GitHub 仓库 Breeze136/kb-rag → Breeze136/dsh-kb-rag(搜索"dsh-kb-rag"时精确匹配同名仓库、提升发现性;旧链接 301 重定向) - 更新全部内部引用(README/SECURITY/package.json repository 字段) - 同步更新两个 awesome 列表条目链接 ## [1.0.6] - 安装指引现代化 + dsh.so 徽章 - README 安装说明改为以 `dsh plugin --profile add dsh-kb-rag` 一键流程为首选(pnpm 要求注明),补充插件市场(dsh-plugin-registry)与手动三种路径 - 增加 dsh.so 安全徽章(扫描状态 passed);仓库已被 dsh.so 注册表收录(artifact: kb-rag) ## [1.0.5] - 安全文档入包 + 移除遗留 shell 调用 - 删除 plugin/host.js 中遗留的 `cmd /c start` 打开文件 RPC(唯一变量路径进 shell 的点) - 新增 SECURITY.md(执行模型/spawn 清单/读写边界/模型下载说明)并随 npm 包分发 - npm-package README 增加 Security 一节 ## [1.0.4] - 声明 dsh.bundle,一键安装即激活 - package.json 增加 `dsh.bundle.patch` 声明并随包分发 `cordis.patch.yml`(插入 `kb-rag` 行) - 用户现在只需 `dsh plugin --profile add dsh-kb-rag` 即可安装并自动激活为 profile layer(无需手改 cordis.patch.yml) - 增加 `exports` 入口(`./cordis.patch.yml`、`./package.json`) ## [1.0.3] - README 全英文化 - 仓库 README.md 与 npm-package/README.md 全部译为英文(代码与功能不变) ## [1.0.2] - npm 文档补丁 - README 增加 npm 版本/下载量、GitHub release、MIT 徽章 - 新增"设计原则"一节:刻意零 UI(无管理面板/前端状态/客户端依赖,一切经由对话与工具返回完成,检索结果内置 DOI 链接渲染)、垂直学术文献、留在甜区 - 同步更新 awesome 列表两处 PR 的定位描述 ## [1.0.1] - npm 静态包补丁 - 静态包启动时自动检测 Python 依赖(pymupdf/faiss-cpu/sentence-transformers/torch),缺失时在宿主日志打印对应 `pip install` 命令(不阻塞加载) - npm-package/README 增加"其他 Harness 用户安装指引"(部署目录 npm install + cordis 组合加载两步) - 仓库 README 增加 npm 静态包一节与目录结构更新 ## [1.0.0] - 2026-xx-xx(发布版) 初始发布:本地文献知识库 RAG(DSH 插件 + Python 引擎)。 - 8 个工具:kb_ingest / kb_zotero / kb_search / kb_rag / kb_scope / kb_dedup / kb_clear / kb_stats - 章节结构化切分(内联标题、摘要自动提升、图注块) - 混合检索(BM25 + bge-small 向量 RRF 融合)+ bge-reranker-base 精排 - 增量入库(sha256)+ 跨路径防重 + 查询缓存 - 引擎守护进程(模型单次加载、崩溃自愈) - Zotero 迁移(元数据覆盖、missing 跳过、dry-run) - 范围控制(封闭库/库+全网/仅全网)+ 严格模式(strict) - 溯源规范:DOI markdown 链接 / 无 DOI 文件名引用 - 客户端来源卡片(可选,随界面能力渲染) - 实测性能:242 篇 85.9s 入库、40× 增量提速、20k 块热检索亚秒级