🎯 什麼情境該想到我

當你「寫的是一份靜態的角色卡模板,卻需要在每次送出時填進角色名、時間、最後一則訊息這類會變的值」的時候。

⚙️ 怎麼用

核心想法:靜態模板 × 動態值

角色卡是靜態模板,對話是動態的。宏(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}}。

🧪 我實際套用的紀錄

  • (尚無)

🔗 相關