Skip to content

AI 指令与宏 ​

AI 负责写故事。故事里夹着一些很短的指令,比如 [health: -15]、[gold: +50],引擎会在玩家看到之前把它们拿掉。玩家读到的是干净的故事,游戏状态在幕后跟着变。

宏的方向正好相反:它是你写在设定里的占位符,比如 {{char}}、{{user}},引擎会在 AI 读到之前把它们填好。

还没看过 变量 的话,先去看那一页。知道了指令改的是什么,指令才好理解。

哪些变量用指令

打开了 精准追踪 的变量(新建的数值和开关变量默认就开着)由追踪小助手在每次回复后去改,不走指令。AI 看它们是只读的,它给这些变量写的指令会被丢掉。AI 能改的其它变量都靠指令来改:没开精准追踪的变量、没填可选值的文字变量,以及列表/表格变量。在 AI 权限 里设成 AI 只读 或 仅引擎(对 AI 隐藏) 的变量,同样不认指令。


为什么要有指令 ​

桌面 RPG 里,主持人说「哥布林砍中你的手臂,扣 15 点血」,玩家就在角色卡上擦掉一个数字重写。在 Yumina 里,AI 是主持人,引擎是那张角色卡。

AI 当然可以直接写「玩家现在有 85 点生命」,但那样引擎就得去解析自然语言,弄清楚是哪个变量变了,再算出新值。这很脆弱,也不可靠。

所以 AI 会在叙述的最后写一个结构化的方括号:

哥布林的爪子划过你的前臂,留下一道火辣辣的疼。
[health: -15]

引擎看到 [health: -15],减去 15,按最小值/最大值卡住,再把这条指令从文字里删掉。玩家只看到故事,状态面板也跟着更新。

AI 不用自己算数,也不用记上下限,这两件事都由引擎来做。


指令语法 ​

每条指令都是同一个格式:

[variable-id: operation value]

方括号里有三部分:哪个变量、做什么、用什么值。

全部九种操作 ​

操作语法示例效果
Set[var: set value] 或 [var: value][location: set "forest"]整个替换变量的值
Add[var: add N] 或 [var: +N][gold: +50]给数值变量加 N
Subtract[var: subtract N] 或 [var: -N][health: -15]给数值变量减 N
Multiply[var: multiply N] 或 [var: *N][damage: *2]把数值变量乘以 N
Toggle[var: toggle][has-key: toggle]翻转开关(true 变 false,false 变 true)
Append[var: append "text"][log: append ", found key"]在文字变量末尾接一段文字
Merge[var: merge {...}][stats: merge {"level": 2}]把一个对象浅合并进 JSON 变量
Push[var: push {...}][inventory: push {"name": "Sword"}]往 JSON 数组末尾加一个元素
Delete[var: delete "key"] 或 [var: delete N][config: delete "old-field"]、[inventory: delete 0]从 JSON 对象里删掉一个键(带引号的字符串),或者按下标从 JSON 数组里删掉一个元素(数字)。delete 后面必须带值,光写 [var: delete] 会悄悄变成一次隐式 set。

写进 JSON 变量内部的路径。 [relations.chars.lin: merge {...}] 会写进 JSON 变量内部。键还不存在时,这次写入会把它建出来:merge 建出对象,push 建出只有一项的列表,add/subtract 从 0 开始算。路径经过一个普通值时(hp 是数字却写 hp.max)会跳过,不会覆盖。数组元素写成 list.0.hp 或 list[0].hp。

值的写法 ​

  • 数字:直接写,10、3.5、-7
  • 字符串:用双引号,"forest"、"magic sword"
  • 布尔值:直接写关键字,true、false
  • JSON:直接写对象或数组,{"level": 2}、[1, 2, 3]

简写的坑 ​

新手最容易在这里搞混:

[health: -10]     → 减 10(subtract 的简写)
[health: +10]     → 加 10(add 的简写)
[health: *2]      → 乘以 2(multiply 的简写)
[health: 10]      → 把 health 设成 10(隐式 set,不是加)

开头的符号(-、+、*)决定是什么操作。没有符号就是 set。 所以 [health: 50] 不是加 50,是把当前值换成 50。

想明确设一个负数(很少见,但可以),写 [health: set -10]。

用 ID 还是显示名 ​

指令里可以用变量的 ID(player-hp),也可以用 显示名(Player HP)。引擎有一张名字到 ID 的对照表,两种都认。ID 更可靠,尤其是显示名里有空格的时候。

JSON 变量的嵌套路径 ​

JSON 变量有嵌套结构时,AI 可以用点号定位到某个字段:

[relationships.aria.trust: +10]
[game-state.factions.ember-court.affinity: set 75]
[inventory.0.durability: -1]

引擎会自己沿着路径找下去,中间的对象不存在就建出来。所以 AI 可以只改复杂对象深处的一个字段,别的都不动。


音频指令 ​

音频用的是类似的方括号语法:[audio: track-id action],动作有 play、stop、crossfade、volume,还能把音轨串起来。完整的音频指令和例子见 音频设计。


JSON Patch 格式 ​

做复杂操作的时候,尤其是从数组里删东西,引擎支持一种用 XML 包起来的 JSON Patch 格式。从 SillyTavern 迁过来的世界用的也是这种格式。

xml
<UpdateVariable target="inventory">
<JSONPatch>
[
  {"op": "remove", "path": "/0"},
  {"op": "replace", "path": "/1/durability", "value": 5}
]
</JSONPatch>
</UpdateVariable>

支持四种 patch 操作:

操作作用示例
replace把某个路径设成一个值{"op": "replace", "path": "/health", "value": 80}
delta加或减(正数加,负数减){"op": "delta", "path": "/gold", "value": -50}
insert加一个新的键值对{"op": "insert", "path": "/inventory/torch", "value": 1}
remove删掉一个键或数组元素{"op": "remove", "path": "/0"}

路径用斜杠写(/health、/inventory/0),引擎内部会转成点路径。

什么时候用 JSON Patch,什么时候用方括号指令

大多数时候方括号指令更简单,AI 写得也更稳。JSON Patch 适合两种情况:

  1. 按下标删数组元素:[inventory: delete 0] 也能用,但处理数组时,有些模型写 JSON Patch 的 {"op": "remove", "path": "/0"} 更稳定。

  2. 一次改同一个变量的好几处:要一次更新一个 JSON 变量的好几个字段时,一个 JSON Patch 块比五条单独的方括号指令清爽。

如果你在给 AI 写行为规则,告诉它用哪种格式。大多数世界只用方括号指令。只有确实要删数组元素、而方括号写法又不够稳的时候,才引入 JSON Patch。


引擎怎么处理 AI 的输出 ​

AI 回复回来之后,引擎会先过一遍解析流程,玩家才看得到。知道这个顺序,指令没被识别的时候好查原因。

第 1 步:去掉思考标签。 有些模型(比如 Gemini)会把内部推理写在 <thinking>...</thinking> 里,引擎先把这些删掉。

第 2 步:提取 JSON Patch 块。 引擎找出 <UpdateVariable> XML 块,转成内部的效果操作。有些模型会把指令包在一个 json 代码块里,这些也在这一步被找出来。

第 3 步:提取音频指令。 所有匹配 [audio: ...] 的内容被拿出来,交给音频系统排队。

第 4 步:提取 JSON 指令。 带 JSON 值的方括号指令(set、merge、push、delete)被拿出来。它们要先处理,因为 JSON 内容里可能有会让简单正则认错的字符。[image: …] 这种嵌图不动。

第 5 步:提取普通指令。 所有匹配 [var: op value] 的内容,也就是 add、subtract、set、toggle 这些简单操作。

第 6 步:清理文字。 拿出来的指令全部删掉,多余的空行合并。得到的是干净的叙述文字,加上一串效果。

之后,回复处理 的规则会在这段干净文字上跑一遍。引擎应用这些效果(连同精准追踪的判断),再检查行为。玩家看到的是干净的文字。

为什么解析顺序重要

JSON 指令要比普通指令先提取,因为像 [stats: merge {"health": 50, "mana": 30}] 这样的指令里有冒号和数字,普通的正则会认错。先处理 JSON,引擎就不会误匹配。

指令没被识别,最常见的原因是 JSON 写坏了:大括号没配对,或者少了引号。引擎遇到解析不了的指令会悄悄跳过,不会崩。

完整参考 → World Spec: Variables


宏:设定里的动态文字 ​

宏是写在设定里的占位符,引擎在发给 AI 之前会把它换成真实的值。长这样:{{char}}、{{user}}、{{turnCount}}。

设定写一次,就能跟着情况变。改了角色名字,所有 {{char}} 自动跟着改。玩家选了一个人设,{{user}} 也跟着变。

常用的宏 ​

宏换成示例
{{char}}当前角色的名字Luna
{{user}}玩家的名字(或当前人设的名字)Kai
{{turnCount}}当前回合数42

这三个能覆盖大多数情况。{{char}} 和 {{user}} 几乎每条设定都会用到。

随机宏 ​

宏行为示例
{{random::a::b::c}}每次随机选一个{{random::sunny::cloudy::rainy}} 可能得到 "cloudy"
{{pick::a::b::c}}稳定地选一个,同一回合里结果不变{{pick::red::blue::green}} 同一回合总是同一个颜色
{{roll::NdS+M}}掷骰:N 个 S 面骰,再加修正值 M{{roll::2d6+3}} 可能得到 11

random 和 pick 的区别:random 每展开一次就重新随机,同一条设定展开两次,结果可能不一样。pick 用宏在设定里的位置和当前回合数算一个稳定的哈希,同一回合里每次都一样,下一回合可能变。

要变化就用 random(天气、人群描写)。同一回合里要前后一致就用 pick(角色的衣服被提到两次,得是同一件)。想做 AI 能接着演的骰子机制,就用 roll。

时间日期宏 ​

宏换成示例
{{time}}当前时间(HH:MM)14:30
{{date}}当前日期(本地格式)2026/5/13
{{weekday}}星期几Tuesday
{{isodate}}ISO 格式日期2026-05-13
{{isotime}}ISO 格式时间14:30:00
{{idle}}距离玩家上一条消息过了多久5 minutes

这些是现实里的时间,不是游戏里的时间。适合现实时间和虚构时间混着用的世界,或者做挂机检测(「玩家 10 分钟没回,角色发一条担心的消息」)。

上下文宏 ​

宏换成
{{lastMessage}}最后一条消息的全文
{{lastUserMessage}}玩家的最后一条消息
{{lastCharMessage}}角色的最后一条消息
{{model}}正在用的 AI 模型名字

玩家人设宏 ​

玩家在 个人主页 → 人设轮播 里创建人设,也就是带外貌、性格、背景故事的身份。玩家用着某个人设时,这些宏从人设里取值:

宏换成
{{persona_name}}当前人设的名字
{{persona_appearance}}外貌描述
{{persona_personality}}性格特点
{{persona_backstory}}经历和来历
{{persona}}上面四项合在一起,再加上人设里额外写的条目

小技巧:在 角色和世界 里放一条叫「关于玩家」的设定,内容写 {{persona}},AI 就一直知道玩家在扮演谁。玩家换了人设,AI 也会自动跟上。

{{user}} 用的是当前人设的名字;没用人设的话,用玩家的用户名。两个都不知道时,用这张卡设的默认玩家名,再没有就是「Player」。

工具宏 ​

