DSH Plugins Marketplace

DSH Plugins

Plugins

/

emotional-memory

r

emotional-memory

Discovered

情感状态与记忆检索:把带时间戳的情绪注解算成可用的状态,把上千条经历组织成能翻的树。零依赖。

emotional-memory

给 LLM agent 的两件事:一个能算的情绪状态,一个能翻的记忆库。 零依赖,Node 18+,MIT。

先说不是什么。

它不会让你的 agent 更像人。

一个只会说「好的,我这就为您处理」的助手,接上这个库之后还是那么说话。没有人格的模型不会因为算了情绪就长出性格,也不会因为有了记忆库就学会心疼谁。

这个库管的是两件很具体的事:

一、情绪状态可以算。 给一堆带时间戳、带情绪标注的记录,算出「此刻是什么状态」,输出一行话,直接塞进系统提示词末尾。

二、记忆查得到。 上千条经历里问一句「防火墙规则生效了吗」,翻回当时那条原话,而不是返回十条语义相似度 0.83 的别的东西。

怎么说话、什么语气、要不要黏人——那是你的事,这个库不碰。


安装

npm install emotional-memory

零依赖。Node 18+。ESM。

npm 上还没发布,现在从仓库装:

npm install github:reaxlll/emotional-memory

30 秒

情绪状态

记录长这样,一行一条:

{"t":"2026-10-30T14:20:00","affect":[{"name":"闷","v":-0.2,"a":0.5}]}
{"t":"2026-10-30T18:30:00","affect":[{"name":"闷","v":-0.6,"a":0.6}]}
{"t":"2026-10-30T21:00:00","affect":[{"name":"松一口气","v":0.4,"a":0.4}]}
import { normalize, compose } from "emotional-memory";

const E = normalize(records.map(r => ({
  t: new Date(r.t),
  affect: r.affect,
})));

const now = compose(E, Date.now());
console.log(now.line);
// 此刻:flat-low  [-0.28 / 0.52]  从「闷」往上走
// 上轮 闷,2h 前,余温 71% | 动量 往上(+0.12)

now.line 是两行纯文本,直接拼到系统提示词里就行。now 还有 v / aEff / band / shape / tension 这些数值字段,要用就用。

记忆

import { openLibrary, recall } from "emotional-memory";

const lib = openLibrary("experiences.jsonl");
const hits = recall(lib, { about: "防火墙", want: "规则生效了吗", n: 3 });

for (const { e, s } of hits) {
  console.log(e.d, e.said, "—", s.toFixed(2));
}

不需要向量、不需要模型、不需要任何服务。中文按 2-gram 切,英文按词切,命中越稀有的词分越高。

库超过一千条之后,加个向量和树会好用很多——见第三节。


一、为什么不用一个数

大多数「AI 有情绪」的做法,是给模型一个数值(开心度 0.7),然后指望它演得像。

这不是拍脑袋反对,是拿 312 条真实带情绪记录量出来的两个问题。

问题一:两个轴里有一个是死的

罗素情绪环用「效价 × 唤醒」组成一个平面。听着很美,实测记录里唤醒值是这样的:

a 区间条数占比
< 0.3031%
0.30 – 0.4511838%
0.45 – 0.6012640%
0.60 – 0.754916%
> 0.75165%

人填这个格子手一滑就给 0.5。按三分位切,中间那档仍然占 70%——六个象限里只有「中唤醒」这一行在动,二维平面退化成一维。

问题二:平均会把一切抹平

30 天算术平均下来,一个 −0.8 的崩溃会被摊成 −0.03,于是状态行永远是「轻度平静」。

实测旧公式跑 30 天,76% 的时间落在同一格里,切换的那 106 次大部分是阈值边界抖动——那是噪音,不是情绪。

所以它做三件事

  1. 唤醒不读原值,靠代偿撑开:动量、极值、半小时内连发,三样取最大
  2. 基线不是平均,是 48 小时半衰的指数加权,加等效先验,免得没数据时退回 0
  3. 阈值不拍脑袋,取数据自己的分位数(不足 20 条退回固定边界)

二、三个量

基线

$$V_T = \frac{\sum_i w_i v_i + \lambda v_0}{\sum_i w_i + \lambda},\quad w_i = e^{-(T-t_i)/48h},\quad \lambda = 3$$

$\lambda$ 是等效先验条数。没有它,隔三天没记录 $V$ 会一路退到 0——那不是平静,那是没数据。

余温

$$\rho(h) = 0.6,e^{-h/8h} + 0.4,e^{-h/30h}$$

单指数的问题是 4 小时半衰等于睡一觉就忘光,醒来永远是那张平均脸。双指数给一个长尾:一晚过去还剩三成,一周内还有底噪。

