Skip to Content

变量管理2.0

适用环境:SillyTavern + LittleWhiteBox


为什么要有 2.0?

1.0 版本需要写”录入”、“变更”、“追加”、“遗忘”这些指令词,结构控制也比较松散:

# 1.0 写法(旧) <plot-log> 录入: 角色: 姓名: "苏晚" 变更: 角色: 生命值: -20 追加: 角色: 技能: - 火球术 遗忘: 角色: 临时状态: </plot-log>

2.0 的设计思路:你看到的就是一个状态面板,改哪里就写哪里。

# 2.0 写法(新) <state> 角色.生命值: -20 角色.技能: +"火球术" 角色.临时状态: null </state>

2.0 的改进:

  • 不用写”录入/变更/追加/遗忘”,直接写就是设置
  • 生命值: -20 就是减,技能: +"火球术" 往数组里加一项(字段不存在会自动创建空数组)
  • 新增 $schema 结构模板,可以限制 AI 只能改哪些字段。2.0默认不守护。
  • 原有 $range/$step/$enum/$ro 等约束规则

一、三种数据类型

在开始之前,你需要知道变量里能放什么东西。

1. 普通值

就是一个单独的东西:数字、文字、真假。

生命值: 100 # 数字 姓名: "苏晚" # 文字(字符串) 是否存活: true # 真/假(布尔)

2. 对象

想象成一个抽屉柜,每个抽屉都有标签(名字)。

角色: 姓名: "苏晚" # 抽屉1 生命值: 100 # 抽屉2 心情: "开心" # 抽屉3

你要拿东西,就说”角色的姓名”,写成 角色.姓名

3. 数组

想象成排队,没有名字,只有位置(从 0 开始数)。

技能: - 火球术 # 第 0 个 - 冰冻术 # 第 1 个 - 治疗术 # 第 2 个

你要拿东西,就说”技能的第 0 个”,写成 技能[0]


二、六种操作

所有操作都写在 <state>...</state> 里面。

1. 设置/覆盖

直接写值,不管原来是什么,直接变成新的。

<state> 角色.地点: "图书馆" 角色.心情: "紧张" 角色.生命值: 80 </state>

也可以用缩进写法(效果一样):

<state> 角色: 地点: "图书馆" 心情: "紧张" 生命值: 80 </state>

2. 数字加减

在数字前面加 +-

<state> 角色.生命值: -20 # 扣 20 点血 角色.好感度: +5 # 加 5 点好感 角色.金币: +100 # 加 100 金币 </state>

3. 直接设置负数

如果你想把一个值直接设成负数(而不是”减去多少”),用括号包起来:

<state> 温度: (-10) # 直接设成 -10 度 角色.债务: (-500) # 直接设成 -500(欠债) </state>

不加括号的 -10 意思是”减去 10”,加了括号的 (-10) 意思是”直接变成 -10”。

4. 往数组里加东西

在内容前面加 +

<state> # 加一个字符串 角色.技能: +"雷电术" # 加多个 角色.技能: +["盾墙", "治疗"] # 加一个对象 角色.背包: +{"名称": "回血药", "数量": 3} </state>

5. 从数组里删东西

按位置删(用 null):

<state> 角色.技能[0]: null # 删掉第 0 个 角色.背包[2]: null # 删掉第 2 个 </state>

注意:删除后索引会重排(后面的元素往前移)。但系统内部做了倒序处理,所以 AI 这回合看到是 # [几],就删几,不需要心算重排后的结果。

按内容删(用 -):

<state> 角色.技能: -"火球术" # 删掉叫"火球术"的 角色.技能: -["火球术", "冰冻术"] # 删掉多个 </state>

匹配规则:精确匹配(JSON 序列化后比较),只删第一个匹配项。如果没找到,静默成功(什么都不做)。

对象必须完全一致:包括字段顺序。写入时 {"数量": 3, "名称": "药"},删除时写 {"名称": "药", "数量": 3} 会找不到。

