🎯 什麼情境該想到我
當你「寫的是一份靜態的角色卡模板,卻需要在每次送出時填進角色名、時間、最後一則訊息這類會變的值」的時候。
⚙️ 怎麼用
核心想法:靜態模板 × 動態值
角色卡是靜態模板,對話是動態的。宏(Macro)就是模板裡的 {{宏名}} 佔位符,在 prompt 送出前被替換成實際值:
{{char}}→ 目前角色名(群組聊天時會隨說話者變){{time}}→ 目前時間(每次生成都不同){{lastMessage}}→ 最後一則訊息(隨對話變)
帶參數的宏用 :: 分隔參數,例如 {{roll::1d20}}、{{random::a::b::c}}。
分類速查
| 類別 | 宏 | 說明/範例 |
|---|---|---|
| 名稱 | {{char}} / {{user}} | 角色名 Alice/使用者名 Bob |
{{charIfNotGroup}} | 非群組時為角色名,群組時為空 | |
{{group}} / {{groupNotMuted}} | 群組全部成員(含靜音)/不含靜音 | |
{{notChar}} | 除目前說話者外的所有參與者,如 Bob, Eve | |
| 角色卡欄位 | {{description}} {{personality}} {{scenario}} {{persona}} {{mesExamples}} | 直接引用角色描述、個性、場景、使用者人設、對話範例 |
| 時間日期 | {{time}} {{date}} {{weekday}} | 本地時間/日期/星期 |
{{isotime}} {{isodate}} | 14:30:00、2024-01-15 | |
{{datetimeformat::YYYY-MM-DD HH:mm}} | 自訂格式 | |
{{time::UTC+8}} | 指定 UTC 偏移的時間 | |
{{idle_duration}} | 距上次使用者訊息多久,如 5 minutes ago | |
| 聊天狀態 | {{lastMessage}} {{lastUserMessage}} {{lastCharMessage}} | 最後一則/最後使用者/最後角色訊息 |
{{firstIncludedMessageId}} | 被放進上下文的第一則訊息 ID | |
| 工具 | {{roll::1d20}} | 擲骰,每次生成重擲 |
{{random::a::b::c}} / {{pick::a::b::c}} | 隨機選擇/穩定隨機 | |
{{input}} {{maxPrompt}} {{model}} | 輸入框內容、最大上下文(如 8192)、模型名 | |
{{banned::word}} | 禁詞,TextGen 用,轉成 logit bias | |
{{outlet::key}} | 讀取 World Info 中 position=outlet 的條目 | |
| 變數 | {{setvar::name::value}} {{getvar::name}} {{incvar::name}} {{decvar::name}} | 聊天區域變數 |
{{setglobalvar::...}} 等 | 跨聊天共享的全域變數 | |
| 格式 | {{trim}} {{newline}} {{space}} {{noop}} | 修剪空白/換行/空格/空字串 |
{{//comment}} | 註解,替換時移除 | |
{{original}} | 覆蓋時的原始內容 |
{{random}} 與 {{pick}}:要不要穩定
兩者都從選項中挑一個,差別在重新生成時會不會變:
| 第 1 次 | 第 2 次 | 第 3 次 | regenerate | |
|---|---|---|---|---|
{{random::晴天::雨天::雪天}} | 雨天 | 晴天 | 雨天 | 雪天 |
{{pick::晴天::雨天::雪天}} | 雨天 | 雨天 | 雨天 | 雨天 |
{{pick}} 以「宏所在的位置」當隨機種子,同一位置結果固定。★ 為什麼需要它:角色卡裡寫「今天的天氣是 {{random::…}}」,使用者每按一次重新生成天氣就變,劇情前後矛盾;用 {{pick}} 則一次決定、之後穩定,同時不同位置仍各自隨機。
變數:讓 prompt 有狀態
變數宏讓角色卡或 WI 條目能讀寫一份鍵值儲存:
{{setvar::mood::happy}} → 存 mood="happy",本身替換成空字串
{{char}} is feeling {{getvar::mood}}. → "Alice is feeling happy."
{{incvar::counter}} → counter += 1
{{setglobalvar::theme::dark}} → 全域存 theme="dark"
- 區域變數:只存在於目前這段聊天;
- 全域變數:跨聊天共享。
★ setvar 替換成空字串這點很重要——寫入動作不會在 prompt 留下痕跡、也不耗 token。配合 WI 的關鍵詞觸發,可以做出「提到某事件時累加計數」這類簡易狀態機。
{{original}}:覆蓋時保留原文
只在覆蓋情境有意義。例如角色卡的 system_prompt 想「沿用全域 Main Prompt,再補一句」:
{{original}}
Additionally, {{char}} always speaks in rhyme.
效果 = 全域 Main Prompt 原文 + 角色專屬指令。沒有它的話,角色卡一旦覆蓋系統提示,就得把全域內容整段複製進來,之後全域改了也不會同步。
{{//comment}}:模板裡的註解
{{//這段註解不會出現在 prompt 中}}
{{char}} says hello.
替換時直接移除,不耗任何 token,適合在角色卡裡留給作者自己的說明。
替換時機:不是一次性全文替換
宏在 pipeline 的多個階段分別處理:
| 時機 | 處理範圍 | 原因 |
|---|---|---|
| 讀取角色卡欄位 | {{char}} {{user}} 等名稱宏 | 角色描述中的名字要立即確定 |
| World Info 條目激活後 | 所有宏 | WI 內容可能含動態值 |
PromptManager.preparePrompt() | 每個 prompt 區段 | 統一處理系統提示 |
| Instruct Mode 序列 | 名稱宏(macro=true 時) | 序列中的 {{char}} 要替換 |
| 正則腳本(substituteRegex≠0) | 所有宏 | 讓正則能動態匹配 |
★ 分階段的理由:不同內容「何時確定」不同。WI 要等掃描完才知道哪些條目激活,替換只能在激活之後;名稱則在讀卡時就能定。
⚠️ 注意 / 什麼時候不適用
- 有副作用的宏會受替換次數影響:
{{incvar}}、{{roll}}、{{random}}每被處理一次就執行一次;若同一段文字在多個階段被替換,或 regenerate 重跑,數值可能不如預期(推測)。設計計數器前先確認該文字會被處理幾次(原文件未說明去重機制)。 {{random}}會破壞可重現性,需要前後一致的設定改用{{pick}}。{{banned::word}}是給 TextGen 用的,會轉成 logit bias;其他後端是否有效,原文未說明。- (推測,原文未提)宏名打錯可能不會報錯,佔位符原樣或空白留在 prompt;除錯時要看最終送出的 prompt。
- 群組聊天時
{{char}}會隨說話者改變;想寫「非群組才出現」的句子要用{{charIfNotGroup}}。
🧪 我實際套用的紀錄
- (尚無)
🔗 相關
- 提示覆蓋與系統提示預設——
{{original}}的應用情境 - World Info世界書——
{{outlet::key}}與 WI 激活後的宏替換 - 正則替換引擎——
substituteRegex控制正則中的宏替換