dsh-semantic-recall · 会话语义检索
换新对话,不再等于从头再来。
插件在后台把最近 N 天(默认 7)的对话建成语义索引——跨工作区、存在本机;
你只要描述个大概(「前几天说的那个…」),就能把当时聊过的话题和原话找回来。
找不准的时候它会反问你,而不是硬编一个答案。
一个 DSH(DeepSeek Harness)插件:只读会话历史、本地建索引、4 个 agent 工具 + 自带设置页。
⚠️ 名字说明:npm 上已有的 dsh-session-recall 是另一个人的同类插件(确定性/字面式检索),
与本插件无关。本插件的包名是 dsh-semantic-recall。
一键安装
方式 A:把这段话贴给 DSH 里的 AI(最省事,走 npm)
用 plugin_manager 执行 install_bundle,target 填 dsh-semantic-recall,
装完提醒我刷新页面。
方式 B:自己执行(创造模式)
action: install_bundle
target: dsh-semantic-recall
方式 C:从源码目录装
git clone https://github.com/zhaohualiuyuejun/dsh-semantic-recall.git
# 然后把上面 clone 出来的目录绝对路径填进 install_bundle 的 target
三种方式等价,装完 刷新一次页面,左侧「设置」里出现「会话语义检索」即成功。
不需要 Node 依赖、不需要手改 profile(bundle 自带 cordis patch)。
网络到不了 npm registry 时,用 GitHub Release 的 tgz 兜底(装法与包名相同,target 换成下面这个地址):
https://github.com/zhaohualiuyuejun/dsh-semantic-recall/releases/download/v0.2.5/dsh-semantic-recall-0.2.5.tgz
装好后的开启步骤见下面「安装」第 3~4 步。
它解决什么(以及不解决什么)
| 管什么 | 什么时候有用 |
|---|
| 上下文压缩 | 当前这场对话太长时,把历史压成摘要 | 一场对话里不爆窗 |
| 本插件 | 换了一场对话之后,过去那些还在不在 | 开新对话、跨工作区找旧话题 |
两者互补:压缩保当前,检索保历史。所以准确的说法是「开新对话不再等于从头再来」,
而不是「解决上下文压缩」。
不要过度承诺:
- 只在索引窗口内(默认 7 天,可调,硬上限 365 天;超出窗口的不在索引里)
- 需要嵌入能力才叫语义检索;没有就只做字面匹配,只能给候选
- 增量是「启动 + 每 6 小时」,刚聊完想立刻搜,点设置页的「增量更新」
- 被排除的工作区不索引
- 召回是「相对准确 + 不确定就反问」,不保证 100%——宁可给候选,不装懂
- ⚠️ 它不是记忆系统:不参与人格/上下文注入,不会自动"记住"你,也不替代专门的记忆插件。
它只做短期原话召回(把索引窗口内的会话原文按语义找回来)。详见下一节「和长期记忆插件的关系」。
和长期记忆插件的关系(推荐搭配)
本插件和「长期记忆插件(core + topics)」不是替代关系,是两层:
| 本插件(会话语义检索) | 长期记忆插件 |
|---|
| 存什么 | 原始对话分片 + 向量 | topic 摘要 / 用户画像 / 长期事实 |
| 召回什么 | 原词原句、精确引用 | 主题级线索、跨会话归纳 |
| 时间尺度 | 短期到中期(indexDays 内) | 长期 |
| 输出形态 | 只给候选原文 | 归纳后的摘要或线索 |
推荐搭配的工作流:
- 记忆插件给你线索:告诉你「上次聊过 XX 方案」;
- 本插件帮你找回原文:当时具体说了哪几句、在哪个会话里、什么时间;
- 引用以原文为准:记忆的归纳只当线索,不当作原话。
一句话:记忆管「我该知道什么」,本插件管「你那天到底怎么说的」。
与其他记忆类插件的可选协作
本节只描述一份可选的锚点约定:不比较、不推荐任何记忆方案,也不要求任何人接入。
接与不接,都不影响本插件和对方插件各自独立工作。
本插件的检索结果里带可下钻的锚点。如果你恰好在做记忆类插件、且觉得有用,可以按下面的形状把「记忆卡片 → 原文」接起来;不接也完全没问题。
锚点字段(本插件输出)
| 字段 | 含义 | 来源 |
|---|
session | 会话 id | $DSH_HOME/sessions/<工作区>/<会话>/ 的目录名 |
hits[].messageId | 消息 id | session.v4.jsonl.zstd 里的 data.id |
hits[].seq | 会话内序号 | 消息顶层的 seq |
workspace | 工作区真实路径 | 目录名反解 |
示例(节选):
{
"found": true,
"sessions": [{
"session": "session-fe4c6d40-889b-411c-bbf6-9329dfbc8161",
"workspace": "H:\\deepseek\\zefeila",
"start": "2026-10-31T12:00:00.000Z",
"end": "2026-10-31T12:40:00.000Z",
"score": 0.82,
"hits": [{
"time": "2026-10-31T12:05:00.000Z",
"role": "user",
"messageId": "e41999df-3a66-44b6-b8b0-49176f17169f",
"seq": 15,
"text": "原话……"
}]
}]
}
下钻方式(本插件已提供)
session_recall({ session: "session-fe4c6d40-..." })
- 传
session ⇒ 跳过语义检索,直接返回该会话的原文块(按时间排序)
- 仍受源隔离约束:调用方不属于任何源 ⇒ 拒绝
- 可选
max_sessions 控制返回块数(默认 20,上限 50)
如果你愿意接(纯可选)
若 topic 卡片带上:
sourceSessionId → 可调用 session_recall({ session: sourceSessionId }) 下钻原文
sourceMessageId → 用于引用定位(当前按会话下钻;消息级定位后续可加)
本插件不需要任何改动即可被这样调用;这只是一份单向、松耦合的约定,不构成对任何记忆方案的评价或推荐。
我们自己的分层记忆(core + topics)已按此形状预留字段名;是否采用、何时采用,由各方自行决定。
跨通道接入(QQ / 微信等桥接)
工具注册在 profile 根,所有 agent 都可见。但桥接通常有两道限制:
-
工具白名单:桥接自己可能只放行部分工具。需要把 session_recall 加进去。
⚠️ 建议只对管理员私聊放行——它返回原文,给群聊放行等于把私聊内容带进群上下文。
-
会话形态:桥接类插件通过工具收发消息,真实对话落在 tool/call / tool/result 里,
而不在 user/message / assistant/message。本插件会自动识别常见命名:
- 发送类(
*send_message*、*send_burst*、*reply_message*)→ 取参数里的真实发言
- 接收类(
*get_unread_messages*、*recent_messages*、*get_group_history*)→ 取返回里的 messages[].text
命中不了的工具名会走普通路径,不影响其他工作区。
实测(QQ 工作区,2026-10-31):
| 指标 | 修复前 | 修复后 |
|---|
| 抽取记录 | 312 条(多为注入) | 1299 条(工具提取占 92.2%) |
| 人设注入残留 | 210/215 条 | 0 |
| 切块 | 519 块 | 1309 块 |
已有索引需要重建一次才会用上新逻辑(设置页「增量更新」)。
它做什么
| 能力 | 说明 |
|---|
| 会话语义检索 | 向量余弦 + BM25 字面,RRF 融合;返回「那场对话」而不是孤零零一句 |
| 对话式反问(差异点) | 最高相关度 < 0.55 时不装懂:返回 fallback_menu(按天话题卡 + 带编号的候选会话),由 agent 先问「是不是这几条里的第几个」,选不中再问时间/关键词 |
| 日摘要 / 话题卡(差异点) | 每天 3~5 个话题;默认用 DSH 自己的模型总结,也可指定本机 Ollama(完全不出网) |
| 自然语言时间锚点(差异点) | 今天 / 昨天 / 前天 / 上周 / 本周 / N 天前 / N 号 / 前几天 |
工具
| 工具 | 用途 |
|---|
session_recall | 核心检索。参数:query / days / role / maxSessions / includeActive / dedupe / freshFirst / session(也接受 max_sessions / include_active / fresh_first 旧写法;no_dedupe=true 可关近重复抑制)。默认只排除「调用方自己这场」会话——当前对话在上下文里,没必要再搜回来;其他刚结束/刚归档的会话照常可搜 |
session_recall_status | 索引多少块、建于何时、是否在建、嵌入来源是否就绪、话题卡几天、嵌入是否上云 |
session_recall_reindex | 手动重建(force=true 全量重嵌) |
session_recall_config | 读/改设置(非密钥项,含 minScore / midScore) |
设置页(普通用户用这个,不用碰 JSON)
装好后:设置 → 会话语义检索(左侧导航里的一项)。普通用户只需要这一页,不用碰任何 JSON。
- 源工作区(置顶,二期:多源分库):点「+ 添加源工作区」建一个隔离域,给它起个名字(比如「泽菲拉」「饭饭」),再把要归入它的工作区勾上。
- 每个源一个独立库:
scopes/<源>/index/;归属该源的工作区 → 建索引、可检索;其他工作区一律拒绝(既不建、也查不到)。
- 源之间互斥:一个工作区只能归一个源;已被别的源占用的行会置灰、点不动。
- 单个源可以包含任意多个工作区(包括全部)——互斥只在源与源之间。
- 顶部下拉可以切换/新建/移除源;移除当前源不会删它的库(保留在
scopes/ 下,方便恢复)。
- 改源名不会重建索引;改工作区归属会触发重建。空源不建索引。
- 没有源 = 老行为:全部工作区共用一个库(
index/),此时旧「排除清单」仍然生效。
- 看状态:索引块数 / 维度 / 体积 / 建于何时 / 话题卡几天 / 是否正在建(带进度条)
- 检索方式:云端 / 本地 两个按钮。云端填 API Key + 接口地址 + 模型;本地填服务地址 + 嵌入模型
- 标题行右侧有 「停用嵌入」(旁边写明:会导致检索能力下降)
- 索引天数;工作区勾选列表(✅ 索引 / □ 不索引;新出现的工作区默认勾上)
- 自动增量开关、每日话题卡开关与总结模型(留空 = 用 DSH 当前模型)
- 旧版人格隔离(配置项
allowFromWorkspaces):只允许指定工作区里发起的会话调用检索;默认留空=不限制。
有源时它会被源取代(源是精确匹配、更强),只在没有源时才生效。
- 「增量更新」「全量重建」两个按钮(全量重建通常需要数分钟,属于正常现象:要把 2000~3000 块逐条嵌入)
为什么不用 DSH 自带的插件设置页:本机 0.2.1-rc.5 的 settings.update() 返回成功但不落盘
(社区里 0.1.7 起就有这个「能读不能写」的问题)。所以这一页直接调插件自己的
/api/session-recall/*(本机同源校验),写 $DSH_HOME/session-recall/config.json——
不依赖那个坏掉的写入路径,任何版本都能用。
版本兼容
| 项 | 声明 |
|---|
engines.node | >=22(依赖 Node 内置的 zlib.zstdDecompressSync) |
engines.dsh | >=0.2.1-rc.5(本插件只在该版本实测通过) |
| 会话格式 | 依赖 session.v4.jsonl.zstd;DSH 大版本换格式时要改 lib/extract.mjs |
| 设置写入 | 不依赖 ctx.settings.update()(该路径在 rc.5 不落盘);走自己的 config.json |
| 失败姿态 | 嵌入不可用 → 自动降级字面检索;Ollama 不可用 → 跳过话题卡;都不影响工具可用 |
实测数据(2026-10-31,本机 i9-13980HX,云端嵌入 Qwen3-Embedding-8B)
建索引
| 场景 | 规模 | 耗时 |
|---|
| 首次全量(新库) | 904 块 | 90.8 秒(约 10 块/秒) |
| 增量(新增 7 块) | 抽取 31 个会话 / 2717 条消息 | 4.0 秒 |
| 增量(新增 16 块) | — | 2.5 秒 |
检索(2841 块 / 4096 维)
| 阶段 | 耗时 |
|---|
| 查询嵌入(云端 API) | 462 ms |
| 向量 + BM25 融合排序(本地) | 77 ms |
| 端到端 | 约 0.5 秒 |
体积:每块 16KB;2841 块 ≈ 44MB(覆盖 21 天);日均约 134 块 ≈ 2.1MB/天。
召回质量:见下一节「判定规则」——15 条带标注查询,12 对 / 1 漏 / 0 假阳性。
外部题库对照(2026-11-04,两套公开题库,只测检索层)
| 题库 | 口径 | 结果 |
|---|
dsh-layered-memory 公开题面 (20 场景 / 65 条消息 / 140 探针) | top-5 拼接覆盖全部 gold 要点 | 关键词 59.2% | 向量 65.0% | 混合 65.0% |
大肥鱼记忆评测公开示例 (360 轮 / 108 题) | 证据轮次进 top-k(只算真长期 80 题) | 关键词 88.8% | 向量 92.5% | 混合 93.8%(@10 97.5%) |
灌水实验(同一题面,按噪声/语料比测;语料 65 块):
| 噪声/语料 | 混合(RRF) | 关键词(BM25) | 向量 |
|---|
| 0(干净) | 60.8% | 58.3% | 61.7% |
| 0.83:1(+54,对齐同类题库比例) | 60.0% | — | — |
| 1:1(+65,合理干扰) | 60.0% | 59.2% | 40.8% |
| 2:1(+130) | 53.3% | — | — |
| 9.2:1(+600,极端压力) | 39.2% | 59.2% | 30.0% |
怎么读:
① 在合理干扰(≤1:1)下,混合几乎不掉(60.8% → 60.0%);只有把噪声堆到语料的 9 倍,才掉到 39.2%——那属于极端压力,不代表真实使用。
② 退化集中在向量通道(1:1 下 −20.9pp),关键词几乎不受影响;混合的稳健性来自关键词兜底。
③ 干净语料靠向量(61.7% vs 58.3%),脏语料靠关键词——混合是保险,不是银弹。
④ 两套题库口径不同(一个要求「覆盖全部要点」,一个只要求「证据轮次出现」),不能互相换算,也不能和别人的端到端分数直接比。
⑤ 复现脚本、逐题数据与完整的局限说明在研发仓库(jianmu/research/bench-*),不在本包内。
以上都是本机单次实测,不是基准测试;换了嵌入服务或对话量,数字会变。判据仍然以"能不能找回原话"为准。
判定规则(2026-10-31 标定,15 条查询)
只靠余弦分分不开真命中与假命中(实测:真命中 0.5780.826,假命中 0.4750.616,区间重叠)。
gap(top1−top2)和 z 分数也分不开。最终采用双条件 + 一道闸门:
found = 非自我引用 && (
top1 ≥ 0.70 // 高分线:实测真命中都在这之上
|| (top1 ≥ 0.55 && 词面证据覆盖 ≥ 50% 或 英文词命中) // 中等:必须有字面词对上
)
- 两条线可调:
minScore(默认 0.70)与 midScore(默认 0.55),改配置文件或
session_recall_config { minScore: 0.65 }。实测出现过「0.661 但确实是正确答案」被判否的情况,
这种时候把 minScore 降到 0.65 即可;不建议低于 0.60(假命中区间 0.475~0.616 会漏进来)。
- 自我引用闸门:top 块是 AI 说的话、且把问题核心串原样复述了一遍 ⇒ 它在「讨论这句话」而不是「回答这句话」。
(实测:MVP 调试那场把测试问句写进了语料,「去哪吃饭」因此拿到 0.576 高分 —— 这是最伤信任的假阳性。)
- 标定结果:15 条查询 12 对 / 1 漏 / 0 假阳性。漏的那条是「C盘满了怎么清理」(换了说法问、词面没对上),
按「宁可给候选,不装懂」给候选菜单。
- 纯字面模式(
none)另用一条更严的规则:只认 df ≤ 5 的稀有实词命中,否则一律给候选。
数据与隐私
- 只读
~/.dsh/sessions/<工作区>/<会话>/session.v4.jsonl.zstd;绝不写回 sessions 目录。
- 索引写在
$DSH_HOME/session-recall/:
- 没有源(老模式):
index/meta.json + index/vectors.f32
- 有源(多源):
scopes/<源id>/index/meta.json + vectors.f32
- 只存「人话」:跳过 reasoning、工具调用/结果、系统注入、自动化会话的人设提示。
- 云端嵌入时,只把切好的文本块发给 API(不发工具调用、不发推理过程);不出网的话把
provider 设成 local 或 none。
这件事在三处明示:建索引日志、session_recall_status 的 embedding.sendsTextToCloud / privacy、设置页的检索方式。
excludeWorkspaces:逗号分隔的目录名子串,命中就不索引(例如 dsh-bili-duty,scratch-notes)。
谁能查:只能由调用方所在的会话通过 session_recall 调,并受两道既有隔离约束——源隔离(不属于任何源的工作区直接拒绝)与人格隔离(allowFromWorkspaces)。桥接通道(QQ/微信等)建议只对管理员私聊放行:它返回原文,给群聊放行等于把私聊内容带进群上下文。
查了留不留痕:不记录查询日志。插件不落盘「谁在什么时候查了什么」,也不把查询历史发给任何第三方;每次检索只读本机索引、把结果返回给当前会话。需要说明的一个例外:语义检索时查询文本本身会发给当前嵌入服务(云端则上云、本地则不出网)用于算向量——这是计算,不是留痕,嵌入服务端是否记录由该服务自己的隐私政策决定;想完全不出网就把 provider 设成 local 或 none。
索引存在哪:只存本机 $DSH_HOME/session-recall/(见上)。删除这个目录 = 连索引一起清掉,原始对话记录不受影响。
存储与体积(会不会无限膨胀)
不会无限膨胀,有三道闸:
- 天数硬上限 365:设置页/工具/配置文件三条入口都会把
indexDays 夹到 1~365,填 100000 也只会按 365 处理。
- 保留窗口 = max(索引天数, 30):旧块最多留 30 天起;把天数改小不会立刻删,而是等窗口滑走。
- 只索引窗口内的会话:窗口外的对话根本不进库。
体积口径(实测,2026-10-31):每块 = 维度 × 4 字节,4096 维 ⇒ 16KB/块。
当前(21 天、2810 块)约 44MB,日均约 134 块 ≈ 2.1MB/天。
| 索引天数 | 约块数 | 约体积 | 首次全量重建 |
|---|
| 7 天 | ~940 | ~15MB | 1~2 分钟 |
| 30 天(建议上限) | ~4000 | ~64MB | 3~5 分钟 |
| 90 天 | ~12000 | ~190MB | 10 分钟级 |
| 365 天 | ~49000 | ~770MB | 半小时级,嵌入费用也显著 |
对话量大的人按比例放大。建议 ≤30 天:够搜「上周 / 上个月」,体积和重建时间都友好。
设置页现在会按你的实际日均块数实时估算体积,超过 90 天会变黄提示。
嵌入费用:每块 1 次嵌入调用;首次全量重建才贵,之后是增量(只嵌新块)。
设计取舍(为什么是这些数字)
| 选择 | 为什么 |
|---|
| 1200 字切块 / 80 字重叠 | 太小丢上下文、太大稀释语义;重叠防止关键句被切断 |
| 4096 维向量 | 嵌入模型的默认维度;每块 16KB,体积可控 |
| 保留窗口 = max(天数, 30) | 改小天数不会立刻丢历史,等窗口滑走 |
| 365 天硬上限 | 防误填三个零;真要更久,建议交给分层记忆插件 |
| 双条件判定(≥0.70,或 ≥0.55 且有字面词) | 单看余弦分不开真假命中(实测区间重叠) |
| 按源分库 | 隔离靠「物理分开」而不是「查询时过滤」——查错库在结构上不可能 |
| 只读会话、不写回 | 出问题最多索引坏,重跑即可;绝不碰你的对话原始记录 |
| 不做注入 | 不占每轮上下文预算;「要不要查、查什么」交给 agent 决定 |
设置(两种方式)
方式一:设置页(推荐)
见上面「设置页」一节,普通用户只用这一种。
方式二:直接改配置文件(没有页面时 / 高级用法)
配置页背后的存储就是它:
$DSH_HOME/session-recall/config.json # Windows: C:\Users\<你>\.dsh\session-recall\config.json
{
"provider": "cloud",
"apiKey": "sk-...",
"apiBase": "",
"localBase": "",
"model": "",
"indexDays": 7,
"minScore": 0.7,
"midScore": 0.55,
"daySummaries": true,
"summaryModel": "",
"autoIndex": true,
"excludeWorkspaces": "dsh-bili-duty,scratch-notes",
"allowFromWorkspaces": ""
}
summaryModel 留空 = 用 DSH 当前模型;填了就用本机 Ollama 的该模型。
- 改完不用重启;下一次自动检查(≤6 小时)会同步,想立刻生效点「增量更新」/「全量重建」。
优先级:默认值 < 插件 row 的 config(bundle patch) < config.json < 设置页 user 层。
嵌入来源
| provider | 需要什么 | 备注 |
|---|
cloud(默认) | 一个 API Key + 兼容 OpenAI 的 /v1 地址 | 全量索引 7 天约 0.2~0.5 元;之后增量几分钱 |
local | 本地嵌入服务(默认 Ollama)+ 一个嵌入模型 | 完全不出网;Ollama 必须带 --embeddings 启动,否则报 This server does not support embeddings |
none | 什么都不用 | 纯 BM25 字面检索:只给候选、不确认命中(长块 BM25 的绝对分不可信,实测过) |
没填 Key 时自动降级成 none,填好 Key 后下一次检查会自动重建为语义索引。
安装(给第一次用的人)
现状(2026-11-01):已发布到 npm(包名 dsh-semantic-recall,当前 0.2.5)。
安装时 target 直接填包名,pnpm 会从 registry 取最新版:
action: install_bundle
target: dsh-semantic-recall
网络到不了 npm registry 时,用 GitHub Release 的 tgz 兜底
(https://github.com/zhaohualiuyuejun/dsh-semantic-recall/releases/download/v0.2.5/dsh-semantic-recall-0.2.5.tgz)。
也可以用源码目录装,适合想改代码的人。
1. 把包放进机器
# 推荐:什么都不用下载,plugin_manager 的 target 直接填包名
# target: dsh-semantic-recall
# 兜底 A(npm 不可达):下载 GitHub Release 的 tgz,把它的绝对路径填进 target
# 兜底 B(想改代码):clone 源码目录,把目录绝对路径填进 target
git clone <这个仓库>
# 目录里应当有 package.json / cordis.patch.yml / index.js / client.js / lib/
2. 用 DSH 的 plugin_manager 装(创造模式)
action: install_bundle
target: <上面那个目录的绝对路径>
装好后 profile 的 dsh.profile.bundles 里出现 dsh-semantic-recall 即生效。
装完刷新一次页面,左侧「设置」里才会出现「会话语义检索」(客户端 bundle 是页面加载时注入的)。
不用手改 profile 的 package.json 或 cordis.patch.yml(bundle 自带 patch)。
3. 零配置就能用(只是字面检索)
装完 3 秒,插件会自己扫近 7 天的会话建索引,不需要 Key、不花钱。
这时 session_recall 能列出「最像的几场对话」,但不敢确认命中(故意的,见上面「判定规则」)。
4. 开启语义检索(推荐,在设置页点几下就行)
刷新页面后打开 设置 → 会话语义检索:
- 「检索方式」点 云端;
- 把 API Key 粘进「API Key」,点「保存 Key」;
- 点 「全量重建」(第一次没有向量,必须重建一次)。
不填 Key 也能用,只是退化成字面检索(只给候选)。想完全不出网就用 本地。
没有 Key?这里有一份注册示例(非广告)
先把话说清楚:这一节不是广告。 作者与下面这家平台没有任何合作、返利或分成;写它只是因为「手上没有任何 OpenAI 兼容的嵌入接口」是最常见的卡点。
任何兼容 OpenAI /v1/embeddings 的服务都能用;也可以完全不联网、用本机 Ollama(见第 6 步)。
以 硅基流动(SiliconFlow) 为例:
- 打开 https://siliconflow.cn,注册并登录(实名与否以平台当前要求为准)。
- 进「API 密钥」页面,新建一个 Key,复制出来(形如
sk-...)。
- 回到 DSH:设置 → 会话语义检索 →「检索方式」点「云端」,把 Key 粘进「API Key」,点「保存 Key」。
- 模型那一栏填
BAAI/bge-m3(留空则默认 Qwen/Qwen3-Embedding-8B)。
- 点 「全量重建」,等它跑完(7 天语料约 1~2 分钟)。
费用与「免费」
- 截至 2026-11 实测,
BAAI/bge-m3 在这个平台不收调用费,有 Key 就能调(实测返回 1024 维向量,单条约 276ms)。
- 但价格与额度以平台当日页面标价为准,随时可能变。哪天开始收费或限额,把模型换成别的、或改用本机 Ollama 即可——插件不绑定任何厂商。
- 同一家的不同模型计费也不同:默认的
Qwen/Qwen3-Embedding-8B 是按量计费的(7 天全量约 0.2~0.5 元)。
三个容易踩的坑
- 换模型必须「全量重建」:不同模型的向量空间和维度不同(
BAAI/bge-m3 是 1024 维,Qwen/Qwen3-Embedding-8B 是 4096 维),只点「增量更新」会得到新旧混在一起的库。
- bge-m3 更小更省:1024 维、每块 4KB(4096 维是 16KB),索引更小、重建更快;代价是语义精度略低于 8B 模型。
- Key 只存在本机
$DSH_HOME/session-recall/config.json:不要贴进对话、不要提交到仓库。插件只会把它发给你配置的那个嵌入接口。
不想注册、也不想联网? 用第 6 步的本机 Ollama:完全免费、不出网,ollama pull bge-m3 并在启动时加 --embeddings 即可(注意本机模型名是 bge-m3,云端才是 BAAI/bge-m3)。
5. 每日话题卡(默认就开,什么都不用装)
话题卡默认用 DSH 自己的当前模型 总结——装完就有,不需要下载任何东西(会消耗正常额度)。
- 想完全不出网:设置页「本地模型」里填本机 Ollama 的模型名,并
ollama pull qwen3:8b(约 5GB)。
- 不想花额度:把「启用」关掉即可。
指定了本地模型但 Ollama 不可用时,会自动跳过,不影响检索。
6. 可选:完全本地嵌入(不出网)
- 给 Ollama 加
--embeddings 启动(否则会报 This server does not support embeddings);
ollama pull bge-m3(约 1.2GB,是嵌入模型,不是对话模型);
- 设置页「检索方式」点 本地 → 模型名填
bge-m3 → 点「全量重建」。
升级(从旧版换到新版,务必先看)
用包名升级(推荐):action: install_bundle + target: dsh-semantic-recall,pnpm 从 registry 取新版,下面 1~3 条的坑都不存在。
1~3 条只针对用 file: / tgz 路径安装的情况。
1. 先备份当前插件目录(仅 file: / tgz 安装需要)
用 tgz 覆盖安装时,pnpm 会把依赖源追平成真实目录:
如果你的 package.json 里写的是 "dsh-semantic-recall": "file:E:/某个目录",
那个源目录会被清空(profile 里拿到的是真实副本,源目录被收走)。
升级前先 zip 备份一份,或确认它本来就来自 registry。
2. tgz 放到不会被清理的固定目录再装(仅 tgz 安装需要)
用 tgz 装完,依赖会记成绝对路径,例如:
"dsh-semantic-recall": "file:E:/QQBot/packages/dsh-semantic-recall-0.2.2.tgz"
把 tgz 放在临时目录,之后一清理,下一次 pnpm 解析依赖就会失败。
建议固定放 ~/dsh-packages/ 这类目录;用包名安装则没有这个问题。
3. installBundle 的 spec 是字符串,而且不会预校验
action: install_bundle
target: <包名 / 绝对路径 / tgz 路径> ← 都是字符串
传对象会报 wire field spec 失败。另外它不会先确认包存不存在——
传一个不存在的名字,pnpm 会真的去 registry 装同名包。别拿它试参数。
最省事、最不容易错的写法就是填包名 dsh-semantic-recall。
4. 升级后必须全量重建(force: true),不是可选项
增量重建按 chunk key 去重:旧块的 key 没变 ⇒ 不会重新嵌入 ⇒
新版本对文本的清洗(比如 0.2.2 的代理项清洗)作用不到旧向量上。
只点「增量更新」会得到一个「清洗前 + 清洗后」混合的库。
所以凡是跨版本升级,请点**「全量重建」**(设置页或 session_recall_reindex { force: true })。
0.2.2 实测:两个库重建后从 1341/1422 块涨到 1379/1473 块——原先被 400 干掉的块进来了,这是预期的。
第一次跑会发生什么(可预期)
| 阶段 | 表现 |
|---|
| 安装后 ~3 秒 | 后台开始扫会话;session_recall_status 里 building: true |
| 7 天语料(约 2000~3000 块) | 纯字面:13 秒;云端语义:**25 分钟**(正常现象,不是卡住——页面上有进度条) |
| 费用 | 全量 7 天约 0.2~0.5 元;之后增量几分钱 |
| 话题卡 | 建完索引后台开始跑,每天 20~60 秒 |
| 失败时 | lastBuild.error + errorHint 说清原因(没 Key / 本地服务没开 embeddings / 401…) |
卸载
action: remove_bundle target: dsh-semantic-recall
索引数据在 $DSH_HOME/session-recall/,删掉整个目录即可清干净(不会碰 ~/.dsh/sessions)。
开发 / 调试
node tools/probe.mjs 1 demo # 最小实验:能不能解开 session.v4.jsonl.zstd
node tools/cli.mjs build none 7 # 建纯字面索引(不花钱)
node tools/cli.mjs query "注入攻击" 7 # 查询
node tools/cli.mjs days 7 qwen3:8b # 生成话题卡(要指定本地模型;CLI 不接 DSH 模型)
node tools/cli.mjs info
日志与状态:$DSH_HOME/session-recall/build.log、state.json、config.json。
常见问题(FAQ)
Q:搜不到怎么办?
- 先看窗口:索引天数够不够到那天(超出窗口的不在库里)
- 点「增量更新」:刚聊完的还没进索引
- 换关键词:语义检索对路径、专名不敏感,用具体词更稳
- 看是不是在别的源里(多源分库,不会跨库查)
Q:为什么显示「字面检索」?
没配嵌入 Key,或 provider=none。字面模式只能给候选,不会给「确认命中」。
Q:Ollama 启动后还是连不上?
嵌入要带 --embeddings 启动;话题卡用对话模型则不需要。设置页有「检测 Ollama」。
Q:改小索引天数,为什么体积没立刻变小?
保留窗口 = max(天数, 30),旧块等窗口滑走,不会立刻删。
Q:删掉索引会丢什么?
只丢索引。对话原文在 ~/.dsh/sessions,重跑就能重建;记忆插件的记忆完全不受影响。
Q:能不能替代记忆插件?
不能。见「和长期记忆插件的关系」——它管「我该知道什么」,本插件管「你那天到底怎么说的」。
Q:多源会不会互相查串?
不会。每个源一个库,查询按调用方工作区路由;不属于任何源的工作区直接拒绝。
Q:装上后工具里没有 session_recall?
先看 bundle 是不是 enabled,而不是「装了依赖」。pluginManager/listBundles 里
installed=true, enabled=false 时,重启多少次都不会加载,日志里也没有线索;
用 pluginManager/setBundleEnabled(参数扁平 { name, enabled })打开,返回
application: "applied" 就是 HMR 热加载生效,不用重启。
Q:装完之后整个 DSH 没法对话,一发消息就报错?
0.2.1 及更早版本的致命 bug:桌面版把 @deepseek-ai/dsh-tools 打进 app.asar,插件取不到就落到本地等价实现,
而它没有把裸参数表编译成 object 根,于是模型请求里 tools[].function.parameters 缺 type,
DeepSeek 判定整批工具定义非法 ⇒ 该会话所有请求失败。升到 0.2.2;急着用就先关掉 bundle。
Q:直接改 config.json 的 indexDays,为什么没生效?
设置页的 user 层优先级最高(默认值 < 插件 row.config < config.json < 设置页)。
走插件自己的接口最稳:POST /api/session-recall/config { patch: {...} }(同时写两层并触发重建)。
已知限制
- 依赖
session.v4.jsonl.zstd 这个格式;DSH 升级换了格式要跟着改 lib/extract.mjs。
- 纯字面模式(
none)只能给候选,别指望它确认命中。
- 话题卡默认走 DSH 自己的模型(会消耗额度);指定本机 Ollama 时若不可用会自动跳过(不报错)。
- 重排(rerank)实测不稳定,未启用。
- 设置页是插件自己的(走
/api/session-recall/*),不依赖 DSH 的 settings.update()——那个路径在 0.2.1-rc.5 上「能读不能写」。
版本
0.3.0(2026-11-05):返回层五项改进,全部只加字段、不删字段。
① 候选编号化:查不到时 fallback_menu.sessions 每条带 index(从 1 起)与 label(日期 · 预览),note 主指令改为「先列编号候选,问是不是这几条里的第几个,选不中再问时间/关键词」——回应真实反馈「反问要给具体选项」。
② 日期就地给:语义命中与下钻的每条 hits 都带 dateText(本地时区 YYYY-MM-DD)/ dateHuman(M月D日 HH:mm)/ ageDays,会话的 start / end 旁加 startDateText / endDateText;命中 note 写死「引用必须带 dateText」,不再只说「X 月 X 日」。
③ 近重复抑制(dedupe 默认开):同一内容在多个会话出现时合并为一条,附 duplicateCount / duplicateTimes / duplicateSessions,避免 top-k 被重复内容占满。内容指纹按「空白归一化 + 前 120 字 + role」算(用户原话与 AI 复述不合并),只合并不丢原始字段。
④ 时效暴露:每条带 isRecent(7 天内);可选 freshFirst=true 让会话按最近出现时间降序(默认 false,与 0.2.5 排序一致)。
⑤ 去重后补位(fillAfterDedupe,默认关):去重后场次不足 maxSessions 时,把被合并掉的会话按原分补回来(带 restoredByFill:true),避免「去重把会话砍到只剩一场」的错觉。默认 false,行为与 0.3.0 初版一致。
默认行为有没有变:只有 ③ 默认开启——返回条数可能变少(重复被合并)。需要 0.2.5 原样时传 dedupe=false;想「合并但别少场」时传 fillAfterDedupe=true。其余为纯新增字段。验证脚本:tools/verify-0.3.0.mjs。
0.2.5(2026-11-01):session_recall_status 增加 version 字段(从同目录 package.json 读取,读不到报 unknown)——排查「装的到底是哪一版」时不用再翻文件。纯新增,不改变任何检索与索引行为。
- 文档修订(2026-11-02):将原「对外接口约定(给记忆插件作者)」一节更名为「与其他记忆类插件的可选协作」,并把「会不会编」对照改为中性的「输出形态」;仅改文档,未动代码、协议字段与任何检索行为。
0.2.4(2026-11-01):修正「排除范围」而不是「要不要索引当前会话」——默认仍然不搜当前这场(它本来就在上下文里),但排除方式从「30 分钟内活跃的所有会话整场排除」改成「只排除调用方自己这场」(用 exec.agent.session.id 精确识别,拿不到 id 才退回旧行为)。
修的是一个边界:自动压缩 → 归档旧会话 → 开新会话的流程里,刚归档的上一场只差几分钟,会被当成「活跃会话」整场藏起来,看起来像检索有盲区;而长期不换会话的人完全不受影响。includeActive=true 时连自己这场也一起搜。验证脚本:tools/verify-0.2.4.mjs。
0.2.3(2026-11-01):用起来更顺手的三个改动——① includeActive 默认改为 true(该默认值已被 0.2.4 推翻:会索引当前对话、带来问句回声;正确做法是只排除调用方自己这场);② 参数改成 camelCase(maxSessions / includeActive),同时继续接受 max_sessions / include_active——以前传 camelCase 会静默少结果,现在两种都认;③ 命中门限( 默认 0.70、 默认 0.55),实测「0.661 但答案正确」被判否的场合可以调到 0.65。
另:检索结果的 里会带上(「索引快照:11:41(1 小时前)——这之后的新对话还没进索引」); 增加 与 字段,明确告知云端嵌入会把发到嵌入服务端; 会报 (宿主工具契约再变时有迹可循)。验证脚本:。