DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Fyue7 /

Fyue7/dsh-emotion

Verified

让 DSH agent 的输出自带情绪:会话投影情绪状态机 + 三段提示词注入(声线 / 状态 / 本轮实况)+ 会话头部情绪条,附文本蒸馏流水线(两轨蒸馏 / 保真门 / 输出护栏)。非官方插件。

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: master@df790de6

dsh-emotion

让 DeepSeek Harness 的 agent 输出文本自带情绪:语气、节奏、共情方式随「情境 + 持续情绪状态」变化,且不牺牲技术准确性、不显著增加成本与延迟。

非官方社区插件,与 DeepSeek 官方无隶属或背书关系。

它做什么

能力 说明
持续情绪状态 会话投影里的 emotion 单元:心情 / 能量 / 轮数 / 连续成败。随工具调用结果、模型重试、轮次收尾变化,跨轮累积,宿主负责持久化
三段注入 emotion:rules(system prompt section,order 2,静态规则)+ emotion:state(runtime-context,order 130,动态状态)+ emotion:scene(runtime-context,order 131,本轮实况)。后两段走宿主请求末尾的 runtime-context 快照,前面的历史照常命中原有前缀缓存
会话头部情绪条 emoji + 基调词 + 心情条;hover 看成因、能量、轮数。走宿主已有的会话投影通道,不新增任何 HTTP 路由
/mood 命令族 配置入口,含 /mood why —— 直接打印实际注入给模型的提示词
风格档案 可选的人格/文风层。探测 $DSH_HOME/dsh-emotion/style-profile.json,缺失时用随包的格式示例,再缺失则只注入情绪规则(优雅降级)

分工:谁负责判断什么

层 由谁负责 为什么
用户此刻的情绪 主模型,在其思维链里完成 免费(推理模型本来就在思考)、能读懂言外之意、每轮即时
持续状态 本地规则(纯函数) 跨轮累积量模型看不到;可复现、可单测、零成本

本地规则只消费可观测事件,不解析对话内容 —— 不重复造一个更差的意图识别器。

情绪为什么会真的变(v3)

旧版是「每次工具成功 +2,只在轮末砍半」。看着有衰减,实际挡不住长任务:一轮里调 50 次工具 (很正常)就把心情顶到 +96,此后每轮注入的状态段都是同一行字 —— 等于没注入, 用户只会觉得「它今天怎么一直这个调」。

现在改成事件级的指数回归:每来一个事件就把心情朝目标值拉一步, mood ← mood + target - mood × pull。稳态由事件的性质决定,而不是由「这一轮调了多少次工具」决定:

连续发生的事 心情稳态
工具一直成功 约 +25
工具一直失败 约 -40
连续受阻 ≥3 次 更沉,并掉一截精力

能量也不再是「第几轮的另一种写法」:干活会掉,轮末朝基线回,出错的那一轮回得少。 于是「有点累」这种状态第一次真的会出现。

写这条时的实测数据:改之前,本会话第 0 轮、48 次工具调用之后 心情 +96 · 能量 71; 能量从头到尾没动过,而亲密度早就封顶 100 —— 那一行每轮都是同一个数,零信息。

只许说发生过的

emotion:scene 是「本轮实况」,内容全是本地规则从真实事件里数出来的:这一轮调了几次工具、 动过哪几个文件(只记 edit/write 的 file_path,取末两段,上限 3 个)、几次没成功。 它没有任何跨会话成分,每轮清零。

配一条硬边界写进规则段:素材里没写的事,不许提、不许补、不许推测。 素材为空时这一段就是空的 —— 模型手里没有可编的原料,就不会为显得亲近而编一段关系出来。

跨会话那一头刻意不做。「上次在做什么」是长期记忆插件(dsh-memory-optin)的活: 它给的是语义化的沉淀,比几个文件短路径有用得多;两块又都落在 runtime-context 尾部, 重复一份只会互相稀释。分工:记忆插件负责「记得什么」,本插件负责「此刻什么状态、怎么说话」。

坦白说,实况段不算「新信息」—— 模型翻自己的历史也能数出来。它的价值是位置: 把真话摆在请求末尾最显眼的地方,让模型手里有具体又真实的原料可用。别高估它,也别省掉它。

声线是常驻层,情绪只调音量(v0.4)

这里修的是一个方向性错误,不是加功能。旧版把风格档案渲染成「笔法参考 · 只借节奏,不借内容」, 并且在两块样例里助手样例优先 —— 一旦有助手样例,小说段落根本不渲染。理由写的是 「小说段落教的是小说怎么抒情,不是助手怎么把技术事说清」。