所以对象数组强烈建议用索引删背包[0]: null

6. 删掉整个属性

把值设成 null

<state> 角色.临时状态: null # 删掉这个属性 敌人: null # 删掉整个敌人对象 </state>

空字符串 vs null

  • 姓名: "" → 设成空字符串(字段还在,值是空)
  • 姓名: null → 删除这个属性(字段消失)

三、同一字段多次操作

按行顺序执行,不是合并成一条:

<state> 角色.生命值: +10 角色.生命值: -5 # 在 +10 之后再 -5,最终 +5 效果 </state>

假设原值 100:

  1. 执行 +10 → 110
  2. 执行 -5 → 105

四、类型要求

每种操作对字段类型有要求,类型不对会失败并记录到变量 LWB_STATE_ERRORS

操作要求的类型字段不存在时
直接赋值任意自动创建
+N / -N(数字加减)数字(或可转成数字的字符串)当作 0
+"xxx" / +[...](数组添加)数组自动创建空数组
-"xxx" / -[...](数组按值删除)数组失败
[索引]: null(数组按位置删除)数组失败
: null(删除属性)任意静默成功(本来就没有)

类型不匹配:如果 $schema 定义了 生命值: 0(数字),但写入字符串 "满血",会被拒绝并记录到变量 LWB_STATE_ERRORS


五、行尾注释

可以在行尾用 # 写注释,不会影响执行:

<state> 角色.生命值: +10 # 吃了回血药 角色.金币: -50 # 买了东西 技能: +"火球术" # 学会了新技能 </state>

注意:引号里的 # 不会被当成注释:

<state> 备注: "今天#很开心" # 这才是注释 </state>

六、结构模板($schema)

核心思想:你先画一张图纸,AI 只能在图纸范围内写。

为什么需要?

假设你有一个”后宫”系统,以后会有张三、李四、王五……这些名字现在还不存在。但你希望:

  • AI 可以随便加新人名
  • 但每个人下面只能有生命值、好感度这些固定字段
  • 不许 AI 乱加”心情”、“战力”这种你没设计的字段

$schema 就能做到。

基本写法

<state> $schema 数据 角色: 姓名: "" 生命值: 0 技能: [] </state>

重要

  • $schema 里的值只用来表示类型,不会真的写入。要初始化数据,需要另外写一段。
  • 路径必须从根变量名开始:上例中 数据 是根变量名,会生成规则路径 数据.角色.姓名 等。

类型占位符

写法表示的类型
""字符串
0数字
truefalse布尔
[]数组(可以往里加东西)
{}对象(可以随便加子字段)

对象的三种形式

1) 空对象(完全自由)

$schema 数据 杂物: {}

效果:杂物 下面可以加任何东西,没有限制。

2) 固定结构对象(白名单)

$schema 数据 角色: 姓名: "" 生命值: 0

效果:角色 下面只能有 姓名生命值,加别的会被拒绝。

3) 通配对象(任意 key,但内部结构固定)

$schema 数据 后宫: "*": 生命值: 0 好感度: 0

效果:

  • 后宫.张三.生命值: 100 — 允许(张三是新人名,生命值在模板里)
  • 后宫.李四.好感度: 50 — 允许
  • 后宫.张三.心情: "开心" — 拒绝(心情不在模板里)

人话:可以加 N 个人,但每个人下面只能有固定的那几个 key。

通配符可以嵌套

$schema 数据 区域: "*": # 任意地点名 NPC: "*": # 任意 NPC 名 好感度: 0

数组的两种形式

1) 空数组(随便加)

$schema 数据 技能: []

效果:可以 push 任何东西进去。

2) 带模板的数组(每个元素有固定结构)

$schema 数据 背包: - 名称: "" 数量: 0

效果:每个背包物品必须有 名称数量,不能有别的字段。


七、约束规则

除了 $schema 限制结构,还可以给具体字段加约束。

写法

<state> 块里,用 $ 开头单独写一行(顶格,不要缩进):

