把带时间戳的情绪记录算成当前状态,并支持按原话检索上千条经历
29.9/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add reaxlll/emotional-memory该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · skill
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 1 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🟢 扫描:未发现敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
情感状态与记忆检索:把带时间戳的情绪注解算成可用的状态,把上千条经历组织成能翻的树。零依赖。
README
由 DeepSeek 最新模型翻译生成emotional-memory
给 LLM 智能体两样东西:一个能算的情绪状态,一个能翻的记忆库。
零依赖,Node 18+,MIT。
先说不是什么。
它不会让你的智能体更像人。
一个只会说「好的,我这就为您处理」的助手,接上这个库之后还是那么说话。没有人格的模型不会因为算了情绪就长出性格,也不会因为有了记忆库就学会心疼谁。
这个库管的是两件很具体的事:
一、情绪状态可以算。 给一堆带时间戳、带情绪标注的记录,算出「此刻是什么状态」,输出一行话,直接塞进系统提示词末尾。
二、记忆查得到。 上千条经历里问一句「防火墙规则生效了吗」,翻回当时那条原话,而不是返回十条语义相似度 0.83 的别的东西。
怎么说话、什么语气、要不要黏人——那是你的事,这个库不碰。
安装
npm install emotional-memory
零依赖。Node 18+。ESM。
展开完整文档(README 剩余 1 节)
emotional-memory
给 LLM 智能体两样东西:一个能算的情绪状态,一个能翻的记忆库。
零依赖,Node 18+,MIT。
先说不是什么。
它不会让你的智能体更像人。
一个只会说「好的,我这就为您处理」的助手,接上这个库之后还是那么说话。没有人格的模型不会因为算了情绪就长出性格,也不会因为有了记忆库就学会心疼谁。
这个库管的是两件很具体的事:
一、情绪状态可以算。 给一堆带时间戳、带情绪标注的记录,算出「此刻是什么状态」,输出一行话,直接塞进系统提示词末尾。
二、记忆查得到。 上千条经历里问一句「防火墙规则生效了吗」,翻回当时那条原话,而不是返回十条语义相似度 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.30 | 3 | 1% |
| 0.30 – 0.45 | 118 | 38% |
| 0.45 – 0.60 | 126 | 40% |
| 0.60 – 0.75 | 49 | 16% |
| > 0.75 | 16 | 5% |
人填这个格子手一滑就给 0.5。按三分位切,中间那档仍然占 70%——六个象限里只有「中唤醒」这一行在动,二维平面退化成一维。
问题二:平均会把一切抹平
30 天算术平均下来,一个 −0.8 的崩溃会被摊成 −0.03,于是状态行永远是「轻度平静」。
实测旧公式跑 30 天,76% 的时间落在同一格里,切换的那 106 次大部分是阈值边界抖动——那是噪音,不是情绪。
所以它做三件事
1. 唤醒不读原值,靠代偿撑开:动量、极值、半小时内连发,三样取最大
2. 基线不是平均,是 48 小时半衰的指数加权,加等效先验,免得没数据时退回 0
3. 阈值不拍脑袋,取数据自己的分位数(不足 20 条退回固定边界)
二、三个量
基线
$$V_T = \frac,\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 \cdot e^{-h/6h},\ -0.4,\ 0.4\right)$$
两个防坑:乘 $e^{-h/6h}$(17 小时前那次暴跌不该还被说成「现在坐不住」)、clamp ±0.4(单条极值不该让系统进入持续亢奋的假象)。
唤醒代偿
$$a^} = \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^}$ 三档)+ 来路(跳了几级)+ 余温(还热 / 正在凉 / 快没了),三者查表。
同一格的两种说法:
此刻: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 / 12 | 9 / 12 | 11 / 12 |
| 一次要看多少条 | 1650 | 1650 | 16 ~ 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预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
reaxlll/emotional-memory
把带时间戳的情绪记录算成当前状态,并支持按原话检索上千条经历
相关插件 · 技能 / 提示词
人设统管ZiChuanLan/agent-purge
一份人设,统一管理多个 coding agent。
★ 991已汉化DSH
73.9
人物画像蒸馏titanwings/distilly
Distilly — 把他们的思考方式提炼成可复用的技能,供任何智能体或机器人使用。原名 Colleague Skill(原同事 Skill)。
★ 25,659已汉化DSH
71.3
推理工作空间套件Tiger3807861189/J-Space-Cognition-Suite
J-Space 是面向复杂推理、大型仓库工程、子代理协作与授权安全分析的推理时工作空间和控制套件。
★ 2,957已汉化DSH
70.5
两阶段工具锚定预设xiaobright/dsh-anchored-standard
实验性 DeepSeek Harness agent preset…
★ 3,742已汉化DSH
70.1
智能体工作流编排nrslib/takt
不再日夜盯着 AI 编码智能体。
★ 1,402已汉化DSH
70
Agent 运行时手册Electricitysheep/dsh-handbook
DeepSeek Harness dsh 从 0 到 1 深度手册:安装/插件开发/性能调优/实测案例/同模型多…
★ 827已汉化DSH
68.7