feat: 完成 D 群組 — 情緒子系統

- RuleBasedEmotionTagger:關鍵字比對輸出情緒類型與強度(衝突/稱讚/輕度負向/一般負向/正向五級優先序)
- EmotionService:以資料庫 EmotionState 為單一事實來源,dominantState 取非平靜維度最高者,
  半衰期時間衰減(預設 2 小時,可覆寫),狀態轉移時清零其餘維度只設定目標維度
- emotion-state-machine:只實作 wiki 狀態圖與可愛度設計明訂的邊(平靜/愉悅/低落/警戒/害羞/彆扭)
- response-style:情緒 → 語氣/句長/主動性,供 F 群組上下文組裝器使用
- apps/api 新增 /emotion/* 端點作為引擎驗證介面
- scripts/smoke/D.mjs:涵蓋 D-1~D-4 全部轉移邊、回應風格對應、時間衰減與跨 session 殘留驗證

npm run restart && npm run smoke -- D 皆通過(D-V),A/B/C 群組冒煙測試無回歸。
This commit is contained in:
Jeffery
2026-08-13 10:18:58 +08:00
parent e6f25008d1
commit 611c9b400c
9 changed files with 483 additions and 6 deletions
+12 -5
View File
@@ -159,11 +159,18 @@ flowchart TB
### D. 情緒子系統
- [ ] **D-1 情緒標記器(S)**:實作 `EmotionTagger` 介面與規則版實作(關鍵字+輸入特徵+角色觸發閾值),輸出情緒類型與強度分數。驗收:正向/負向/衝突語氣輸入分別得到對應標記。依據:§聊天系統架構圖「情緒標記器:為輸入評估情緒強度」。
- [ ] **D-2 時間衰減(S)**:以半衰期函數隨時間回歸平靜,衰減速度由角色參數決定;跨 session 保留殘留情緒。驗收:模擬經過數小時後情緒值回落,重新開啟 session 仍讀得到殘留。依據:§情緒狀態機「跨 session 保留情緒殘留……但隨時間衰減」。
- [ ] **D-3 情緒→回應風格輸出(S)**:把當前情緒轉為回應參數(語氣、句長、主動性),提供給 F 群組的上下文組裝器。驗收:愉悅時參數指向「多話」、低落時指向「簡短」。依據:§情緒狀態機「情緒狀態影響:回覆語氣、用詞選擇、主動性」。
- [ ] **D-4 情緒狀態機(M)**:實作平靜/愉悅/低落/警戒的轉移規則(正向互動、負向事件、偵測衝突語氣、確認無威脅、衝突持續),並加入害羞與彆扭兩個狀態(供 G-4 可愛度行為使用)。驗收:依 wiki 狀態圖的每條轉移邊都有對應測試通過。依據:§情緒狀態機 stateDiagram、§可愛度設計「情緒引擎新增『羞』狀態」。
- [ ] **D-V 階段驗證(XS)**:`npm run restart && npm run smoke -- D`(D.mjs:連續負向輸入→進警戒;模擬時間推進→回平靜;跨 session 讀回殘留)。
- [x] **D-1 情緒標記器(S)**:實作 `EmotionTagger` 介面與規則版實作(關鍵字+輸入特徵+角色觸發閾值),輸出情緒類型與強度分數。驗收:正向/負向/衝突語氣輸入分別得到對應標記。依據:§聊天系統架構圖「情緒標記器:為輸入評估情緒強度」。
- [x] **D-2 時間衰減(S)**:以半衰期函數隨時間回歸平靜,衰減速度由角色參數決定;跨 session 保留殘留情緒。驗收:模擬經過數小時後情緒值回落,重新開啟 session 仍讀得到殘留。依據:§情緒狀態機「跨 session 保留情緒殘留……但隨時間衰減」。
- [x] **D-3 情緒→回應風格輸出(S)**:把當前情緒轉為回應參數(語氣、句長、主動性),提供給 F 群組的上下文組裝器。驗收:愉悅時參數指向「多話」、低落時指向「簡短」。依據:§情緒狀態機「情緒狀態影響:回覆語氣、用詞選擇、主動性」。
- [x] **D-4 情緒狀態機(M)**:實作平靜/愉悅/低落/警戒的轉移規則(正向互動、負向事件、偵測衝突語氣、確認無威脅、衝突持續),並加入害羞與彆扭兩個狀態(供 G-4 可愛度行為使用)。驗收:依 wiki 狀態圖的每條轉移邊都有對應測試通過。依據:§情緒狀態機 stateDiagram、§可愛度設計「情緒引擎新增『羞』狀態」。
- [x] **D-V 階段驗證(XS)**:`npm run restart && npm run smoke -- D`(D.mjs:連續負向輸入→進警戒;模擬時間推進→回平靜;跨 session 讀回殘留)。
> **實作記錄(D 群組)**:
> - 情緒引擎放在 `apps/api/src/emotion/`。`EmotionState` 的 6 個浮點欄位(calm/joy/sad/alert/shy/grumpy)**不是各自獨立累積**,而是「當下只有一個主導情緒+其強度」:`dominantState()` 取非平靜維度中數值最高且超過門檻(5)者,狀態轉移發生時會把其餘維度清零、只把目標維度設成新強度。這是刻意的設計取捨——避免連續多種訊號各自累加造成無來源依據的隱性轉移,讓 D-4 的邊全部可預期、可測試。之後 G 群組若要疊加「性格參數影響情緒外顯度」,建議在讀出 `dominantState` 之後的顯示層做縮放,不要改動這裡的儲存邏輯。
> - D-4 狀態機**只實作 wiki 狀態圖 + 可愛度設計明訂的邊**(`apps/api/src/emotion/emotion-state-machine.ts`),刻意不外推「愉悅/低落/害羞」之間的直接互轉——這些狀態目前只能先衰減回平靜,才能被新訊號帶往別的狀態。害羞(被稱讚觸發)與彆扭(被忽略/比較等小型負向觸發,被哄後快速恢復)是本群組依「可愛度設計」章節新增,wiki 原始狀態圖沒有畫出來。
> - 時間衰減用半衰期公式(預設 2 小時,`processInput`/`getState` 皆可傳 `halfLifeMs` 覆寫),衰減與情緒讀寫都走資料庫的 `EmotionState.updatedAt`,天生跨 session/跨行程持久,不需要额外的殘留儲存機制。
> - `GET/POST /emotion/:characterId`、`.../input`、`.../response-style` 同樣是**工程內部驗證用端點**,性質與 C 群組的 `/memory/*` 一致,等 F/H 群組整合對話管線時再決定去留。
> - D-1 的關鍵字表與觸發優先序(衝突 > 稱讚/害羞 > 輕度負向/彆扭 > 一般負向 > 正向)、D-3 的回應風格對照表都只是 Mock 階段的規則版本;R-3 接上真實 LLM 後,這兩塊要嘛保留作為安全網、要嘛被模型自身的語氣調節取代,屆時再一併決定。
### E. 關係子系統