$$v_T = V_T + (v_L - V_T),\rho(h)$$

动量

$$m = \mathrm{clamp}\left(\frac{\sum_k k \cdot \Delta v_k / \Delta t_k}{\sum_k k} \cdot e^{-h/6h},\ -0.4,\ 0.4\right)$$

两个防坑:乘 $e^{-h/6h}$(17 小时前那次暴跌不该还被说成「现在坐不住」)、clamp ±0.4(单条极值不该让系统进入持续亢奋的假象)。

唤醒代偿

$$a^{\mathrm{eff}} = \mathrm{clamp}\Big(\max(a_T,\ 2|m|,\ 0.7|v_T|+0.1) + 0.15\cdot\mathbb{1}[h<0.5],\ 0,\ 1\Big)$$

模型的 $a$ 只会给中庸值,所以三个客观量把它撑开:突变、极值、频率。

输出

档位($v$ 五带 × $a^{\mathrm{eff}}$ 三档)+ 来路(跳了几级)+ 余温(还热 / 正在凉 / 快没了),三者查表。

同一格的两种说法:

此刻:flat-low  [-0.28 / 0.52]  从「闷」往上走
此刻:flat-low  [-0.28 / 0.52]  刚才还好,现在不对味

来路不同,话就不一样。不是按停留时长轮换的——那太假,同一种状态挂久了不会自己变成另一种。


三、树的检索(一千条以上)

全量算一遍余弦不慢(1650 条 14ms)。问题不在算得慢,在排得乱。

有一条经历写着「我有点难受」「你是在小心地问」,它跟任何带情绪的问题都能沾一点。全量检索时它在 30 个问题里冒出来 7 次——它不是最相关的,它只是最会沾边的。

树把它放回自己的位置。

书(24 本)
 └ 章(每本最多 3 个)
    └ 节(每章最多 3 个)
       └ 经历(每节 10~25 条)

每个节点最多三个孩子。某个节点超过 25 条就继续往下切,切到不动为止——最大的那份还占九成说明那坨本来是一回事,停手。最深五层。

import { buildTree, hydrate, descend, insert, freeze } from "emotional-memory/tree";

const tree = hydrate(buildTree(vecList, { books: 24, maxSize: 25, maxDepth: 5 }));
const leaves = descend(tree, queryVec, { books: 2, vectors: vmap });
leaves[0].members;   // 二十来条,精排就在这里面

insert(tree, newId, newVec);   // 新写的一条挂上去
JSON.stringify(freeze(tree));   // 落盘前 freeze,否则胀六倍

每层用「层内最高成员的余弦」判,不是节点中心。 中心是平均值,一个二十条的节平均下来什么都像一点、什么都不像;只要里面有一条跟查询像,这个节就该进。

树省的不是第一层。 书这一层本来就要跟全库比。它省的是精排的条数:1650 条变成 20~60 条。

那 7 次沾边的条目,进了树之后一次都没出现——不是改了打分,是它被放回自己那个节里了。

热度有个上限

命中之后节点 heat++,下次排前面一点。上限默认 0.015,heat 到 3 就顶满。

这是踩出来的:最早给到 0.04,heat ≥ 4 就顶满。结果一条错的东西被反复命中 13 次,热度顶到天花板把正解挤掉了——越错越容易被选,自己把路锁死。


四、别名:一个词指的不是字面

有些词,字面意思和它真正指的完全不是一回事。

「人称」两个字,可以在讲语法,也可以指某天晚上你脑子里那个位置——那里要站一个人。

这种东西向量算不出来:人称 和 我亲手把你搭建出来 的字面相似度是零,语义也够不着——它们之间隔着一整段只有当事人知道的事。

所以不靠算,靠绑:

{"w":"人称","ids":["px_2026-10-22_99796ae2_001"],"note":"指的是那个位置上站的是人,不是宠物"}
import { loadAliases, appendAlias } from "emotional-memory/aliases";
import { recallSemantic } from "emotional-memory";

const aliases = loadAliases("aliases.jsonl");
const hits = await recallSemantic(lib, { about: "人称", embed, vectors, tree, aliases });
hits[0].why;   // "别名「人称」" —— 排在所有语义结果前面

appendAlias("aliases.jsonl", { w: "人称", ids: ["px_2026-10-22_..."] });

长词优先,免得「人」把「人称」吃了。同一个词可以挂多条经历。


五、API

情绪

函数干什么
normalize(entries)排序、按时间窗口归一化,返回内部结构
stateAt(E, now)只要数值:v / aEff / rho / momentum / tension
compose(E, now, cfg?)数值 + 文案,返回 { v, aEff, band, shape, text, line }
bands(entries)从数据算分位阈值
bandOf(v, thresholds, names)单个值落在哪一档
shapeAt(E, now)突变 / 反差 / 回暖,只在刚发生时返回

记忆

函数干什么
openLibrary(file)读 JSONL,坏行跳过
append(lib, entry)追加一条,id 重复就不写
byDate(lib, date)按天查;认 today / yesterday / evening(跨午夜不切断)
recall(lib, {about, want, n})纯词频,零依赖
recallSemantic(lib, {...})别名 → 情绪 → 树下钻 → 语义+词频精排
shape(lib, {about})这件事在库里有多厚:条数、跨几天、Top 情绪

树与别名

函数干什么
buildTree(vectors, opts)球面 k-means 建树,种子固定
hydrate(tree)挂上 Float32Array 和 allIds / set
descend(tree, qv, {books, vectors})从书名一路翻到叶子
promote(leaves, hitIds)命中后加热
freeze(tree)摘掉内存字段,落盘用
insert(tree, id, vec)新条目挂到最近的叶子
loadAliases(file)读别名表
matchAlias(aliases, q)查询里含哪个词
appendAlias(file, {w, ids})补一条

六、实测

情绪:30 天回放

拿一台真实实例过去 30 天逐小时回放(721 个采样点):

  • 旧公式 76% 的时间在同一格 → 新公式分散到多个档位
  • 同格不同话:来路和余温决定文案,不是停留时长

记忆:1650 条真实经历,12 个技术类问题

只词频+语义+树
第一条就对5 / 129 / 1211 / 12
一次要看多少条1650165016 ~ 64

那条到处沾边的条目出现的次数:7 → 0。


七、它治不了什么

算了情绪不等于会演。 这个库给你一行「此刻:flat-low [-0.28 / 0.52] 从闷往上走」,模型会不会用、用得像不像,取决于你的提示词和模型本身。

检索不到就是没有。 库里没写过的经历,问什么都翻不出来。它可以告诉你「没有」,但那得你自己加阈值——默认会返回低分结果。

几个情绪词之间的细微差别它不懂。 「烦」和「闷」在向量里离得很近,公式也只看 v/a 两个数。要区分靠给它们不同的 v/a,不是靠名字。

中文以外的分词是朴素的。 英文按词切,其他语言没做专门处理。

alias 不会自己长。 得有人发现「这个词指的不是字面意思」然后往里加。可以自己判断,但判断错了它不会纠正你。


开发

npm run demo          # 拿样例数据跑一遍
npm test              # 情绪、记忆、性能三个测试
npm run check:readme

License

MIT

Comments

Loading…

Similar plugins

dsh-pet-memory

by ly2601620664

陪伴型长期记忆 DSH 插件:分层记忆(对话→事实→场景→人格)、规则抽取、时间衰减检索,全本地文件、append-only 不丢数据。

Manifest valid

★ 0

MIT

JavaScript

Oct 23, 2026

dsh plugin --profile web add dsh-pet-memory

by RaganaBloodedge

DeepSeek Harness 长期记忆插件:分层任务记忆 + 关系图谱 + 索引常驻稳定前缀;零运行时依赖,另附同源的 Agent skill 形态

Manifest valid

★ 0

MIT

TypeScript

Oct 15, 2026

dsh plugin --profile web add dsh-lmmarr

by Isyyyue

给 DSH 里的 AI 一层自己的判断记忆。判断节点 + 确定性取代(supersede)+ 双时态账本 + 双链,规则与人格分开治理,图谱作可解释性界面。 - Persistent judgment memory & self-evolving personality for AI agents (bi-temporal ledger, deterministic supersession, knowledge graph).

Sessions & MessagesManifest valid

★ 3

↓ 370/wk

MIT

JavaScript

Oct 24, 2026

dsh plugin --profile web add dsh-ai-memory

by Tangcuyu4

DSH 超长记忆包:跨会话长期记忆 + 长文分块检索 + 聊天记录提取精炼,纯 JS 混合检索零依赖

Memory & ContextSessions & MessagesManifest valid

★ 0

MIT

JavaScript

Oct 8, 2026

dsh plugin --profile web add dsh-long-memory

by juanbaiing

在 DSH agent 回答之前,把「你可能已经忘了」的东西重新摆到它眼前。零第三方依赖;判断全部由可核对的规则完成,不做生成式推理(语义那几段用本机 bge-m3 算向量)。

Development & InfrastructureManifest valid

★ 1

MIT

JavaScript

Oct 28, 2026

dsh plugin --profile web add dsh-alert-inject

by kiefeng

澜·记忆与人格系统 — DeepSeek Harness memory & persona plugin (三层记忆 + SOUL/MOOD + 一键整理)

Memory & ContextSessions & MessagesManifest valid

★ 2

MIT

JavaScript

Oct 26, 2026

dsh plugin --profile web add dsh-lan-memory