跳到主要内容

DeepSeek Harness 本地聊天记忆:按来源保存聊天原文,关键词 + 向量检索,可选大模型重排与检索摘要。源码公开,仅限非商用

浏览文件
main

分支

README.md

160 行 · 10.8 KB预览历史下载
1# dsh-companion-memory
2
3给 DeepSeek Harness 里的鱼用的本地聊天记忆(源码公开,仅限非商用,见文末[许可证](#许可证))。它把聊过的原文存进本机 SQLite,回答前按「这段对话从哪来」检索相关历史,带着时间和场合交给模型;鱼也可以自己调用工具翻记忆、记一笔。
4
5**不绑定任何桥接。** 插件只用 DSH 官方接口(会话事件、`ctx.tools`、运行时上下文、web 服务)。不管消息是从 DSH 桌面端、官方 QQ 机器人、qq-bridge、AstrBot 还是别的什么进来,只要进了 DSH 会话就能记;各家的差别只体现在配置里,或者由桥接调用下面的开放接口。
6
7## 它解决什么
8
9| 问题 | 做法 |
10| --- | --- |
11| 桥接换会话 / `/reset` / 自动轮换后就失忆 | 记忆按**来源**(主人 / 群 / 私聊)存,不跟会话 ID 走 |
12| 鱼靠 `send_message` 一类工具说话,助手正文是空的 | 这类工具的文字参数也算「她说的话」(可配置) |
13| 桥接每次都塞一大段固定说明,检索被它淹没 | 跨轮重复出现的长行自动识别为套话,不入库、不参与检索;检索词按稀有度挑选 |
14| 每一步都检索、往历史里追加一条消息 | 每条新的用户消息只检索一次,结果放进 DSH 运行时上下文:内容不变就不重发,变了只追加在已缓存历史之后,不破坏 KV 缓存前缀 |
15| 私聊里的话被群友套出来 | 别人的私聊只在和他的私聊里可见;主人和群聊内容默认共享,可分别关闭 |
16
17## 记忆怎么工作(RAG)
18
19**存**:每一轮存一条原文,包括对方说的话、鱼通过读消息工具读到的别人的发言、鱼的回复(含通过发送工具说的话)。同一场合里重复读到的同一句话只存一次;桥接每次都会塞进来的固定说明不入库。
20
21**查**:每条新消息查一次,三处共用同一条检索路径:自动回忆、`chat_memory_search` 工具、`api/search`。
221. 关键词:中文按两字切词;某个字在查询里的两字组合都没被索引过时,才单独用这个字去匹配。只用查询里最有区分度的词,太常见的词(「什么」「今天」)自动去掉。
232. 向量(可选):配了 embedding 才启用,补上换了说法、没有共同字的情况。
243. 融合:两路按各自最高分归一后相加;候选中越新的最多加 15%,用来区分分数接近的新旧记录。
254. 前后文:命中的那一轮会带上同一场合里的前一轮和后一轮(6 小时内),「那就海边吧」这种才看得懂。
265. 给模型时按时间从早到晚排列,并说明「同一件事前后说法不同时,以较晚的为准」。
27
28**评测**:`npm run eval` 在一份模拟的 QQ 群聊和私聊语料上跑检索(加 `--embed bge-m3` 测向量模式),`npm test` 里有不得低于的下限。「答案在给模型的证据里」的比例:
29
30| 查询类型 | 只用关键词 | + nomic-embed-text | + bge-m3 |
31| --- | --- | --- | --- |
32| 原话 / 关键词 | 100% | 100% | 100% |
33| 换说法 | 67% | 67% | 83% |
34| 答案在下一轮 | 100% | 100% | 100% |
35| 事实变过(搬家) | 100% | 100% | 100% |
36| 夹在长提示里 | 0% | 0% | 0% |
37| 完全没有共同字 | 0% | 0% | 50% |
38| **合计(不含最后一类)** | **76%** | **76%** | **82%** |
39| 排第一就命中 | 59% | 71% | 76% |
40
41`nomic-embed-text` 主要针对英文,对中文帮助有限;`bge-m3` 是中文首选。2 万条记录时,关键词检索中位数约 17ms。
42
43**聊天模型(可选):重排 + 检索摘要**。在重排里配一个聊天模型(协议 `chat`,任何 OpenAI 兼容接口都行;DeepSeek 会自动关闭推理以提速),它会同时做两件事:
44
45- **重排**:每次检索时,给最多 12 条候选打 0–10 分,判为无关的直接丢弃。它能推理隐含关系,比如「有什么不能吃的」对上「花生过敏」。
46- **检索摘要**:在后台为每条记忆写一句摘要,把隐含的意思写明白,比如「去字节报到」写成「入职字节跳动公司,找到新工作」。攒够 10 条打包成一次请求。摘要**只用来检索**,命中后给模型的仍然是原文,所以摘要写错最多影响能不能找到,不会把编造的内容当成记忆。
47
48实测(19 条查询;DeepSeek `deepseek-flash` + 本机 bge-m3):
49
50| 配置 | 答案在证据里(不含语义题) | 语义题(无共同字) | 证据平均长度 | 每次检索耗时 |
51| --- | --- | --- | --- | --- |
52| 只用关键词 | 76% | 0% | — | 几毫秒 |
53| + bge-m3 | 82% | 50% | 727 字 | 0.3 秒 |
54| + 重排 + 摘要(不装 Ollama) | 82% | 50% | 371 字 | 0.9 秒 |
55| + bge-m3 + 重排 | 100% | 50% | 442 字 | 1.0 秒 |
56| **+ bge-m3 + 重排 + 摘要** | **100%** | **100%** | **485 字** | **1.1 秒** |
57
58成本(`deepseek-flash` 高峰价,非高峰减半):
59- 摘要:每条记忆约 40 输入 + 14 输出 token,每 1000 条约 $0.03,只在入库时付一次。
60- 重排:每次检索约 460 输入 + 28 输出 token,每 1000 次检索约 $0.17。
61
62模型出错或超时时,重排退回原来的检索顺序,摘要留到下次再补,记忆照常工作。配置时密钥只填**环境变量名**(如 `DEEPSEEK_API_KEY`),不要填密钥本身。
63
64评测命令:`DEEPSEEK_API_KEY=… npm run eval -- --embed bge-m3 --rerank deepseek-flash`。
65
66**零配置**:没配向量模型时,插件会检查本机 Ollama 有没有已经装好的中文向量模型(bge-m3、qwen3-embedding、multilingual-e5 等)。有就自动用上,并在后台给已有记忆补齐向量;只连本机,不下载、不外发。想装的话执行 `ollama pull bge-m3`(约 1.2GB)。
67
68**可以接别的模型吗**:聊天用哪个大模型由 DSH 决定,插件通过 DSH 的上下文接口提供记忆,换模型照样能用。向量和重排模型可以接本机 Ollama,或任何 OpenAI 兼容的向量接口(OpenAI、硅基流动、阿里百炼兼容模式等)、通用 `/rerank` 接口和 DashScope 重排;远程服务要求 HTTPS,并需要明确允许发送。
69
70## 来源怎么认
71
72按顺序:
73
741. 桥接通过 `api/bind` 明确告诉插件某个会话属于哪个群 / 哪个人;
752. 按**会话 ID** 匹配规则(默认能认出 `group:123456`、`private:123456` 和 `xxx-g-123456-…` / `xxx-p-123456-…` 这类 ID);规则是纯数据,可在设置页改;
763. 都认不出来:默认单独隔离(只能看到自己);只有你一个人用 DSH 时可以设为「当作主人」。
77
78只读会话 ID,**不读消息内容**——群友可以在消息里随便写 `private:主人QQ`,会话 ID 他改不了。`ownerIds` 里的账号的私聊归入「主人」。
79
80可见范围:
81
82| 当前对话 | 能想起 |
83| --- | --- |
84| 主人的私聊 | 主人 + 所有群 |
85| 某个群 | 这个群 + 其他群 + 主人 |
86| 某人的私聊 | 和他的私聊 + 所有群 + 主人 |
87| 认不出来源的会话 | 只有它自己 |
88
89「其他群」「主人」两项可以在设置页分别关掉。
90
91## 给模型的工具
92
93- `chat_memory_search`:按关键词翻以前聊过的原文。
94- `chat_memory_note`:把约定、喜好这类值得长期记住的事写一笔。
95- `chat_memory_forget`:按编号删除一条记忆,**只能删当前场合的**(防止群友让她删掉主人的记忆)。
96
97- `chat_memory_setup`:查看 / 修改记忆设置。用户不用懂配置,对鱼说「帮我把聊天记忆设置好,我的 QQ 是 ……」就行。修改**一律弹出 DSH 的批准**,而且只能在主人自己的对话里改,群里、别人的私聊里调用会被拒绝。
98
99工具声明固定不变,不影响缓存;前三个不需要时可以在设置页关掉。
100
101## 开放接口(给桥接或其他本机程序)
102
103只接受本机请求,请求头带 `Authorization: Bearer <令牌>`(令牌在设置页「桥接接口」里,也保存在数据目录的 `api-token` 文件中)。
104
105```http
106POST /dsh-companion-memory/api/ingest {"origin":"group:123456","speaker":"小红","text":"我下个月搬去杭州","sourceKey":"msg-889","time":1762500000000}
107POST /dsh-companion-memory/api/search {"origin":"private:10001","query":"搬家","limit":5}
108POST /dsh-companion-memory/api/bind {"sessionId":"<DSH 会话 ID>","origin":"group:123456"}
109```
110
111- `ingest`:桥接把它看到的群消息直接存进来(例如鱼潜水时没进 DSH 的群聊)。同一个 `sourceKey` 重复提交只存一次。
112- `search`:按某个来源的可见范围检索,返回命中片段和一段可直接拼进提示词的 `evidence`。
113- `bind`:会话 ID 认不出来源时,告诉插件它属于哪里。
114
115`origin` 取值:`owner`、`group:<号>`、`private:<号>`。
116
117## 模型接口(可选)
118
119默认不联网、不下载模型,用 FTS5 关键词检索就能工作。可以接 Ollama 或 OpenAI 兼容的 embedding,以及通用 `/rerank` 或 DashScope 重排。远程地址必须 HTTPS,并且要在设置里明确允许发送;密钥只从环境变量读取。
120
121## 安装
122
123普通 npm 包。用 DSH 插件管理器安装,并把 `cordis.patch.yml` 里的 bundle patch 应用到目标 profile(命令以你的 DSH 版本文档为准):
124
125```text
126dsh plugin --profile desktop add <path-to-dsh-companion-memory>
127```
128
129装好后在设置页填上**主人账号**;用 qq-bridge 这类会话 ID 里带群号的桥接,默认规则就能认出来源,其他桥接按上面「来源怎么认」配置。
130
131## CLI
132
133只处理你明确指定的 JSONL,不扫描 `.dsh` 历史。`export` 输出的 JSONL 可以直接 `import` 回去:
134
135```text
136companion-memory import --dir ./data --file history.jsonl --scope owner
137companion-memory search --dir ./data --query "之前的计划" --scope group:123456
138companion-memory export --dir ./data --scope owner > owner.jsonl
139companion-memory stats --dir ./data
140```
141
142删除记忆会删掉原文、索引并留下墓碑,防止被自动重新导入;它不会抹除 DSH 自己的会话日志、WAL 或备份。
143
144## 测试
145
146```text
147npm test # 单元测试 + 检索质量下限
148npm run eval # 打印检索评测报告(只用关键词)
149npm run eval -- --embed bge-m3 # 加本机 Ollama 向量
150DEEPSEEK_API_KEY=… npm run eval -- --embed bge-m3 --rerank deepseek-flash # 再加重排和检索摘要
151```
152
153## 许可证
154
155源码公开,**仅限非商业用途**,许可证为 [PolyForm Noncommercial 1.0.0](LICENSE)。
156
157- 可以:个人使用、学习研究、爱好项目,学校、公益组织、政府机构使用;可以修改,也可以分享修改后的版本,但要附上许可证和 `Required Notice` 那一行。
158- 不可以:用于商业用途,例如付费产品或服务、公司内部的商业业务、收费代部署。需要商用请先联系作者授权。
159
160注意:非商用许可不属于 OSI 定义的「开源」许可证,所以这里称作「源码公开」。许可证以 LICENSE 文件的英文原文为准。