<state> $schema 数据 角色: 姓名: "" 生命值: 0 $ro 数据.角色.姓名 $range=[0,100] 数据.角色.生命值 $step=20 数据.角色.生命值 数据: 角色: 姓名: "苏晚" 生命值: 100 </state>

路径必须写完整:从根变量名开始,比如 数据.角色.姓名,不能省略成 角色.姓名

规则速查表

写法意思效果
$ro 路径只读不许改这个字段
$range=[最小,最大] 路径数值范围超出范围自动限制
$step=N 路径步长限制每次最多变化 N
$enum={值1;值2;值3} 路径枚举只能是这几个值之一

规则行为

$ro(只读)

$ro 数据.角色.姓名 # AI 写 数据.角色.姓名: "新名字" → 被拒绝

$range(自动限制到范围内)

$range=[0,100] 数据.角色.生命值 # AI 写 角色.生命值: +200 → 自动限制到 100 # AI 写 角色.生命值: -999 → 自动限制到 0

$step(自动限制单次变化量)

$step=20 数据.角色.生命值 # AI 写 角色.生命值: +50 → 自动限制到 +20 # AI 写 角色.生命值: -100 → 自动限制到 -20

$enum(不在列表里直接拒绝)

$enum={晴;阴;雨;雪} 数据.天气 # AI 写 天气: "晴" → 允许 # AI 写 天气: "龙卷风" → 被拒绝

规则的持久性

规则只需初始化时注册一次,之后一直生效。

规则存储在聊天元数据(chatMetadata.LWB_RULES_V2)里。每次执行 <state> 时会自动加载已有规则,所以 AI 日常回复只需要写数据操作。

如果 AI 在回复里又写了 $schema

会合并,不是覆盖:

  • 新路径的规则会被添加
  • 同路径的规则会被更新
  • 不会删除已有规则
# 初始化时 $schema 数据 角色: 姓名: "" 生命值: 0 # AI 后来又写 $schema 数据 角色: 新字段: "" # 结果:角色 下现在有 姓名、生命值、新字段 三个允许的字段

八、错误反馈

每次执行 <state> 后,系统会把错误/修正信息写入本地变量 LWB_STATE_ERRORS

  • 有问题:变量里有内容
  • 没问题:变量被清空

在世界书里使用

{{getvar::LWB_STATE_ERRORS}} 如果上面有内容,说明你上回合的状态更新有问题,请注意修正。

AI 会看到什么?

有问题时:

- 角色.心情: 字段不在结构模板中 - 角色.生命值: +100 超出范围,已限制到 100 - 角色.等级: +5 超出步长限制,已限制到 +1

没问题时:(空的)


九、让 AI 看到当前状态

在世界书里用这个宏:

{{xbgetvar_yaml_idx::数据}}

它会自动把变量展开成 YAML,并且给数组加上位置注释。

AI 看到的效果

姓名: "苏晚" 生命值: 100 技能: - 火球术 # [0] - 冰冻术 # [1] 背包: - 名称: 回血药 # [0] 数量: 3

有了 # [0] 这样的注释,AI 就知道要删第几个了。


十、初始化变量

完整初始化模板

在小白x的”循环任务”面板新建任务,触发时机选”角色卡初始化”:

<<taskjs>> await LWB_StateV2.applyText(` <state> # ===== 1. 结构模板(只定义类型,不写入数据)===== $schema 数据 角色: 姓名: "" 生命值: 0 魔法值: 0 金币: 0 技能: [] 背包: "*": 0 后宫: "*": 生命值: 0 好感度: 0 当前地点: "" # ===== 2. 约束规则 ===== $range=[0,999] 数据.角色.生命值 $range=[0,999] 数据.角色.魔法值 $step=50 数据.角色.生命值 $step=50 数据.角色.魔法值 # ===== 3. 初始数据(真正写入的值)===== 数据: 角色: 姓名: "苏晚" 生命值: 100 魔法值: 50 金币: 0 技能: [] 背包: {} 当前地点: "新手村" </state> `, { silent: true }); <</taskjs>>

说明

  • <<taskjs>>...<</taskjs>> 是小白x循环任务的 JS 语法
  • { silent: true } 是配合总结系统初始化专用参数,照抄就行
  • $schema 只定义结构/类型,数据: 段才是真正写入的初始值

十一、让 AI 在 RP 中使用变量系统

设计完变量结构后,还需要在世界书里告诉 AI 怎么用。

推荐的世界书配置

创建一个常驻条目,示例如下:

# 变量系统 ## 当前状态 ```yaml {{xbgetvar_yaml_idx::数据}} ``` <varevent> [event.showErrors] condition: var(`LWB_STATE_ERRORS`) != val(``) display: " 【上回合状态更新反馈】 {{getvar::LWB_STATE_ERRORS}} ⚠️ 上回合状态更新有误,请注意修正。 " </varevent> ## 更新语法 每次回复末尾输出 `<state>` 块更新状态: | 操作 | 写法 | 示例 | |------|------|------| | 设置 | `路径: 值` | `数据.角色.地点: "森林"` | | 设负数 | `路径: (-N)` | `数据.温度: (-10)` | | 加 | `路径: +N` | `数据.角色.金币: +100` | | 减 | `路径: -N` | `数据.角色.生命值: -20` | | 数组加 | `路径: +"项"` | `数据.技能: +"火球术"` | | 数组删 | `路径[n]: null` | `数据.技能[0]: null` | | 删属性 | `路径: null` | `数据.临时状态: null` | 注意: - 冒号后必须有空格 - 删除数组元素时,索引参考上方状态里的 `# [n]` 注释 - 同一字段可多次操作,按顺序执行

格式提醒(可选)

可以在低深度里加:

回复末尾务必输出状态更新: <state> # 本回合的状态变化 </state>

约束提示(可选)

如果设置了 $range$step 等规则,可以加一句让 AI 心里有数:

> 生命值范围 0-999,每次最多变化 50。超出会自动修正。

完整示例

假设你的变量结构是:

数据: 角色: 姓名: "苏晚" 生命值: 100 金币: 50 技能: - 火球术

AI 回复末尾应该输出类似:

<state> 数据.角色.生命值: -15 数据.角色.金币: +30 数据.角色.技能: +"冰冻术" </state>

说明

  • 上面的varevent块代表有错误时,AI才看得到display部分,否则整个块对它隐藏

十二、边界细节

一条消息里可以有多个 <state> 吗?

可以,全部会执行。

文本里提到 <state> 这个词会误触发吗?

不会。系统会从每个 </state> 往前找最近的 <state>,配对成功才执行。

所以:

  • ✅ 未闭合的 <state> 会被忽略
  • ✅ 被 HTML 注释包裹的也能执行
  • ✅ 世界书里的 <state> 不会执行(只有聊天消息里的才会)

十三、语法速查卡

<state> # ===== 结构模板 ===== $schema 数据 角色: 姓名: "" # 字符串 生命值: 0 # 数字 技能: [] # 数组 背包: "*": 0 # 物品名: 数量 后宫: "*": # 任意人名 生命值: 0 好感度: 0 # ===== 约束规则(路径从根变量名开始)===== $ro 数据.角色.姓名 $range=[0,100] 数据.角色.生命值 $step=20 数据.角色.生命值 $enum={晴;阴;雨} 数据.天气 # ===== 数据操作 ===== # 设置值 数据.角色.名字: "苏晚" 数据.角色.生命值: 100 # 设置负数(用括号) 数据.温度: (-10) # 数字加减 数据.角色.生命值: -20 # 扣血 数据.角色.金币: +50 # 加钱 # 数组加东西 数据.角色.技能: +"火球术" 数据.角色.技能: +["冰冻", "雷电"] 数据.角色.背包: +{"名称": "药水", "数量": 1} # 数组删东西 数据.角色.技能[0]: null 数据.角色.技能: -"火球术" # 删除属性 数据.角色.临时状态: null </state>

十四、常见问题

Q:$schema 和约束规则有什么区别?

  • $schema:限制能有哪些字段(结构白名单)
  • $ro/$range/$step/$enum:限制字段的值怎么改(值约束)

Q:规则可以组合用吗?

可以:

$range=[0,100] 数据.角色.生命值 $step=20 数据.角色.生命值

执行顺序:先 step 限制变化量,再 range 限制最终值。

例如当前值 90,AI 写 +50

  1. +50 超过 step=20 → 限制为 +20
  2. 90 + 20 = 110
  3. 110 超过 range max=100 → 限制为 100

最终值:100

Q:为什么我的 <state> 没生效?

检查:

  1. 是不是写在 AI 的回复里?(世界书里的不执行)
  2. 标签有没有配对?<state></state> 要成对
  3. 冒号后面有没有空格?姓名:"苏晚" 是错的,要写 姓名: "苏晚"

Q:我不想限制结构,AI 随便写行不行?

可以。不写 $schema 就行,2.0 默认是自由的。

Q:规则注册后怎么删除?

目前没有删除规则的语法。如果需要重置,可以清空聊天或重新初始化角色卡。

Q:还是不懂?

把2.0教程全部复制给 AI 看,然后直接问:

查看所有,然后请为我的聊天变量系统生成初始化 <state> 块,要求:

  1. 先写 $schema 数据 结构模板(用 "" 表示字符串、0 表示数字、[] 表示数组、{} 表示自由对象)
  2. 如果有”未来会出现的人名/物品名”,用 "*" 通配
  3. 再写 数据: 初始值
  4. 最后补上 $range$step 等约束规则(路径必须从根变量名开始,如 数据.角色.生命值

我的需求:……(描述你的 RPG/背包/队友等)


附录:背包系统设计参考

这一节针对”背包”这个最常见的需求,列出几种设计方案供参考。

方式 1:纯名字列表

$schema 数据 背包: [] # 数据示例 背包: - 回血药 - 蓝药 - 武器

操作方式

<state> 背包: +"新物品" # 加 背包[0]: null # 删第 0 个 背包: -"回血药" # 按名字删 </state>

优点:简单。 缺点:没法记数量。


方式 2:对象数组(需要多个属性时推荐)

$schema 数据 背包: - 名称: "" 数量: 0 # 数据示例 背包: - 名称: 回血药 # [0] 数量: 3 - 名称: 蓝药 # [1] 数量: 5

操作方式

<state> # 加物品 背包: +{"名称": "武器", "数量": 1} # 改第 1 个的数量 背包[1].数量: +2 # 删第 0 个 背包[0]: null </state>

优点:每个物品可以有多个属性(名称、数量、描述等)。 缺点:操作时需要知道索引。


方式 3:物品名做 key(只需要数量时推荐)

$schema 数据 背包: "*": 0 # 数据示例 背包: 回血药: 3 蓝药: 5 武器: 1

操作方式

<state> # 加/改数量 背包.回血药: +1 背包.新武器: 1 # 删物品 背包.回血药: null </state>

优点:不用找索引,直接按名字改。 缺点:每个物品只能存一个值(数量)。


方式 4:物品名做 key,值是对象

$schema 数据 背包: "*": 数量: 0 描述: "" # 数据示例 背包: 回血药: 数量: 3 描述: "恢复 50 HP" 蓝药: 数量: 5 描述: "恢复 30 MP"

操作方式

<state> 背包.回血药.数量: +1 背包.新物品: 数量: 1 描述: "神秘的道具" </state>

优点:既能按名字访问,又能存多个属性。 缺点:结构稍复杂。


选择建议

  • 只需要记”有什么” → 方式 1
  • 需要记”有几个” → 方式 3
  • 需要记”有几个 + 其他属性” → 方式 2 或 4