dsh-data-domain-knowledge 0.1.0-alpha.1 → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +69 -110
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -8,33 +8,24 @@
8
8
  模型不可用时明确降级到 FTS,不会伪造向量结果。Markdown 中的
9
9
  本地图片会与正文一起交给 WeMM,因此图片不是独立知识类型。
10
10
 
11
- ## 承载范围(五个视图 = 一个挂载单元)
11
+ ## 承载范围(五个视图)
12
12
 
13
- 侧边栏 `TitaAgent` 面板声明的五个视图就是本插件的全部范围,`src/` 的目录结构按它们组织:
13
+ 侧边栏 `TitaAgent` 面板的五个视图就是本插件的全部范围:
14
14
 
15
- | 视图 | 面板标签 | 宿主实现 | 冻结契约 |
16
- | --- | --- | --- | --- |
17
- | `overview` | Agent 概览 | `src/index.ts` | 各设置的挂载节点 |
18
- | `intent` | 意图 | `src/intent/`(0020 自 `data-domain-intent` 并入);配置与留痕落 `<agentRoot>/intent/` | `intent_*` 工具名、`/intent/*` 路由、`tita-data-agent-intent` 设置、`titaDataAgentIntent` 服务 |
19
- | `knowledge` | 知识库 | `src/runtime.ts` + `src/knowledge-bases.ts` + `src/convert/`(0018 并入) | `recall_knowledge` 等工具名、`/knowledge/*` 与 `/convert` 路由、`tita-data-agent-knowledge` / `-convert` 设置 |
20
- | `memory` | 记忆 | `src/runtime.ts` 的 `memoryPath()`(版本快照、`feedback.ndjson`、`access-audit.ndjson`),根在 `<agentRoot>/memory/<库 id>` | 磁盘目录布局 |
21
- | `eval` | 评测 | `eval/evaluate-retrieval.mjs`(开发期能力,不注册运行时入口) | 问题集格式 |
22
-
23
- **架构原理:插件的数量应当等于用户入口的数量,而不是概念的数量;而插件的名字应当指向主干概念,不指向子域。**
24
- 意图/知识库/记忆/评测是四个概念,但它们共享同一条流水线(记录意图 → 录入来源 → 编译知识 →
25
- 沉淀记忆 → 评测质量)和同一个入口(面板的五个 Tab)。0020 之前,「意图」Tab 已经长在本插件里、
26
- 宿主实现却在第二个挂载单元里——**声明的信息架构与代码布局不一致**,这才是要修的东西。
27
-
28
- 意图的**配置与留痕**落在 `<agentRoot>/intent/`(0033):`registry.json` 是分类 / act 词表的
15
+ | 视图 | 面板标签 | 冻结契约 |
16
+ | --- | --- | --- |
17
+ | `overview` | Agent 概览 | 各设置的挂载节点 |
18
+ | `intent` | 意图 | `intent_*` 工具名、`/intent/*` 路由、`tita-data-agent-intent` 设置、`titaDataAgentIntent` 服务;配置与留痕落 `<agentRoot>/intent/` |
19
+ | `knowledge` | 知识库 | `recall_knowledge` 等工具名、`/knowledge/*` 与 `/convert` 路由、`tita-data-agent-knowledge` / `-convert` 设置 |
20
+ | `memory` | 记忆 | 版本快照、`feedback.ndjson`、`access-audit.ndjson`,根在 `<agentRoot>/memory/<库 id>`;磁盘目录布局 |
21
+ | `eval` | 评测 | 问题集格式(开发期能力,不注册运行时入口) |
22
+
23
+ 意图的**配置与留痕**落在 `<agentRoot>/intent/`:`registry.json` 是分类 / act 词表的
29
24
  真源(面板的「意图配置」直接读写这个文件,不再写宿主的 settings 文档),`records.ndjson`
30
25
  是每轮一条记录的 append-only 留痕(按 `sessionKey#turn` 读时取最后一条,所以内存里
31
26
  「一轮一条」的语义不变)。读接口 `/intent/store` 与 `/intent/overview` 读的是**留痕**:
32
27
  引擎内存里那份有 600 秒 TTL,重启或隔一段时间回来不该让面板上的历史凭空消失。
33
28
 
34
- `src/intent/` 的宿主半与 `src/client/IntentTab.tsx` 的浏览器半之间**必须保留 HTTP 边界**(`/intent/*`):
35
- 两者分处两个 bundle,无法直接互相 import。这与 0018 删掉的 `POST /knowledge/sources` 不同——
36
- 那次是宿主→宿主的回环,可以改成进程内调用。
37
-
38
29
  ## 五段链路
39
30
 
40
31
  | 段 | 名称 | 产物 | 关键配置 |
@@ -45,14 +36,14 @@
45
36
  | 4 | 召回 Recall | 多路(关键词 + 向量 + 知识图谱)召回 wikiNotes,取 `recallTopK`(默认 10),再解析父级 wiki 与该 wiki 的 1 跳知识图谱 | `recallTopK` / `recallMode` |
46
37
  | 5 | 重排 Rerank | BERT cross-encoder 对候选 wikiNotes 精排,取 `contextTopN`(默认 5)组成 `context` | `contextTopN` / `rerankProvider` / `rerankModel` |
47
38
 
48
- > 0033:第 4、5 段在**界面上是同一栏**(知识库 → 召回重排)。它们在流水线上仍是两步
49
- > ——架构图里也仍是两个节点——但「跑一次真实召回长什么样」是一个问题,分两栏会让用户
50
- > 先跑召回、再切到重排才能看到同一次运行的全貌,而且两栏各有一个测试按钮。
39
+ > 第 4、5 段在**界面上是同一栏**(知识库 → 召回重排)。它们在流水线上仍是两步,
40
+ > 但「跑一次真实召回长什么样」是一个问题,分两栏会让用户先跑召回、再切到重排才能
41
+ > 看到同一次运行的全貌。
51
42
 
52
43
  > 术语迁移:SourceRaw 曾叫 SourceRow / SourceTruth。磁盘目录 `01_source_truth`、工具名
53
44
  > `add_source` / `query_source_truth`、HTTP `/knowledge/sources`、设置键 `allowSourceTruth`
54
45
  > 与错误码 `SOURCE_TRUTH_DISABLED` 作为兼容名保留,语义等同 SourceRaw。
55
- > 本轮的「编译 / 构建」仍是同一次 `rebuild()`:编译的**按主题聚合**由 Agent 完成,
46
+ > 「编译 / 构建」是同一次 `rebuild()`:编译的**按主题聚合**由 Agent 完成,
56
47
  > **wiki↔wiki 双向链接**与**分阶段重建**是设计目标,图谱当前只有 `supported_by` 溯源边。
57
48
 