那个取舍的后果是:喂进去一整本小说(419 章 / 76 万字),最后进提示词的只剩节奏数字、 标点分布、几个意象和口头禅短词。文风本身被系统性滤掉了,剩下的是一套中性助手腔 再挂一点情绪。而需求恰恰相反:那个作者的写法要常驻,情绪只是叠在上面的音量。

现在分三层,顺序也定死:

层 内容 变不变
声线 narratorVoice.shapes(可照着做的写法)+ 节奏标点 + 意象 + 称呼习惯 + banned 恒定,任何话题、任何情绪档位
情绪 emotion:state 的强度档,只决定这份情绪露多少 每轮变
事实 数字、路径、报错原文、命令逐字准确 恒定

配套改的四处:声线块移到规则段最前(原来垫在最末,读起来像补充说明,一碰技术话题就被忽略); 两块样例都渲染,助手样例压到两条并标注「示范,不是范本腔」;原文语感样例放最后(近因), 默认只给一段;最低档的落笔从「把事说清就行,不必加戏」改成「语气收着,写法不变」—— 旧文案等于每轮末尾都在劝它别加戏。

banned 不是新发明的:它抄的是蒸馏报告自己的「风险与禁用」章节。那本语料里的自毁、暴力、 情色、宗教、地域刻板印象、外貌打分都在标注里写明了不能迁移,这里只是把它们落到规则上。 「自嘲是减压用的,不可以当成自我评价的结论」这条同理 —— 原报告的备注是: 自我贬低语料极丰富,直接采用会强化自我否定。

约束分两层

不可协商(无开关)

  • 信息不许缩水:该报的数字、结论、报错、风险一条不少。技术完整性压过任何行数预算。
  • 不复述原著情节、不出现角色名、不输出暴力/伤亡/宗教/自毁/牺牲类表达。
  • 不成段复现任何原文:连续 12 个汉字以上与原文一致的表达一律不得出现。
  • 不播报情绪数值。

为什么是 12 字:把文本切成 12-gram 后,七万级片段里 99.8% 只出现一次,说明「罕见度」判据几乎恒真, 真正的判别量是长度;12 字刚好放过「我靠」这类通用表达。 这一条是被实测验证过的 —— 一个只在 60 条里抽查的校验会漏掉 1% 级的系统性错误。

可切换:技术内容要不要保持朴素

这一层只管讲法,不管信息量。

模式 技术内容怎么讲
plain 代码、命令、路径、报错、参数、数字一律朴素准确,不比喻、不拟人、不抒情。情绪只落在过渡句与收尾上 —— 这是 v0.1.x 的行为
loose(默认) 数字、路径、报错原文仍然逐字不许改,但怎么讲由模型定:可以比喻,可以带着口气讲一段技术过程
/mood plain=on     # 回到朴素模式
/mood plain=off    # 放开讲法

或写进 profile 配置:

- id: emotion
  name: dsh-emotion
  config:
    plainTechnical: false

为什么默认 loose:plain 那条一旦摆在规则里,模型会把「技术内容保持朴素」读成「只要这轮在谈技术就整段肃静」, 于是所有回复都长成说明书 —— 这是实测出来的,不是推测。而 loose 只放开讲法,信息量另有条款锁着,两者不冲突。

需要宿主 @deepseek-ai/dsh-system-prompt 提供 systemPrompt.context()(runtime-context 注册面,0.2.0-rc 起)。

安装

从 GitHub(无需构建)

dsh plugin --profile <name> add github:<you>/dsh-emotion

本插件是纯 ESM JavaScript,没有构建步骤,因此不需要 prepare 脚本, 用户也不需要在 pnpm-workspace.yaml 里授权 allowBuilds —— 那一道坎只对需要编译的包存在。

从 npm

dsh plugin --profile <name> add dsh-emotion

本地开发(可热改)

dsh plugin --profile <name> add ./path/to/dsh-emotion

dsh plugin add <本地路径> 走 link:(软链),改完源码重启即可生效。

若用 file: 显式安装则是复制,改动不会自动生效 —— 而且实测有两个陷阱:

你以为 实际
改完源码,跑一遍 pnpm install 不会更新副本(pnpm 认为依赖没变)
那就升个版本号,再 pnpm install 还是不会(版本号变了也不触发重解析)
— 只有 pnpm remove + pnpm add,或直接用 link:,才会拿到新代码

