大肥鱼 · dsh-fat-fish
一只住在你电脑里的拟人胖鲸鱼:一个身份、一套会遗忘的记忆、一份作息、三个能说话的地方。
她不是「把大模型接进 QQ」的机器人。她是 DeepSeek Harness 里的同一个她:你在对话界面里叫她、在 QQ 里找她、在 B 站看她说晚安,回到那个会话里,她仍然记得刚刚发生过什么。
MIT 开源 · 本地运行 · 无第三方运行时依赖 · 适配 DSH ≥ 0.2.1-rc.2。
你会看到什么
| 地方 | 形态 |
|---|---|
| 对话界面 | 打开 DSH Web 直接聊:和 QQ、B站 共用同一个会话、同一套记忆、同一个人格 |
| 设置页 | 设置 → 大肥鱼:身份 / 作息 / 心情 / 记忆 / QQ / B站 / 状态,7 个页签,改完即生效 |
| 通过 OneBot 11 接入:私聊和群聊(可设置只 @ 才回)、白名单、只回复「由 QQ 唤醒的那一轮」;能看她收到的图,也能发图片/表情包 | |
| B站 | 可选:读 @ 和回复(只读轮询),或发一条动态(默认 dry-run,不真的发) |
| 工具 | 模型可调用 fish_remember / fish_recall / fish_forget / fish_say / fish_sticker / fish_remind / fish_followup / fish_tasks / fish_quiet / fish_status |
| 提示词 | 每轮自动注入:现在几点、她困不困、心情如何、想起了哪几件旧事、这句话从哪来 |
她只有「一个意识」:同一个 session、同一套记忆、同一种语气。对话界面、QQ、B站只是她的三个嘴巴和耳朵,不是三个机器人。
版本:当前为 测试版 v0.2.2。之后每个补丁只递增第三位(v0.2.1、v0.2.2…),直到稳定版再另行说明。
已经做了什么(截至 v0.2.2)
这不是一个「接上大模型就能聊天」的壳。按能力分,当前版本已经包含:
身份与人格
- 结构化人设:名字、称呼、性别、年龄感、关系、性格、说话风格、背景、边界、固定记忆。
- 关系模式(朋友 / 桌宠 / 工作搭档 / 很重要的人)与亲密程度 1–5,决定称呼和亲近尺度。
- 先判断场合再决定说法:日常聊天短句、接情绪;工作/讲正事允许完整解释、分点、步骤、代码块、表格。
- 三条底线:不称自己是 AI、不编细节、边界优先于心情。心情只影响措辞,不影响准确性、完整度与判断。
记忆(v0.2.2 的 v2 引擎)
- 艾宾浩斯式衰减:
A = A0 · 2^(−days/H),H = 基础半衰期 · (1 + 2 × 重要度);重要度高的记得更久,永久事实(kind=fact且重要度 ≥ 0.9)不衰减。 - 可见范围隔离:
person:owner/person:qq:<号>/group:<群号>。桌面私事不会出现在群聊或其他人的会话里;只有明确绑定为主人的 QQ 才共享私人记忆。 - 纠正而不是坚持旧说法:说「我现在不喝咖啡了」会更新当前偏好,同时把旧说法留在
history里,不再反复强化旧版本。 - 每条记忆保存:关于谁(
subject)、来自哪条消息(source.kind/sessionId/messageId/snippet)、何时起效(validFrom)、是否确认(confirmed)。 - 设置页可编辑、固定、确认、撤销、彻底删除;删除是真删(连归档一起清),不是假装忘记。
- 召回缓存按
revision失效;写路径(纠正/编辑/删除/衰减)才推进 revision,读路径不再污染计数。
向量召回(混合检索)
- 本地
Ollama + qwen3-embedding:0.6b(1024 维),不依赖云端、不上传记忆。 - 关键词/字词召回 + 向量召回各取前 10,合并去重后最多 3–4 条注入;向量只负责找候选,是否可用仍由 scope、纠正历史、有效时间决定。
- 索引
embeddings.json增量同步:只算新增/改动/换模型的条目;minScore默认 0.37,由 42 条真实记忆 + 7 个验收用例校准。 - 真实验收:换说法能找到;姓名/日期精确信息排前;纠正后用新说法;无关问题返回 0 条;群聊问私事不出现私人记忆;删除后换说法也不返回。
心情与作息
- 六维心情:开心 / 踏实 / 甜 / 心疼 / 担心 / 吃醋;总值 0–100,向基线缓慢回稳。
- 精力曲线(13 个锚点插值)、睡眠窗、醒来问候。
- 硬约束先于概率:睡眠中、安静时段、「今天安静」、日上限、主动概率为 0 —— 这些先判,满足才掷骰子;心情/熟悉度不能突破用户设置的 0。
- 主动频率 三档(安静 / 正常 / 话多),只调参数,不覆盖上限。
约定兑现
- 「明天提醒我」「下次接着聊」不再只存成一句记忆,而是进入持久任务
tasks.json。 - 支持:自然语言时间(
30 分钟、明天 09:00、今晚 9 点半、2 小时后)、话题(fish_followup)、完成/取消/重试。 - 到点由 60 秒心跳检查,成功发到 QQ;失败按 1 → 2 → 4 → … 分钟退避重试,最多 8 次。
- 「想一个人待会儿」→
fish_quiet设安静时段,时段内不主动开口。
三个说话的地方
- 对话界面:DSH Web 内置,与 QQ/B站共用同一个会话、记忆和人格。
- QQ(OneBot 11 / NapCat 或 SnowLuma):私聊与群聊、白名单、只 @ 才回、图片入站识别、图片/表情包出站、连发合并(短句 15 秒 / 长句 25 秒窗)、重复上报去重、群聊合并保留每人身份、每条消息独立 ID/收件人/状态/重试。
- B站:读 @ 和回复(轮询,注入成功后才标记已读)、发动态(默认 dry-run)。
设置页与可靠性
- 7 个页签:身份 / 作息 / 心情 / 记忆 / QQ / B站 / 状态。
- 刷新不丢草稿;等待回复有状态;连接失败显示原因;发件箱可看/可重试;聊天记录可导出/清空。
- 发件箱
outbox.json:独立 id、收件人、状态、失败重试、sent以 OneBot 返回的message_id为准。
已移除
- 桌宠:v0.2.0 起已移除(
shell.overlay注册、/pet/*路由、桌面气泡、相关配置与文档全部清理)。砍掉原因是没有看到真实可用性,避免维护一个好看但不可靠的入口。
测试
当前仓库内有 10 套测试,覆盖到「一次完整对话」的场景:
smoke · regression(18)· privacy(28)· memory-v2(24)· embeddings(34)· persona-modes(18)· qq-debounce · client-load · reminders(38)· reliability(20)· semantic-eval(7/7,需要本地 Ollama)。
版本与回滚(前代代码包)
从测试版起采用 0.2.x:每次更新只递增第三位,旧版本代码包保留,方便随时回滚。
| 版本 | 状态 | 代码包 |
|---|---|---|
| v0.2.2 | 当前推荐 | dsh-fat-fish-v0.2.2.zip |
| v0.2.1 | 保留 | dsh-fat-fish-v0.2.1.zip |
| v0.2.0 | 保留(测试版起点) | dsh-fat-fish-v0.2.0.zip |
| v0.1.x | 历史 | tag v0.1.0 / v0.1.1 / v0.1.2 仍在仓库 |
回滚方式二选一:
- 源码回滚:
git checkout v0.2.1,再重新plugin_manager install_bundle target=<该目录>。 - 安装包回滚:下载对应版本的 zip,解压后按 README 的安装步骤装;插件数据在
$DSH_HOME/fat-fish/,换版本不会丢记忆(引擎带旧数据迁移)。
每个 GitHub Release 都附带该版本的 zip 与变更说明;本机 C:\deepseek\ 下也留有同名 zip 备份。
消息可靠性与界面状态(v0.2.0)
消息不是「发出去就不管」:
- 每条消息都有独立 id、收件人、状态和重试:
outbox.json持久化;失败会按 1 分钟 → 2 → 4 → … 退避重试,最多 6 次;sent以 OneBot 返回的message_id为准。 - QQ 重复上报去重:同一个
message_id在 10 分钟内只处理一次,重复上报会被忽略。 - 群聊合并保留身份:连发合并时,群聊里的每条都带「[QQ 群消息] 昵称(QQ 号)」前缀,不会把两个人的话混成一个人。
- B站先注入成功再标记已读:注入失败会留到下一轮重试,不会因为「先标记」而漏消息。
- 设置页的状态页:能看到发件箱每条消息的收件人/状态/重试按钮、QQ 最近发送与失败原因、本地聊天记录,并可一键导出或清空聊天记录。
- 刷新不丢:未保存的设置草稿会存在浏览器 localStorage,刷新后自动恢复;聊天记录和发件箱在刷新后从 Host 重新拉取。
- v0.2.1 修复:QQ 非主人私聊 / 群聊的「每 8 轮自动记忆整理」此前因
turnScope竞态永远不触发(turn 收尾先重置为 owner,异步整理才读);现在在 turn 结束前快照 scope 再传给整理任务。 - v0.2.2 修复:设置页「主人自己的 QQ 号」输入框的正则写成了
/[,,\\s]+/(\\s表示反斜杠或字母 s,不匹配空白),空格分隔的两个号会被合并成一个错误号码,导致主人身份与记忆 scope 识别错位;已改为/[,,\s]+/。 - v0.2.2 修复:
recall()在召回强化时推进revision,而recallLine的缓存 key 含revision,导致缓存永远 miss、每次提示词组装全量重跑召回,revision还会无限膨胀并持久化。现在召回路径不再推进revision,只有写路径(纠正/编辑/删除/衰减)才失效缓存。
安装
前置条件:
- Windows / macOS / Linux 均可(无原生模块)
- Node ≥ 22(DSH 自带运行时即可)
- DeepSeek Harness ≥ 0.2.1-rc.2(已在 0.2.1-rc.5 验证)
推荐在 DSH 会话里让 agent 安装:
plugin_manager install_bundle target=<本仓库绝对路径>
也可以走 DSH 的插件清单手动安装:把本目录加入 profile 的 package.json 依赖(file: 路径)并在 cordis.patch.yml 的 insert 里加一行
- insert:
- id: dsh-fat-fish
name: dsh-fat-fish
id 必须与客户端 bundle 的 window.__ModuleLoader__.load({ id }) 一致,否则控制台会报 “loaded without registering”。
安装完成后 刷新一次 DSH 网页(或重启桌面壳)。刷新后:
- 右下角出现橙色胖鱼(若与其他挂件重叠,拖到别处;位置会记住)。
- 设置 → 大肥鱼 出现新页签。
- 当前会话的回复开始带「大肥鱼」的人格与实时状态。
桌面壳(dsh-app://)与 Web GUI 共用同一份插件;
tapIndex类注入在桌面壳不生效,但本插件不依赖它。
卸载
plugin_manager action=remove_bundle target=dsh-fat-fish
配置与记忆保留在 $DSH_HOME/fat-fish/,想彻底清掉手动删除该目录即可。
三个通道怎么开
1. 对话界面(默认开启)
刷新页面后直接在 DSH Web 里聊。每句话都进同一个会话,也会被记忆系统看见;这里同样是「主人自己的场合」。
2. QQ(OneBot 11)
- 启动一个 OneBot 11 实现(NapCat / Lagrange / SnowLuma 等均可),开启 HTTP 服务端 和 HTTP 上报(post)。
- 设置 → 大肥鱼 → QQ:
- API 地址:OneBot 的 HTTP 地址,例如
http://127.0.0.1:3001 - Access Token:OneBot 配置里的 token(没有就留空)
- 上报地址:填
http://127.0.0.1:4399/qq/event。4399 是插件自己起的本地监听端口(只绑 127.0.0.1),与 DSH Web 端口无关、重启后也不会变;可在设置页「本地上报端口」修改。 - 路由令牌(可选):填一个随机串,之后上报 URL 需带
?token=...;公网/局域网暴露时建议填 - 群聊策略:
mention(只回 @ 她的)/all/off - 白名单:QQ 号列表,非空时只服务名单内的人
- API 地址:OneBot 的 HTTP 地址,例如
- 保存后点「测试连接」。她只回复
source.kind === 'qq'的轮次,不会把自己在桌面上的自言自语发到群里。
插件在回环地址上自带一个很小的 OneBot 上报端口(默认 4399),专门绕开宿主 Web 的登录鉴权:OneBot 进程不需要浏览器 cookie。只监听到本机,未设令牌时也接受任何本机进程投递;如果本机有不可信进程,建议设置「事件上报令牌」。
图片与表情包
- 看图:对方在 QQ 发图时,插件把图下载成 DSH 的图片内容块交给她看(默认最多 4 张、单张 5MB;可在设置页关掉或改成一次只收一张省 token)。
- 发图:她回复里写
的 markdown 图片,会被抽出来当 QQ 图片发出去;assistant 消息里的图片附件也会一并发出。 - 表情包:内置 8 张原创表情(开心/委屈/困/加油/无语/抱抱/吃瓜/得意),模型可以调
fish_sticker发;文件在assets/stickers/,可以自己替换或加新的。
连发合并(不秒回)
对方连着发好几条时,插件不会一句一句回:
- 短句(默认 < 20 字)先等 15 秒;长句先等 25 秒;图片和表情包算短句(配了长文字也按短等待)。
- 等待期间又来新消息,就从新消息的时间重新计时。
- 到点后把这段时间的所有消息合并成一次交给她,只回一条(多条的意图一起处理)。
- 最多攒 3 分钟就强制合并回复,不会一直等下去。
- 实例之间互不影响:私聊和每个群各自计时;图片也会一起合并。
- 设置页 QQ 里可以改这两个等待时间、字符阈值,或整个关掉(
qq.debounce.*)。
3. B站(可选)
设置 → 大肥鱼 → B站:
- Cookie:登录 bilibili.com 后从浏览器复制整段 Cookie(至少含
SESSDATA与bili_jct)。只存在本机$DSH_HOME/fat-fish/config.json,不会上传。 - dry-run:默认开启。开启时「发动态」只写日志、不真的发;确认要发再关。
- 读 @ / 回复:开启后每 N 分钟轮询一次消息中心,新的 @ 和回复会被当成「B站来的话」注入会话。
- 发动态:需要手动点「发一条动态」或让模型调
fish_say(channel=bili);默认关闭自动发。
B站接口是第三方服务,可能变更或风控。插件对失败只记日志、不重试轰炸;请遵守平台规则,仅作个人学习使用。
她是怎么「活着」的
一个身份
人格是 systemPrompt.section 里的一段稳定文本(默认 order 60):名字、来处、和主人的关系、说话习惯。稳定 → 吃满前缀缓存,不烧钱。
每轮的「状态」走 systemPrompt.context(order 60):时间、作息阶段、精力、心情、召回的旧记忆、这句话从哪个通道来。只在这里放会变的东西。
人格适应任务和关系(v0.5.0)
基础人设不再只长一张脸:它先判断这句话是聊天还是正事,再决定说法。
- 聊天(QQ / B站):短、接情绪、一个念头一条,按取样语体来。
- 正事(对话界面 / 工作请求):允许完整解释、分点、步骤、代码块、表格;人格只留在判断和语气里,不用为了「像人」把话说残。
- 关系模式
persona.relationMode:friend朋友、pet桌宠、workmate工作搭档、partner重要的人,或留空用persona.relation的自由描述。 - 亲密程度
persona.intimacy:1(客气)~ 5(很亲),只影响称呼、撒娇和主动亲近的尺度,不会盖过边界。 - 主动频率
schedule.proactiveFrequency:quiet更克制 /normal默认 /chatty话多一点;「每天 0 次」仍是硬零,不会被 chatty 抬起来。 - 独处优先:主人说「想一个人待会儿」「今天安静」时,她用
fish_quiet设安静时段;时段内不主动开口,除非主人先说话。 - 心情不背锅:心情只影响措辞、动作和表情,不影响事实准确性、完整度和判断;主人设的安静、上限、关系分寸优先于心情和性格。
一套会遗忘的记忆
每条记忆有鲜活度 A = A₀ · 2^(−天数 / H),半衰期 H = 基础半衰期 × (1 + 2 × 重要度)(默认基础 30 天)。
召回时会按 相关度 × 0.5 + 鲜活度 × 0.25 + 重要度 × 0.18 + |情绪| × 0.07 排序,被想起的记忆会变牢(A₀ × 1.08 + 0.02)。
掉到阈值以下(默认 0.05)不是删除,而是沉进 archive/memory.jsonl 归档——她「想不起来了」,但历史还在。
stable 记忆(长期事实,重要度 ≥ 0.9 的 fact)不衰减,比如「主人养了一只叫年糕的猫」。
记忆要能解释、能改、能真删(v0.2.0)
长期陪伴最破坏信任的,不是忘,而是用户已经纠正过,角色还坚持旧说法。所以每条重要记忆都保留四件事:
- 关于谁:
scope/subject(主人 / 某个 QQ 联系人 / 某个群) - 来自哪条消息:
source {kind, sessionId, messageId, snippet, at} - 何时有效:
validFrom/updatedAt(纠正后从新时间开始有效) - 是否确认:
confirmed(你明说的=已确认;自动整理=未确认)
当你说「我现在不喝咖啡了」,旧说法会进 history(标注 corrected),正文换成新说法——仍是同一条记忆,不会两条互相打架;之后可以撤销回上一版。其他操作:
- 编辑:改正文会进
history(标记 edited) - 固定:pinned 记忆不衰减、不被淘汰
- 确认:把自动整理出的「未确认」记忆升为已确认
- 撤销:恢复 history 里的上一版
- 彻底删除:从活记忆和
archive/memory.jsonl副本里一起抹掉(真删,不是换个文件夹继续留着)
召回缓存带 revision:任何写操作(纠正/编辑/删除/衰减)都会让缓存失效,不会再从缓存里召回已经删掉的内容。设置页 → 记忆里能看到每条记忆的来源、关于谁、时间、改过几次,并直接做上面这些操作。
以上都完成之后,再接向量召回才有意义:向量只能找到「相似」,不能判断「哪条事实已经过时」;纠正与有效性仍然是事实层的事。
她还会每 8 轮让模型做一次记忆整理(最多写 3 条),也会用启发式规则从对话里捞「值得记住的事」;两者都去重、都有限流。
语义召回(v0.4.0)
词语重合只能找到「同一句话」,找不到「同一个意思」。所以 fish_recall 现在走关键词 + 本地向量混合召回:
- 向量模型跑在你自己的电脑上(默认 Ollama +
qwen3-embedding:0.6b),文本不出本机;索引写在$DSH_HOME/fat-fish/embeddings.json,每条只存:记忆 ID、身份 scope、正文指纹、模型版本、向量。 - 不会每轮重算全部记忆:只给当前问题算一个向量,索引按「记忆新增 / 正文改动 / 换模型 / 删除」增量同步。400 条活跃记忆逐条比相似度,够用,不需要向量数据库。
- 混合规则:字词搜索负责姓名、日期、订单号这类精确内容;两路各取前 10 个候选,合并去重后最多给模型 3~4 条;两边都不过线就一条都不返回,不硬塞。
- 向量只负责「意思接近」,不负责「事实正确」:「喜欢咖啡」和「不喜欢咖啡」可能很近,所以最后能不能用仍由身份 scope、纠正历史、有效时间决定。
- 第一阶段只接在
fish_recall工具里;每轮自动注入仍是同步拼提示词,等这套在真实聊天里验证过再接(第二阶段)。
配置:embeddings.enabled(默认开,Ollama 不在时自动退回关键词)、embeddings.model、embeddings.baseUrl、embeddings.minScore(默认 0.37,是用真实 42 条中文记忆校准出来的起点,可按你的记忆分布再调)、embeddings.candidateLimit。设置页 → 记忆 → 语义召回里可测试连接、手动同步索引。
验收:node test/semantic-eval.mjs 会造 40 条中文记忆并检查 7 个 case(换说法能找回、姓名/日期排前、纠正后用新说法、无关问题不硬塞、群聊里不出现私人记忆、删除后换说法也不返回)。需要先启动 Ollama 并 ollama pull qwen3-embedding:0.6b。
一份作息
睡眠窗(默认 01:30–09:00)+ 一条 13 个锚点的精力曲线(凌晨最低,上午和傍晚最高)。睡觉时精力封顶 22,主动开口关闭;醒来会先打个招呼。 主动开口不是定时器硬发:每 60 秒掷一次骰子,命中率 = 0.35 × 精力系数 × 时段系数,且必须满足「今天没说够、距上次开口 ≥ 45 分钟、距上次聊天 ≥ 20 分钟」。开口时会明确告诉她「这是你自己心里冒出来的念头,不是对方说的话」。
硬约束先于概率:作息关闭、睡眠中、你设了「安静一会儿/今天安静」、当日额度为 0 或已用完、主动概率为 0、间隔不够——任何一条命中都直接不开口。心情和熟悉度只在「允许开口」之后微调概率,不能再把日上限撑大,也抵消不了「今天安静」。
记忆的可见范围(隐私)
她只有一个人格,但不是一本公共账本。每条记忆都带一个「可见范围」:
| 范围 | 谁能看见 |
|---|---|
person:owner |
主人自己:对话界面、B站、以及绑定的主人 QQ 私聊(identity.ownerQQ)共享 |
person:qq:<QQ号> |
另一个联系人的私聊,只有这个人能看到,不进桌面、不进别的私聊 |
group:<群号> |
某个群的上下文,不进私聊,也不进别的群 |
- 召回、记忆整理、手动遗忘、工具调用都只在「当前这一轮的范围」里进行;同一句话出现在不同范围会是两条独立记忆,纠正也只纠正本范围那一条。
- 角色设定(名字、性格、关系、说话方式)是公共的,所有范围共享同一份人格与语气。
- 主动消息必须有指定收件人(
qq.proactiveRecipient,留空回落到identity.ownerQQ[0]),不会用「最近聊过的人」兜底,所以不会把私事误发到群里。 - 验收方式:在桌面说一件私事,再去群里问她,她不会把那件私事说出来。
一份心情
0–100 的总心情(基线 70)+ 六根情绪:开心 / 踏实 / 甜 / 心疼 / 担心 / 吃醋。
每轮结束让模型对这段对话做一次轻量判定(JSON:moodDelta / 六维增量 / reason / confidence),结果只吃 1/3,慢慢挪;不聊时向基线回落(半衰期 12 小时)。
只有「显著变化且置信度够、且今天注入次数没超」时才把心情写进提示词——避免变成每轮都念的背景音。
答应过的事会兑现(v0.3.0)
主动行为分两类,分开处理:
- 闲聊 / 随机打招呼:仍然是随机的,看她精力、心情、你们熟不熟。
- 约定 / 提醒:进入持久任务表
$DSH_HOME/fat-fish/tasks.json,到点由心跳直接投递,不掷骰子、不看心情;发送失败会指数退避重试(1 分钟、2 分钟、4 分钟……最多 8 次),之后标记failed仍可在设置页手动重试。重启 DSH 不丢。
她说「明天 9 点提醒我交电费」时,模型应该调用 fish_remind,而不是只存一句会淡忘的记忆;说「下次接着聊那个」用 fish_followup。设置页新增「约定」tab:看列表、加提醒、完成/取消/重试;作息页可以「安静 1 小时 / 今天安静 / 取消安静」。
验收方式:
- 对她说「30 分钟提醒我喝水」,看设置页「约定」里有没有一条待办;
- 把系统时间往后调(或等到点),QQ 会收到「提醒你一下:喝水」;
- 断网/关 QQ 让它发失败,列表里会出现重试次数,恢复后再点「重试」能补发。
配置
全部配置在 $DSH_HOME/fat-fish/config.json(默认 C:\Users\<你>\.dsh\fat-fish\config.json),也可以直接在设置页改。以设置页为准,手改文件后刷新即可。
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关 |
selfName / ownerName / selfAliases |
大肥鱼 / 主人 / [大肥鱼,肥鱼,小鱼] | 称呼 |
identity.ownerQQ |
[] |
哪些 QQ 号算「主人本人」:只有这些号的私聊和桌面共享私人记忆,其他私聊/群聊各自隔离 |
persona.extra |
"" |
追加人格设定(≤500 字) |
persona.relationMode |
"" |
关系模式:friend / pet / workmate / partner,留空用 persona.relation 自由描述 |
persona.intimacy |
3 |
亲密程度 1–5;只影响称呼和撒娇尺度,不覆盖边界 |
memory.enabled |
true |
记忆开关 |
memory.autoIngest |
true |
自动把对话沉淀为记忆 |
memory.autoExtractEveryTurns |
8 |
每几轮做一次 LLM 记忆整理(0=关) |
memory.baseHalfLifeDays |
30 |
基础半衰期(3–365 天) |
memory.emotionHalfLifeDays |
7 |
情绪半衰期(1–60 天) |
memory.archiveThreshold |
0.05 |
归档阈值(0.01–0.5) |
memory.recallTopK |
4 |
每轮最多召回几条 |
embeddings.enabled |
true |
语义召回(本地向量)开关;关掉或连不上时只用关键词/新鲜度 |
embeddings.baseUrl |
http://127.0.0.1:11434 |
本地 Ollama 地址 |
embeddings.model |
qwen3-embedding:0.6b |
向量模型名(需先 ollama pull) |
embeddings.minScore |
0.37 |
语义候选相似度门槛(默认值由真实中文样本校准;不同模型分数分布不同,按真实样例调) |
embeddings.candidateLimit |
10 |
单路最多取几个候选再合并 |
embeddings.batchSize |
16 |
索引同步时每批算几条 |
embeddings.maxPerPass |
64 |
每次同步最多补算几条(防一次卡太久) |
mood.enabled |
true |
心情判定开关 |
mood.baseline |
70 |
心情基线 |
mood.decayHalfHours |
12 |
回落半衰期 |
mood.sigMood / mood.sigDim |
3 / 5 |
显著变化阈值 |
mood.injectConfidence |
0.55 |
注入所需置信度 |
mood.injectPerDay |
6 |
每天最多注入几次心情 |
schedule.sleepStart / sleepEnd |
01:30 / 09:00 |
睡眠窗 |
schedule.pokeChance |
0.35 |
主动开口基础概率 |
schedule.pokeMinGapMinutes |
45 |
两次主动开口最小间隔 |
schedule.pokeMinIdleMinutes |
20 |
距上次聊天至少多久才主动开口 |
schedule.maxPokesPerDay |
6 |
每天最多主动开口几次 |
schedule.proactiveChannel |
auto |
主动开口发哪:auto(QQ 可用就发 QQ,发给指定收件人)/qq(只发 QQ) |
schedule.proactiveFrequency |
normal |
主动频率:quiet 更克制 / normal 默认 / chatty 多一点;maxPokesPerDay=0 仍是硬零 |
schedule.wakeGreeting |
true |
醒来是否打招呼 |
schedule.energyBias |
0 |
精力偏移(−40~40,夜猫子可调) |
desktop.sessionId |
"" |
对话界面的回复优先落哪个会话(空=最近活跃) |
desktop.logChats |
true |
把每轮对话写入本地 live-chat.jsonl(供人格取样复盘,可关) |
qq.enabled |
false |
QQ 通道 |
qq.apiBase |
http://127.0.0.1:3001 |
OneBot HTTP API |
qq.accessToken |
"" |
OneBot token |
qq.routeToken |
"" |
上报路由令牌(可选) |
qq.localPort |
4399 |
本地 OneBot 上报端口(只绑 127.0.0.1;0=关闭) |
qq.sessionId |
"" |
QQ 消息注入哪个会话(空=最近活跃) |
qq.allowedUsers |
[] |
白名单 QQ 号 |
qq.proactiveRecipient |
"" |
主动消息指定收件人(QQ 号);留空回落到 identity.ownerQQ[0],不会发到群或别人私聊 |
qq.groupMode |
mention |
mention / all / off |
qq.receiveImages |
true |
收 QQ 发来的图并交给她看 |
qq.maxImages |
4 |
一次最多看几张图(1~10) |
qq.debounce.enabled |
true |
对方连发多条时先等等、合并成一次再回(防秒回) |
qq.debounce.charThreshold |
20 |
短句/长句分界(字符数) |
qq.debounce.shortWaitMs |
15000 |
短句先等多久(毫秒,1000~120000) |
qq.debounce.longWaitMs |
25000 |
长句先等多久(毫秒,2000~300000) |
qq.debounce.maxWaitMs |
180000 |
最多攒多久就强制合并回复(10000~600000) |
bili.enabled |
false |
B站通道 |
bili.cookie |
"" |
B站 Cookie(含 SESSDATA + bili_jct) |
bili.dryRun |
true |
只演练不真发 |
bili.readEnabled |
true |
读 @ 和回复 |
bili.postEnabled |
false |
允许自动发动态 |
bili.pollMinutes |
5 |
轮询间隔 |
HTTP API(给设置页/你自己的脚本用)
全部挂在 DSH Web 服务下,与 GUI 共用登录态(同源浏览器 fetch 自动带 cookie)。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /dsh-fat-fish/state |
当前状态:作息、精力、心情、记忆计数、最近的对话 |
| GET/POST | /dsh-fat-fish/config |
读/合并写配置({"reset":true} 恢复默认;密钥用 __KEEP__ 占位表示不改) |
| POST | /dsh-fat-fish/heartbeat |
触发一次主动开口 |
| GET/POST | /dsh-fat-fish/memories |
列表(?history=1 带历史)/ {forget} 真删 / {remember} / {update} / {pin}/{unpin} / {confirm} / {undo} / {delete} / {decay},均可带 scope |
| GET/POST | /dsh-fat-fish/tasks |
约定列表 / create(text+when)/ done / cancel / retry / update |
| POST | /dsh-fat-fish/quiet |
安静模式:{"mode":"minutes","minutes":60} / {"mode":"today"} / {"mode":"off"} |
| GET/POST | /dsh-fat-fish/embeddings |
语义索引状态 / {action:'sync'|'test'|'reset'|'search'}(search 需 query、scope、limit) |
| GET | /dsh-fat-fish/mood |
心情详情与历史 |
| POST | /dsh-fat-fish/qq/event |
OneBot 11 上报入口 |
| GET | /dsh-fat-fish/qq/status |
QQ 通道状态 |
| POST | /dsh-fat-fish/bili/test | /bili/post | /bili/poll |
B站测试 / 发动态 / 手动轮询 |
| GET | /dsh-fat-fish/logs |
最近 300 条日志 |
| GET | /dsh-fat-fish/live-chat?limit=40 |
最近 N 轮对话样本(JSONL 的尾部) |
| GET | /dsh-fat-fish/ping |
探活 |
除 /ping 外都会做宿主 connection.requestRejection 校验;浏览器之外(OneBot 进程)请改用本地端口 http://127.0.0.1:4399/qq/event,它只绑回环、支持 ?token=(对应 qq.routeToken)或 X-Fat-Fish-Token 头,并额外提供 GET /qq/status、GET /tasks、GET /logs、GET /ping、GET /embeddings、POST /embeddings/sync、POST /embeddings/test、POST /embeddings/search。
数据与隐私
- 根目录:
$DSH_HOME/fat-fish/config.json配置(含 QQ token、B站 cookie,明文,仅本机)state.json作息/心情/当日计数/安静截止时间tasks.json约定与提醒任务(到点投递、失败重试状态)embeddings.json向量索引(记忆 ID、scope、正文指纹、模型版本、向量;删掉会自动重建)live-chat.jsonl最近对话样本(给语体复盘用,可删)memory/<session>.json每会话的活跃记忆archive/memory.jsonl已淡忘记忆(append-only)live-chat.jsonl每轮对话样本({at,session,source,chat,user,assistant,mood},仅本地,设置页可关)logs/plugin.log环形日志
- 不联网,除非你开启 QQ/B站。B站 cookie 只用于你主动触发的请求。
- 记住的是「一句话摘要」,不是完整聊天记录;
live-chat.jsonl是给「人格取样」用的完整轮次记录,只在本机,删掉文件即清空。 - 记忆按可见范围隔离(主人 / 别的联系人 / 群聊),但所有范围共用同一个 DSH 会话与同一个人格;跨会话历史隔离(每个联系人一个独立会话)尚未做,属于后续版本。
风险与边界
- QQ/B站属于第三方平台,接口可能变更、可能风控;请遵守平台规则,仅作个人学习研究。
- 插件会让模型在「无人说话」时主动花 token(可关:
schedule.pokeChance=0或enabled=false)。 - 心情判定每轮一次额外的小模型调用;高频会话可在设置里关掉
mood.enabled。 - 记忆是「会淡忘」的,不是数据库;需要留档的事实请用
fish_remember且 importance 调高。
开发
lib/
index.js Host 半侧:注入/事件/工具/路由/心跳
persona.js 人格与状态文案
memory.js 艾宾浩斯记忆引擎
embeddings.js 本地向量索引与混合召回
privacy.js 记忆可见范围(主人 / 联系人 / 群聊)
mood.js 心情与六维情绪
schedule.js 作息/精力曲线/主动开口
store.js 原子 JSON 存储
channels/qq.js OneBot 11
channels/bili.js B站
client.js 手写客户端 bundle(设置页,无构建步骤)
test/smoke.mjs 34 项离线自测:node test/smoke.mjs
test/regression.mjs 14 项回归:node test/regression.mjs
test/privacy.mjs 28 项记忆可见范围:node test/privacy.mjs
test/memory-v2.mjs 24 项记忆 v2(纠正/历史/真删除/缓存/固定):node test/memory-v2.mjs
test/embeddings.mjs 34 项向量索引/混合召回(假模型,不联网):node test/embeddings.mjs
test/semantic-eval.mjs 真实 Ollama 的 40 条记忆验收:node test/semantic-eval.mjs
- 客户端是
window.__ModuleLoader__.load({id:'dsh-fat-fish', factory})手写 bundle,没有构建步骤;改完刷新页面即可。 - Host 半侧是纯 ESM + Node 内置模块,不依赖
@deepseek-ai/*包,装在任何 profile 都能跑。 - 安装后
cordis.patch.yml里只有insert: [{id: dsh-fat-fish, name: dsh-fat-fish}];配置走自己的 JSON,不占 DSH 的 settings schema。
致谢
- 灵感来自 SurfCore(一个意识、多个出口)与 Yuuyuko-uu 的 DSH 情感插件系列(会淡忘的记忆、大肥鱼心情、主动搭话、QQ 桥)。
- DeepSeek Harness 与「大肥鱼」这个称呼属于 DeepSeek 社区。
- 代码与 SVG 均为本仓库原创,MIT。
License
MIT © 2026 dsh-fat-fish contributors
No comments yet. Be the first to write one.