跳到主要内容

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

main

分支

README.md

dsh-companion-memory#

给 DeepSeek Harness 里的鱼用的本地聊天记忆(源码公开,仅限非商用,见文末许可证)。它把聊过的原文存进本机 SQLite,回答前按「这段对话从哪来」检索相关历史,带着时间和场合交给模型;鱼也可以自己调用工具翻记忆、记一笔。

不绑定任何桥接。 插件只用 DSH 官方接口(会话事件、ctx.tools、运行时上下文、web 服务)。不管消息是从 DSH 桌面端、官方 QQ 机器人、qq-bridge、AstrBot 还是别的什么进来,只要进了 DSH 会话就能记;各家的差别只体现在配置里,或者由桥接调用下面的开放接口。

它解决什么#

问题做法
桥接换会话 / /reset / 自动轮换后就失忆记忆按来源(主人 / 群 / 私聊)存,不跟会话 ID 走
鱼靠 send_message 一类工具说话,助手正文是空的这类工具的文字参数也算「她说的话」(可配置)
桥接每次都塞一大段固定说明,检索被它淹没跨轮重复出现的长行自动识别为套话,不入库、不参与检索;检索词按稀有度挑选
每一步都检索、往历史里追加一条消息每条新的用户消息只检索一次,结果放进 DSH 运行时上下文:内容不变就不重发,变了只追加在已缓存历史之后,不破坏 KV 缓存前缀
私聊里的话被群友套出来别人的私聊只在和他的私聊里可见;主人和群聊内容默认共享,可分别关闭

记忆怎么工作(RAG)#

存:每一轮存一条原文,包括对方说的话、鱼通过读消息工具读到的别人的发言、鱼的回复(含通过发送工具说的话)。同一场合里重复读到的同一句话只存一次;桥接每次都会塞进来的固定说明不入库。

查:每条新消息查一次,三处共用同一条检索路径:自动回忆、chat_memory_search 工具、api/search。

  1. 关键词:中文按两字切词;某个字在查询里的两字组合都没被索引过时,才单独用这个字去匹配。只用查询里最有区分度的词,太常见的词(「什么」「今天」)自动去掉。
  2. 向量(可选):配了 embedding 才启用,补上换了说法、没有共同字的情况。
  3. 融合:两路按各自最高分归一后相加;候选中越新的最多加 15%,用来区分分数接近的新旧记录。
  4. 前后文:命中的那一轮会带上同一场合里的前一轮和后一轮(6 小时内),「那就海边吧」这种才看得懂。
  5. 给模型时按时间从早到晚排列,并说明「同一件事前后说法不同时,以较晚的为准」。

评测:npm run eval 在一份模拟的 QQ 群聊和私聊语料上跑检索(加 --embed bge-m3 测向量模式),npm test 里有不得低于的下限。「答案在给模型的证据里」的比例:

查询类型只用关键词+ nomic-embed-text+ bge-m3
原话 / 关键词100%100%100%
换说法67%67%83%
答案在下一轮100%100%100%
事实变过(搬家)100%100%100%
夹在长提示里0%0%0%
完全没有共同字0%0%50%
合计(不含最后一类)76%76%82%
排第一就命中59%71%76%

nomic-embed-text 主要针对英文,对中文帮助有限;bge-m3 是中文首选。2 万条记录时,关键词检索中位数约 17ms。

聊天模型(可选):重排 + 检索摘要。在重排里配一个聊天模型(协议 chat,任何 OpenAI 兼容接口都行;DeepSeek 会自动关闭推理以提速),它会同时做两件事:

  • 重排:每次检索时,给最多 12 条候选打 0–10 分,判为无关的直接丢弃。它能推理隐含关系,比如「有什么不能吃的」对上「花生过敏」。
  • 检索摘要:在后台为每条记忆写一句摘要,把隐含的意思写明白,比如「去字节报到」写成「入职字节跳动公司,找到新工作」。攒够 10 条打包成一次请求。摘要只用来检索,命中后给模型的仍然是原文,所以摘要写错最多影响能不能找到,不会把编造的内容当成记忆。

实测(19 条查询;DeepSeek deepseek-flash + 本机 bge-m3):

配置答案在证据里(不含语义题)语义题(无共同字)证据平均长度每次检索耗时
只用关键词76%0%—几毫秒
+ bge-m382%50%727 字0.3 秒
+ 重排 + 摘要(不装 Ollama)82%50%371 字0.9 秒
+ bge-m3 + 重排100%50%442 字1.0 秒
+ bge-m3 + 重排 + 摘要100%100%485 字1.1 秒

成本(deepseek-flash 高峰价,非高峰减半):

  • 摘要:每条记忆约 40 输入 + 14 输出 token,每 1000 条约 $0.03,只在入库时付一次。
  • 重排:每次检索约 460 输入 + 28 输出 token,每 1000 次检索约 $0.17。

模型出错或超时时,重排退回原来的检索顺序,摘要留到下次再补,记忆照常工作。配置时密钥只填环境变量名(如 DEEPSEEK_API_KEY),不要填密钥本身。

评测命令:DEEPSEEK_API_KEY=… npm run eval -- --embed bge-m3 --rerank deepseek-flash。

零配置:没配向量模型时,插件会检查本机 Ollama 有没有已经装好的中文向量模型(bge-m3、qwen3-embedding、multilingual-e5 等)。有就自动用上,并在后台给已有记忆补齐向量;只连本机,不下载、不外发。想装的话执行 ollama pull bge-m3(约 1.2GB)。