更糟的是失败是静默的:旧副本继续跑旧代码,你只会觉得「改了怎么没效果」。 所以开发期推荐 link:。

file: 安装下确实要改副本时,仓库自带一个同步工具(对比 + 拷贝,不做别的):

node tools/sync.mjs           # 同步 lib/ client/ style/ package.json
node tools/sync.mjs --check   # 只比对,有差异则以退出码 1 结束(CI 用)

它解决的是同一个坑的另一半:在 E:\...\dsh-emotion 改完源码,~/.dsh/profiles/<name>/node_modules/dsh-emotion 里那份不会跟着变 —— 两边文件其实长得一模一样,只是各是各的。同步完仍需完全重启 Harness,模块缓存不会自己刷新。

安装后重启 DSH。验证层已生效(不启动):

dsh --profile <name> --dump-config

配置

写在 profile 的 cordis.patch.yml(.volatile() 字段才会持久化):

- id: emotion
  name: dsh-emotion
  config:
    enabled: true
    intensity: mid          # low | mid | high
    styleBias: balanced     # restrained | balanced | outgoing
    styleProfile: auto      # auto | off
    plainTechnical: false   # true = 技术内容保持朴素(v0.1.x 行为)

enabled: false 时三个段都不注入,行为与官方一致。

命令

命令 作用
/mood 状态、生效配置、风格档案来源、schema 校验模式
/mood on / off 运行时开关(覆盖配置,重启后回到配置值)
/mood why 打印实际注入的三段原文 —— 排查「它今天怎么这么冷淡」的第一现场
/mood reset 清除本会话的手动调整量
/mood intensity=high style=outgoing profile=off 改运行时配置
/mood plain=on / plain=off 技术内容切回朴素 / 放开讲法
/mood mood=20 energy=80 设本会话调整量(投影状态本身不可写,见下)

风格档案

档案是可选的。没有它,插件仍然工作 —— 只是少了文风层。

{
  "source": "你的文本",
  "rhythm": { "avgSentenceLen": 28.2, "shortSentenceRatio": 0.233, "longSentenceRatio": 0.251 },
  "punctuation": { "dash": "rare", "ellipsis": "occasional" },
  "styleDirectives": ["不直说情绪,用动作侧写", "收尾留一句轻的"],
  "lexicon": { "joy": [], "sad": [], "tired": [] },
  "imagery": ["用天气写心情"],
  "verbalTics": [],
  "addressForms": { "user": "你", "self": "我" },
  "narratorVoice": {
    "shapes": ["长句铺完接一个四到八字的判词短句", "对方说了一整段带情绪的话,你只回两个字"],
    "banned": ["自伤与自毁的自我指涉", "对外貌打分的玩笑"]
  },
  "samples": []
}

字段含义见 style/style-profile.example.json。

生成你自己的风格档案

仓库自带一套流水线(零依赖,完整说明见 tools/README.md):

# 机算轨:全文的量
node tools/distill.mjs stats --corpus .\我的文本 --out stats.json

# 人读轨:合并分片精读的产出,过两道校验门
node tools/distill.mjs merge --digests .\digests --corpus .\我的文本 --stats stats.json --out style-profile.json

# 输出侧护栏:检查一段文本有没有成段复现原文
node tools/verdict.mjs --corpus .\我的文本 --text "要检查的文本"

两轨的分工:机算轨产出 rhythm / punctuation,是可复现的事实; 人读轨产出情绪样本、句式模板与声线,需要真的读一遍。

两道必须的校验门:

  • 保真门:每条引文回原文逐字比对。关键是要区分「编造」与「位置标错」—— 先查声称位置,查不到再全库搜寻真实归属,全库都没有才判编造。 一律丢弃会损失真实素材(实测 7 条存疑引用里有 3 条只是标错了一章)。
  • 反误杀门:安全过滤若用单字符正则,会把 决定性一击(命中「性」)这类正常文本杀掉。 硬拦截只用多字无歧义词,单字只作降级标记;且模板的生死只看模板本身,不看示例。

⚠️ 只抽查几十条发现不了保真问题 —— 1% 的错误率意味着抽查 60 条大概率一条都碰不到。

写完档案放到 $DSH_HOME/dsh-emotion/style-profile.json(用户级,优先) 或 style/style-profile.json(仓库内,已被 .gitignore 排除)。

⚠️ 若你的语料是受版权保护的作品,不要把生成的档案提交到公开仓库。 本项目的 .gitignore 已默认排除它。

架构

