Skip to content

巨集

巨集(Macro)是寫成 {{name}} 形式的占位符,在 prompt 組裝時被替換為動態內容。它們出現在預設、世界書、角色卡、聊天訊息、斜線指令、正則替換裡——任何會被拼裝進 prompt 的文字欄位都能用巨集。請求到達 AI 時,所有的巨集都已經被替換成最終字串。

Macros 2.0 / 實驗性巨集引擎

本頁的特性跑在 chevrotain 實作的新巨集引擎上——也就是 SillyTavern 引入的 Macros 2.0。Luker 預設啟用它,可以在「使用者設定 → 聊天/訊息處理 → 實驗性宏引擎」切換。

實驗性引擎關掉時,巨集仍然能用,但下面這些特性會退化到老的正則管線、不再可用:

特性需要實驗性引擎
{{if}} / {{else}} / {{each}} 控制流
範圍巨集,如 {{setvar::k}}body{{/setvar}}
變數簡寫({{.var}}{{$var}}、運算式)
旗標(#/、……)
巨集參數內巢狀巨集
範圍巨集內容保留前導空白
多遍展開時的穩定替換順序(從左到右、內層先於外層)
一般 {{user}}{{setvar::name::value}}{{time}}……兩種都能用

本頁裡某個特性看起來不生效,先檢查這個開關。

語法

巨集的形態是 {{name}}{{name::arg}},或 {{name::arg1::arg2}}。巨集名大小寫不敏感——{{user}}{{User}}{{USER}} 解析到同一個巨集。花括號和巨集名之間允許空白:{{ user }} 等價於 {{user}}

巨集識別子必須符合 ^[a-zA-Z][\w-_]*$——字母開頭,後接字母、數字、底線或連字號。唯一的例外是註解巨集 {{//}}

參數

參數之間用 :: 分隔:

text
{{setvar::hp::50}}
{{datetimeformat::YYYY-MM-DD HH:mm:ss}}
{{roll::3d6+4}}

單個 : 作為兜底也認({{roll: 1d20}}),但因為正文裡經常出現單冒號,推薦用 ::

逗號列表巨集({{random}}{{pick}})兩種寫法都行:

text
{{random::red::green::blue}}
{{random::red,green,blue}}

列表項裡要寫字面意義的逗號,跳脫為 \,

巢狀巨集

巨集可以巢狀。內層先解析,結果作為外層的參數:

text
{{setvar::greeting::Hello, {{user}}!}}
{{if {{getvar::showHeader}}}}# Header{{/if}}
{{each::{{getvar::roster}}}}- {{loop_value::name}}{{/each}}

巢狀深度沒有硬上限,但巢狀太深通常是巨集在做太多事——拆成中間變數。

範圍巨集

部分巨集({{if}}{{each}}{{trim}}{{//}},以及大多數接受參數的自訂巨集)支援「開 / 閉標籤 + 中間內容」的寫法。中間的內容會作為最後一個位置參數傳給巨集:

text
{{if .ready}}
角色已就緒。
{{/if}}

{{each::npcs}}
- {{loop_key}}: {{loop_value::hp}} HP
{{/each}}

預設情況下,範圍巨集的內容會被自動整理——首尾空白被剝掉,首個非空行的共同前導縮排會從所有行裡減掉。要原樣保留空白,在巨集名前加 # 旗標:

text
{{#if .verbose}}
   這一段的縮排和首尾空白都會原樣保留。
{{/if}}

跳脫

要輸出字面的雙花括號,前面加反斜線:

text
\{{not a macro}}

反斜線在後處理階段被去掉,原始的 {{...}} 字串原樣到達 AI。這是在 prompt 裡教模型巨集語法而不讓範例真的被執行的官方寫法。

老式標籤

5 個非花括號的老式標籤,會在巨集解析前被自動重寫:

老式現代等價
<USER>{{user}}
<BOT> / <CHAR>{{char}}
<GROUP> / <CHARIFNOTGROUP>{{group}} / {{charIfNotGroup}}

新內容用花括號寫法。老式標籤為向後相容保留。

{{time_UTC+N}} 同樣會被重寫為 {{time::UTC+N}}

變數簡寫

除了完整的 {{getvar::name}} / {{setvar::name::value}},Luker 還提供一套變數運算式簡寫,讀起來跟賦值語句一樣:

寫法含義回傳
{{.name}}區域變數目前值
{{$name}}全域變數目前值
{{.name = 5}}賦值''(空字串)
{{.name += 1}}加(數值加 / 字串拼接)''
{{.name -= 1}}減(僅數值;非數值會告警)''
{{.name++}}自增''
{{.name--}}自減''
{{.name ?? "default"}}取值,未定義時取預設值或預設
{{.name || "default"}}取值,falsy 時取預設值或預設
{{.name ??= "x"}}未定義時才賦值新值
{{.name ||= "x"}}falsy 時才賦值新值
{{.name == 5}}字串相等"true" / "false"
{{.name != 5}}字串不等"true" / "false"
{{.name > 5}}數值 >"true" / "false"
{{.name >= 5}}數值 >="true" / "false"
{{.name < 5}}數值 <"true" / "false"
{{.name <= 5}}數值 <="true" / "false"

. 永遠指 chat 區域變數,$ 永遠指全域變數。兩個作用域不共用命名。

變數運算式可以和控制流組合:

text
{{if .hp <= 0}}
你死了。
{{else}}
你還剩 {{.hp}} 點 HP。
{{/if}}

變數名允許底線和連字號,但最後一個字元必須是 word 字元:my-var 合法,my- 不合法(會和 -- 自減運算子衝突)。

預設值惰性求值

??|| 只在變數缺失 / falsy 時才會求 fallback 運算式。這能讓你跳過昂貴的預設值:{{.cachedSummary ?? {{summary}}}} 只在快取未命中時才會呼叫 {{summary}}

控制流

條件 — {{if}} / {{else}}

text
{{if condition}}then-content{{/if}}
{{if condition}}then{{else}}otherwise{{/if}}
{{if !condition}}negated{{/if}}

condition 可以是:

  • 字面值——空字串、falseoff0 視為 falsy;其他都 truthy。
  • 已註冊的巨集名(不帶花括號)——{{if description}}# Description{{/if}} 會先把 description 解析出來。
  • 巢狀巨集——{{if {{getvar::showHeader}}}}...{{/if}}
  • 變數簡寫——{{if .ready}}{{if $debugFlag}}
  • 變數運算式——{{if .hp > 0}}
  • 以上任何一種前面加 ! 取反——{{if !.dead}}

分支惰性求值

只有命中的那個分支裡的巢狀巨集才會被解析。{{if .casting}}{{.mana -= 10}}{{/if}}.casting 為 false 時不會扣魔。把 {{if}} 套在昂貴或有副作用的巨集外面是安全的。

迭代 — {{each}}

text
{{each::collection}}
{{loop_key}}: {{loop_value}}
{{/each}}

collection 接受三種形式:

  1. 內嵌 JSON 字面量——{{each::["sword","shield"]}}{{each::{"a":1,"b":2}}}
  2. 變數名——{{each::npcs}} 讀區域變數 npcs(找不到 fallback 到全域),並把字串當 JSON 解析。
  3. 解析結果為集合的巢狀巨集——{{each::{{getglobalvar::roster}}}}

body 裡:

巨集含義
{{loop_key}}目前 key(陣列時是字串形式的索引)
{{loop_value}}目前值整體(物件會自動 JSON 字串化)
{{loop_value::field}}用點號路徑深入值,語義同 {{getvar}}

巢狀 {{each}} 會自然遮蔽外層的 loop_key / loop_value。物件的迭代順序遵循 JavaScript 原生規則——字串 key 走插入順序,整數樣 key 走升序。

空集合或不可迭代值繪製為空字串。引擎沒有對迭代次數加保護——body 裡再次 {{each}} 自己負責防爆。

註解 — {{//}}

行內:

text
{{// 這一行被忽略}}

範圍形式:

text
{{//}}
多行註解。
裡面可以寫 {{巨集}} 和 \{跳脫\}——什麼都不會執行。
{{///}}

註解解析為空字串,內容原樣吃掉。給 prompt 裡要標註但不想讓 AI 看見的部分用。

變數

變數分兩種作用域:

  • 區域——按聊天獨立儲存,持久化在 chat_metadata.variables 裡。用來存聊天專屬狀態(HP、目前任務、回合數)。
  • 全域——跨所有聊天共享,存在擴充設定裡。用來存跨 chat 計數器、外掛設定。

巨集變數簡寫用途
{{getvar::name}}{{.name}}讀區域
{{getglobalvar::name}}{{$name}}讀全域
{{hasvar::name}}(別名 varexists"true" / "false"
{{hasglobalvar::name}}(別名 globalvarexists"true" / "false"

不存在的變數回傳空字串。

巨集變數簡寫用途
{{setvar::name::value}}{{.name = value}}設定區域
{{addvar::name::value}}{{.name += value}}加(數值)/ 拼接(字串)/ push(JSON 陣列)
{{incvar::name}}{{.name++}}+1
{{decvar::name}}{{.name--}}-1
{{deletevar::name}}(別名 flushvar刪除

全域對應的有 setglobalvar / addglobalvar / incglobalvar / decglobalvar / deleteglobalvar

addvar 是重載的:兩側都是數值字串時做數值加;現有值是 JSON 陣列時 push;否則按字串拼接。

{{noop}} 錨住空白

拼接、範圍巨集 body、+= " text" 等場景下,前導/尾端空白經常被引擎的自動整理或參數 trim 吃掉。在要保留的空白前後插一個 {{noop}}(解析為空字串)就能錨住它。例如 {{addvar::story::{{noop}} 這是新的一段。}}

結構化值的點號路徑

變數可以存任何 JSON-可序列化的值。當變數存的是 JSON 字串化的物件 / 陣列時,點號路徑直接生效:

text
{{setvar::npcs::{"alice":{"hp":40},"bob":{"hp":30}}}}
{{getvar::npcs.alice.hp}}    → 40
{{getvar::npcs.alice}}        → {"hp":40}
{{getvar::list.0}}            → list 的第一個元素

對非 JSON 值用點號路徑時,會 fallback 到字面鍵查找——名字真的就是 a.b 的變數也能讀到。

這套和 {{each}} 配合很自然:一個 NPC 名冊、一份背包字典、一本任務日誌都可以塞進單一變數,每輪 prompt 組裝時再繪製到 prompt 或世界書條目裡:

text
{{each::npcs}}
- {{loop_key}}: {{loop_value::hp}} HP
{{/each}}

逐樓層變數

原生 SillyTavern 裡,副作用巨集 {{setvar::hp::50}} 只在 prompt 範本 裡(預設、世界書、首樓)才會執行。AI 在回覆裡寫同樣的字面量什麼都不會發生,還會原樣顯示出來污染敘事。

Luker 用逐樓層變數提取解決這個問題。一條訊息(AI 回覆、使用者訊息、swipe、續寫)儲存時,Luker 會:

  1. 掃描文字裡的 {{setvar}}{{addvar}}{{incvar}}{{decvar}}{{deletevar}}
  2. 把裡面巢狀的展示巨集({{user}}{{getvar::other}}{{time}}……)對目前狀態求值。
  3. 把 op 立刻 apply 到 chat_metadata.variables
  4. message.extra.var_ops 上追加一條結構化記錄。
  5. 從可見文字裡把字面量刪掉。

當你刪訊息、切 swipe、重新生成、編輯時,Luker 會重播剩餘的 op log,讓變數狀態跟可見的時間線保持一致。

這就是「逐樓層變數」面板背後的機制——每條帶 op 的訊息按鈕欄會出現一個燒瓶圖示,點開可以查看 / 編輯 / 刪除 / 新增 op。結果就是 AI 能直接在自己回覆裡擁有和修改狀態,而這個狀態能扛住使用者慣常的所有結構性操作。

完整的特性頁面(重播語義、swipe 生命週期、op 編輯器、推薦的創作範式)見 逐樓層變數

全域變數不會被提取

{{setglobalvar}} 這一家不在逐樓層提取的範圍內——全域狀態是跨 chat 的,跟某條訊息綁不到一起。它們保留原生 SillyTavern 的語義。

內建巨集目錄

/? macros 會打開應用內瀏覽器,裡面是同一份描述、能搜尋、能看即時簽名。

名稱與參與方

巨集回傳
{{user}}目前 persona 名
{{char}}目前角色名
{{group}}(別名 charIfNotGroup群聊成員名字(逗號分隔),單聊時是角色名
{{groupNotMuted}}group 但排除被靜音的成員
{{notChar}}除目前發言者之外的所有參與方

角色卡欄位

巨集回傳
{{charDescription}}(別名 description描述欄位
{{charPersonality}}(別名 personality人格欄位
{{charScenario}}(別名 scenario場景欄位
{{persona}}目前 persona 描述
{{charPrompt}}主提示詞 override
{{charInstruction}}Post-History Instructions override
{{charDepthPrompt}}@ Depth Note
{{charCreatorNotes}}(別名 creatorNotes作者筆記
{{charVersion}}卡片版本號
{{charFirstMessage}}(別名 greeting主問候語(索引 0)
{{greeting::N}}第 N 條問候語——0 是主,1+ 是 alt greetings
{{mesExamples}}對話範例,啟用 instruct 時自動按 instruct 格式化
{{mesExamplesRaw}}對話範例原文
{{original}}角色級 override 裡的原始 prompt 內容。單次有效——同一次替換裡第二次呼叫回傳 ""。只在 charPrompt / charInstruction 內有意義。

聊天狀態

巨集回傳
{{lastMessage}}最後一條訊息的文字
{{lastMessageId}}最後一條訊息的索引
{{lastUserMessage}}最後一條使用者訊息文字
{{lastCharMessage}}最後一條角色訊息文字
{{firstIncludedMessageId}}目前 context 視窗裡第一條訊息的索引
{{firstDisplayedMessageId}}聊天捲動區裡第一條可見訊息的索引
{{lastSwipeId}}最後一條訊息的 swipe 數(1-based)
{{currentSwipeId}}目前活躍 swipe 的索引(1-based)
{{allChatRange}}形如 0-12 的全訊息範圍字串,空 chat 時是空字串
{{idleDuration}}(別名 idle_duration距上一次使用者訊息的可讀時長
{{lastGenerationType}}normal / impersonate / regenerate / quiet / swipe / continue

時間與日期

巨集回傳
{{time}}本地時間(locale 短格式,如 3:45 PM
{{time::UTC+2}}指定 UTC 偏移的本地時間
{{date}}本地日期(locale 長格式)
{{weekday}}星期名(Monday、……)
{{isotime}}HH:mm
{{isodate}}YYYY-MM-DD
{{datetimeformat::FORMAT}}用 moment.js 格式字串繪製目前時間
{{timeDiff::A::B}}兩個時間之間可讀的絕對差

變數

完整列表見上面的 變數 一節。

控制流與工具

巨集回傳
{{if cond}}…{{/if}}條件內容
{{else}}{{if}} 裡標記 else 分支
{{each::col}}…{{/each}}迭代
{{loop_key}} / {{loop_value}} / {{loop_value::path}}{{each}} body 裡
{{trim}}行內:後處理時把巨集周圍的換行刪掉。範圍形式:回傳剝掉首尾空白後的內容
{{newline}} / {{newline::N}}1 個或 N 個換行
{{space}} / {{space::N}}1 個或 N 個空格
{{noop}}空字串。在拼接 / 範圍巨集裡作為空白錨點很好用
{{reverse::text}}反轉字串
{{//comment}}(別名 comment註解(輸出為空)

隨機

巨集回傳
{{roll::1d20}}用 droll 語法擲骰(1d63d6+4……)。只有數字 N 時等價 1dN。每次繪製都重擲。
{{random::red::green::blue}}隨機一項。每次繪製都重擲。
{{pick::red::green::blue}}隨機一項,但對同一 chat 同一位置穩定。Seed = chat hash + content hash + 位置 + reroll seed。用 /reroll-pick 重置。

環境與 API

巨集回傳
{{model}}目前 model 識別字
{{maxPrompt}}(別名 maxPromptTokensprompt 上下文 token 上限
{{maxContext}}(別名 maxContextTokens上下文 token 上限
{{maxResponse}}(別名 maxResponseTokens回應 token 上限
{{isMobile}}行動裝置時 "true"
{{hasExtension::name}}指定擴充已載入啟用時 "true"
{{input}}目前輸入框內容
{{outlet::key}}指定 key 的世界書 outlet 內容
{{banned::word}}給 Text Completion 後端註冊一個 banned word;回傳空

作者筆記與摘要

巨集回傳
{{authorsNote}}目前 chat 生效的作者筆記
{{charAuthorsNote}}角色級作者筆記
{{defaultAuthorsNote}}預設作者筆記
{{summary}}聊天摘要——只在 Summarize 擴充載入後才註冊

推理範本

巨集回傳
{{reasoningPrefix}}推理段前綴
{{reasoningSuffix}}推理段後綴
{{reasoningSeparator}}推理與答案之間的分隔

Instruct 與系統提示詞

下面這些只在對應的 instruct / sysprompt 開關打開時回傳內容。

巨集回傳
{{systemPrompt}}目前生效的系統提示詞。啟用 Prefer Character Prompt 時會切到 {{charPrompt}}
{{defaultSystemPrompt}}(別名 instructSysteminstructSystemPrompt設定的預設系統提示詞
{{instructStoryStringPrefix}} / {{instructStoryStringSuffix}}story string 包裹
{{instructUserPrefix}}(別名 instructInput) / {{instructUserSuffix}}使用者回合 sequence
{{instructAssistantPrefix}}(別名 instructOutput) / {{instructAssistantSuffix}}(別名 instructSeparator助理回合 sequence
{{instructFirstAssistantPrefix}}(別名 instructFirstOutputPrefix) / {{instructLastAssistantPrefix}}(別名 instructLastOutputPrefix首 / 末助理變體
{{instructFirstUserPrefix}}(別名 instructFirstInput) / {{instructLastUserPrefix}}(別名 instructLastInput首 / 末使用者變體
{{instructSystemPrefix}} / {{instructSystemSuffix}}系統回合 sequence
{{instructSystemInstructionPrefix}}last-system sequence
{{instructStop}}stop sequence
{{instructUserFiller}}對齊填充
{{exampleSeparator}}(別名 chatSeparator) / {{chatStart}}context 範本標記

擴充註冊

只在對應擴充安裝且啟用時才存在:

巨集來源回傳
{{charPrefix}}Stable Diffusion正面圖像生成 prompt 前綴
{{charNegativePrefix}}Stable Diffusion負面圖像生成 prompt 前綴
{{summary}}Summarize儲存的聊天摘要

第三方擴充可以註冊更多巨集,巨集瀏覽器會標出每個巨集的來源。

旗標

旗標是放在開頭 {{ 和巨集名之間的單字元,用來改變巨集的解析或求值行為。

/ — 閉合標記

標記一個標籤是範圍巨集的閉合半邊。和同名的開標籤配對,把兩者之間的內容作為巨集的最後一個位置參數傳入:

text
{{if .ready}} ...body... {{/if}}
{{each::npcs}} ...body... {{/each}}
{{setvar::longText}} ...body... {{/setvar}}

閉合標籤自身不接受參數。

# — 保留空白

只對範圍巨集有效。 預設情況下,引擎會先對 body 做自動整理(剝掉首尾空白,再把第一個非空行的共同前導縮排從所有行裡減掉)才交給 handler。加上 # 旗標會跳過這一步,body 原樣傳入:

text
{{#if .verbose}}
    這 4 空格縮排
    以及首尾的換行都會原樣保留。
{{/if}}

這個旗標也相容老的 Handlebars 風格寫法 {{#if …}} —— 跟 {{if …}} + 不自動 trim 等價。

加在非範圍巨集前面時,# 被接受但沒有任何效果。

! ? ~ > — 預留未實作

旗標預期含義現狀
!比同段文字裡的其他巨集先解析僅解析
?比其他巨集後解析僅解析
~標記為可重新求值僅解析
>| 當輸出過濾器的管道僅解析(見下文 管道符

這些 token 解析器能識別,但執行時沒有任何 hook 消費它們。{{if !.dead}} 裡的 ! 是另一回事——那是 {{if}} 內部的條件取反,不是這裡的旗標。

旗標之間、旗標和巨集名之間都允許空白,多個可以組合:{{ #each ::list}} … {{/each}}

| — 管道符(參數終止符)

管道符 \| 在巨集參數裡有特殊語義,即使沒加 > 旗標也一樣。lexer 看到 \| 就會從「參數」模式切走,所以:

text
{{getvar::name|filter}}

…會被解析為 getvar 巨集 + 單個參數 name + 一個名為 filter 的「過濾器」識別字。過濾器執行鏈沒接上,所以 filter 這個名字會被丟掉,整個巨集的行為等價於 {{getvar::name}}。直接後果是:參數裡出現字面 \| 會把那個參數截斷——想要在參數裡保留字面管道符,跳脫成 \|

text
{{setvar::menu::sword \| shield \| bow}}

管道符是預留語法

今天寫 {{macro|uppercase}}不會把內容轉大寫——只是不報錯地解析掉、丟掉過濾器名、把管道符前面的參數交給巨集。要做字串變換,寫一個註冊巨集,或者用 regex 擴充。完整的管道過濾鏈留給將來的引擎版本。

斜線指令裡的管道巨集 —— {{pipe}}{{var::name}}

斜線指令閉包裡(STscript —— /command1 | /command2 | … 鏈式呼叫、Quick Reply 等),斜線指令作用域會再綁兩個巨集:

巨集作用域含義
{{pipe}}斜線指令閉包上一條指令的輸出
{{var::name}}斜線指令閉包/let / /var 建立的閉包內變數。和聊天變數(用 {{getvar::name}} 存取)不是一回事

這兩個只在斜線指令解析器裡存在。不在 STscript 脈絡裡出現時,它們會原樣輸出。

STscript 裡的 \| 是指令管道符,那是指令解析器的特性,不是巨集引擎的。在單個巨集的參數內部,\| 按上面管道符那一節的規則處理。

解析語義

引擎一次性掃每段文字,從左到右展開巨集。

  • 巢狀巨集先解析,外層拿到的是已經求值完的字串參數。除非該巨集定義打開 delayArgResolution(目前只有 {{if}}{{each}})。
  • 副作用巨集{{setvar}}{{addvar}}{{incvar}}{{decvar}}{{deletevar}})立即生效,同一 pass 裡後面的巨集能讀到新值。
  • 逐樓層提取(見 逐樓層變數)發生在訊息儲存時,不是 prompt 組裝時。
  • 未知巨集保留原始 {{...}}(巢狀參數仍然會展開)。不拋錯也不告警。
  • 參數數量 / 類型不符:預設 strictArgs: true 時記一條 runtime warning 並保留原始巨集文字;strictArgs: false 時記 warning 但 handler 仍然跑。
  • 結果規範化——每個 handler 的回傳都會被規範化:null / undefined''Date → ISO 字串,陣列 / 物件 → JSON.stringify(...),其他 → String(...)。這就是 {{loop_value}} 對一個物件會輸出 JSON 的原因。
  • 後處理清掃——殘留的 {{trim}} 標記和零散的 else 哨兵字元會在後處理裡被清掉。

自訂與外掛巨集

擴充可以註冊自己的巨集:

js
const ctx = Luker.getContext();

ctx.macros.register('myStatus', {
    description: '回傳外掛狀態字串。',
    category: 'utility',
    handler: () => 'My plugin is active.',
});

ctx.macros.register('greet', {
    description: '問候。',
    unnamedArgs: [
        { name: 'name', type: 'string', description: '問候對象' },
    ],
    handler: (mctx) => `Hello, ${mctx.unnamedArgs[0]}!`,
});

註冊之後 {{myStatus}}{{greet::Bob}} 就能在所有支援巨集的位置使用,包括世界書、預設、聊天訊息。

完整的註冊 API——參數類型、別名、範圍巨集、惰性解析、dynamicMacros 單次注入——在 Extension API · 巨集與變數

探索與除錯

  • 在任何解析巨集的欄位裡輸入 {{ 會觸發自動補全。任何位置按 Ctrl+Space 也能召出來。
  • 設定 → 自動補全設定 → 在所有巨集欄位中顯示 裡把全域補全打開。
  • 執行 /? macros(或 /? macro / /? 4)打開 巨集瀏覽器——一個可搜尋的彈窗,列出所有註冊的巨集、分類、別名、參數、回傳類型、範例、來源(核心 / 擴充 / 第三方)。
  • 執行 /reroll-pick 重置目前 chat 裡所有 {{pick}} 的結果。可傳一個種子(/reroll-pick mySeed)以重現。

巨集在哪些欄位裡生效

凡是會被 prompt 管線吃進去的文字欄位:

  • 角色卡欄位(description、personality、scenario、first message、alt greetings、對話範例、@ Depth Note、character prompts)
  • 世界書條目正文、key、header
  • 預設裡的提示詞條目
  • 作者筆記
  • 聊天訊息(使用者和 AI),帶逐樓層變數提取
  • 斜線指令參數、Quick Reply 腳本
  • 正則擴充的替換內容
  • 巨集自己的參數(巢狀)

那些只存字面文字、不會到 model 的欄位——比如連線設定裡的 API URL——不會解析巨集。拿不準時用 {{date}} 試一下:如果顯示成字面的 {{date}},那個欄位就不跑巨集。

常見範式

預設裡的條件段

text
{{if .difficulty == "hard"}}
世界冷酷無情,失敗不可逆。
{{else}}
世界寬容,死亡只是挫折,不是終點。
{{/if}}

一個能扛住 swipe 和刪除的計數器

在角色卡的 first_mes 或 alt greeting 裡初始化變數:

text
{{setvar::turn::0}}

在 AI 回覆裡(透過一條指示 AI 寫這個的世界書條目),讓 AI 自己 incr:

text
{{incvar::turn}}

逐樓層提取會把字面量刪掉、把 op 記下來,使用者看到的是乾淨敘事、變數正常遞增。刪那條訊息時,重播會把 op 撤掉。

對目前 chat 穩定的隨機選擇

text
{{pick::酒館鬥毆::一個安靜的夜晚::不速之客}}

擲一次後,對該 chat 的該位置就鎖住了——這種「今天的氛圍」式的隨機就該在重繪製裡不抖。

繪製一個結構化集合

text
# 進行中的任務
{{each::quests}}{{if {{loop_value::status}} == "active"}}
- {{loop_value::name}}
{{/if}}{{/each}}

AI 用 {{setvar::quests::…}} 在回覆裡維護 quests 結構;上面那段寫在世界書裡,每輪 prompt 時按目前狀態鋪開。

根據 flag 切換的作者筆記

text
{{if $debug_verbose}}
[敘事聲音:把推理過程講得格外清楚]
{{/if}}

從 Quick Reply 或斜線指令切的全域 flag,成為一個可選的指令,只在需要時隨 prompt 一起發出去。

參考

基於 SillyTavern 建構