可以接别的模型吗:聊天用哪个大模型由 DSH 决定,插件通过 DSH 的上下文接口提供记忆,换模型照样能用。向量和重排模型可以接本机 Ollama,或任何 OpenAI 兼容的向量接口(OpenAI、硅基流动、阿里百炼兼容模式等)、通用 /rerank 接口和 DashScope 重排;远程服务要求 HTTPS,并需要明确允许发送。

来源怎么认#

按顺序:

  1. 桥接通过 api/bind 明确告诉插件某个会话属于哪个群 / 哪个人;
  2. 按会话 ID 匹配规则(默认能认出 group:123456、private:123456 和 xxx-g-123456-… / xxx-p-123456-… 这类 ID);规则是纯数据,可在设置页改;
  3. 都认不出来:默认单独隔离(只能看到自己);只有你一个人用 DSH 时可以设为「当作主人」。

只读会话 ID,不读消息内容——群友可以在消息里随便写 private:主人QQ,会话 ID 他改不了。ownerIds 里的账号的私聊归入「主人」。

可见范围:

当前对话能想起
主人的私聊主人 + 所有群
某个群这个群 + 其他群 + 主人
某人的私聊和他的私聊 + 所有群 + 主人
认不出来源的会话只有它自己

「其他群」「主人」两项可以在设置页分别关掉。

给模型的工具#

  • chat_memory_search:按关键词翻以前聊过的原文。

  • chat_memory_note:把约定、喜好这类值得长期记住的事写一笔。

  • chat_memory_forget:按编号删除一条记忆,只能删当前场合的(防止群友让她删掉主人的记忆)。

  • chat_memory_setup:查看 / 修改记忆设置。用户不用懂配置,对鱼说「帮我把聊天记忆设置好,我的 QQ 是 ……」就行。修改一律弹出 DSH 的批准,而且只能在主人自己的对话里改,群里、别人的私聊里调用会被拒绝。

工具声明固定不变,不影响缓存;前三个不需要时可以在设置页关掉。

开放接口(给桥接或其他本机程序)#

只接受本机请求,请求头带 Authorization: Bearer <令牌>(令牌在设置页「桥接接口」里,也保存在数据目录的 api-token 文件中)。

POST /dsh-companion-memory/api/ingest  {"origin":"group:123456","speaker":"小红","text":"我下个月搬去杭州","sourceKey":"msg-889","time":1762500000000}
POST /dsh-companion-memory/api/search  {"origin":"private:10001","query":"搬家","limit":5}
POST /dsh-companion-memory/api/bind    {"sessionId":"<DSH 会话 ID>","origin":"group:123456"}
  • ingest:桥接把它看到的群消息直接存进来(例如鱼潜水时没进 DSH 的群聊)。同一个 sourceKey 重复提交只存一次。
  • search:按某个来源的可见范围检索,返回命中片段和一段可直接拼进提示词的 evidence。
  • bind:会话 ID 认不出来源时,告诉插件它属于哪里。

origin 取值:owner、group:<号>、private:<号>。

模型接口(可选)#

默认不联网、不下载模型,用 FTS5 关键词检索就能工作。可以接 Ollama 或 OpenAI 兼容的 embedding,以及通用 /rerank 或 DashScope 重排。远程地址必须 HTTPS,并且要在设置里明确允许发送;密钥只从环境变量读取。

安装#

普通 npm 包。用 DSH 插件管理器安装,并把 cordis.patch.yml 里的 bundle patch 应用到目标 profile(命令以你的 DSH 版本文档为准):

dsh plugin --profile desktop add <path-to-dsh-companion-memory>

装好后在设置页填上主人账号;用 qq-bridge 这类会话 ID 里带群号的桥接,默认规则就能认出来源,其他桥接按上面「来源怎么认」配置。

CLI#

只处理你明确指定的 JSONL,不扫描 .dsh 历史。export 输出的 JSONL 可以直接 import 回去:

companion-memory import --dir ./data --file history.jsonl --scope owner
companion-memory search --dir ./data --query "之前的计划" --scope group:123456
companion-memory export --dir ./data --scope owner > owner.jsonl
companion-memory stats  --dir ./data

删除记忆会删掉原文、索引并留下墓碑,防止被自动重新导入;它不会抹除 DSH 自己的会话日志、WAL 或备份。

测试#

npm test                                   # 单元测试 + 检索质量下限
npm run eval                               # 打印检索评测报告(只用关键词)
npm run eval -- --embed bge-m3             # 加本机 Ollama 向量
DEEPSEEK_API_KEY=… npm run eval -- --embed bge-m3 --rerank deepseek-flash   # 再加重排和检索摘要

许可证#

源码公开,仅限非商业用途,许可证为 PolyForm Noncommercial 1.0.0。

  • 可以:个人使用、学习研究、爱好项目,学校、公益组织、政府机构使用;可以修改,也可以分享修改后的版本,但要附上许可证和 Required Notice 那一行。
  • 不可以:用于商业用途,例如付费产品或服务、公司内部的商业业务、收费代部署。需要商用请先联系作者授权。

注意:非商用许可不属于 OSI 定义的「开源」许可证,所以这里称作「源码公开」。许可证以 LICENSE 文件的英文原文为准。