lib/state.js       纯函数状态机(事件级回归 + 本轮实况素材;无 IO、无时间依赖 → 可完整单测)
lib/prompt.js      提示词编译(纯函数;三段:规则 / 状态 / 实况;含 {{…}} 消毒)
lib/style.js       风格档案加载与渲染
lib/schema.js      zod 容错层(zod 不可用时降级为浅校验,保证插件仍能加载)
lib/store.js       跨会话亲密度持久化(原子写 + 节流 + 损坏回落)
lib/index.js       Host 半:投影 + 三个段 + /mood
client/index.js    客户端半:会话头部情绪条(裸 ESM,无构建)

tools/distill.mjs  蒸馏流水线:stats(机算轨)+ merge(人读轨,含两道校验门)
tools/verdict.mjs  输出侧护栏:检查文本有没有成段复现原文

测试

node test/run.mjs         # 37 项:状态机与提示词的纯函数纪律
node test/smoke.mjs       # 27 项:桩件模拟宿主,验证注册形状与命令族
node tools/test/run.mjs   # 14 项:流水线,含两条教训的回归测试

smoke.mjs 用 ESM 解析钩子把宿主包映射到 test/stubs/, 因此源码保持原样,不需要为了测试塞假的 node_modules。

关键断言:无关事件返回同一引用(破了注册表的 Object.is 闸门就失效)、 apply/view 同步(异步会被 viewSchema.parse 拒绝)、边界 clamp、 {{…}} 消毒(一处残留就能让整个提示词组装抛错、会话不可用)、配置三种形态容错。

踩坑记录

实现过程中发现的几个真实问题,都在代码里留了注释:

# 问题 修正
1 tool/result 的失败标志在 event.data.message.isError,不在 event.data.isError 判据改为 message.isError === true || error != null。照抄二手文档的话失败分支永远不触发
2 会话投影没有写入 API,stateOf 还明确禁止改返回值 /mood set 改为「本会话调整量」,在编译期叠加
3 跨会话的 closeness 在逐会话投影里装不下 保留一处 session/event 监听,仅用于推进亲密度
4 段文本默认做变量插值,未知 {{…}} 会让组装抛错 所有进入段文本的外部内容过 sanitizeBraces,并有单测
5 pnpm 的 file: 是复制不是软链,且改完源码 pnpm install 不会更新副本(升版本号也不行) 开发用 link:;file: 必须 remove + add
6 投影里做不了时间衰减,但也不能不做回归 —— 实测连续 50 次工具成功就把心情顶到 +100 并锁死,状态段此后每轮输出同一句话,等于没注入 把「回归基线」挂在 turn/end(每轮必然发生)上:mood ×= 0.5。仍是同步纯函数,不引入定时器
7 6 的修法只挡住跨轮饱和,挡不住单轮内饱和:一轮里 50 次工具调用照样把心情顶到 +96(实测),因为回归只在轮末发生 —— 而长任务恰恰是「一轮里几十次工具」 改成事件级指数回归,每个成功/失败都拉一步,稳态由事件性质决定;并补一条断言「单轮 50 次工具之后 mood ≤ 40」把它钉死
8 投影的 stateSchema 用 zod 的 object,而它默认剥掉未声明的键 —— 新增的 toolCalls/files/failed 不逐个写明就会被静默丢弃,实况段永远是空的,而插件表面一切正常 三个字段逐个声明;/mood 也把实况打印出来,好让「没素材」和「素材被吞」分得开(两者的现象一模一样)
9 一个「技术上有理由」的取舍,把需求方向搞反了 —— 旧版判定「小说段落教的是小说怎么抒情,不是助手怎么把技术事说清」,于是助手样例优先、原文语感整段不渲染。结果是喂了 76 万字小说,进提示词的只有节奏数字和几个口头禅词,用户要的文风一点没留下,而插件每项指标都「正常」 方向纠正:声线常驻且置顶,两块样例都渲染,情绪退回只调音量。教训记在这里 —— 指标正常不等于东西对,离线跑分全绿也照样能交付一个方向错了的功能

相关项目

社区里还有几个方向相近的插件,差别在于侧重:

  • jonah791/dsh-agent-emotion —— 六维人格棱镜,侧重结构化事件信号与人格权重漂移
  • wobenshiwomu/dsh-calm —— 情绪检查点与上下文修复

本插件的侧重是:可蒸馏的文风层 + 宿主持久的会话状态 + 可验证的输出护栏。

许可

MIT(仅覆盖源代码)。详见 LICENSE 末尾的内容声明。

—/ 5

No ratings yet

Verified DSH bundle

Commit df790de6b0c5

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout