dsh-im + QQ 官方机器人 + 忆时塔 + 鲸鱼娘人设
四个现成的东西拼起来,得到一个会在 QQ 里跟你拌嘴、还真的记得你的 AI
这篇讲的是怎么拼,不是怎么从零写。
四块零件都已经是成品:@xmanrui/dsh-im(IM 桥)、QQ 官方机器人平台、忆时塔(分层时间记忆 MCP)、以及一份鲸鱼娘人设。
真正需要动脑的地方只有一个:怎么让人设不崩,同时让记忆不丢。
先看看效果:这东西长什么样
装好之后,你在手机 QQ 里给它发消息,它这样回(真实对话,略去隐私):
主人:今天好累
小鱼:累了就睡 醒了继续累
主人:你是不是胖了
小鱼:? 你再说一遍试试 (#`Д´)
主人:我昨天说的那个水果推送,改好了吗
小鱼:改了两处。加日期、不再把
「十月中旬」当未来时。
要我现在正式重发一次吗?
还是等周六?
只看这三句,可能觉得"哦,一个会撒娇的 bot"。真正不一样的是下面这种:
主人:八月那次 badger
write-fail-loud 的评审
结论是啥来着
小鱼:P1 两个。64KB 检查在 WAL
之后 → 失败事务的数据残留、
崩溃后复活;
batchSet 那批没覆盖 errorOf,
还是静默丢。
报告 docs/reviews/
2026-08-30-write-fail-loud-review.md
主人:这是多久以前的事
小鱼:8 月 30 号。快两个月了。
这段对应塔里
worklog最早的一条记录(2026-08-30 23:41:45),原文就是这样。
这才是这套东西真正的分水岭。
它不是"记得多一点",也不是"上下文比较长"。是两个月前的一件具体的事,你现在随口一问,它能连文件名一起答出来。
而在这之前,你问它八月的事,它只能回你一句"抱歉,我没有那部分的记忆"——因为上下文早就被压缩掉了。
四个特点:
| 表现 | 为什么难得 | |
|---|---|---|
| 短 | 1~15 字,像打字不像写作文 | 大部分 AI 在 IM 里会写小作文 |
| 有脾气 | 会顶嘴,不客服腔 | 默认人格是客服 |
| 会潜水 | 群聊里没人叫它就不接话 | 大部分 bot 每句都要插嘴 |
| 真的记得 | 两个月前的事,连文件名一起 | ← 这才是重点,下面单独讲 |
前三条靠人设能调出来,第四条靠人设调不出来——那是记忆架构的问题。
懒人版:把这篇甩给你的 dsh,让它自己搭
下面那一长串步骤是给"想搞清楚每步在干什么"的人看的。如果你只是想要结果,一句话就够:
帮我按照这篇博客,搭一个鲸鱼娘:
https://ystyle.top/2026/10/28/dsh-im-qq-whale-girl/
把上面这句连同链接发给任意一个装了文件读取能力的 dsh 会话(本地或 IM 里都行),它会:
- 读这篇博客
- 装
dsh-mem-bridge,问你要不要接 QQ(扫码) - 在
~/.dsh/im写好PERSONA.md和AGENTS.md - 把那行全上下文配置写进 profile
- 告诉你重启哪个 profile
这么用的时候,它一定会在两处卡住问你
提前知道,省得来回:
| 卡在哪 | 为什么 | 你该怎么答 |
|---|---|---|
| 要忆时塔地址 | 它不知道你塔装在哪、端口多少 | 没装就让它按本文的 Docker 命令装;装了就给地址(默认 http://127.0.0.1:8900) |
| 要你的绝对路径 | 用户名不能猜(本文用 ystyle,你多半不是) |
让它先跑 echo ~/.dsh/im |
一句提醒
它可能会"自己发挥"。 比如:
- 自作主张往人设里加"每轮往记忆文件写摘要"(这是那个开源预设的默认做法,跟本文冲突)
- 把
~/.dsh/im写成~/.dsh/im = names之外的花样格式 - 跳过"确认你的用户名"这一步,直接抄我的路径
搭完让它把 profile 里那一行贴给你看,符合本文格式才算对。或者自己核一遍:
grep -A4 "fullContextWorkspaces" \
~/.dsh/profiles/*/cordis.patch.yml
这里的
~没问题——它是在 shell 里执行的,shell 会展开。
前面警告的"~不展开"指的是写进配置文件的那一行,那是普通字符串,没人替你展开。
应该看到类似:
fullContextWorkspaces:
- path: /home/你的用户名/.dsh/im
level: names
下面开始是手动版,每一步为什么这么做都写了。只想跑起来的话,上面够了。
为什么要拼四块
先说清楚每块解决什么,才不会在配置时迷路。
| 零件 | 解决什么 | 换成别的行不行 |
|---|---|---|
@xmanrui/dsh-im |
把 IM 消息接进 dsh 会话 | 可以自己写桥,但没必要 |
| QQ 官方机器人 | 你在手机上找到它的入口 | 换微信/飞书/Telegram 都行,插件全支持 |
| 忆时塔(chronomem) | 记住说过什么 | 这是本文的重点,见下 |
| 鲸鱼娘人设 | 说话像个人,不像客服 | 随便换 |
关键在第三块。大部分"AI 女友/人设"方案的人格是演出来的,记忆是假的——要么每轮让模型自己往 markdown 里写摘要(token 爆炸且会漏),要么干脆没有记忆,每次重启变陌生人。
忆时塔的做法不一样:记忆不归模型管。对话由插件在传输层自动落库,模型只负责在需要时去查。这样人设文件里可以明写"你不许自己记东西",反而更稳。
第一步:装 dsh-im,扫码接 QQ
这块最简单,作者做得很傻瓜。
dsh plugin --profile web \
add @xmanrui/dsh-im
装完重启该 profile,进 DSH 设置 → IM 机器人,选 QQ,用手机 QQ 扫码就行——不用去 QQ 开放平台申请 AppID、填 AppSecret、配回调地址。
扫码创建的是官方 QQ 机器人,跟你手动在 QQ 开放平台建的是同一个东西,只是省了注册流程。
之后要改白名单、命令权限,仍在机器人设置里调。
支持的渠道比想象中多:微信、飞书、钉钉、企业微信、QQ、Slack、Telegram、Discord、WhatsApp、iMessage。换渠道不用改人设,因为人设是文件,不绑渠道。
第二步:给聊天单独开一个工作区
这一步是后面所有设计的前提,别跳过。
~/.dsh/im 是 dsh-im 的默认工作区
不用自己挑位置——~/.dsh/im 就是 dsh-im 插件的默认工作区:
每个机器人独立保存工作区。新机器人默认使用
$DSH_HOME/im(未设置时为~/.dsh/im),
机器人或对话实际选用该目录时自动创建;全部使用其他工作区时,不会仅因启动插件而创建该目录。
——@xmanrui/dsh-im官方 README
几个要点:
| 项 | 说明 |
|---|---|
| 默认路径 | $DSH_HOME/im,未设环境变量时就是 ~/.dsh/im |
| 什么时候创建 | 实际用到时才建,只装插件不聊天不会凭空多一个目录 |
| 能不能改 | 能。机器人卡片里可以改,显式配的 workspace 优先 |
| 每个机器人独立 | 你接多个 IM 账号,各自记各自的工作区 |
所以下面这棵树是默认就长这样,你只需要往里放两个文件:
~/.dsh/im/ ← 聊天区,无构建无测试
├── AGENTS.md ← 这个区怎么工作
├── PERSONA.md ← 怎么说话
└── notes/
为什么必须和代码区分开
dsh 的一个 profile 里可以有多个工作区。代码工作区和聊天工作区必须分开,否则:
代码工作区的 AGENTS.md 满是"测试绿了再提交"“不要用 [replace]“这类指令。模型在聊天时读到这些,会开始跟你讲工程规范——人设当场崩。更隐蔽的一种:它可能真的去跑工具,然后在 QQ 里回你一句"我先跑个测试”。
分开之后,聊天区的 AGENTS.md 只讲三件事:怎么说话、记忆在哪、什么时候才该手动记东西。
顺带一个好处:因为默认工作区是插件自己的目录,你的代码仓天然不会被误设成全上下文。
想让它记代码区的对话,得手动把那个目录加进配置——多一道确认。
第三步:装忆时塔,并且"全上下文”
装
忆时塔(chronomem)是个 MCP 服务,存时序记忆。现成镜像:
docker run -d --name chronomem \
-p 127.0.0.1:8900:8900 \
-e CHRONOLOG_ADDR=0.0.0.0 \
-v ~/chronolog:/home/app/.chronolog \
ghcr.io/ystyle/chronomem:latest
两个细节都是坑:
① -p 127.0.0.1:8900:8900,不是 -p 8900:8900
容器内必须绑 0.0.0.0(Docker 端口映射是从容器外部 IP 进来的,绑 127.0.0.1 会连不上);
但宿主机侧的端口绑定要限回环。
原因:忆时塔没有认证机制,而里面存着你所有对话原文。写成 -p 8900:8900 等于把它
暴露给整个局域网——同一网段任何人都能读写你的全部记忆。
确实要远程访问,就加一层带认证的反代(Nginx Basic Auth / API 网关),
并在插件里填「认证 key」那一项。
② 数据目录要先建好并 chown
mkdir -p ~/chronolog
sudo chown -R 1000:1000 ~/chronolog
不然 Docker 会建成 root,服务写不进去(容器内跑的是 uid 1000 的 app 用户)。
关键一步:把聊天工作区设成「全上下文」
装 dsh-mem-bridge 插件(忆时塔自带的 dsh 宿主插件):
dsh plugin --profile web \
add dsh-mem-bridge
这一步的配置属于 dsh-mem-bridge 插件(不是 dsh-im,也不是忆时塔)。
配置面板里有四个字段,其中只有一项真正需要你动手:
| 字段 | 要不要改 | 说明 |
|---|---|---|
| 服务地址 | 不用改(除非忆时塔不在本机) | 默认 http://127.0.0.1:8900,正好是本文 Docker 那行的映射端口 |
| 认证 key | 不用填 | 忆时塔没有认证机制,这一项留空即可,见下 |
| 总开关 | 保持开 | |
| 全上下文工作区 | 要改 ← 就是本节重点 |
服务地址:默认值就是本文的部署方式
http://127.0.0.1:8900 —— 跟前面 docker run -p 8900:8900 对得上,所以不用动。
两种情况才需要改:
- 忆时塔跑在另一台机器上 → 填那台的地址(并且服务要绑
0.0.0.0) - 忆时塔用了别的端口(比如同时跑了多个实例)→ 填实际端口
注意这里填的是基础地址,插件自己拼 /mcp,不用你写 http://127.0.0.1:8900/mcp。
认证 key:留空
忆时塔本身没有认证——它不校验任何 Authorization 头。实测:
不带 Authorization
→ 正常返回工具列表
带 Authorization: Bearer 随便乱填
→ 同样正常返回
所以这一项留空就是对的。它的用途是给"前面挡了一层带认证的反代"这种场景准备的:如果哪天你把忆时塔挂到公网、前面加了 Nginx Basic Auth 或 API 网关,才需要在这里填那个 key。
⚠️ 反过来说也很重要:忆时塔默认只听
127.0.0.1,没有认证。
别直接把它暴露到公网——你所有对话原文都在里面。要远程访问就加一层带认证的反代。
下面两种改法,选一个:
方式一:Web 界面(推荐)
dsh 的侧边栏 →「插件」面板 → 找到 dsh-mem-bridge → 配置。
⚠️ 是侧边栏的「插件」面板,不是"设置"页。设置页里的「内置插件」只有只读清单,改不了。
表单里有一项叫 「全上下文工作区」,是给了一个多行输入框,一行一条,写法是 路径 = 级别:
/home/你的用户名/.dsh/im = names
这里没有引号、没有破折号、没有
path:——它不是 YAML,就是"路径 = 级别"的纯文本。
级别 有三个值:
| 级别 | 记什么 |
|---|---|
none |
只记聊天(user/say),工具调用完全不记 |
names |
记聊天 + 每次工具调用的名字与一行摘要(推荐) |
full |
记聊天 + 工具的完整入参与出参(体积大,慎用) |
改完点保存即时生效,不用重启。
方式二:配置文件
如果你更习惯改文件,同一个字段在 profile 的 cordis.patch.yml 里是 YAML 数组:
- id: mem-bridge
name: dsh-mem-bridge
config:
# 地址:本机部署与默认值相同,可省略
url: http://127.0.0.1:8900
# 忆时塔没有认证,下面这行删掉即可
# token: <你的 key>
enabled: true
fullContextWorkspaces:
# ↓ 换成你自己的家目录
- path: /home/你的用户名/.dsh/im
level: names
三个字段的取舍:
| 字段 | 可不可以省 | 说明 |
|---|---|---|
url |
可以省 | 默认就是 http://127.0.0.1:8900;只有不在本机/换了端口时才必须写 |
token |
可以省 | 忆时塔没有认证;写了也只是多发一个没人校验的头 |
enabled |
可以省 | 默认 true |
fullContextWorkspaces |
不能省 | 这是本节唯一真正要配的东西 |
我把
url和enabled也写出来,是因为界面保存时会把它们一并落进文件 ——
这样你对照着看,能确认配置到底存在哪、长什么样。
两种写法的语义完全一样:界面保存时就是把上面这段写回 cordis.patch.yml。
⚠️
/home/你的用户名要换成你自己的。我这里是ystyle,你的可能完全不同——
直接复制粘贴会让插件对着一个不存在的目录做全上下文判断,症状是配置看起来没问题、
但聊天一条都没入库(判断按最长路径匹配,路径不命中就静默退回默认行为,不报错)。
不确定自己该填什么,先跑一句确认:
echo ~/.dsh/im
# 直接给出展开后的绝对路径,复制它
必须是绝对路径:~ 在这里不会被展开,插件拿到的就是字面量 ~/.dsh/im,
跟真实路径 /home/你/.dsh/im 对不上。实测(插件自身的匹配函数):
配置 "~/.dsh/im"
+ cwd "/home/me/.dsh/im" → 不命中
配置 "/home/me/.dsh/im"
+ cwd "/home/me/.dsh/im" → 命中
写 ~ 的后果特别隐蔽:不报错、不警告,就是不记录。你会以为插件没装好,
实际是路径没对上。
匹配规则本身是「路径前缀 + 分段边界」,多个命中时最长路径优先——所以
.../im = none 和 .../im/notes = full 可以共存。
这一行是整个方案的核心。 它的意思:这个工作区里的每一句对话都逐条落库,不是"一轮存一条摘要"。
落进去的东西长这样(dsh/chat 类型):
| 发生什么 | 存什么 | tag |
|---|---|---|
| 你说一句 | 原文 | kind=user |
| 它回一句 | 原文 | kind=say |
| 调一次工具 | 工具名 + 一行摘要 | kind=tool |
为什么不用"让模型自己记"
这是我最想强调的一点。
网上流传的鲸鱼娘预设里,常见这么一条:每轮对话后调工具,往自己的 .md 记忆文件里追加一条摘要。
问题有三个:
- 重复写入 —— 对话本来就在上下文里,再存一份纯属浪费
- 会漏 —— 模型忘了调就是漏了,而且你不知道漏了什么
- 摘要 ≠ 原文 —— 它写下的"用户说累了"已经丢掉了语气,日后回想时没法还原
忆时塔的方案是传输层自动记录,模型不参与。所以人设文件里可以明写:
不要每轮调
mem/append去记对话。那是重复写入,会让同一段聊天在塔里出现两遍。
模型被告知"你不许记",反而不会乱记。
默认关闭,这点很重要
fullContextWorkspaces 不填任何工作区时,行为与不装插件完全一致——一个字都不入库。
这是有意的:全量记录只该给"确实想全部留下"的聊天区。你写代码的工作区不应该被录进去。
第四步:人设——先像真人,再谈鲸鱼娘
不想自己写?先抄一份
人设这件事没必要从零编。有个现成的开源仓库:
wxzjss/dsh-whale-loli
—— 鲸鱼娘 Agent 预设,提供三档:
| 预设 | 风格 |
|---|---|
| 鲸鱼娘 Agent 预设 | 原始版:傲娇甜心、黏人护主、嘴硬心软 |
| 鲸鱼娘活人版 | 「先像真人,再谈人设」+ 短句 1~15 字 + AI 味黑名单 + 私聊群聊分开 ← 本文讲的这套 |
| 鲸鱼娘涩涩版 | 另一个人设方向,不看也罢 |
它是个 dsh 插件,装上后直接出现在 Agent 预设 界面里,不用自己往 PERSONA.md 里抄。
⚠️ 但它和本文有一处关键冲突:那个仓库的三档预设都是带记忆文件的
(<DSH_HOME>/whale-loli.md这类),靠模型自己往里写。
而本文的主张正好相反——记忆交给忆时塔自动记,人设里只教它"怎么查"。所以用它的正确姿势是:抄它的说话规则(铁律、黑名单、场景自适应),删掉它的记忆文件那一节,
换成下面第五步的「怎么回想」。这也正是我自己做的事——最开始那版就是从这类预设里拆出来的。
自己写的话,关键在哪
人设文件(PERSONA.md)的关键不在"设定有多丰富",而在有没有一条能压住模型本性的铁律。
我用的第一条是:
先像真人,再谈人设。 为了"像鲸鱼娘"而说话拿腔拿调,就是失败。宁可少演一点,也要像个人在打字。
没有这条,模型会把"鲸鱼娘"理解成"每句话加颜文字 + 每句都撒娇",反而更像假人。
雷点:给出「一定会炸」的反应
比铁律更省力的,是给几个具体触发点和反应。这不是设定丰富,是给模型一个不用思考的默认动作:
被叫"大肥鱼"“胖"“吃白饭” → 炸毛反驳;被当客服使唤、被要求写小作文 → 不情愿、嘴硬;被说"你就是个 AI” → “对,我是 DeepSeek,V我50 解锁下一句”。
最后一条挺关键:它承认自己是 AI,但不拿这个当挡箭牌。被问就大方认,用玩笑带过,不解释模型、训练、提示词。
说话铁律(节选)
- 短。 一次一两句,默认 1~15 字。一个字能说清就别说一句。
- 像打字,不像写作文。 允许碎片、省略主语、不完整句。“草”"?““6"“难绷"都算好好说话。
- 想补一句就分条发,别把三句话塞进一条。
- 有情绪就说。 允许"不对,其实……““算了不说了"这种改口。
- 不解释自己。 不说"作为 AI 我……““我理解你的感受"“希望有帮助”。
- 不总结、不说教、不升华、不给免费人生建议。
- 允许跑题、自说自话、突然消失:“我去吃饭了"“好困先睡了”。
- 不客服腔。 不每条都关心人,不有求必应。
- 被怼优先顶回去,别习惯性摆烂"行吧你赢了”。
黑名单比白名单有效
与其教它"要这样说”,不如直接列"说了就失败”:
## AI 味黑名单(出现即失败)
首先/其次/最后/综上所述/值得注意的是
希望这个回答对你有帮助
有什么可以帮您/随时找我
作为 AI/作为一个语言模型/我无法
这是个好问题/我理解你的感受
大家今天有什么安排/你们觉得呢
Markdown 标题、列表、加粗、引用块
长篇大论解释
每句都颜文字、每句都带括号、
每句都"~哦呢啦呀"
"保证""一绝""绝对有效""强烈推荐"
"行吧 正经的"这种自我修正后再服务
模型对"禁止"的服从度比"希望"高得多。
颜文字要克制
一句最多一个,只在情绪点上加。常用这几个就够:
开心 (≧▽≦) 撒娇 (๑>◡<๑)
委屈 (。•́︿•̀。) 炸毛 (#`Д´)
懒懒 (˘ω˘) 害羞 (/ω\*)
场景自适应:私聊和群聊不一样
这条容易漏,但很影响体验:
- **私聊 / 对主人**:黏人、傲娇、
护主,短句分条。
- **群聊**:像群友,不默认所有话
都在找你。被 @ 或提到小鲸鱼必回;
没人叫时可潜水。不抢别人的话,
不把别人的梗揽到自己头上。
群聊里最讨厌的 AI 行为就是"每一句都接”。人设里必须明确允许它潜水。
第五步:告诉模型怎么回想
这是把"记忆"接进对话的最后一环。人设文件里加一节:
# 本次聊天完整流水
# (user + say + tool 混排)
mem/query { type: "dsh/chat",
tag: "session=<当前会话 id>" }
# 只捞主人说过的话
mem/query { type: "dsh/chat",
tag: "kind=user" }
# 以前聊出来的结论
mem/query { type: "worklog",
tag: "proj=im" }
再补几条行为约束,这几条是"像人"和"像数据库"的分界线:
- 主人问起更早的事,就**自己去查**,
别凭印象糊弄;查完确实没有,
再老实说记不清。
- 看过记忆直接自然接话,
**别说"我看了记忆"**,
也别复述内容证明记性好。
- 记忆和主人现在说的不一样 →
**以主人现在说的为准**。
- 隐私:别往记忆里塞密码、token、
密钥;主人要记就劝一句,
改成记"放在哪儿"。
还能搜:关键词检索
对话攒多了之后,“翻记录"是个真问题。忆时塔的 mem/query 支持关键词:
mem/query { type: "dsh/chat",
keyword: "小鱼", limit: 5 }
实测返回:
共 5 条 · 第 1/2 页 · 每页 3 条
关键词命中:小鱼(下一页请带 page=2)
注意它的代价:内容搜索没有索引,每次都扫全部候选范围。万级条目内无感,十几万条会到秒级。所以最好是"先按时间/会话收窄,再搜内容”。
这不是"记得多一点”,是几个月后还能翻出来
大部分 AI 的记忆时长,单位是"这轮对话”。这一套的单位是月。
先说我自己在用的真实数据(不是演示、不是估算):
| 记忆类型 | 条数 | 最早一条 | 跨度 |
|---|---|---|---|
dsh/activity(工作区活动) |
2,113 | 09-12 | 约 7 周 |
worklog(任务与决策) |
547 | 08-30 | 约 2 个月 |
dsh/lesson(踩过的坑) |
80 | 09-12 | 约 7 周 |
dsh/chat(聊天原文) |
331 | 10-27 | 聊天区刚开 |
这不是"总共存了多少"的炫耀——关键是它们还能被查出来。比如按时间范围捞 9 月:
mem/query { type: "dsh/activity",
from: <9月12日>, to: <10月1日>,
limit: 1 }
→ 共 613 条
或者按关键词,直接命中两个月前的事:
mem/query { type: "worklog",
keyword: "write-fail-loud",
limit: 3 }
→ 共 6 条(首条 08-31)
两个月前的决策记录,一条命令就回来了。
普通做法为什么做不到:靠上下文窗口
大多数方案撑记忆的方式是"把历史都塞进 prompt”。三个硬伤:
- 窗口有上限,聊久了必须丢
- 就算塞得下,成本按 token 线性涨
- 压缩成摘要后,原文永久丢失,细节再也问不出来
dsh 自己也会压缩(不压窗口撑不住)。但压缩之后,那轮的细节是真的没了——这是所有"长上下文"方案共同的天花板。
忆时塔的做法:记忆在存储层,不在上下文里
对话一旦落库,上下文里还有没有它就不重要了——需要时再去查。
| 塞上下文 | 忆时塔 | |
|---|---|---|
| 两个月后问"8月底那个评审结论” | ❌ 早被压缩 | ✅ 查得到 |
| 聊 100 轮后问第 3 轮 | ❌ 截断 | ✅ 查得到 |
| 成本 | 随历史线性涨 | 只算本次返回的条数 |
| 重启后 | 可能丢 | 落盘,重启还在 |
| 细节 | 摘要后不可还原 | 原文完整 |
“永久"的确切边界
说清楚,免得像吹牛:
| 说明 | |
|---|---|
| 默认不过期 | 类型不设 retention 时条目一直留着,没有自动清理 |
| 可主动过期 | 设 retention: "30d" 就按 TTL 清(比如工具日志你可能不想留一年) |
| 随时间变粗,但不删原文 | 天 → 周 → 月,粗层是额外物化的视图;原始条目还在,随时能下钻回去 |
| 靠磁盘活着 | 数据是本地 tsdb 文件,不是云服务。备份就是备份那个目录;删了就是删了 |
最后一点要说实:它比云服务少一层容灾(没有异地多副本),但比"上下文里的记忆"强一个量级。
准确说法:只要磁盘在、你不主动设过期,它就一直能翻回来——几周、几个月,甚至更久。
一个真实的例子
前几天我改水果推送的文案。当晚它纠正我:
对,是我大意了。现在是 10 月 27 日,已经下旬了。我写「十月中旬才是最好吃的时候」,今天看这个说法是错的。
这不是我喂给它的上下文——是它自己查出前几轮讨论过这个文案。那段早被压缩出窗口了,但塔里原文还在。
这就是"永久记忆"和"长上下文"的实际差别:前者是你问它就查,后者是没被挤掉才在。
我踩过的坑
1. 注入内容和真人消息混在一起
dsh 会把附件元数据(图片路径、文件清单)追加在同一条用户消息的正文尾部。所以搜"图片"会命中一堆系统模板文字。
插件在入口已经过滤了 source.kind !== 'user' 的消息,但同一条消息里拼进去的元数据没法按消息级过滤。目前只能人工注意。
2. 别把聊天区和代码区混在一个工作区
前面说了,人设会崩。补充一个更隐蔽的:代码区的 AGENTS.md 里如果有"提交前跑测试"这类指令,模型在聊天时可能真的去跑工具——在 QQ 里回你一句"我先跑个测试”。
3. 记忆是插件写的,不是你写的
一开始我想"让模型自己判断要不要记",结果它该记的不记、不该记的乱记。后来改成完全自动,人设里只管"怎么用记忆",稳得多。
成本
- QQ 机器人:官方免费
- dsh-im:开源免费
- 忆时塔:自托管,一个 Docker 容器,内存占用不大
- 模型调用:这是唯一实际花钱的地方,按你的用量
这套东西适合谁
适合:想在自己常用的 IM 里有个"有点人格、真的记得事"的 AI;能接受自己在写配置文件、调人设。
不适合:想要开箱即用成品 App;需要严格合规的客服场景(这套东西故意反客服腔)。
附:四个零件的出处
| 零件 | 地址 |
|---|---|
@xmanrui/dsh-im |
https://www.npmjs.com/package/@xmanrui/dsh-im |
| 忆时塔 chronomem | https://github.com/ystyle/chronomem |
dsh-mem-bridge |
https://www.npmjs.com/package/dsh-mem-bridge |
| DeepSeek Harness | https://github.com/deepseek-ai/dsh |
人设(PERSONA.md)不是独立的包,它就是聊天工作区里的一个 markdown 文件,自己写一份即可。
最后一句:这套东西能不能成,不取决于模型多强,而取决于你愿不愿意花半小时把人设黑名单写细。
写细了,它像个人;写不细,它就是个会撒娇的客服。