# 检索栈实测事实 写实现之前把所有"我以为"换成实测。每条都是跑出来的,不是查文档推的。 环境:Node 22.19.0 / Windows / pnpm 10.13.1 依赖版本:`@node-rs/jieba` 2.0.3、`pinyin-pro` 3.29.4、`minisearch` 7.2.0 --- ## 1. jieba 的导入方式 - **是 CommonJS**。ESM 里 `import { cut } from '@node-rs/jieba'` 会 `SyntaxError: Named export 'cut' not found`。 只能 `import { Jieba } from '@node-rs/jieba'`(`Jieba` 这个类名可以具名导入,函数不行)。 - **包没有 `exports` 字段**,子路径必须带扩展名:`@node-rs/jieba/dict.js`。 写成 `@node-rs/jieba/dict` 会 `ERR_MODULE_NOT_FOUND`。 - 调用形态: ```js import { Jieba } from '@node-rs/jieba' import { dict } from '@node-rs/jieba/dict.js' const jieba = Jieba.withDict(dict) // 词典是 Uint8Array,装载一次即可复用 jieba.cut('知乎搜索') // → string[] jieba.cutForSearch('知乎搜索') // → string[] ``` --- ## 2. 分词必须用 `cutForSearch`,不能用 `cut` 同一段文本"知乎站内搜索,按关键词找问题与回答": | 方法 | 结果 | |---|---| | `cut` | `["知","乎","站内搜索",",","按","关键词","找","问题","与","回答"]` | | `cutForSearch` | `["知","乎","站内","搜索","站内搜索",",","按","关键","关键词","找","问题","与","回答"]` | `cutForSearch` 是搜索引擎模式:**长词既保留整词、又切出子词**。 **后果(实测)**:索引侧用 `cut` 时,查「搜索」**召不回**描述里写着"站内搜索"的工具 —— 因为索引里那个 token 是"站内搜索",和查询的"搜索"字面不等。换成 `cutForSearch` 后立刻召回。 **结论**:索引侧与查询侧统一用 `cutForSearch`(两边必须同一套分析,这是搜索引擎的铁律)。 --- ## 3. 标点 token 会污染召回(最隐蔽的一个坑) ```js jieba.cutForSearch('zhihu_search') // → ["zhihu","_","search"] ``` **下划线单独成了一个 token**。 索引侧如果原样保留,每个带下划线的工具名都会在索引里留下一个 `"_"` 词项;查询侧同样会切出 `"_"`。于是: > 查 `zhihu_search` → **返回全部 5 个工具**(实测) 修法:**丢弃所有不含字母或汉字的 token**。 ```js const useful = (token) => /[a-z0-9\u4e00-\u9fff]/.test(token) ``` 修完:查 `zhihu_search` → 只回 `zhihu_search` + `web_search`(后者含 `search` 词项),正确。 --- ## 4. pinyin-pro 的正确用法 三个都踩过: | 写法 | 结果 | 问题 | |---|---|---| | `pinyin('知乎搜索', { toneType: 'none' })` | `"zhi hu sou suo"` | **默认是空格分隔的音节,不是连写** | | `pinyin('zhihu_search', { toneType: 'none' })` | `"z h i h u _ s e a r c h"` | **纯英文被逐字母拆开** | | `pinyin('知乎搜索', { toneType: 'none', type: 'array', nonZh: 'consecutive' }).join('')` | `"zhihusousuo"` | ✅ | | 同上 + `pattern: 'first'` | `"zhss"` | ✅ 首字母 | `nonZh: 'consecutive'` 是关键:它让非中文段整体保留,不再逐字母拆。 --- ## 5. 拼音必须按词分段,不能整段连写 整段连写时,"知乎站内搜索" → `"zhihuzhanneisousuo"`,查 `sousuo` **召不回** —— 拼音中间隔着"站内"的音。 按词分段(每个中文词一个拼音 token,词间空格分隔,空格在分词时被丢掉): ``` "zhi hu zhannei sousuo zhanneisousuo an guanjian guanjianci zhao wenti yu huida" ``` 查 `sousuo` ✅、查 `zhanneisousuo` ✅、查 `zhannei` ✅。 **而且拼音 token 的切分也要走 `cutForSearch`** —— 只有这样"站内"和"搜索"才各自有拼音。 --- ## 6. minisearch 的用法 - 自定义 `tokenize` ✅、字段 `boost` ✅、`prefix: true` ✅ - **`discard` 要传 id 字符串**:`ms.discard('zhihu_hot')`。 传对象会抛 `MiniSearch: cannot discard document with ID [object Object]: it is not in the index`。 - `add` 增量 ✅、`documentCount` 可读 ✅ --- ## 7. 最终召回实测 字段:`name` / `namePinyin` / `nameInitials` / `desc` / `descPinyin` boost:`{ name: 4, namePinyin: 3, nameInitials: 3, desc: 1 }`,`prefix: true` | 查询 | 召回 | |---|---| | `zhihu_search` | zhihu_search, web_search | | `zhihu` | zhihu_search | | `search` | zhihu_search, web_search | | `搜索` | web_search, zhihu_search | | `站内搜索` | zhihu_search | | `sousuo` | web_search, zhihu_search | | `zhanneisousuo` | zhihu_search | | `sketchup` | sketchup_status | | `模型` / `moxing` | sketchup_status | | `图片` / `tupian` | read_image | | `点击` / `dianji` | computer_click | | `网页` / `wangye` | web_search | | `不存在的词` | (空) | 中文、英文、拼音全拼、拼音首字母、中英混排都能召回。 --- ## 8. 仍未验证的事(写进待办,不写进假设) - `ctx.tools.register` 的 `output.render` 返回值形状 —— 从社区插件与 DSH 源码推的是 `ContentBlock[]`,但**没有在本部署里跑通过一个真工具**。 - `ctx.systemPrompt.section` 的确切签名(order 是否必填、text 是否支持函数)。 - 插件挂上 profile 后,`system-prompt/assemble` 过滤是否真的对**已有会话**生效。 - 子代理的装配是否也走同一个 waterfall。