dsh-doc-index
  English
一个 dsh bundle:将工作区变为可检索的本地知识库。它会对本地的 Markdown / 纯文本 / PDF / DOCX / PPTX / XLSX 文档建立语义索引,并支持用自然 语言或关键词查询——返回命中文档、带精确行号的片段与相关度分数。
- 词法检索:SQLite FTS5(
node:sqlite,无原生依赖)+自研 CJK 分词器,
让中文搜索开箱即用。
- 语义检索:provider 插槽——默认自带宽容零依赖的本地嵌入器,也支持按需
接入神经网络的 transformers provider。
- 结果以 RRF(倒数排名融合) 融合。
- 增量更新:仅对变更文件重索引(可选文件监听),并保留全量重建兜底。
- 容量与排除规则可控,索引规模有界、目标明确。
- 提供 dsh 服务(
ctx.docIndex)+四个模型可调用工具(doc_scan、
doc_query、doc_reindex、doc_stats),同时提供独立 CLI(docindex)。
该 bundle 遵循 dsh 标准分发格式:package.json 声明 dsh.bundle.patch 指向 cordis.patch.yml,入口模块是带 static inject / static Config 的 Cordis Service——与 @deepseek-ai/dsh-session-query-sqlite 同形态,但为工作区文档 重新实现,并通过自有工具提供检索能力。
---
环境要求
- Node.js >= 22.5(使用内置
node:sqlite)。 - 要运行在 dsh 中,需要一个可提供
@deepseek-ai/cordis、
@deepseek-ai/dsh-tools 与 @deepseek-ai/schemastery 的 DeepSeek Harness 环境(这些被声明为 peer 依赖)。
> 说明: Node 将 node:sqlite 标记为实验特性,启动时会有提示(不影响功能)。 > 可用 --disable-warning=ExperimentalWarning 关闭。
---
安装接入 dsh
在 profile 目录(或 Harness 任意位置)添加 bundle 并通过补丁层挂载:
dsh plugin add <本包路径>本包也已发布到 npm,可独立使用 CLI:
npm install -g dsh-doc-index # 提供 `docindex` CLI
npm install dsh-doc-index # 或作为项目依赖加入自带的 cordis.patch.yml 只插入一行插件:
- insert:
- id: doc-index
name: dsh-doc-index不额外配置时,它会把当前工作目录索引到 $DSH_HOME/doc-index/index.db (DSH_HOME 默认 ~/.dsh),并开始监听变更。
如需自定义,在 profile 的 cordis.patch.yml 里覆盖(后层补丁会整体替换 该行的 config,请把关心的键全部写上):
- id: doc-index
config:
roots:
- /path/to/your/workspace
- /another/vault
dbPath: /path/to/index.db
update: watch
embedding:
provider: transformers # 需要 `npm i @huggingface/transformers`
excludes:
- vendor/
- '*.tmp'加载后,四个工具即可被模型调用,其他插件也可使用 ctx.docIndex 服务:
const hits = await ctx.docIndex.query({ query: 'RLHF and llama.cpp' })
// hits[0].path, hits[0].line, hits[0].snippet, hits[0].score---
独立 CLI
同一套引擎也可以在命令行使用:
# 索引当前目录(可用 --root 多次;--db 指定库)
docindex scan
docindex scan --root ./docs --root ./specs
# 检索
docindex query "flux pipeline timeout" --top 5
docindex query "向量数据库" --mode semantic --json
# 重建
docindex reindex --full
# 查看统计
docindex statsdocindex --help 查看全部参数。环境变量:DOCINDEX_DB、DOCINDEX_ROOTS (按路径分隔符拆分)。
---
模型工具
| 工具 | 用途 |
|---|---|
doc_scan | 增量扫描/刷新/清理工作区(可选 path 限定子树,force 忽略变更检测)。 |
doc_query | 检索。query(必填)、topK、mode(auto/lexical/semantic)、highlight、snippetChars。每条命中返回 路径+行号+片段+分数。 |
doc_reindex | 重建。full 先清空索引;path 限定范围重建。 |
doc_stats | 索引统计(文档数、片段数、嵌入数、体积、根目录)。 |
---
配置参考
| 键 | 默认值 | 含义 |
|---|---|---|
roots | [] → process.cwd() | 扫描的工作区根目录。 |
dbPath | '' → $DSH_HOME/doc-index/index.db | SQLite 路径(可用 :memory:)。 |
openAt | startup | 打开库的时机:startup / first-use / never。 |
update | watch | watch(fs.watch+防抖)或 manual。 |
watchDebounceMs | 1500 | 监听重扫的防抖毫秒。 |
excludes | [] | 额外的 gitignore 风格排除规则(见下)。 |
includeHidden | false | 是否索引点号开头的文件/目录。 |
followSymlinks | false | 是否跟随目录符号链接(自动断环)。 |
maxDocs | 20000 | 最大文档数。 |
maxSegments | 300000 | 最大文本片段数。 |
maxEmbeddedSegments | 50000 | 最多获得向量的片段数。 |
maxFileBytes | 5 MB | 大于此字节数的文件跳过。 |
maxDepth | 64 | 根目录下最大目录深度(0 表示不限制)。 |
maxWalkedFiles | 200000 | 单次扫描最多收集的候选文件数(内部检查文件预算为其 4 倍)。若因此被截断,则本次扫描不做删除。 |
tokenizer.cjkN | 2 | CJK n-gram 深度:1(一元)/2(+二元)/3(+三元)。 |
segmentChars | 400 | 单个索引片段约最大字符数。 |
snippetChars | 240 | 每条命中的最大片段长度。 |
textExtensions | [] | 额外按纯文本处理的扩展名(如 .csv)。 |
search.topK | 10 | 每次查询默认命中数(1–50)。 |
search.minScore | 0 | 过滤低于此归一化分数的结果。 |
search.mode | auto | auto / lexical / semantic。 |
search.highlight | true | 是否对命中词加 … 标记。 |
search.matchOp | and | 查询各「组」之间用 and 还是 or。 |
search.rrfK | 60 | RRF 常数。 |
search.semanticWeight | 0.5 | 语义列表在 RRF 中的权重(词法为 1 - w)。 |
embedding.provider | ngram | none / ngram / transformers。 |
embedding.dim | 256 | ngram 嵌入器维度。 |
embedding.model | '' | transformers 模型 id(默认多语言 MiniLM)。 |
embedding.device / cacheDir / quantized | auto / HF 缓存 / true | transformers 相关选项。 |
journalMode | wal | SQLite 日志模式。 |
---
语义嵌入(provider 插槽)
DeepSeek 未提供官方 embedding API,因此本插件内置可插拔的 EmbeddingProvider 插槽:
ngram(默认,零依赖):基于 CJK n-gram +拉丁词的确定性特征哈希嵌入器,
完全离线、无需下载模型,提供一个「共享 token 越近」的向量空间,作用相当于 重排序器:语义候选被限定为与查询有 token 重合的片段,因此无匹配的查询 会返回空结果而非“噪声命中”。需要真正学到语义(含跨语言召回)时切换 provider。
transformers(可选):通过@huggingface/transformers加载小型 ONNX
编码器。安装后设置 embedding.provider: 'transformers';默认模型为多语言, 中文可直接使用。
none:关闭语义路径(仅词法)。- 宿主应用也可自实现并注入自定义 provider(例如远程 HTTP embedding 接口)。
若请求的 provider 无法构建(例如缺少可选的 @huggingface/transformers), 引擎会优雅降级:记警告并以纯词法继续。doc_query 会在「需要语义但不可用」 时把结果标记为 degraded。
---
检索原理
1. 片段:每篇文档按约 segmentChars 切分为若干片段,每个片段记录起始的 1 基行号,因此每条命中都可定位到行。 2. 分词:拉丁词统一小写;CJK 连续段展开为 n-gram(深度 2 时 你好世界 → 你 好 世 界 你好 好世 世界)。索引侧与查询侧使用同一套 分词,中文关键词检索开箱即用。查询时,一个表意字串内部用 OR 匹配其 n-gram(于是“苹果手机”也能命中只含“苹果”或“手机”的文档),字串/词之间用 AND(或 OR)组合。 3. 词法:FTS5 bm25() 排序命中。 4. 语义:查询向量与库内片段向量做余弦相似度(受 maxEmbeddedSegments 约束;向量存于 SQLite)。 5. 融合:两份排序结果用 RRF 合并,并归一化到 [0, 1]。
---
排除规则、二进制与容量
- 默认排除表:
node_modules/、.git/、.svn/、.hg/、.cache/、
.next/、.nuxt/、.output/、dist/、build/、coverage/、 .DS_Store、*.pyc/pyo、*.exe/dll/so/dylib/o/obj、Thumbs.db、 .docindex/。excludes 追加 gitignore 风格规则(支持 **、*、?、 [...]、! 取反前缀、目录尾 /、锚定前缀 /)。
- 未知扩展名按内容嗅探,疑似二进制则跳过(
binary)。无文本层的 PDF 以
no-text-layer 跳过(需 OCR)。空文件以 empty 跳过。
- 达到
maxDocs/maxSegments后新文档会以max-docs/max-segments跳过,
并在 doc_scan 输出中报告。
---
开发
npm install # 仅安装 TypeScript 与 dsh 类型包(dev)
npm run build # tsc -> dist/
npm test # 构建并运行完整测试(node:test,无额外依赖)核心(src/engine.ts、src/db.ts、src/embedding.ts 等)零运行时依赖, 并有完整单元测试覆盖:分词、忽略规则、抽取(文本/PDF/OOXML)、发现、增量 更新、中文检索、RRF 排序、片段高亮、容量限制,以及「无嵌入模型时优雅降级」。
示例工作区见 example/。
许可证
[MIT](LICENSE)
遇到问题或希望支持新的文档类型?欢迎在 github.com/JohnXu22786/docindex 提交 issue。