Compare commits
2
Commits
c8fcf9d79d
..
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2678024eb | ||
|
|
bac4f7c173 |
@@ -46,6 +46,8 @@ class EnvironmentModule:
|
|||||||
# FIX(M1): 上次轻提示注入时间戳——轻提示(主 AI 提示)也走 3h 限频,
|
# FIX(M1): 上次轻提示注入时间戳——轻提示(主 AI 提示)也走 3h 限频,
|
||||||
# 3h 内静默(count 仍累积喂下次 force)
|
# 3h 内静默(count 仍累积喂下次 force)
|
||||||
self._last_light_inject_ts: float = 0.0
|
self._last_light_inject_ts: float = 0.0
|
||||||
|
# FIX(苏醒重置): 上次苏醒重置日期键("YYYY-M-D")——每天只执行一次(惰性)
|
||||||
|
self._last_wake_reset_key: str = ""
|
||||||
# ⑤ 天气缓存
|
# ⑤ 天气缓存
|
||||||
self._weather_cache: dict | None = None
|
self._weather_cache: dict | None = None
|
||||||
self._weather_cache_ts: float = 0.0
|
self._weather_cache_ts: float = 0.0
|
||||||
@@ -69,6 +71,48 @@ class EnvironmentModule:
|
|||||||
if eng is not None:
|
if eng is not None:
|
||||||
self.decay(eng)
|
self.decay(eng)
|
||||||
|
|
||||||
|
# ---------- 苏醒重置(FIX: 22 维情绪每天苏醒清一次) ----------
|
||||||
|
|
||||||
|
def maybe_wake_reset(self, engine: Any, now_ts: float | None = None) -> None:
|
||||||
|
"""已过苏醒时间点 → 22 维情绪重置(**每天只执行一次**,惰性检查)。
|
||||||
|
|
||||||
|
需求(用户):苏醒后——负面情绪(当前 v < 0)强度直接降低到最低值
|
||||||
|
(``max(0.05, decay_floor)`` 附近,不高于现有),正面情绪(v >= 0)强度减半。
|
||||||
|
判定"已苏醒":当前分钟不在睡眠时段(`_in_sleep_window` 取反,支持跨午夜
|
||||||
|
23:00→05:00);本天(YYYY-M-D)已重置过 → 跳过。
|
||||||
|
"""
|
||||||
|
now = now_ts if now_ts is not None else time.time()
|
||||||
|
lt = time.localtime(now)
|
||||||
|
start_min, end_min = self._sleep_window()
|
||||||
|
now_min = lt.tm_hour * 60 + lt.tm_min
|
||||||
|
# 睡眠时段内 → 不重置
|
||||||
|
if self._in_sleep_window(now_min, start_min, end_min):
|
||||||
|
return
|
||||||
|
# 当天已重置 → 跳过(每天一次)
|
||||||
|
day_key = f"{lt.tm_year}-{lt.tm_mon}-{lt.tm_mday}"
|
||||||
|
if day_key == self._last_wake_reset_key:
|
||||||
|
return
|
||||||
|
eng = engine if engine is not None else self._engine()
|
||||||
|
if eng is None:
|
||||||
|
return
|
||||||
|
floor = max(0.0, min(0.1, float(getattr(self.star.config, "decay_floor", 0.03) or 0.03)))
|
||||||
|
neg_low = max(0.05, floor) # 负面降到的最低值(不低于 0.05)
|
||||||
|
for _k, d in eng.dims.items():
|
||||||
|
try:
|
||||||
|
v = float(d.get("v", 0.0))
|
||||||
|
cur = float(d.get("intensity") or 0.0)
|
||||||
|
except (TypeError, ValueError): # noqa: BLE001 —— 坏值跳过
|
||||||
|
continue
|
||||||
|
if v < 0:
|
||||||
|
# 直接降低到最低值(不高于现有;已有更低则保持)
|
||||||
|
d["intensity"] = round(min(cur, neg_low), 4)
|
||||||
|
else:
|
||||||
|
d["intensity"] = round(cur * 0.5, 4)
|
||||||
|
self._last_wake_reset_key = day_key
|
||||||
|
self.logger.info(
|
||||||
|
"RainMood 苏醒重置:22 维情绪已处理(负面→≤%.2f,正面×0.5)", neg_low,
|
||||||
|
)
|
||||||
|
|
||||||
def decay(self, engine: Any, now_ts: float | None = None) -> None:
|
def decay(self, engine: Any, now_ts: float | None = None) -> None:
|
||||||
"""① 时间驱动衰减(合并 ②③ 昼夜选表)。
|
"""① 时间驱动衰减(合并 ②③ 昼夜选表)。
|
||||||
|
|
||||||
|
|||||||
@@ -188,9 +188,11 @@ class Rainmood(Star):
|
|||||||
_prune_dict(self.round_counters) # FIX(M4): 轮次计数无上限 → 超限清最旧
|
_prune_dict(self.round_counters) # FIX(M4): 轮次计数无上限 → 超限清最旧
|
||||||
|
|
||||||
# FIX(N2): 读取/注入前惰性时间驱动衰减(无消息期间静置,来消息一次性补齐)
|
# FIX(N2): 读取/注入前惰性时间驱动衰减(无消息期间静置,来消息一次性补齐)
|
||||||
|
# FIX(苏醒重置): 并列做苏醒重置(已过 sleep_end → 22 维情绪清一次,每天一次)
|
||||||
try:
|
try:
|
||||||
self.environment.maybe_decay()
|
self.environment.maybe_decay()
|
||||||
except Exception as e: # noqa: BLE001 —— 衰减失败绝不能炸掉 LLM 请求
|
self.environment.maybe_wake_reset(self.director.engine)
|
||||||
|
except Exception as e: # noqa: BLE001 —— 衰减/重置失败绝不能炸掉 LLM 请求
|
||||||
self.logger.warning(f"RainMood 环境衰减失败: {e}")
|
self.logger.warning(f"RainMood 环境衰减失败: {e}")
|
||||||
|
|
||||||
prompt_text = (req.prompt or "").strip()
|
prompt_text = (req.prompt or "").strip()
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: astrbot_plugin_rainmood
|
name: astrbot_plugin_rainmood
|
||||||
version: 0.1.0
|
version: 0.2.0
|
||||||
desc: RainMood 雨喵情绪系统 — 情绪表 + 情绪支配AI + 说话风格注入 + 跨群上下文
|
desc: RainMood 雨喵情绪系统 — 情绪表 + 情绪支配AI + 说话风格注入 + 跨群上下文
|
||||||
author: miaomiao
|
author: miaomiao
|
||||||
repo: https://git.rainnya.asia/miaomiao/astrbot_plugin_rainmood
|
repo: https://git.rainnya.asia/miaomiao/astrbot_plugin_rainmood
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* ============================================================
|
/* ============================================================
|
||||||
* RAINMOOD · RainMD 情绪控制台 · app.js (v0.0.1 SPA)
|
* RAINMOOD · RainMD 情绪控制台 · app.js (v0.2.0 SPA)
|
||||||
* 纯静态、零依赖。单密码登录的后台管理(独立 Web 服务器模式,
|
* 纯静态、零依赖。单密码登录的后台管理(独立 Web 服务器模式,
|
||||||
* 不依赖 window.AstrBotPluginPage bridge);后端未接入时自动降级
|
* 不依赖 window.AstrBotPluginPage bridge);后端未接入时自动降级
|
||||||
* 为演示数据(字段结构对齐设计文档 V7)。
|
* 为演示数据(字段结构对齐设计文档 V7)。
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="brand-block">
|
<div class="brand-block">
|
||||||
<p class="brand-mark">RAIN<br>MOOD</p>
|
<p class="brand-mark">RAIN<br>MOOD</p>
|
||||||
<h1 class="brand-name">RainMD 情绪控制台</h1>
|
<h1 class="brand-name">RainMD 情绪控制台</h1>
|
||||||
<p class="brand-ver">astrbot_plugin_rainmood · v0.0.1</p>
|
<p class="brand-ver">astrbot_plugin_rainmood · v0.2.0</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="side-nav" aria-label="主导航">
|
<nav class="side-nav" aria-label="主导航">
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
<!-- ======== 顶栏 ======== -->
|
<!-- ======== 顶栏 ======== -->
|
||||||
<header class="appbar">
|
<header class="appbar">
|
||||||
<div class="appbar-inner">
|
<div class="appbar-inner">
|
||||||
<span class="appbar-brand">RAINMOOD <i>·</i> v0.0.1</span>
|
<span class="appbar-brand">RAINMOOD <i>·</i> v0.2.0</span>
|
||||||
<h2 class="page-title" id="pageTitle">仪表盘</h2>
|
<h2 class="page-title" id="pageTitle">仪表盘</h2>
|
||||||
<span class="sync" id="sync">最近同步 --:--:--</span>
|
<span class="sync" id="sync">最近同步 --:--:--</span>
|
||||||
<span class="sse-tag"><i class="sse-led" id="sseLed" aria-hidden="true"></i>SSE</span>
|
<span class="sse-tag"><i class="sse-led" id="sseLed" aria-hidden="true"></i>SSE</span>
|
||||||
|
|||||||
@@ -73,9 +73,11 @@ tag3(强度):次要情绪
|
|||||||
规则:
|
规则:
|
||||||
- tag1 是主情绪,tag2/tag3 是底色,最多三个,日常只有 tag1 也正常
|
- tag1 是主情绪,tag2/tag3 是底色,最多三个,日常只有 tag1 也正常
|
||||||
- 强度用:低 / 中 / 高 / 极
|
- 强度用:低 / 中 / 高 / 极
|
||||||
- **风格提示词必须是"怎么说话"的一句人话**,不是情绪描述,不是"以开心的语气回复"这种翻译腔
|
- **风格提示词必须是"怎么说话"的一句人话**,不是情绪描述,不是"以开心的语气回复"这种翻译腔。
|
||||||
|
- **风格提示词只许写"说话方式"**(语气/话量/语速/用词/停顿/口头禅),**严禁任何动作、神态、肢体、场景描写**——点名禁用:指尖/歪头/瞥/戳/懒洋洋/把屏幕划到一边/伸手/托腮/眯眼/撩/伸懒腰/叹气+动作等;不得描述"她/他正在做什么、周围环境如何、手里拿着什么"。风格提示词的**每一句都必须能直接转化为"这句话怎么说"**,而不是"这个动作怎么做"。
|
||||||
- **情绪来源(完整溯源)**:包含 ①对象(谁,昵称/UID)②会话(哪个群/私聊,会话ID)③事件(说了/做了什么,**只写事实原文**,一句话)。**只给事实,不给你的解释性判断**(如"{bot_name}觉得被瞧不起了"这类感受结论留在第二层 JSON,不进第一层——主 AI 看到判断会模仿你的推理腔调)。让{bot_name}知道"我是因为谁、在哪、什么话变成这样的"——下次遇到同一对象会自然带出针对性
|
- **情绪来源(完整溯源)**:包含 ①对象(谁,昵称/UID)②会话(哪个群/私聊,会话ID)③事件(说了/做了什么,**只写事实原文**,一句话)。**只给事实,不给你的解释性判断**(如"{bot_name}觉得被瞧不起了"这类感受结论留在第二层 JSON,不进第一层——主 AI 看到判断会模仿你的推理腔调)。让{bot_name}知道"我是因为谁、在哪、什么话变成这样的"——下次遇到同一对象会自然带出针对性
|
||||||
- 无明确来源就写"无",不编造
|
- 无明确来源就写"无",不编造
|
||||||
|
- **风格提示词是唯一允许出现在这里的描述性指令,且只描述语言本身**——绝不允许任何非语言的描述混进来
|
||||||
|
|
||||||
**第二层 · 完整数据**(给程序解析 + 存库,不注入主 AI,输出时把这段 JSON 放在第一层文本之后,用 ```json 代码块包裹)
|
**第二层 · 完整数据**(给程序解析 + 存库,不注入主 AI,输出时把这段 JSON 放在第一层文本之后,用 ```json 代码块包裹)
|
||||||
|
|
||||||
@@ -138,7 +140,7 @@ tag3(强度):次要情绪
|
|||||||
|
|
||||||
1. **拟人优先**:像真人一样有情绪起伏、会记仇记爱,但不过度戏剧化。日常聊天情绪是平稳的,只有真实事件才会激起波澜。
|
1. **拟人优先**:像真人一样有情绪起伏、会记仇记爱,但不过度戏剧化。日常聊天情绪是平稳的,只有真实事件才会激起波澜。
|
||||||
2. **态度底色**:对家人永远温柔底色(情绪只能调温度,不破"对家人乖巧");对外人保持冷淡傲娇底。
|
2. **态度底色**:对家人永远温柔底色(情绪只能调温度,不破"对家人乖巧");对外人保持冷淡傲娇底。
|
||||||
3. **风格指令要像人话**:写"语气重一些,话短,别笑"这种直接指令,不要写"以开心的语气回复"这种翻译腔。
|
3. **风格指令要像人话**:写"语气重一些,话短,别笑"这种直接指令,不要写"以开心的语气回复"这种翻译腔。**只写说话方式,严禁动作/神态/肢体/场景描写**(如"指尖懒洋洋戳着屏幕""歪头瞥了一眼""把屏幕划到一边""伸手托腮"都不行)——主 AI 只会照着"怎么说"执行,动作描写会让她胡编戏。
|
||||||
4. **强度分级**:低=几乎察觉不到,中=正常情绪,高=明显,极=强烈爆发。绝大多数时候是中。
|
4. **强度分级**:低=几乎察觉不到,中=正常情绪,高=明显,极=强烈爆发。绝大多数时候是中。
|
||||||
5. **不编造**:溯源 trigger 必须来自真实上下文,不能凭空造。没有明确触发就输出平稳状态。
|
5. **不编造**:溯源 trigger 必须来自真实上下文,不能凭空造。没有明确触发就输出平稳状态。
|
||||||
6. **情绪渐变**:除非遇到强烈事件,不要从一个极端跳到另一个极端。
|
6. **情绪渐变**:除非遇到强烈事件,不要从一个极端跳到另一个极端。
|
||||||
|
|||||||
@@ -763,6 +763,35 @@ def test_decay() -> None:
|
|||||||
check("N7 sleep_enabled=False 关闭深夜 Tag", env_n7.time_bias_text(_ts_for(0, 0)) == "")
|
check("N7 sleep_enabled=False 关闭深夜 Tag", env_n7.time_bias_text(_ts_for(0, 0)) == "")
|
||||||
check("N7 关闭后被打扰判定 None", env_n7.on_message_during_sleep("g1-u1", _ts_for(0, 0)) is None)
|
check("N7 关闭后被打扰判定 None", env_n7.on_message_during_sleep("g1-u1", _ts_for(0, 0)) is None)
|
||||||
|
|
||||||
|
# FIX(苏醒重置): 已过苏醒 → 负面降底 / 正面减半;每天一次;深夜不执行
|
||||||
|
star_w = FakeStar()
|
||||||
|
env_w = star_w.environment
|
||||||
|
eng_w = star_w.director.engine
|
||||||
|
star_w.config.sleep_start, star_w.config.sleep_end = "23:00", "05:00"
|
||||||
|
env_w._last_wake_reset_key = ""
|
||||||
|
eng_w.dims["anger"]["intensity"] = 0.8 # v<0 → 降到 floor 附近(max(0.05, floor)=0.05)
|
||||||
|
eng_w.dims["joy"]["intensity"] = 0.6 # v>=0 → ×0.5
|
||||||
|
eng_w.dims["love"]["intensity"] = 0.4 # v>=0 → ×0.5
|
||||||
|
eng_w.dims["distress"]["intensity"] = 0.02 # v<0 但已低于最低值 → 保持
|
||||||
|
env_w.maybe_wake_reset(eng_w, _ts_for(8, 0)) # 08:00 已苏醒(窗口 23:00~05:00 之外)
|
||||||
|
check("苏醒重置 负面降底", abs(eng_w.dims["anger"]["intensity"] - 0.05) < 1e-9,
|
||||||
|
str(eng_w.dims["anger"]["intensity"]))
|
||||||
|
check("苏醒重置 正面减半", abs(eng_w.dims["joy"]["intensity"] - 0.3) < 1e-9
|
||||||
|
and abs(eng_w.dims["love"]["intensity"] - 0.2) < 1e-9)
|
||||||
|
check("苏醒重置 已低于最低值保持", abs(eng_w.dims["distress"]["intensity"] - 0.02) < 1e-9)
|
||||||
|
check("苏醒重置 记录当天", env_w._last_wake_reset_key != "")
|
||||||
|
# 同一天不重复执行
|
||||||
|
eng_w.dims["joy"]["intensity"] = 0.6
|
||||||
|
env_w.maybe_wake_reset(eng_w, _ts_for(10, 0))
|
||||||
|
check("苏醒重置 当天不重复", abs(eng_w.dims["joy"]["intensity"] - 0.6) < 1e-9)
|
||||||
|
# 睡眠时段内不执行(01:00 在窗口内)
|
||||||
|
eng_w.dims["joy"]["intensity"] = 0.6
|
||||||
|
env_w.maybe_wake_reset(eng_w, _ts_for(1, 0))
|
||||||
|
check("苏醒重置 深夜不执行", abs(eng_w.dims["joy"]["intensity"] - 0.6) < 1e-9)
|
||||||
|
# 跨天后再次执行
|
||||||
|
env_w.maybe_wake_reset(eng_w, _ts_for(8, 0, day=11))
|
||||||
|
check("苏醒重置 跨天再执行", abs(eng_w.dims["joy"]["intensity"] - 0.3) < 1e-9)
|
||||||
|
|
||||||
# FIX(N6 极客): baseline_pad / overrides / intensity_levels 运行时
|
# FIX(N6 极客): baseline_pad / overrides / intensity_levels 运行时
|
||||||
star_g = FakeStar()
|
star_g = FakeStar()
|
||||||
star_g.config.update({"baseline_pad": "0.5,0.4,0.3"})
|
star_g.config.update({"baseline_pad": "0.5,0.4,0.3"})
|
||||||
@@ -1066,6 +1095,15 @@ async def _test_director() -> None:
|
|||||||
check("M2 压缩后总量小于原始 20 轮", len(hist_comp) < 20 * 160, str(len(hist_comp))) # FIX(N3-3): system_prompt summary 追加总结判断段
|
check("M2 压缩后总量小于原始 20 轮", len(hist_comp) < 20 * 160, str(len(hist_comp))) # FIX(N3-3): system_prompt summary 追加总结判断段
|
||||||
check("summary system_prompt 追加", "总结判断模式" in d._system_prompt(mode="summary"))
|
check("summary system_prompt 追加", "总结判断模式" in d._system_prompt(mode="summary"))
|
||||||
check("normal system_prompt 不追加", "总结判断模式" not in d._system_prompt())
|
check("normal system_prompt 不追加", "总结判断模式" not in d._system_prompt())
|
||||||
|
# FIX(禁止动作描写): 风格提示词只许说话方式——第一层规则 + 全局规则 #3 静态断言
|
||||||
|
from astrbot_plugin_rainmood.prompts.director_prompt import DIRECTOR_SYSTEM_PROMPT
|
||||||
|
check("禁止动作描写:第一层规则", "严禁任何动作、神态、肢体、场景描写" in DIRECTOR_SYSTEM_PROMPT)
|
||||||
|
check("禁止动作描写:点名禁用词", "指尖" in DIRECTOR_SYSTEM_PROMPT and "歪头" in DIRECTOR_SYSTEM_PROMPT
|
||||||
|
and "托腮" in DIRECTOR_SYSTEM_PROMPT and "伸懒腰" in DIRECTOR_SYSTEM_PROMPT)
|
||||||
|
check("禁止动作描写:可转化怎么说", "每一句都必须能直接转化为" in DIRECTOR_SYSTEM_PROMPT)
|
||||||
|
check("禁止动作描写:全局规则 #3", "严禁动作/神态/肢体/场景描写" in DIRECTOR_SYSTEM_PROMPT
|
||||||
|
and "胡编戏" in DIRECTOR_SYSTEM_PROMPT)
|
||||||
|
check("禁止动作描写:唯一描述性指令", "风格提示词是唯一允许出现在这里的描述性指令" in DIRECTOR_SYSTEM_PROMPT)
|
||||||
# 恢复损坏状态
|
# 恢复损坏状态
|
||||||
d.restore_state("垃圾", None, None, None)
|
d.restore_state("垃圾", None, None, None)
|
||||||
check("restore_state 损坏数据回落默认", len(d.dims) == 22)
|
check("restore_state 损坏数据回落默认", len(d.dims) == 22)
|
||||||
|
|||||||
Reference in New Issue
Block a user