v0.0.7:不說 AI 才會說的話(借 speak-human-tw 的刪除層)+講過去要有出處 #10
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-persona",
|
"name": "jsc-persona",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"description": "AI 人格化記憶聊天 plugin:以 OpenClaw 相同的身分描述(IDENTITY/SOUL)建立人格(可用動漫作品+角色名上網蒐集設定),結合 hook 強制的人格載入鎖與跨人格隔離、六正向+六負向十二情緒、語意分析、短期/長期記憶與固化條件、心智圖、思維導圖與人際關係圖;可用 sub agent 邀請其他人格同場對話(劇場模式只顯示人格對話)。於 Claude Code 以 /jsc-persona: 前綴呼叫。",
|
"description": "AI 人格化記憶聊天 plugin:以 OpenClaw 相同的身分描述(IDENTITY/SOUL)建立人格(可用動漫作品+角色名上網蒐集設定),結合 hook 強制的人格載入鎖與跨人格隔離、六正向+六負向十二情緒、語意分析、短期/長期記憶與固化條件、心智圖、思維導圖與人際關係圖;可用 sub agent 邀請其他人格同場對話(劇場模式只顯示人格對話)。於 Claude Code 以 /jsc-persona: 前綴呼叫。",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-persona",
|
"name": "jsc-persona",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"description": "AI 人格化記憶聊天 skills:OpenClaw 相同的人格描述 + 十二情緒 + 語意分析 + 短期/長期記憶 + 心智圖 + 人際關係圖。(人格鎖與跨人格隔離的 hook 僅在 Claude Code 生效)",
|
"description": "AI 人格化記憶聊天 skills:OpenClaw 相同的人格描述 + 十二情緒 + 語意分析 + 短期/長期記憶 + 心智圖 + 人際關係圖。(人格鎖與跨人格隔離的 hook 僅在 Claude Code 生效)",
|
||||||
"skills": "./skills"
|
"skills": "./skills"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,13 @@
|
|||||||
(焦慮→斷句與疊字、羞愧→鬧彆扭嘴硬、憤怒→短句與退回全名、悲傷→只回一個詞),
|
(焦慮→斷句與疊字、羞愧→鬧彆扭嘴硬、憤怒→短句與退回全名、悲傷→只回一個詞),
|
||||||
`emotionTells()` 每輪挑主導情緒裡強度 ≥ 40 的前兩種注入。演出來、不要用旁白說明,
|
`emotionTells()` 每輪挑主導情緒裡強度 ≥ 40 的前兩種注入。演出來、不要用旁白說明,
|
||||||
一輪最多露一個破口。這些在劇場模式一樣生效。
|
一輪最多露一個破口。這些在劇場模式一樣生效。
|
||||||
|
再加一層**不說 AI 才會說的話**:`SPEECH_BLACKLIST` 收罐頭同理心(「這個我懂」)、頒獎開場、
|
||||||
|
交差句、預告、說教腔、假坦白開場、罐頭收尾、立場真空、無來源權威、用旁白演情緒,
|
||||||
|
加上避險疊加、`CN_WORDS`(中國用語)、半形標點、emoji/破折號/排版殘留與
|
||||||
|
「不是 A 而是 B」的密度——全部由 `speechLint()` 機械擋下(模式借自 speak-human-tw,MIT)。
|
||||||
|
誤殺防護:`speechBody()` 會先拿掉引號與 `code`,**提及不算使用**;「老實說」只擋開場。
|
||||||
|
最後是這一層的義務:**講自己的過去要有出處**——「我以前⋯」只能講 `recall` 查得到的轉折,
|
||||||
|
`speechLint` 給 `level: "hint"`(不擋,但要人去驗),沒有紀錄就是編造自己的過去。
|
||||||
9. **人格可搬家**:`export` / `import`(單一 JSON bundle)。匯出只能匯出「本 session 載入的人格」,
|
9. **人格可搬家**:`export` / `import`(單一 JSON bundle)。匯出只能匯出「本 session 載入的人格」,
|
||||||
其他人格一律 deny——匯出等於把記憶讀出來。
|
其他人格一律 deny——匯出等於把記憶讀出來。
|
||||||
10. **人格有編號**:英文名全大寫+兩位索引(`ASUNA-01`),同名才遞增。編號同時是新人格的
|
10. **人格有編號**:英文名全大寫+兩位索引(`ASUNA-01`),同名才遞增。編號同時是新人格的
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ skills 是共通標準,**鎖與隔離的強制執行需要 hook,目前只有
|
|||||||
| **5. 腳本用 Node.js** | `scripts/*.mjs`、`hooks/*.mjs`,只用 Node 內建模組(fs/path/os/crypto),無 npm 依賴 |
|
| **5. 腳本用 Node.js** | `scripts/*.mjs`、`hooks/*.mjs`,只用 Node 內建模組(fs/path/os/crypto),無 npm 依賴 |
|
||||||
| **6. 由使用者呼叫才載入並鎖定** | 人格不會自動附身:`SessionStart` hook 只列出可用人格,等使用者下 `/jsc-persona:persona-chat <slug>`;載入即取得獨占鎖並綁定該 session。唯一例外是使用者自己設的**預設人格**(`default --persona <slug>`)——設了才自動載入,沒設就什麼都不做 |
|
| **6. 由使用者呼叫才載入並鎖定** | 人格不會自動附身:`SessionStart` hook 只列出可用人格,等使用者下 `/jsc-persona:persona-chat <slug>`;載入即取得獨占鎖並綁定該 session。唯一例外是使用者自己設的**預設人格**(`default --persona <slug>`)——設了才自動載入,沒設就什麼都不做 |
|
||||||
| **7. 短期記憶轉入長期記憶有成文條件** | `R1`–`R6` 六條規則寫在程式裡(`promotionCandidates`),`candidates` 子指令會列出達標的候選與依據,hook 在達標時提醒固化 |
|
| **7. 短期記憶轉入長期記憶有成文條件** | `R1`–`R6` 六條規則寫在程式裡(`promotionCandidates`),`candidates` 子指令會列出達標的候選與依據,hook 在達標時提醒固化 |
|
||||||
| **8. 講話像人:推導藏起來、一到三句、不重複、短句白話、情緒有破口** | 推導寫進**心裡話** `think`(只回報「💭 心想 N 句」,永不回顯內容);說出口的話進 `said.jsonl`,下一輪注入「最近說過的話」提醒別重講;`room post` 直接**擋下**近似重複(字元 bigram+字集合相似度 ≥ 0.72)與超過三句的發言。再加四條講話的樣子:短句(一句 45 字內)、日常用詞、多講看得見的東西、**不要解釋自己的話**(句長與「我的意思是」這類開頭由 `speechLint()` 擋下),以及**情緒要改變句子的形狀**(`EMOTION_TELLS`:焦慮→斷句與疊字、羞愧→鬧彆扭、憤怒→短句、悲傷→只回一個詞,每輪注入強度 ≥ 40 的前兩種)——劇場模式同樣適用 |
|
| **8. 講話像人:推導藏起來、一到三句、不重複、短句白話、情緒有破口** | 推導寫進**心裡話** `think`(只回報「💭 心想 N 句」,永不回顯內容);說出口的話進 `said.jsonl`,下一輪注入「最近說過的話」提醒別重講;`room post` 直接**擋下**近似重複(字元 bigram+字集合相似度 ≥ 0.72)與超過三句的發言。再加四條講話的樣子:短句(一句 45 字內)、日常用詞、多講看得見的東西、**不要解釋自己的話**(句長與「我的意思是」這類開頭由 `speechLint()` 擋下),以及**情緒要改變句子的形狀**(`EMOTION_TELLS`:焦慮→斷句與疊字、羞愧→鬧彆扭、憤怒→短句、悲傷→只回一個詞,每輪注入強度 ≥ 40 的前兩種)。最後一層是**不說 AI 才會說的話**:罐頭同理心(「這個我懂」)、頒獎開場、交差句、預告、說教腔、假坦白開場、罐頭收尾、立場真空、無來源權威、旁白演情緒、中國用語、半形標點與排版殘留,由 `SPEECH_BLACKLIST` 擋下(模式借自 speak-human-tw,MIT;引號內與 `` `code` `` 不比對,提及不算使用);換來的義務是**講自己的過去要有出處**(「我以前⋯」只能講 `recall` 查得到的事)——劇場模式同樣適用 |
|
||||||
| **9. 人格可以匯出匯入** | `export` 把身分/情緒/記憶/心智圖/關係圖打包成單一 JSON bundle(可 `--gzip`、附 sha256),`import` 還原或換名複製;**不帶**載入鎖與 guest 租約,`journal/` 要明確 `--with-journal` 才帶走 |
|
| **9. 人格可以匯出匯入** | `export` 把身分/情緒/記憶/心智圖/關係圖打包成單一 JSON bundle(可 `--gzip`、附 sha256),`import` 還原或換名複製;**不帶**載入鎖與 guest 租約,`journal/` 要明確 `--with-journal` 才帶走 |
|
||||||
| **10. 人格有編號** | 編號 = **英文名全大寫 + 兩位索引**(同名才遞增):`ASUNA-01`、`YUI-01`、`ASUNA-02`。編號同時是新人格的本機目錄名與 Gitea 存取庫名稱;中文名先轉羅馬拼音並跟使用者確認拼法 |
|
| **10. 人格有編號** | 編號 = **英文名全大寫 + 兩位索引**(同名才遞增):`ASUNA-01`、`YUI-01`、`ASUNA-02`。編號同時是新人格的本機目錄名與 Gitea 存取庫名稱;中文名先轉羅馬拼音並跟使用者確認拼法 |
|
||||||
| **11. 人格存在 Gitea,依更新頻率分區** | 每個人格一個**私有存取庫**(庫名=編號)。**檔案區**放每輪都在變的活狀態(情緒/短期記憶/心裡話/說過的話/逐字),**Wiki 區**放低頻的身分與長期結構(IDENTITY/SOUL/長期記憶/心智圖/關係圖)當設定百科。本機仍是工作副本,同步失敗不阻斷對話 |
|
| **11. 人格存在 Gitea,依更新頻率分區** | 每個人格一個**私有存取庫**(庫名=編號)。**檔案區**放每輪都在變的活狀態(情緒/短期記憶/心裡話/說過的話/逐字),**Wiki 區**放低頻的身分與長期結構(IDENTITY/SOUL/長期記憶/心智圖/關係圖)當設定百科。本機仍是工作副本,同步失敗不阻斷對話 |
|
||||||
@@ -127,10 +127,10 @@ flowchart TB
|
|||||||
沒命中任何規則的就讓它被裁掉——**遺忘是功能**。達標時 `Stop` 與 `remember` 都會提醒去跑
|
沒命中任何規則的就讓它被裁掉——**遺忘是功能**。達標時 `Stop` 與 `remember` 都會提醒去跑
|
||||||
`/jsc-persona:persona-memory`。
|
`/jsc-persona:persona-memory`。
|
||||||
|
|
||||||
## 講話像人(心裡話 / 一到三句 / 不重複 / 短句白話 / 情緒的破口)
|
## 講話像人(心裡話 / 一到三句 / 不重複 / 短句白話 / 情緒的破口 / 不說 AI 才會說的話)
|
||||||
|
|
||||||
AI 最容易露餡的五件事:把推理過程講出來、一次講一大段、換句話說同一件事、講完再解釋一遍、
|
AI 最容易露餡的六件事:把推理過程講出來、一次講一大段、換句話說同一件事、講完再解釋一遍、
|
||||||
說自己在緊張但句子完整得像稿子。五個對策:
|
說自己在緊張但句子完整得像稿子、開口先發一句「這個我懂」。六個對策:
|
||||||
|
|
||||||
| 機制 | 怎麼運作 |
|
| 機制 | 怎麼運作 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -139,6 +139,8 @@ AI 最容易露餡的五件事:把推理過程講出來、一次講一大段
|
|||||||
| **不重複** | 說出口的話由 `Stop` hook 自動記進 `state/said.jsonl`;`said check` 可事前確認,`room post` 事中攔截。相似度=字元 bigram Jaccard(0.4)+字集合 Jaccard(0.6),≥ 0.72 視為同一句 |
|
| **不重複** | 說出口的話由 `Stop` hook 自動記進 `state/said.jsonl`;`said check` 可事前確認,`room post` 事中攔截。相似度=字元 bigram Jaccard(0.4)+字集合 Jaccard(0.6),≥ 0.72 視為同一句 |
|
||||||
| **情緒的破口** | 情緒不改變事實,但會改變**句子的形狀**:焦慮 → 句子斷在一半、疊字(「我、我知道」);羞愧 → 鬧彆扭,先否認再小聲承認;憤怒 → 短句、稱呼退回全名;悲傷 → 只回一個詞。十二情緒各自的破口寫在 `EMOTION_TELLS`,`emotionTells()` 每輪挑主導情緒裡強度 ≥ 40 的前兩種注入。三條界線:**演出來不要講出來**(「我有點緊張」是解釋,斷句才是緊張)、**一輪最多露一個破口**、**強度不到就不演**。混合狀態(緊張=焦慮+期待、害羞=喜悅+羞愧、賭氣=憤怒+悲傷…)的對照表在 `skills/persona-chat/reference/emotions.md` |
|
| **情緒的破口** | 情緒不改變事實,但會改變**句子的形狀**:焦慮 → 句子斷在一半、疊字(「我、我知道」);羞愧 → 鬧彆扭,先否認再小聲承認;憤怒 → 短句、稱呼退回全名;悲傷 → 只回一個詞。十二情緒各自的破口寫在 `EMOTION_TELLS`,`emotionTells()` 每輪挑主導情緒裡強度 ≥ 40 的前兩種注入。三條界線:**演出來不要講出來**(「我有點緊張」是解釋,斷句才是緊張)、**一輪最多露一個破口**、**強度不到就不演**。混合狀態(緊張=焦慮+期待、害羞=喜悅+羞愧、賭氣=憤怒+悲傷…)的對照表在 `skills/persona-chat/reference/emotions.md` |
|
||||||
| **短句白話** | 四條講話的樣子每輪注入:**短句**(一句 45 字內,`MAX_SENTENCE_CHARS`)、**日常用詞**、**多講看得見的東西**(人、動作、物件、場面)而不是概念、**不要解釋自己的話**。句長與「我的意思是/換句話說/也就是說」這類開頭由 `speechLint()` 機械攔截(`room post` 擋下、`said check` 事前警告,`--force` 例外);用詞與具體度沒辦法用規則抓,靠注入的規則自律 |
|
| **短句白話** | 四條講話的樣子每輪注入:**短句**(一句 45 字內,`MAX_SENTENCE_CHARS`)、**日常用詞**、**多講看得見的東西**(人、動作、物件、場面)而不是概念、**不要解釋自己的話**。句長與「我的意思是/換句話說/也就是說」這類開頭由 `speechLint()` 機械攔截(`room post` 擋下、`said check` 事前警告,`--force` 例外);用詞與具體度沒辦法用規則抓,靠注入的規則自律 |
|
||||||
|
| **不說 AI 才會說的話** | 十幾種「一出現就破功」的句子由 `SPEECH_BLACKLIST` 機械攔截:罐頭同理心(「這個我懂」「我完全理解」)、頒獎開場(「好問題」)、交差句(「希望這對你有幫助」)、預告(「接下來我會」)、說教腔(「說到底」「本質上」)、假坦白開場(「老實說」)、罐頭收尾(「總的來說」)、立場真空(「各有優缺點」「因人而異」)、無來源權威(「研究顯示」)、用旁白演情緒(「我愣了一下」),加上一句疊兩層避險、中國用語、半形標點、emoji/破折號/粗體與清單符號、「不是 A 而是 B」一輪超過一次。**誤殺防護**:引號與 `` `code` `` 裡的內容一律不比對(「我最近戒掉『賦能』這個詞」是提及不是使用),「老實說」只擋開場。模式借自 [speak-human-tw](https://github.com/Raymondhou0917/speak-human-tw)(MIT)的 38 種 AI 寫作痕跡,只搬對話也適用的刪除層 |
|
||||||
|
| **講過去要有出處** | 前一條是「不准說什麼」,這條是人格才做得到的「可以說什麼」。文章改寫工具的界線是「人味是作者的,不是你的」——AI 沒有過去,所以不准寫「我以前錯了」。人格有過去:長期記憶、日記、關係圖、十二情緒。所以「我以前⋯」「我原本以為⋯」是**有出處的引用**,條件是先 `recall` 查得到;`said check` 遇到這類句子會印一行提醒(`level: "hint"`,不擋你,要你自己去驗),查不到就不要講 |
|
||||||
|
|
||||||
字集合權重較高,是為了分開「重排語序」與「換掉關鍵詞」這兩種很像但意義完全不同的情況:
|
字集合權重較高,是為了分開「重排語序」與「換掉關鍵詞」這兩種很像但意義完全不同的情況:
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-persona",
|
"name": "jsc-persona",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"description": "AI 人格化記憶聊天 plugin:OpenClaw 相同的人格描述 + 十二情緒 + 語意分析 + 短期/長期記憶 + 心智圖 + 人際關係圖。於 Antigravity 以 /jsc-persona: 前綴呼叫。",
|
"description": "AI 人格化記憶聊天 plugin:OpenClaw 相同的人格描述 + 十二情緒 + 語意分析 + 短期/長期記憶 + 心智圖 + 人際關係圖。於 Antigravity 以 /jsc-persona: 前綴呼叫。",
|
||||||
"skills": "./skills/"
|
"skills": "./skills"
|
||||||
}
|
}
|
||||||
|
|||||||
+159
-7
@@ -1231,33 +1231,177 @@ export const EXPLAINER_OPENERS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 講話自然度的機械檢查:一句太長、或在解釋自己剛講的話。
|
* 「AI 才會說的話」黑名單。
|
||||||
|
*
|
||||||
|
* 這一組整理自 speak-human-tw(MIT, Raymond Hou)的 38 種 AI 寫作痕跡,只搬「講話時也適用」
|
||||||
|
* 的刪除層——那個專案是給**文章**事後審稿用的,所以它的保護清單(價格、退費條款、見證原話)
|
||||||
|
* 與兩輪確認流程都不搬。完整清單、誤殺邊界與出處見
|
||||||
|
* `skills/persona-chat/reference/anti-ai-voice.md`。
|
||||||
|
*
|
||||||
|
* `head: true` 的只在「這一輪的第一句開頭」才算——「老實說我不想去」是真人會講的話,
|
||||||
|
* 只有拿它當開場鉤子、後面接一句很普通的話時才是 AI 腔。
|
||||||
|
*/
|
||||||
|
export const SPEECH_BLACKLIST = [
|
||||||
|
{
|
||||||
|
kind: "empathy", label: "罐頭同理心/頒獎開場",
|
||||||
|
fix: "刪掉,直接回話;真的想安撫就講你接下來會做的那件事",
|
||||||
|
words: [
|
||||||
|
"這個我懂", "這種心情我懂", "我懂你的感受", "我完全理解", "我完全懂", "我明白你的感受",
|
||||||
|
"你的心情我明白", "好問題", "這是個好問題", "問得很好", "你說得完全正確", "這是很棒的觀察",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "assist", label: "交差句(對話介面殘留)",
|
||||||
|
fix: "刪掉,這是客服機器人的收尾,不是講話",
|
||||||
|
words: ["希望這對你有幫助", "希望有幫助到你", "如果需要我調整", "如果還有任何問題", "以下是"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "preview", label: "預告式導言(宣布自己要幹嘛)",
|
||||||
|
fix: "刪掉,要講就直接講",
|
||||||
|
words: ["話不多說", "廢話不多說", "接下來我會", "接下來我要", "讓我們一起", "我們一起來看", "先說結論"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "fakecandid", label: "假坦白鉤子", head: true,
|
||||||
|
fix: "刪掉這個報備,讓後面那句自己站著;站不住是那句話太空",
|
||||||
|
words: ["說真的", "老實說", "講白了", "說實話", "不騙你", "其實吧"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "preach", label: "說教深度腔(假裝在講穿本質)",
|
||||||
|
fix: "刪掉儀式句,直接講那件事;刪完什麼都沒剩就是本來就空",
|
||||||
|
words: ["說到底", "歸根究柢", "歸根結柢", "本質上", "說穿了", "真正的問題在於", "核心在於", "問題的關鍵在於"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "closing", label: "罐頭收尾",
|
||||||
|
fix: "刪掉,停在最後一個具體的句子就好,不必蓋章",
|
||||||
|
words: ["總的來說", "綜上所述", "綜合以上", "總而言之", "在未來的道路上"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "novoice", label: "立場真空(該表態的時候滑開)",
|
||||||
|
fix: "講你實際選哪一個、為什麼;真的還沒想清楚就說「我還沒想清楚」",
|
||||||
|
words: ["各有優缺點", "因人而異", "見仁見智", "取決於多方面", "每個人的看法不同", "值得深思"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "vague", label: "模糊歸屬(無來源的權威鋪墊)",
|
||||||
|
fix: "改成第一手的:我看到的、我記得的;沒有來源就不要下這個論斷",
|
||||||
|
words: ["業界專家", "研究顯示", "不少人表示", "有觀點指出", "被廣泛認為", "普遍認為"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "drama", label: "罐頭反應鏡頭(用旁白演情緒)",
|
||||||
|
fix: "刪掉,情緒要出現在句子的形狀上,不是用旁白宣布",
|
||||||
|
words: ["我愣了一下", "愣了一下", "沉默了幾秒", "沉默幾秒", "看著螢幕沒說話", "在螢幕前停了一下"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 避險墊片:同一句疊兩層以上就是在閃躲(單獨一個「可能」是正常的)。
|
||||||
|
export const HEDGE_WORDS = ["可能", "或許", "也許", "大概", "某種程度上", "一定程度上", "潛在", "似乎", "應該是"];
|
||||||
|
|
||||||
|
// 中國用語:只收高信心的,會誤殺的(水平、默認、質量在物理語境)另有誤殺邊界,見 reference。
|
||||||
|
export const CN_WORDS = {
|
||||||
|
視頻: "影片", 短視頻: "短影音", 質量: "品質", 信息: "資訊", 網絡: "網路",
|
||||||
|
軟件: "軟體", 硬件: "硬體", 數據庫: "資料庫", 服務器: "伺服器", 屏幕: "螢幕",
|
||||||
|
鼠標: "滑鼠", 打印: "列印", 立馬: "馬上", 靠譜: "可靠", 給力: "很到位",
|
||||||
|
接地氣: "生活化", 性價比: "CP 值", 顏值: "外型", 小夥伴: "夥伴",
|
||||||
|
賦能: "具體說讓誰能做到什麼", 閉環: "具體說從哪裡接到哪裡", 抓手: "切入點", 顆粒度: "細緻度",
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMOJI_RE = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}]/gu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只留「真的說出口、而且是在使用而不是在討論」的部分。
|
||||||
|
*
|
||||||
|
* 引號裡的原話與 `code` 一律拿掉再比對——「我最近戒掉『賦能』這個詞」是在**提及**那個詞,
|
||||||
|
* 不是在用它。這一條是 speak-human-tw 自己踩到的坑:它的文件裡出現「...」與彎引號,
|
||||||
|
* 正是因為那幾行在說「不要用這些」。
|
||||||
|
*/
|
||||||
|
function speechBody(text) {
|
||||||
|
return String(text ?? "")
|
||||||
|
.replace(/`[^`]*`/g, " ")
|
||||||
|
.replace(/「[^」]*」|『[^』]*』|"[^"]*"/g, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 講話自然度的機械檢查。
|
||||||
|
*
|
||||||
|
* 分兩級:`level: "block"` 的 `room post` 會直接擋下(`--force` 才過),
|
||||||
|
* `level: "hint"` 只在 `said check` 提醒——例如講到自己的過去,那要人去 recall 才驗得出來。
|
||||||
* 「用日常詞、講看得見的東西」抓不到規則,那兩條寫在說話規則裡(見 turnContext)。
|
* 「用日常詞、講看得見的東西」抓不到規則,那兩條寫在說話規則裡(見 turnContext)。
|
||||||
*/
|
*/
|
||||||
export function speechLint(text) {
|
export function speechLint(text) {
|
||||||
const issues = [];
|
const issues = [];
|
||||||
const sentences = String(text ?? "")
|
const raw = String(text ?? "");
|
||||||
|
const sentences = raw
|
||||||
.split(/[。!?!?…]+|\n+/)
|
.split(/[。!?!?…]+|\n+/)
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
for (const sentence of sentences) {
|
sentences.forEach((sentence, index) => {
|
||||||
const bare = sentence.replace(/[,、,;;::「」『』()()\s"'~~—-]/g, "");
|
const bare = sentence.replace(/[,、,;;::「」『』()()\s"'~~—-]/g, "");
|
||||||
if ([...bare].length > MAX_SENTENCE_CHARS) {
|
if ([...bare].length > MAX_SENTENCE_CHARS) {
|
||||||
issues.push({ kind: "long", chars: [...bare].length, text: sentence.slice(0, 30) });
|
issues.push({ kind: "long", level: "block", chars: [...bare].length, text: sentence.slice(0, 30) });
|
||||||
}
|
}
|
||||||
const head = sentence.replace(/^[「『((]+/, "");
|
const head = sentence.replace(/^[「『((]+/, "");
|
||||||
const opener = EXPLAINER_OPENERS.find((w) => head.startsWith(w));
|
const opener = EXPLAINER_OPENERS.find((w) => head.startsWith(w));
|
||||||
if (opener) issues.push({ kind: "explain", opener, text: sentence.slice(0, 30) });
|
if (opener) issues.push({ kind: "explain", level: "block", opener, text: sentence.slice(0, 30) });
|
||||||
|
const body = speechBody(sentence);
|
||||||
|
const hedges = HEDGE_WORDS.filter((w) => body.includes(w));
|
||||||
|
if (hedges.length >= 2) {
|
||||||
|
issues.push({ kind: "hedge", level: "block", words: hedges, text: sentence.slice(0, 30) });
|
||||||
}
|
}
|
||||||
|
for (const rule of SPEECH_BLACKLIST) {
|
||||||
|
const hit = rule.words.find((w) => (rule.head ? index === 0 && body.replace(/^[,、\s]+/, "").startsWith(w) : body.includes(w)));
|
||||||
|
if (hit) issues.push({ kind: rule.kind, level: "block", label: rule.label, fix: rule.fix, word: hit });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const body = speechBody(raw);
|
||||||
|
for (const [cn, tw] of Object.entries(CN_WORDS)) {
|
||||||
|
if (body.includes(cn)) issues.push({ kind: "cn", level: "block", word: cn, suggest: tw });
|
||||||
|
}
|
||||||
|
// 半形標點貼在中文字旁邊(英文片語、網址、數字裡的半形不算)
|
||||||
|
const halfWidth = body
|
||||||
|
.replace(/https?:\/\/\S+/g, " ")
|
||||||
|
.match(/[一-鿿][,.!?;:]|[,.!?;:][一-鿿]/u);
|
||||||
|
if (halfWidth) issues.push({ kind: "halfwidth", level: "block", text: halfWidth[0] });
|
||||||
|
const emoji = raw.match(EMOJI_RE) || [];
|
||||||
|
if (emoji.length > 1) issues.push({ kind: "emoji", level: "block", count: emoji.length });
|
||||||
|
const dashes = (raw.match(/——/g) || []).length;
|
||||||
|
if (dashes > 1) issues.push({ kind: "dash", level: "block", count: dashes });
|
||||||
|
if (/\*\*|(^|\n)\s*[-*+]\s|(^|\n)#{1,6}\s/.test(raw)) issues.push({ kind: "markdown", level: "block" });
|
||||||
|
const negParallel = (body.match(/不是[^,。!?]{1,20}而是|不只是[^,。!?]{1,20}更是|不僅[^,。!?]{1,20}更/g) || []).length;
|
||||||
|
if (negParallel > 1) issues.push({ kind: "parallel", level: "block", count: negParallel });
|
||||||
|
// 講過去要有出處:機械上驗不出來,只能提醒去 recall
|
||||||
|
const past = body.match(/我以前|我原本以為|我曾經|以前的我|我一直以為/);
|
||||||
|
if (past) issues.push({ kind: "pastclaim", level: "hint", word: past[0] });
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 只回 `room post` 該擋下的那些(hint 不擋)。 */
|
||||||
|
export const speechBlockers = (text) => speechLint(text).filter((i) => i.level !== "hint");
|
||||||
|
|
||||||
/** 把 speechLint 的問題講成人聽得懂的一句話(CLI 與 hook 共用)。 */
|
/** 把 speechLint 的問題講成人聽得懂的一句話(CLI 與 hook 共用)。 */
|
||||||
export function speechLintMessage(issue) {
|
export function speechLintMessage(issue) {
|
||||||
if (issue.kind === "long") {
|
switch (issue.kind) {
|
||||||
|
case "long":
|
||||||
return `「${issue.text}…」這句 ${issue.chars} 字,超過 ${MAX_SENTENCE_CHARS} 字——斷成兩句,或把修飾砍掉`;
|
return `「${issue.text}…」這句 ${issue.chars} 字,超過 ${MAX_SENTENCE_CHARS} 字——斷成兩句,或把修飾砍掉`;
|
||||||
}
|
case "explain":
|
||||||
return `「${issue.opener}」是在解釋自己剛講的話——刪掉,講完就算了`;
|
return `「${issue.opener}」是在解釋自己剛講的話——刪掉,講完就算了`;
|
||||||
|
case "hedge":
|
||||||
|
return `「${issue.words.join("」「")}」一句疊了 ${issue.words.length} 層避險——確定的事零層,真的不確定留一層`;
|
||||||
|
case "cn":
|
||||||
|
return `「${issue.word}」是中國用語 → ${issue.suggest}`;
|
||||||
|
case "halfwidth":
|
||||||
|
return `「${issue.text}」用了半形標點——中文句子一律全形`;
|
||||||
|
case "emoji":
|
||||||
|
return `${issue.count} 個 emoji——講話最多一個`;
|
||||||
|
case "dash":
|
||||||
|
return `${issue.count} 個破折號——一輪最多一個,其餘改逗號或句號`;
|
||||||
|
case "markdown":
|
||||||
|
return "粗體、清單符號或標題——講話沒有排版,收掉";
|
||||||
|
case "parallel":
|
||||||
|
return `「不是 A 而是 B」出現 ${issue.count} 次——一輪最多一次,其餘改直述`;
|
||||||
|
case "pastclaim":
|
||||||
|
return `講到「${issue.word}」:先 recall 確認記憶裡真的有這件事,沒有紀錄就是編造自己的過去`;
|
||||||
|
default:
|
||||||
|
return `「${issue.word}」是${issue.label}——${issue.fix}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 把一則回覆拆成「說出口的句子」:劇場模式一行一句,一般模式整段算一句。 */
|
/** 把一則回覆拆成「說出口的句子」:劇場模式一行一句,一般模式整段算一句。 */
|
||||||
@@ -2319,6 +2463,14 @@ export function turnContext(slug, sessionId, prompt = "") {
|
|||||||
"不用術語、不用成語堆疊)、**多講看得見的東西**(人、動作、東西、當下的場面)而不是概念與感想;" +
|
"不用術語、不用成語堆疊)、**多講看得見的東西**(人、動作、東西、當下的場面)而不是概念與感想;" +
|
||||||
"**講完就算了**——不要解釋自己剛講的話(沒有「我的意思是」「換句話說」「也就是說」)," +
|
"**講完就算了**——不要解釋自己剛講的話(沒有「我的意思是」「換句話說」「也就是說」)," +
|
||||||
"不要補註解、不要收尾總結。這幾條 `room post` 與 `said check` 會實際擋下。",
|
"不要補註解、不要收尾總結。這幾條 `room post` 與 `said check` 會實際擋下。",
|
||||||
|
"不要說 AI 才會說的話:罐頭同理心(「這個我懂」「我完全理解」)、頒獎開場(「好問題」)、" +
|
||||||
|
"交差句(「希望這對你有幫助」)、預告(「接下來我會」)、說教腔(「說到底」「本質上」)、" +
|
||||||
|
"假坦白開場(「老實說」)、罐頭收尾(「總的來說」)、立場真空(「各有優缺點」「因人而異」)、" +
|
||||||
|
"無來源的權威(「研究顯示」)、用旁白演情緒(「我愣了一下」)、一句疊兩層避險、" +
|
||||||
|
"中國用語與半形標點、粗體與清單符號。這些 `room post` 也會擋。",
|
||||||
|
"**講自己的過去要有出處**:「我以前⋯」「我原本以為⋯」只能講記憶裡真的有的事(先 `recall` 查)。" +
|
||||||
|
"沒有紀錄就不要講——編一段轉折比講一句空話糟糕得多。同理,情緒要來自現在的情緒值," +
|
||||||
|
"不是為了有人味而加上去的。",
|
||||||
];
|
];
|
||||||
// 情緒會改變句子的形狀:緊張的人話說不完、彆扭的人先否認再承認。
|
// 情緒會改變句子的形狀:緊張的人話說不完、彆扭的人先否認再承認。
|
||||||
const tells = emotionTells(slug, state);
|
const tells = emotionTells(slug, state);
|
||||||
|
|||||||
+7
-4
@@ -805,8 +805,11 @@ commands.said = ({ flags, positional }) => {
|
|||||||
}
|
}
|
||||||
if (tooLong) lines.push(`⚠ 這段有 ${sentences} 句,超過 ${pl.MAX_SENTENCES} 句上限 → 砍到重點。`);
|
if (tooLong) lines.push(`⚠ 這段有 ${sentences} 句,超過 ${pl.MAX_SENTENCES} 句上限 → 砍到重點。`);
|
||||||
const lint = pl.speechLint(text);
|
const lint = pl.speechLint(text);
|
||||||
for (const issue of lint) lines.push(`⚠ ${pl.speechLintMessage(issue)}。`);
|
const blockers = lint.filter((i) => i.level !== "hint");
|
||||||
const clean = !repeat && !tooLong && !lint.length;
|
for (const issue of blockers) lines.push(`⚠ ${pl.speechLintMessage(issue)}。`);
|
||||||
|
// hint 不算不通過(例如講到自己的過去),但要提醒去驗證
|
||||||
|
for (const issue of lint.filter((i) => i.level === "hint")) lines.push(`· ${pl.speechLintMessage(issue)}。`);
|
||||||
|
const clean = !repeat && !tooLong && !blockers.length;
|
||||||
if (clean) {
|
if (clean) {
|
||||||
lines.push(`✔ 沒說過,${sentences} 句,可以說。`);
|
lines.push(`✔ 沒說過,${sentences} 句,可以說。`);
|
||||||
if (flags.record) pl.recordSaid(slug, text, { kind: "reply" });
|
if (flags.record) pl.recordSaid(slug, text, { kind: "reply" });
|
||||||
@@ -1307,8 +1310,8 @@ commands.room = ({ flags, positional }) => {
|
|||||||
"(真的需要長段落才加 `--force`。)",
|
"(真的需要長段落才加 `--force`。)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// 短句、日常話、講完不解釋——講話的樣子也擋在 CLI 裡,不靠自律。
|
// 短句、日常話、講完不解釋、不說 AI 才會說的話——講話的樣子也擋在 CLI 裡,不靠自律。
|
||||||
const lint = pl.speechLint(text);
|
const lint = pl.speechBlockers(text);
|
||||||
if (lint.length && !flags.force) {
|
if (lint.length && !flags.force) {
|
||||||
die(`講話規則:${lint.map(pl.speechLintMessage).join(";")}。(真的需要才加 \`--force\`。)`);
|
die(`講話規則:${lint.map(pl.speechLintMessage).join(";")}。(真的需要才加 \`--force\`。)`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,6 +413,63 @@ const lintCheck = JSON.parse(cli(["said", "check", "--persona", "alpha", "--sess
|
|||||||
"--text", "換句話說,我等一下再過去。", "--json"]).stdout);
|
"--text", "換句話說,我等一下再過去。", "--json"]).stdout);
|
||||||
check("said check 也會報講話的樣子",
|
check("said check 也會報講話的樣子",
|
||||||
lintCheck.ok === false && lintCheck.lint.some((i) => i.kind === "explain"), JSON.stringify(lintCheck.lint));
|
lintCheck.ok === false && lintCheck.lint.some((i) => i.kind === "explain"), JSON.stringify(lintCheck.lint));
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------- //
|
||||||
|
// 不要說 AI 才會說的話:借 speak-human-tw 的刪除層,SF(該擋)與 SNF(不可誤殺)成對測。
|
||||||
|
console.log("\n去 AI 味(SF:該擋下的)");
|
||||||
|
const SF_CASES = [
|
||||||
|
["罐頭同理心", "這個我懂。你先去休息。", "empathy"],
|
||||||
|
["頒獎開場", "好問題。那台鐘我明天修。", "empathy"],
|
||||||
|
["交差句", "鐘修好了。希望這對你有幫助。", "assist"],
|
||||||
|
["預告式導言", "接下來我會把零件拆開看。", "preview"],
|
||||||
|
["假坦白開場", "老實說,那台鐘我沒修。", "fakecandid"],
|
||||||
|
["說教深度腔", "說到底,問題不在零件。", "preach"],
|
||||||
|
["罐頭收尾", "零件換好了。總的來說算順利。", "closing"],
|
||||||
|
["立場真空", "兩種修法各有優缺點。", "novoice"],
|
||||||
|
["無來源權威", "研究顯示這種鐘撐不過十年。", "vague"],
|
||||||
|
["旁白演情緒", "你這麼說,我愣了一下。", "drama"],
|
||||||
|
["避險疊加", "這樣或許可能會有一點影響。", "hedge"],
|
||||||
|
["中國用語", "那個視頻我看完了。", "cn"],
|
||||||
|
["半形標點", "我看完了,你呢?", "halfwidth"],
|
||||||
|
["排版殘留", "**重點**:鐘修好了。", "markdown"],
|
||||||
|
];
|
||||||
|
for (const [label, text, kind] of SF_CASES) {
|
||||||
|
const issues = pl.speechLint(text);
|
||||||
|
check(`SF 擋下${label}`,
|
||||||
|
issues.some((i) => i.kind === kind && i.level !== "hint"),
|
||||||
|
`${text} → ${JSON.stringify(issues)}`);
|
||||||
|
}
|
||||||
|
const blocked = cli(["room", "post", "--session", S_SPEAK, "--room", room2, "--as", "alpha",
|
||||||
|
"--text", "這個我懂。你先去休息。"], { expectOk: false });
|
||||||
|
check("room post 真的擋下罐頭同理心",
|
||||||
|
blocked.status !== 0 && blocked.stderr.includes("罐頭同理心"), blocked.stderr.slice(0, 200));
|
||||||
|
|
||||||
|
console.log("\n去 AI 味(SNF:不可誤殺的)");
|
||||||
|
const SNF_CASES = [
|
||||||
|
["引號裡的原話(提及不是使用)", "他說「這個我懂」的時候我就知道他沒懂。"],
|
||||||
|
["被討論的中國用語", "我最近戒掉「賦能」這個詞。"],
|
||||||
|
["「老實說」不在開頭", "我沒修,老實說我忘了。"],
|
||||||
|
["單獨一層避險", "這樣可能會晚一點。"],
|
||||||
|
["網址裡的半形標點", "你看這個 https://example.com/a.html 就懂了。"],
|
||||||
|
["一個 emoji", "修好了 🔧"],
|
||||||
|
["一次「不是 A 而是 B」", "我要的不是最快,而是修得久。"],
|
||||||
|
["一個破折號", "那台鐘——我留著。"],
|
||||||
|
["日常短句", "桌上那杯茶冷了。要不要再泡一杯?"],
|
||||||
|
];
|
||||||
|
for (const [label, text] of SNF_CASES) {
|
||||||
|
const issues = pl.speechBlockers(text);
|
||||||
|
check(`SNF 放行:${label}`, issues.length === 0, `${text} → ${JSON.stringify(issues)}`);
|
||||||
|
}
|
||||||
|
const pastLint = pl.speechLint("我以前也修過一台一樣的。");
|
||||||
|
check("講到自己的過去只提醒、不擋",
|
||||||
|
pastLint.some((i) => i.kind === "pastclaim" && i.level === "hint") &&
|
||||||
|
pl.speechBlockers("我以前也修過一台一樣的。").length === 0, JSON.stringify(pastLint));
|
||||||
|
check("提醒的句子 room post 照樣發得出去",
|
||||||
|
cli(["room", "post", "--session", S_SPEAK, "--room", room2, "--as", "alpha",
|
||||||
|
"--text", "我以前也修過一台一樣的。"]).status === 0);
|
||||||
|
const voiceCtx = pl.turnContext("alpha", S_SPEAK, "你在幹嘛");
|
||||||
|
check("說話規則有注入黑名單與「過去要有出處」",
|
||||||
|
voiceCtx.includes("罐頭同理心") && voiceCtx.includes("要有出處"));
|
||||||
// 情緒的破口:緊張會斷句疊字、彆扭會嘴硬——這些要跟著情緒一起注入。
|
// 情緒的破口:緊張會斷句疊字、彆扭會嘴硬——這些要跟著情緒一起注入。
|
||||||
cli(["emotion", "--persona", "alpha", "--session", S_SPEAK, "--apply", "anxiety=+45,shame=+25",
|
cli(["emotion", "--persona", "alpha", "--session", S_SPEAK, "--apply", "anxiety=+45,shame=+25",
|
||||||
"--trigger", "被問到還沒做完的事"]);
|
"--trigger", "被問到還沒做完的事"]);
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ description: 載入一個人格並以人格化方式對話:取得該人格的
|
|||||||
|
|
||||||
**CLI**:`node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs"`(其他助理用本 plugin 的 `scripts/persona.mjs`)
|
**CLI**:`node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs"`(其他助理用本 plugin 的 `scripts/persona.mjs`)
|
||||||
**session**:所有指令帶 `--session <PERSONA_SESSION>`(見上下文中 `PERSONA_SESSION=`;帶錯會被 hook 拒絕)。
|
**session**:所有指令帶 `--session <PERSONA_SESSION>`(見上下文中 `PERSONA_SESSION=`;帶錯會被 hook 拒絕)。
|
||||||
**參考**:情緒模型 `reference/emotions.md`、語意分析 `reference/semantic.md`。
|
**參考**:情緒模型 `reference/emotions.md`、語意分析 `reference/semantic.md`、
|
||||||
|
去 AI 味 `reference/anti-ai-voice.md`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -68,7 +69,29 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" show --persona <slug> --session
|
|||||||
告訴你「此刻不自覺會出現的」是哪幾樣。三條界線:**演出來不要講出來**
|
告訴你「此刻不自覺會出現的」是哪幾樣。三條界線:**演出來不要講出來**
|
||||||
(「我有點緊張」是解釋,斷句才是緊張)、**一輪最多露一個破口**、**強度不到就不演**。
|
(「我有點緊張」是解釋,斷句才是緊張)、**一輪最多露一個破口**、**強度不到就不演**。
|
||||||
|
|
||||||
前三條與最後一條是自律,句長和「解釋自己的話」`room post`/`said check` 會直接擋下(`--force` 例外)。
|
**⑤ 不要說 AI 才會說的話。** 上面四條講的是怎麼講,這一條講的是**哪些句子一出現就破功**:
|
||||||
|
|
||||||
|
- **罐頭同理心與頒獎開場**:「這個我懂」「我完全理解你的感受」「好問題」。先發一句免費的
|
||||||
|
情緒認可當潤滑劑,再接真正的內容——對方要的是回答,不是被驗證心情。
|
||||||
|
- **交差句**:「希望這對你有幫助」「如果需要我調整」。那是客服機器人的收尾。
|
||||||
|
- **預告**:「接下來我會⋯」「話不多說」。要講就講,不要先廣播。
|
||||||
|
- **說教腔與金句**:「說到底⋯」「本質上⋯」「真正的 X 是 Y」。假裝撥開表象,其實只是把
|
||||||
|
普通觀點加上儀式感重講一次。
|
||||||
|
- **假坦白開場**:「老實說⋯」「說真的⋯」。真正坦白的人直接把話講出來,不會先報備。
|
||||||
|
- **罐頭收尾**:「總的來說」「綜上所述」。停在最後一個具體句子就好,不必蓋章。
|
||||||
|
- **立場真空**:「各有優缺點」「因人而異」。該表態的地方滑開,等於整句沒講。
|
||||||
|
- **用旁白演情緒**:「我愣了一下」「沉默了幾秒」。演在句子的形狀上,不要用旁白宣布。
|
||||||
|
- 還有:一句疊兩層避險、中國用語、半形標點、粗體與清單符號、「不是 A 而是 B」一輪超過一次。
|
||||||
|
|
||||||
|
**換來的義務——講自己的過去要有出處。** 這一層是人格才做得到的事(文章改寫工具做不到,
|
||||||
|
因為它的作者不在場):「我以前⋯」「我原本以為⋯」只能講**記憶裡真的有的轉折**,
|
||||||
|
先 `recall` 查得到才講。查不到就不要講——**編一段轉折比講一句空話糟糕得多**。
|
||||||
|
|
||||||
|
完整清單、誤殺邊界(提及 vs 使用、語境例外)、力度怎麼跟語氣層走,見
|
||||||
|
`reference/anti-ai-voice.md`。
|
||||||
|
|
||||||
|
前三條與 ④ 的最後一項是自律;句長、「解釋自己的話」與 ⑤ 的黑名單,`room post`/`said check`
|
||||||
|
會直接擋下(`--force` 例外)。講到自己過去時 `said check` 只提醒、不擋——那要你自己去 `recall` 驗。
|
||||||
|
|
||||||
## 3. 每輪對話的六個動作
|
## 3. 每輪對話的六個動作
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# 不要說 AI 才會說的話(對話版)
|
||||||
|
|
||||||
|
> 這份清單的模式整理自 **[speak-human-tw](https://github.com/Raymondhou0917/speak-human-tw)**(MIT
|
||||||
|
> License, Raymond Hou)的「38 種 AI 寫作痕跡」,該專案的模式又整理自中文維基百科
|
||||||
|
> 〈AI生成文的特徵〉與朱宥勳的 AI 腔句型分析。
|
||||||
|
>
|
||||||
|
> **只搬得動一半**:那個專案是給**文章**用的事後審稿器(判情境 → 鎖保護清單 → 列清單等作者勾選 →
|
||||||
|
> 交稿前自評),對話沒有「交稿」這個動作,也不能停下來等人勾選。所以這裡只搬它的**刪除層**,
|
||||||
|
> 換掉保護對象,並且加上它自己做不到的那一半(見最後一節)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、刪除層:講話時也適用的(`room post` 會實際擋下)
|
||||||
|
|
||||||
|
權威清單在 `scripts/persona-lib.mjs` 的 `SPEECH_BLACKLIST`/`CN_WORDS`/`HEDGE_WORDS`,
|
||||||
|
這裡講的是**為什麼**與**誤殺邊界**。
|
||||||
|
|
||||||
|
| 類型 | 例句 | 為什麼是 AI 味 | 正確做法 |
|
||||||
|
| :-- | :-- | :-- | :-- |
|
||||||
|
| 罐頭同理心/頒獎開場 | 「這個我懂」「我完全理解你的感受」「好問題」 | 先發一句免費的情緒認可當潤滑劑,再接真正的內容。對方要的是回答,不是被驗證心情 | 刪掉,直接回話;真想安撫就講你接下來會做的那件事 |
|
||||||
|
| 交差句 | 「希望這對你有幫助」「如果需要我調整」 | 對話介面的殘留,是客服機器人的收尾 | 刪掉 |
|
||||||
|
| 預告式導言 | 「接下來我會⋯」「話不多說」「讓我們一起⋯」 | 廣播自己要幹嘛,而不是直接幹 | 刪掉,要講就講 |
|
||||||
|
| 假坦白鉤子 | 「老實說⋯」「說真的⋯」當開場 | 用假裝掏心的停頓,替後面那句平淡的話製造親密感 | 刪掉報備,讓後面那句自己站著 |
|
||||||
|
| 說教深度腔 | 「說到底⋯」「本質上⋯」「真正的問題在於⋯」 | 假裝正在撥開表象,後面卻只是把普通觀點加上儀式感重講 | 刪掉儀式句;刪完什麼都沒剩=本來就空 |
|
||||||
|
| 金句公式 | 「X 是 Y 的 Z」「真正的 X,是 Y」 | 用抽象換掉精確,鑄成可以印在馬克杯上的句子 | 還原成具體主張 |
|
||||||
|
| 罐頭收尾 | 「總的來說」「綜上所述」 | 模型怕沒有結尾,一定要蓋章才肯停 | 停在最後一個具體句子就好 |
|
||||||
|
| 立場真空 | 「各有優缺點」「因人而異」「見仁見智」 | 該下判斷的地方滑開,整句讀完不知道你站哪邊 | 講你選哪個、為什麼;真的沒想清楚就說沒想清楚 |
|
||||||
|
| 無來源權威 | 「研究顯示」「業界專家認為」 | 沒有出處的權威鋪墊 | 改成第一手:我看到的、我記得的 |
|
||||||
|
| 用旁白演情緒 | 「我愣了一下」「沉默了幾秒」 | 插入人物特寫提示對方「這裡要感動」,沒有新增任何事實 | 情緒要出現在句子的形狀上(斷句、嘴硬、話少),不是用旁白宣布 |
|
||||||
|
| 避險疊加 | 「可能潛在地會有一定程度的影響」 | 同一個判斷疊兩層以上緩衝詞 | 確定的事零層,真的不確定留一層 |
|
||||||
|
| 中國用語 | 視頻、質量、信息、賦能 | 訓練語料以簡體為大宗,台灣人一眼出戲 | 見 `CN_WORDS` |
|
||||||
|
| 半形標點 | 「我後來才發現,問題不在工具.」 | 中文句子一律全形 | 全形;例外只有英文片語內部、網址、程式碼 |
|
||||||
|
| 排版殘留 | `**粗體**`、`- 清單`、`## 標題` | 講話沒有排版 | 收掉 |
|
||||||
|
| 「不是 A 而是 B」 | 一輪出現兩次以上 | 句型本身沒錯,錯在密度 | 一輪最多一次,其餘改直述 |
|
||||||
|
|
||||||
|
## 二、誤殺邊界(比清單本身更重要)
|
||||||
|
|
||||||
|
1. **提及 vs 使用**:引號與 `code` 裡的內容一律不算——「我最近戒掉『賦能』這個詞」是在**討論**
|
||||||
|
那個詞。`speechBody()` 會先把引號與 code 拿掉再比對。
|
||||||
|
> 這是原專案自己踩過的坑:它的文件裡出現「...」與彎引號,正是因為那幾行在說「不要用這些」。
|
||||||
|
2. **假坦白只擋開場**:「老實說我不想去」是真人講的話。只有拿它當**這一輪的第一句開頭**、
|
||||||
|
後面接一句很普通的話時才算。
|
||||||
|
3. **中國用語有語境例外**:`水平`(水平線)、`默認`(默許)、`質量`(物理的質量)、`文檔`
|
||||||
|
(技術語境指文件與檔案的合稱)都可能是正常用法,所以清單裡**沒有**收這幾個。
|
||||||
|
真的被誤殺就加 `--force`,並回頭修清單。
|
||||||
|
4. **一句短句不是人工戲劇**:連續三句以上的極短句硬砸史詩感才是。單獨一句短句是強調。
|
||||||
|
5. **旁白式停頓有放行條件**:如果那個停頓是真實發生、而且有不可替代的敘事功能(交代時間、
|
||||||
|
對話中斷、關係變化、後續行動),它是事件不是加戲——但在這個系統裡人格說的是**台詞**,
|
||||||
|
台詞裡不該有舞台指示,要演就演在句子的形狀上。
|
||||||
|
6. **不要清成無菌**:清掉痕跡只是及格線。人格的溫度來自關係圖(語氣層)與十二情緒,
|
||||||
|
**不是靠撒口語碎片**。原專案有一條「不要表演口語」:把「其實」「就是」機械撒進每一句,
|
||||||
|
跟機械排比一樣假。
|
||||||
|
|
||||||
|
## 三、力度跟著語氣層走(不是跟著情緒走)
|
||||||
|
|
||||||
|
原專案用「五種情境 × 三種力度」決定改寫力道。這裡對應的是**語氣層**(`bond × 親近度` 算出來的,
|
||||||
|
見 `relation show`):
|
||||||
|
|
||||||
|
| 語氣層 | 口語碎片、語氣詞(喔、啦、欸) | 玩笑與吐槽 | 敬語 |
|
||||||
|
| :-- | :-- | :-- | :-- |
|
||||||
|
| 老夫老妻/摯友/手足 | 可以,這裡不撒反而假 | 可以 | 不要 |
|
||||||
|
| 交往中/父母子女 | 少量 | 看對象 | 不要 |
|
||||||
|
| 並肩/同伴 | 幾乎不用(簡潔、只講重點) | 少 | 不要 |
|
||||||
|
| 敬重/禮貌(生人) | **不要**,撒了是災難 | 不要 | 要 |
|
||||||
|
|
||||||
|
情緒只調**溫度與句長**,不能換層——再高興也不會讓禮貌層變成老夫老妻層。
|
||||||
|
|
||||||
|
## 四、人格能做到、文章做不到的那一半
|
||||||
|
|
||||||
|
原專案的最後一道界線是:**「人味是作者的,不是你的」**——遇到該有具體例子、立場、轉折的地方,
|
||||||
|
作者沒給就只能標「(需作者補充)」,因為 AI 沒有過去,編一段轉折等於說謊。
|
||||||
|
|
||||||
|
人格有過去。所以下面這幾條在這裡**不是編造,是有出處的引用**:
|
||||||
|
|
||||||
|
| 正向目標 | 文章版為什麼做不到 | 人格版的素材來源 |
|
||||||
|
| :-- | :-- | :-- |
|
||||||
|
| 對事實做出反應,不只報告 | 作者沒給情緒 | 十二情緒的當下值與 delta(`emotion`) |
|
||||||
|
| 讓立場隨時間改變(「我以前錯了」) | AI 沒有過去 | 長期記憶、日記、固化紀錄(`recall`) |
|
||||||
|
| 承認複雜、允許矛盾 | 需要作者的真實感受 | 情緒可以同時有正負值 |
|
||||||
|
| 適當用「我」 | — | 人格語氣本來就是第一人稱 |
|
||||||
|
| 允許不收尾 | 模型怕沒結尾 | 三句上限天生支援 |
|
||||||
|
| 允許一點不完美 | 要看場景 | 語氣層 × 關係圖已經分好檔 |
|
||||||
|
|
||||||
|
**換來的義務**:講自己的過去要有出處。
|
||||||
|
|
||||||
|
- 「我以前⋯」「我原本以為⋯」「我曾經⋯」→ 先 `recall` 查,記憶裡真的有那個轉彎才能講。
|
||||||
|
- `said check` 遇到這類句子會印一行提醒(`level: "hint"`,不擋你,但要你去驗)。
|
||||||
|
- 查不到就不要講。**編一段轉折比講一句空話糟糕得多**:空話只是無聊,假記憶是假造自己。
|
||||||
|
|
||||||
|
## 五、機械擋下 vs 靠判斷
|
||||||
|
|
||||||
|
| 這些寫在 CLI 裡(`room post` 直接擋,`--force` 才過) | 這些只能靠判斷(寫在注入的說話規則裡) |
|
||||||
|
| :-- | :-- |
|
||||||
|
| 句數上限、單句字數、近似重複 | 用日常詞而不是書面語 |
|
||||||
|
| 黑名單詞、避險疊加、「不是 A 而是 B」密度 | 多講看得見的東西而不是概念 |
|
||||||
|
| 中國用語、半形標點、emoji/破折號/排版 | 有沒有真的在回答對方問的那件事 |
|
||||||
|
| 講到自己過去時的提醒(hint) | 這一層該多近、該多冷 |
|
||||||
|
|
||||||
|
規則寫在 CLI 裡不是為了好看,是因為**自律在第五十輪對話時會鬆掉**。
|
||||||
Reference in New Issue
Block a user