宏作用
{{// comment text}}注释。展开后什么都没有,可以在设定里留笔记,不会发给 AI
{{trim}}吃掉前后的空白。宏留下难看的空隙时,用来精细控制格式

变量兜底 ​

宏名对不上任何内置宏时,引擎会看它是不是某个变量的 ID。比如有个 ID 是 mood 的变量,当前值是 "suspicious",那 {{mood}} 就会换成 "suspicious"。这只对由字母、数字和下划线组成的 ID 有效:{{player_hp}} 可以,{{player-hp}} 不行。画布上新建的变量 ID 是一串带减号的随机字符,所以想当宏用,先在变量设置的 高级 里给它改一个简单的 ID。

既不是内置宏也不是变量的话,引擎就把 {{xxx}} 原样留着。不报错,也不会崩。


好用的做法 ​

按比例掉血的战斗 ​

定义一个 health(数值,0-100),在行为规则里按武器类型写好伤害范围。AI 写叙述,再在后面带上指令:

强盗的匕首划过你的肋下,伤口不深,但火辣辣地疼。
你踉跄后退,仍然护着要害。
[health: -8]

你抡起巨剑横扫过去。强盗想躲,剑刃还是砍中了他的肩膀。
他惨叫一声。
[enemy-hp: -22]

玩家看到的是一场战斗,数字在背后记账。

关系递进加条件设定 ​

拿一个数值变量(aria-trust,0-100)配几条按阈值发送的设定(画布上的条件设定)。AI 用指令更新信任值,设定系统会按阶段自动调整 AI 对这个角色表现的认知:

  • 设定「Aria:戒备」(条件:aria-trust < 30):客气,保持距离
  • 设定「Aria:升温」(条件:aria-trust >= 30 AND < 60):会说自己的想法,偶尔笑一下
  • 设定「Aria:亲近」(条件:aria-trust >= 60):会示弱,护着你,有只有你们懂的梗

AI 用指令推动数值:一次友好的互动之后写 [aria-trust: +5]。(打开了精准追踪的话,改为由追踪小助手来推。)设定系统决定 AI 看到哪个版本的 Aria。两边互相不知道对方存在,靠这个变量来传话。

用宏写会变的场景描写 ​

一条能跟着情况变的设定:

{{user}} 走进房间时,{{char}} 抬起头。现在是 {{time}},
外面的天气{{random::晴朗::阴沉::下着毛毛雨}}。

{{char}} 今天看起来{{mood}}。

如果角色是 Luna,玩家是 Kai,现在下午两点,mood 变量是「心事重重」,AI 收到的是:

Kai 走进房间时,Luna 抬起头。现在是 14:00,
外面的天气阴沉。

Luna 今天看起来心事重重。

掷骰判定 ​

在一条给旁白的指令设定里写:

{{user}} 尝试有风险的行动时,用掷骰结果判定成败。
掷骰:{{roll::1d20}}。15 以上成功,10-14 部分成功,
10 以下失败。

AI 拿到的是一个实际的数字,可以照着往下写,结果由骰子决定,不用 AI 自己拿主意。


常见错误 ​

指令放错地方。 引擎已经告诉 AI 把指令放在回复最后了。如果某个模型还是写在句子中间,解析照样能用,只是查问题更麻烦。

指望 AI 算数。 AI 写 [health: -15],减法由引擎来做。别在行为规则里写「算出新的生命值」这种话,AI 只要写对指令,算数和最小/最大值的限制都由引擎处理。

指令里的 JSON 写错。 大括号没配对或少了引号,引擎会悄悄跳过这条指令。merge 或 push 没生效的话,检查一下 JSON 是否合法。引擎不会崩,只是跳过去。

把 {{random}} 和 {{pick}} 搞混。 如果角色的眼睛颜色每次展开都在变,说明你该用 pick 的地方用了 random。稳定的属性用 pick,故意要变化的用 random。

用 JSON Patch 把事情搞复杂。 除非确实要从数组里删东西,否则就用方括号指令。更简单,AI 写得更稳,也更好查问题。

忘了 {{user}} 会跟着人设变。 玩家换了人设,{{user}} 就变成新人设的名字。通常这正是你想要的,但如果你用 {{user}} 的地方不该在一局中途变,要注意这一点。


另见 ​

指令语法和操作规范 → World Spec: Variables