58
49
  - 设置项**不再有独立入口**:它们挂在 Agent 概览架构图的对应节点上(SourceRaw → 收录开关与
@@ -72,15 +63,15 @@
72
63
 
73
64
  ## 本地启用 WeMM
74
65
 
75
- 在 Apple Silicon 上执行一次:
66
+ 在 Apple Silicon 上执行一次(在插件包目录内):
76
67
 
77
68
  ```bash
78
- ./plugins/dsh-data-domain-knowledge/scripts/setup-embedding.sh
69
+ bash scripts/setup-embedding.sh
79
70
  ```
80
71
 
81
72
  脚本会把 Python 环境放到 `~/.tita-data-agent/embedding-venv`,把量化模型放到
82
- `~/.tita-data-agent/models/WeMM-Embedding-2B-Quantized`,不会把 1.8GB 权重提交
83
- 进仓库。新配置会自动发现这两个路径;如果 DSH 中已经保存过旧配置,则在
73
+ `~/.tita-data-agent/models/WeMM-Embedding-2B-Quantized`。新配置会自动发现这两个路径;
74
+ 如果 DSH 中已经保存过旧配置,则在
84
75
  Tita Data Agent → Agent 概览 →「构建」节点的设置中确认:
85
76
 
86
77
  - Provider:`WeMM Python(本地)`
@@ -90,7 +81,7 @@ Tita Data Agent → Agent 概览 →「构建」节点的设置中确认:
90
81
  - 设备:`自动(优先 MPS)`
91
82
 
92
83
  知识库默认保存在 `~/.tita-data-agent/knowledge`;设置了 `DSH_KNOWLEDGE_ROOT`
93
- 或已有旧的仓库内 `domain_knowledge` 时会优先沿用。点击「编译」后,
84
+ 或已有旧的 `domain_knowledge` 目录时会优先沿用。点击「编译」后,
94
85
  `03_compiled` 会生成 `wiki_notes.json`、`knowledge_graph.json`、
95
86
  `vector-manifest.json`、`vectors.binary` 和 `vectors.int8`。未修改的 wikiNote
96
87
  会复用已有向量,只有新增或变更的 note 重新编码;编码与重排各自使用常驻 Python
@@ -100,8 +91,7 @@ BERT 重排默认使用 `BAAI/bge-reranker-base`;如需完全离线,可把
100
91
  `~/.tita-data-agent/models/bge-reranker-base`(会被自动发现),或设置
101
92
  `DSH_RERANK_MODEL` / 在设置里填写本地路径。未安装时召回会降级到融合分数并给出警告。
102
93
  **目录必须含分词器**(`tokenizer.json` 或 `sentencepiece.bpe.model`)——只有权重时模型会
103
- 加载成功但给所有输入同一个分(`RERANK_TOKENIZER_DEGENERATE`),见
104
- `docs/0024-本地模型运行时.md`。
94
+ 加载成功但给所有输入同一个分(`RERANK_TOKENIZER_DEGENERATE`)。
105
95
 
106
96
  知识写入、编译、SourceRaw 读取和召回支持统一的
107
97
  `sessionId/turnId/intentId/taskId/subtaskId/runId/traceId/domain` lineage。卡片把
@@ -112,20 +102,12 @@ BERT 重排默认使用 `BAAI/bge-reranker-base`;如需完全离线,可把
112
102
  可以通过工具 `check_knowledge_embeddings` 或 `GET /knowledge/embedding/health`
113
103
  查看 provider、模型、设备、索引条数和最后一次错误。
114
104
 
115
- ## 格式转换(convert seam,0018)
116
-
117
- 多格式转换的 **DSH 宿主层**自 0018 起就在本插件里:不再是第二个挂载插件,而是本插件的一个
118
- 子模块(`src/convert/`),共用同一次挂载、同一个侧边栏 Tab、同一套 runtime。
105
+ ## 格式转换(convert)
119
106
 
120
- ```
121
- plugins/dsh-data-domain-knowledge/src/convert/
122
- ├─ host.ts 工具 /convert_*、路由 /convert/**、设置、面板
123
- ├─ ingest.ts 唯一写入口:ingestDelivery() → runtime.addSource()
124
- ├─ core-loader.ts 可选内核的懒加载与降级
125
- └─ types.ts
126
- packages/dsh-convert-core 内核(不挂载,可独立运行:node bin/tita-convert.mjs)
127
- └─ @firecrawl/anydoc MIT / Rust / 预编译绑定:doc docx ppt pptx xls xlsx odt ods odp rtf epub csv 文本层 PDF
128
- ```
107
+ 多格式转换是本插件的一个子模块(`src/convert/`),与知识库共用同一次挂载、同一个
108
+ 侧边栏 Tab、同一套 runtime。解析由可选内核 `dsh-convert-core` 完成(内核不挂载,可独立
109
+ 运行),底层是 `@firecrawl/anydoc`(MIT / Rust / 预编译绑定),支持 doc、docx、ppt、
110
+ pptx、xls、xlsx、odt、ods、odp、rtf、epub、csv 与文本层 PDF。
129
111
 
130
112
  | 项 | 值 |
131
113
  | --- | --- |
@@ -134,40 +116,30 @@ packages/dsh-convert-core 内核(不挂载,可独立运行:node bin/tita
134
116
  | 设置命名空间 | `tita-data-agent-convert`(冻结契约,由本插件安装) |
135
117
  | 交付目录 | `~/.tita-data-agent/convert-out/<taskId>/`(`index.md` / `assets/` / `manifest.json`) |
136
118
 
137
- **依赖是可选的。** `dsh-convert-core` 声明在 `optionalDependencies`,由
138
- `core-loader.ts` 在首次调用时 `import()`。平台没有 anydoc 预编译绑定、或用户显式不装时,
119
+ **依赖是可选的。** `dsh-convert-core` 声明在 `optionalDependencies`,首次调用时才加载。
120
+ 平台没有 anydoc 预编译绑定、或用户显式不装时,
139
121
  `convert_*` 与 `/convert` 面板返回 `DEPENDENCY_MISSING` 并给出安装建议,**knowledge 本体
140
- (`add_source` / 编译 / 召回)照常可用**。不要把它改成静态 import:有一条测试守着这件事。
122
+ (`add_source` / 编译 / 召回)照常可用**。
141
123
 
142
- **入库是进程内调用。** `ingestDelivery()` 直接调 `runtime.addSource()`,不再走
143
- `POST /knowledge/sources` 回环。少了一次 JSON 序列化,也因此没有旧的 ~1.2 MB body 上限。
144
- 单条 SourceRaw **没有字符数上限**:`maxSourceBytes` 在 0029 撤掉了(先撤面板上的字段,
145
- 后来连运行层那道闸门一起撤 —— 只撤面板等于留一个用户改不了、也找不到的硬上限)。
124
+ **单条 SourceRaw 没有字符数上限**:`maxSourceBytes` 已撤除。
146
125
  真正的边界是转换内核的 `maxInputBytes`(管入口大小)与嵌入分批(管模型输入)。
147
126
 
148
- 三条红线(改这一块前先读):
149
-
150
- 1. 内核不 import knowledge,产物只写 `convert-out/`;
151
- 2. 转换代码写知识库只能经 `ingest.ts` 的 `IngestTarget.addSource`,别处不许直接落 `01_source_truth`;
152
- 3. 0017 的网页 / 视频 / OCR 应做成**可选适配器包**注册进内核,不要继续往本插件里塞 ffmpeg / whisper。
153
-
154
- ## 多知识库与文档(0021)
127
+ ## 多知识库与文档
155
128
 
156
129
  一个**知识库 = 独立配置 + 独立内容**(产品参照 WeKnora)。注册表持久化在
157
130
  `<dataHome>/knowledge-bases.json`,`dataHome = process.env.TITA_DATA_AGENT_HOME || ~/.tita-data-agent`。
158
- 默认库 id 固定为 `default`,其 `rootDir` 始终来自插件设置 `rootDir`(缺省
159
- `defaultKnowledgeRoot()`);**不传 `kb` 的旧接口与旧工具一律作用于默认库,行为不变**。
131
+ 默认库 id 固定为 `default`,其 `rootDir` 始终来自插件设置 `rootDir`;
132
+ **不传 `kb` 的旧接口与旧工具一律作用于默认库,行为不变**。
160
133
 
161
134
  ```ts
162
135
  interface KnowledgeBase { id; name; description; rootDir; isDefault; sourceTypes; wikiTypes; wikiSchemaMode; createdAt; updatedAt; sourceCount; cardCount; noteCount; buildVersion }
163
136
  ```
164
137
 
165
138
  `sourceCount` / `cardCount` / `noteCount` / `buildVersion` 和每条分类/类型的计数都是
166
- **计算字段**,由 `KnowledgeBases.refresh()` 从真实 runtime 重新统计并缓存进同一个 JSON,
167
- 所以 `list()` / `get()` 保持同步签名,HTTP `GET /knowledge/bases` 在返回前先
168
- `await refresh()`。
139
+ **计算字段**,由真实 runtime 重新统计并缓存进同一个 JSON,
140
+ HTTP `GET /knowledge/bases` 在返回前先刷新。
169
141
 
170
- 0029 / 0032:**根路径下是六段**:
142
+ **根路径下是六段**:
171
143
 
172
144
  | 段 | 装什么 | 删掉的后果 |
173
145
  | --- | --- | --- |
@@ -178,15 +150,14 @@ interface KnowledgeBase { id; name; description; rootDir; isDefault; sourceTypes
178
150
  | `tmp/` | 运行期临时文件,二级按日期分桶 | 只影响未完成的任务 |
179
151
  | `resource/` | 模型、Python 环境、`knowledge-bases.json` | 失去语义检索(嵌入/重排不可用) |
180
152
 
181
- **记忆与意图为什么与知识树平级**(0032 的架构调整):记忆装的是版本快照与审计流水 ——
153
+ **记忆与意图为什么与知识树平级**:记忆装的是版本快照与审计流水 ——
182
154
  那是**关于这个 Agent 的动作留痕**,不是关于知识的断言;留在知识树里会让「备份知识树」
183
155
  被迫连审计一起备份,也让「知识树有哪几段」这个问题混进一个横切层。意图则是**会话级**的
184
- (同一句话在哪个知识库下说,理解方式不该变),所以它不分库;它的两份东西原来一个住在
185
- 宿主的 settings 文档里、一个只在内存里,都不是「这个 Agent 自己的文件」。搬出来之后,
186
- 知识、记忆、意图三段都是可以直接备份、直接 diff 的用户文件。
156
+ (同一句话在哪个知识库下说,理解方式不该变),所以它不分库。这样知识、记忆、意图三段
157
+ 都是可以直接备份、直接 diff 的用户文件。
187
158
 
188
- 知识树内部(`domain_knowledge/<知识库 id>/`)0032 起是**四段**:`00_schema`(0030 撤,
189
- 它从建出来起就是空目录)与 `04_memory`(0032 搬去 `memory/`)都已不在。
159
+ 知识树内部(`domain_knowledge/<知识库 id>/`)现在是**四段**:`00_schema` 与 `04_memory`
160
+ 都已不在(后者搬去了 `memory/`)。
190
161
 
191
162
  | 段 | 装什么 | 删掉的后果 |
192
163
  | --- | --- | --- |
@@ -195,23 +166,22 @@ interface KnowledgeBase { id; name; description; rootDir; isDefault; sourceTypes
195
166
  | `03_compiled/` | wikiNotes / 向量 / 知识图谱 | 可重建 |
196
167
  | `05_diagnosis/` | 智能归因图谱 | 可重建 |
197
168
 
198
- `00_schema` 撤掉的依据:它从建出来那天起就是一个**空目录**。分类注册表与 Wiki 类型
199
- 注册表一直住在 `<agentRoot>/resource/knowledge-bases.json`(那是配置,跟着根路径走);
200
- `00_schema` 里没有一行代码写过东西。「磁盘上有一个文件夹」本身是一种承诺,空着就是
201
- 误导。`ensureLayout` 会在启动时把空的 `00_schema` 删掉 —— **非空则保留**:那就不属于
202
- 「没用的空文件夹」了,删掉别人的文件比留一个空目录危险得多。
169
+ `00_schema` 已撤掉:它从建出来那天起就是一个**空目录**,分类注册表与 Wiki 类型注册表
170
+ 一直住在 `<agentRoot>/resource/knowledge-bases.json`(那是配置,跟着根路径走)。
171
+ 启动时会把空的 `00_schema` 删掉 —— **非空则保留**:那就不属于「没用的空文件夹」了,
172
+ 删掉别人的文件比留一个空目录危险得多。
203
173
 
204
174
  `01_source_truth` 按分类分目录(而不是平铺)的理由与 `02_wiki` 一致:分类是每份来源的
205
175
  必备元数据(frontmatter 的 `source_type`),提到目录名上之后「按分类找东西」在文件系统
206
176
  层面直接成立,不依赖界面。平铺在真实使用里会先变成垃圾场 —— 几十份来源混在一层,`ls`
207
177
  读不出「我的指标口径在哪」。启动时一次性把平铺的文档搬进分类目录;读路径不依赖这个层级
208
- (`sourceIndex` 递归扫全段建 slug → path 索引),所以旧布局、新布局与两者并存的中间态
178
+ (启动时会递归扫全段建 slug → path 索引),所以旧布局、新布局与两者并存的中间态
209
179
  都能读。**改分类 = 换目录**:`add_source` 用同一 `sourceId` 配新 `sourceType` 时,旧目录
210
180
  里那份会被删掉,不会留下孤儿。
211
181
 
212
- `resource/` 是 0029 新增的:模型与虚拟环境原来住在 `~/.tita-data-agent`,现在搬进
213
- 根路径 —— 「这个 Agent 的全部磁盘足迹」因此是一个目录,备份 / 迁移 / 整个删掉都只
214
- 需要认一个路径。启动时一次性搬迁(先 `rename`,跨卷**不自动复制**,因为可能有几个
182
+ `resource/` 装模型、虚拟环境与 `knowledge-bases.json` —— 「这个 Agent 的全部磁盘足迹」
183
+ 因此是一个目录,备份 / 迁移 / 整个删掉都只需要认一个路径。模型与虚拟环境原先住在
184
+ `~/.tita-data-agent`,启动时一次性搬迁(先 `rename`,跨卷**不自动复制**,因为可能有几个
215
185
  GB);旧的 `TITA_DATA_AGENT_HOME` 留在解析链上作为兜底,所以搬与没搬都能跑。
216
186
  搬迁**按条目合并**:`resource/models/` 里已经有用户自己放的模型时,仍会把旧的嵌入模型
217
187
  搬进来,只跳过同名的那个 —— 否则「目标目录存在」会让另一样东西永远搬不过来。
@@ -221,31 +191,24 @@ GB);旧的 `TITA_DATA_AGENT_HOME` 留在解析链上作为兜底,所以搬
221
191
  的顺序解析。设置面板用占位符显示解析后的真实路径,并在**家当不在 `resource/` 下时
222
192
  逐条告警**(「还在旧位置、重启会搬」和「根本不是路径、迁移搬不了」要做的事不同)。
223
193
 
224
- > ⚠️ 这三个字段的 schema **不能写 `.min(1)`**。schemastery 与 zod 不同,它会**校验默认值
225
- > 本身**,`z.string().min(1).default('')` 会让 `settings.installSection()` 在登记命名空间
226
- > 时直接抛 —— 后果是整个设置分区注册不上(面板「设置服务不可用」)、家当迁移不执行、
227
- > `settings.yaml` 里的值一个都不生效,而界面上没有任何原因。见
228
- > `src/settings-schema.ts` 的模块注释与 `tests/settings-schema.test.mjs`。
229
-
230
- 向量清单里记的模型名也从绝对路径改成了 `local:<模型目录名>` —— **身份与位置解耦**,
194
+ 向量清单里记的模型名是 `local:<模型目录名>` —— **身份与位置解耦**,
231
195
  否则把模型搬一次就让整个索引判成「配置不匹配」而作废。
232
196
 
233
- 0029:**「文件类型 → 解析引擎」那张规则表已撤。** 它唯一被读的地方是浏览器决定
197
+ **「文件类型 → 解析引擎」那张规则表已撤。** 它唯一被读的地方是浏览器决定
234
198
  「这份文件直读还是送转换内核」,而那个判断现在只看扩展名本身(`DIRECT_READ_EXT`);
235
199
  真正决定用哪个适配器的是**转换内核自己的格式表**(`ANYDOC/SCANNED/IDDOC/TEXT_EXTENSIONS`),
236
200
  它从来不读这张表 —— 也就是说「把 .docx 设成直读文本」这类配置**改了不生效**。撤掉
237
201
  之后:上传选择框的 `accept` 问内核要(`/convert/api/capabilities`),文件类型筛选项
238
202
  和计数从**这一库真实的文档**推导。两边都不可能再与内核漂移。
239
203
 
240
- **文档扩展名 `ext` 的推导是有损的**(`deriveSourceExt`,代码注释同):优先取
204
+ **文档扩展名 `ext` 的推导是有损的**:优先取
241
205
  `metadata.source_path` 的 basename 扩展名;否则 `source_format === 'markdown'` → `.md`,
242
- `'office'` → `.docx`,其余 → `.` + `source_format`;都没有 → `.md`。只有上游
243
- `convert/ingest.ts` 写了 `source_path` 时才精确。
206
+ `'office'` → `.docx`,其余 → `.` + `source_format`;都没有 → `.md`。只有来源写了
207
+ `source_path` 时才精确。
244
208
 
245
- 文档的 `parser` 字段说的是**谁解析了这份文档**:经转换内核进来的由
246
- `convert/ingest.ts` 写 `metadata.parser`(如 `anydoc@1/pdf`),直接写进来的
247
- (`add_source`、浏览器直读文本)是 `direct` —— 没有解析器。0029 之前这里会按扩展名
248
- 猜一个引擎名(`.pdf` → `anydoc`),那是在陈述一件没有发生过的事。
209
+ 文档的 `parser` 字段说的是**谁解析了这份文档**:经转换内核进来的会写
210
+ `metadata.parser`(如 `anydoc@1/pdf`),直接写进来的
211
+ (`add_source`、浏览器直读文本)是 `direct` —— 没有解析器。
249
212
 
250
213
  ### HTTP API
251
214
 
@@ -293,7 +256,7 @@ GET /knowledge/assets/:kb/* → 知识库
293
256
  **回滚 = 用快照内容重新调用 `addSource`**:因此回滚前会把当时的 head 再快照一次,
294
257
  历史只增不减,**回滚本身也可回滚**。回滚 `current-*` 是 no-op。
295
258
 
296
- ### 召回链路(0031 起:两路 + 图谱 boost)
259
+ ### 召回链路(两路 + 图谱 boost)
297
260
 
298
261
  ```text
299
262
  query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
@@ -303,22 +266,21 @@ query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
303
266
  → R5 弃答(insufficientEvidence)
304
267
  ```
305
268
 
306
- - **R0 分词**(`fts.ts`):中文按**单字 + 相邻双字**切分(不用词典),ASCII 按 `unicode61`
307
- 的口径切开。0030 的 `tokens()` 只按非字母数字切,整句中文会变成一个 token ——
308
- 实测 `排查`→6 命中、`怎么排查指标异常`→**0**。
269
+ - **R0 分词**:中文按**单字 + 相邻双字**切分(不用词典),ASCII 按 `unicode61`
270
+ 的口径切开。
309
271
  - **R1 关键词路**:SQLite FTS5 + **BM25**(`node:sqlite`,零新依赖;不可用时如实降级)。
310
272
  中文之所以不用内置 tokenizer:`unicode61` 不切中文,`trigram` 要求查询 ≥3 字符且会把
311
273
  整句变成互相 AND 的三字窗口。分数按本次最高分归一到 0..1,再过 `keywordThreshold`;
312
274
  另有**多字词覆盖率**门槛(只数双字词与 ASCII 词,单字在中文里不是证据)。
313
275
  - **R1 向量路**:**编码器健康门**(两段无关文本必须余弦 < 0.99,否则整路停用)→
314
- 查询编码一次 → binary/Hamming 预筛 → int8 精算**原始余弦**(`dot / 127²`,0030 多除了
315
- 一个 dimension,把余弦压进 ±0.001、排序完全没有区分度)→ `vectorThreshold`。
316
- - **R2 融合**:**RRF**(`k=60`,关键词 0.3 / 向量 0.7)——只吃**名次**,所以三路分数
317
- 量纲不同这件事不再影响排序(0030 的加权和让向量输出常数时所有命中同分)。
276
+ 查询编码一次 → binary/Hamming 预筛 → int8 精算**原始余弦**(`dot / 127²`)→
277
+ `vectorThreshold`。
278
+ - **R2 融合**:**RRF**(`k=60`,关键词 0.3 / 向量 0.7)——只吃**名次**,所以各路分数
279
+ 量纲不同这件事不再影响排序。
318
280
  - **R2 图谱**:**boost,不是第三路**。它给已经在候选池里的条目加一个 RRF 项,**不能自己
319
281
  制造候选**。代价是「只提到某份来源、没提到任何卡片正文」的查询没有第二条路 ——
320
282
  由关键词索引收录 **note 的溯源身份**(sourceRefs + 来源标题)来补偿。
321
- - **R3 重排**:作用在**候选池**上(不再是已截断的 topK,0014 点名的「分层只做了一半」),
283
+ - **R3 重排**:作用在**候选池**上(不是已截断的 topK),
322
284
  低于 `rerankThreshold` 的一律丢弃并报出丢了几条。阈值与 WeKnora 同刻度(**原始 logit**)。
323
285
  worker 用 `predict_raw()`(`activation_fn=Identity`)返回 logit —— `CrossEncoder.predict`
324
286
  对 `num_labels == 1` 默认套 sigmoid,直接拿返回值比 0.2 会让阈值永远不生效;展示用的
@@ -331,8 +293,7 @@ query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
331
293
  并标注判据强度(`rerank-threshold` / `vector-threshold` / `relative-only` / `not-needed`)。
332
294
  宁可说「没有」,也不凑满 topN 让模型编。
333
295
  - **降级不谎报**:`routes` 只列**通过健康门且真的跑起来**的路;每一路的状态、候选数、
334
- 最高分、阈值与降级原因都在 `diagnostics.routes` 里。0030 的向量路坏掉时照样上报
335
- `routes: ["fts","vector","graph"]`,是这次要修的核心问题之一。
296
+ 最高分、阈值与降级原因都在 `diagnostics.routes` 里。
336
297
  - `mode: 'graph'` 与 `hybrid` **同义**(图谱不能制造候选,「只靠图谱」不成立)。
337
298
  - 阈值初值:`keywordThreshold 0.3` / `rerankThreshold 0.2` 沿用 WeKnora 的出厂值;
338
299
  **`vectorThreshold` 是 0.5,不是它的 0.15** —— 那个数是它自己 embedding 模型的刻度,
@@ -344,14 +305,12 @@ query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
344
305
  余弦 < 0.99),重排器查分词器词表(`vocab_size ≥ 1000` 且探针句有非 `<unk>` 条目,否则
345
306
  `RERANK_TOKENIZER_DEGENERATE`)。原因是「能加载」不等于「可用」:本地快照缺分词器文件时,
346
307
  重排器照样加载、照样出分(实测三条无关文本 `0.999 / 0.998 / 0.9994`),只有探针能发现。
347
- 修法与运维见 `docs/0024-本地模型运行时.md`。
348
308
  - **编码器指纹** `ENCODER_FINGERPRINT` 写在向量清单里:同一个模型换了「怎么调它」也必须让
349
- 已有向量作废,否则 `rebuild all` 会按 contentHash 把旧向量原样复用(0031 实测过:
350
- 修好编码器后构建仍报 `reused: 44`,修复等于没发生)。
309
+ 已有向量作废,否则 `rebuild all` 会按 contentHash 把旧向量原样复用。
351
310
 
352
311
  `listDocuments()` / `readDocument(docId)` / `listVersions(docId)` /
353
- `revertDocument(docId, versionId)` / `deleteDocument(docId)` 是 `KnowledgeRuntime` 上
354
- 新增的文档接口;详情页的 `assets[].url` 即 `/knowledge/assets/<kbId>/<rel>`。
312
+ `revertDocument(docId, versionId)` / `deleteDocument(docId)` 是文档接口;
313
+ 详情页的 `assets[].url` 即 `/knowledge/assets/<kbId>/<rel>`。
355
314
 
356
315
  ## 评测
357
316
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-data-domain-knowledge",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "Tita Data Agent knowledge lifecycle console: intent recording, source truth, wiki compiler, memory, retrieval evaluation, and the multi-format conversion host",
5
5
  "type": "module",
6
6
  "main": "lib/types/index.js",
@@ -55,7 +55,7 @@
55
55
  "@deepseek-ai/schemastery": "~3.18.4"
56
56
  },
57
57
  "optionalDependencies": {
58
- "dsh-convert-core": "0.1.0-alpha.1"
58
+ "dsh-convert-core": "0.1.0-alpha.2"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@deepseek-ai/cordis": "~4.0.4",
@@ -87,7 +87,7 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@antv/g6": "^5.1.1",
90
- "dsh-data-contracts": "0.1.0-alpha.1"
90
+ "dsh-data-contracts": "0.1.0-alpha.2"
91
91
  },
92
92
  "scripts": {
93
93
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json",