词条与世界书
词条是你喂给 AI 的"记忆碎片"——把角色设定、世界观、剧情线索、写作风格拆成独立的小卡片,引擎会根据情况自动挑选、组装,拼成最终发给 AI 的提示词。
简单版
词条到底是什么?
你可以把词条想象成一本百科全书里的"页"。AI 的记忆是有限的(上下文窗口),你不可能把整个世界观一股脑塞进去。词条系统的思路是:把信息拆碎,按需投喂。
比如你的世界里有一座"暗影森林",但玩家可能一百条消息都不会提到它。那这段描述就不需要每次都占用宝贵的 token。你可以把它做成一个带关键词的词条——只有玩家聊到"森林"、"暗影"、"树林"的时候,引擎才会把这段描述塞进提示词里。
编辑器里怎么操作
在编辑器里,词条有两个关键设置:
- 发送身份(Send as):决定 AI 怎么看待这段内容
- Instruction — AI 当成系统规则遵守(最常用)
- User — AI 以为是玩家说的话
- AI — AI 以为是自己说过的话
- 标签(Tags):用来分类管理——Characters、Plot、Style、Example、Preset 等
词条放在哪个分组决定了它的行为:
- PRESETS — 始终发送
- EXAMPLES — 示例对话
- CHAT HISTORY — 关键词触发
- POST — 后置兜底指令
引擎内部的 Role 字段
引擎底层每个词条还有一个 role 字段(character、lore、plot、style、example、greeting、system、custom 等),用于内部分类。在编辑器里,这个字段通过 Tags 自动映射——比如加 Characters 标签对应 role: character。你不需要手动管 role,知道有这么回事就行。
关键词触发
给词条添加关键词(keywords),引擎会扫描玩家最近的消息。只要消息里出现了关键词,这个词条就会被激活,内容被注入提示词。
比如一个词条的关键词是 ["森林", "树林", "暗影"],那玩家说"我走进了那片树林"时,这个词条就会被触发。
alwaysSend:永远生效
如果你希望一个词条不管玩家说什么都会被发送给 AI——比如角色的核心描述、世界的基本规则——把 alwaysSend 设为 true 就行了。这类词条不受关键词限制,每次对话都会包含在提示词里。
最简单的角色词条
json
{
"name": "艾莉丝",
"content": "艾莉丝是一位年轻的精灵法师,银色长发,翠绿色的眼睛。她说话温柔但偶尔毒舌,喜欢用植物做比喻。",
"role": "character",
"section": "system-presets",
"alwaysSend": true,
"keywords": [],
"enabled": true
}就这么简单。这个词条会在每次对话中都被发送给 AI,因为 alwaysSend 是 true。
详细版
WorldEntry 全字段详解
以下是 worldEntrySchema 中定义的每一个字段。源码位于 packages/engine/src/world/schema.ts。
基础字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 唯一标识符,引擎内部用来追踪词条 |
name | string (min 1) | 是 | 词条名称,显示在编辑器里,也用于示例对话中解析角色名 |
content | string | 是 | 词条的正文内容。支持宏替换(如 {{char}}、{{user}}) |
role | enum | 是 | 词条角色,见上面的表格。可选值:system, character, personality, scenario, lore, plot, style, example, greeting, custom |
enabled | boolean | 否 | 是否启用,默认 true。禁用的词条完全不参与匹配和注入 |
投送控制
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
section | enum | -- (必填) | 词条投送到提示词的哪个区域。四个选项详见下方"四大区域" |
position | number | 0 | 同一区域内的排序权重。数字越小越靠前。支持浮点数,比如 2.5 可以插在 2 和 3 之间 |
alwaysSend | boolean | false | 永远发送,不需要关键词触发 |
depth | number (int) | -- | 仅 chat-history 区域有效。表示从聊天记录末尾往回数第几条消息的位置插入。比如 depth: 4 就是插在倒数第 4 条消息的位置 |
apiRole | enum | -- | 编辑器里叫 Send as。system(Instruction)= AI 当规则遵守,user(User)= AI 以为是玩家说的,assistant(AI)= AI 以为是自己说过的。用 assistant 可以"预填充"AI 的回复开头 |
关键词匹配
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
keywords | string[] | [] | 主关键词列表。任意一个命中即视为匹配(OR 逻辑) |
matchWholeWords | boolean | false | 是否只匹配完整单词。开启后 "for" 不会匹配 "forest" |
useFuzzyMatch | boolean | false | 模糊匹配(基于 Levenshtein 编辑距离)。5 个字符以下的关键词允许 1 个字符的拼写错误,更长的允许 2 个。注意:对中日韩文字无效,仅适用于拉丁字母 |
secondaryKeywords | string[] | [] | 二级关键词。用来在主关键词命中后做进一步筛选 |
secondaryKeywordLogic | enum | "AND_ANY" | 二级关键词的组合逻辑(详见下方) |
条件与递归
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
conditions | Condition[] | [] | 基于游戏变量的触发条件。每个条件包含 variableId、operator(eq/neq/gt/gte/lt/lte/contains)、value |
conditionLogic | "all" | "any" | "all" | 多个条件之间的逻辑关系。all = 全部满足才触发,any = 任一满足即触发 |
preventRecursion | boolean | false | 阻止本词条的内容被用于递归扫描。即使本词条被触发了,它的内容也不会去触发其他词条 |
excludeRecursion | boolean | false | 让本词条完全不参与递归扫描。只有第一轮(直接匹配玩家消息)才会考虑它 |
组织管理
| 字段 | 类型 | 说明 |
|---|---|---|
tags | string[] | 自定义标签,方便分类和筛选 |
folderId | string | 所属文件夹的 ID,用于在编辑器里分组管理 |
presetId | string | 关联的预设 ID(内部使用) |
四大区域(section)详解
词条被投送到提示词的位置,由 section 字段决定。你可以把发给 AI 的完整提示词想象成一个三明治:
[system-presets] <-- 最上面:核心设定,AI 最先看到
[examples] <-- 示例对话:教 AI 怎么说话
[chat-history] <-- 中间:玩家和 AI 的对话记录
(depth 插入点) <-- depth 词条在这里见缝插针
[post-history] <-- 最下面:兜底指令,AI 最后看到system-presets — 系统预设
这是提示词的"开头"部分。放在这里的内容 AI 一定会看到,而且会最先看到。适合放:
- 角色核心描述(character)
- 世界观总述(lore)
- 写作风格要求(style)
- 场景设定(scenario)
这个区域的词条通常设为 alwaysSend: true,因为它们是世界运转的基础。
examples — 示例对话
专门用来放示例对话的区域。role 为 example 的词条会被特殊解析——引擎会把它们转换成一组 user/assistant 消息对,让 AI "看到"几轮示范对话。
示例对话的格式:
<START>
{{user}}: 你好,艾莉丝。
{{char}}: *微微侧头,银发滑过肩膀* 哦?来访者吗。你看起来比上一棵枯萎的仙人掌还要迷茫。
<START>
{{user}}: 这里是什么地方?
{{char}}: *轻轻一笑* 你管这叫"什么地方",我管这叫家。翡翠森林,精灵的领地——至少在人类砍掉最后一棵树之前是这样。关键点:
- 用
<START>分隔不同的对话段落 {{user}}代表玩家,{{char}}代表角色- 也可以直接用角色名(比如
艾莉丝:)代替{{char}} - 引擎会在每段前面自动插入
[Example Chat]标记
chat-history — 聊天历史中的深度插入
这个区域的词条不会出现在提示词的开头或结尾,而是"插入"到聊天历史的特定位置。通过 depth 字段控制插入点。
这特别适合需要"提醒" AI 当前状态的内容——比如角色当前的情绪、场景的环境描述。放在聊天历史中间,比放在开头更容易被 AI "注意到"(因为 AI 对中间位置的注意力分配不同)。
post-history — 后历史/越狱指令
放在所有聊天消息之后、AI 开始生成回复之前的"最后一句话"。因为 AI 对最后看到的内容印象最深,这里适合放:
- "记住,你是 XX,不要打破角色"
- "用中文回复"
- "回复长度控制在 500 字以内"
- 任何你希望 AI "最后再强调一遍"的指令
深度插入(depth)
depth 是 chat-history 区域的专属字段。它的意思是"从聊天记录末尾往回数第 N 条消息的位置"。
举个例子。假设聊天记录是:
[1] 用户: 你好
[2] AI: 你好呀
[3] 用户: 森林在哪?
[4] AI: 往北走
[5] 用户: 好的,出发如果一个词条的 depth 是 2,那它会被插在倒数第 2 条消息之前(也就是 [4] 前面),变成:
[1] 用户: 你好
[2] AI: 你好呀
[3] 用户: 森林在哪?
--- [词条内容插入在这里] ---
[4] AI: 往北走
[5] 用户: 好的,出发depth: 0 意味着插在最末尾(和 post-history 效果类似)。数字越大,插入位置越靠前。
模糊匹配(useFuzzyMatch)
开启后,引擎会用 Levenshtein 编辑距离算法做容错匹配。简单说就是允许拼写错误:
- 关键词 5 个字符以内:允许 1 个字符的差异(
"magic"可以匹配"magik") - 关键词 5 个字符以上:允许 2 个字符的差异(
"forest"可以匹配"forset")
限制:模糊匹配对中日韩文字(CJK)不生效。CJK 文字只用精确匹配或子串匹配。
另外,关键词还支持正则表达式。如果你的关键词写成 /pattern/flags 的格式(比如 /dark\s*forest/i),引擎会把它当正则来用。
二级关键词逻辑(secondaryKeywords)
主关键词是"门槛"——只要命中任意一个就算匹配。但有时候你需要更精细的控制,这就是二级关键词的用武之地。
工作流程: 先检查主关键词(至少一个命中),然后用二级关键词做进一步筛选。
四种组合逻辑:
| 逻辑 | 含义 | 例子 |
|---|---|---|
AND_ANY | 主关键词命中 且 二级关键词至少命中一个 | 主词 ["森林"],二级 ["精灵", "树人"]:玩家说"森林里的精灵" -> 触发;说"森林真美" -> 不触发 |
AND_ALL | 主关键词命中 且 二级关键词全部命中 | 主词 ["森林"],二级 ["夜晚", "危险"]:只有"夜晚的森林很危险"才触发 |
NOT_ANY | 主关键词命中 且 二级关键词一个都没命中 | 主词 ["森林"],二级 ["安全", "美丽"]:只要玩家没提到"安全"或"美丽"就触发——这是一个"排除"逻辑 |
NOT_ALL | 主关键词命中 且 二级关键词没有全部命中 | 主词 ["森林"],二级 ["A", "B"]:只要不是 A 和 B 同时出现就触发 |
递归触发(recursion)
递归是一个强大但需要小心使用的功能。它的意思是:词条 A 被触发后,A 的内容本身会被当作"新的文本"再扫描一遍,看看能不能触发词条 B。
例子: 词条 A 关键词是"森林",内容里提到了"精灵"。词条 B 的关键词是"精灵"。如果开启了递归(世界设置中 lorebookRecursionDepth > 0),那玩家只要提到"森林",就会连锁触发 A 和 B。
递归深度由世界设置的 lorebookRecursionDepth 控制,范围 0-10,默认 0(关闭)。
两个安全阀:
preventRecursion: true— 本词条被触发后,它的内容不会被拿去扫描别的词条。"我可以被触发,但我不会触发别人。"excludeRecursion: true— 本词条完全不参与递归轮次。只有玩家消息的直接匹配才能触发它。"只有玩家亲口说的话才能叫醒我。"
条件触发(conditions)
除了关键词,词条还可以基于游戏变量的状态来决定是否生效。
每个条件包含三个要素:
variableId— 要检查哪个变量operator— 比较运算符:eq(等于)、neq(不等于)、gt(大于)、gte(大于等于)、lt(小于)、lte(小于等于)、contains(字符串包含)value— 比较的目标值
conditionLogic 控制多个条件之间的关系:
"all"— 所有条件都满足才触发(AND)"any"— 任一条件满足即触发(OR)
条件和关键词是"双重门槛"——两者都要通过才会触发。如果一个词条同时有关键词和条件,那流程是:先看关键词是否命中,再看条件是否满足,两者都通过才注入。
文件夹组织
当你的世界有几十上百个词条时,列表会变得很混乱。文件夹(entryFolders)让你按逻辑分组管理。
每个文件夹有自己的 section(对应四大区域之一)和 order(排序),还可以折叠(collapsed)。词条通过 folderId 关联到文件夹。
这纯粹是编辑器里的组织工具,不影响运行时行为。
apiRole 覆盖
默认情况下,所有词条都以 system 身份发送。但有些场景你需要不同的身份:
apiRole: "user"— 以用户身份发送。某些模型对"用户说的话"比"系统指令"更重视。apiRole: "assistant"— 以 AI 助手身份发送。这相当于"预填充"——告诉 AI "你之前说过这些话",可以用来引导回复风格。
用法:在 system-presets 或 post-history 区域的词条上设置 apiRole 即可。
position 排序
position 是一个浮点数,控制同一区域内词条的先后顺序。数字越小越靠前。
支持小数点,所以你可以用 2.5 把一个词条插在 position 为 2 和 3 的词条之间,而不需要重新编号所有词条。
当两个词条的 position 相同时,关键词匹配得分更高的优先。
示例对话格式
role 为 example 的词条需要遵循特定格式,引擎才能正确解析成 user/assistant 消息对:
<START>
{{user}}: 你能治好这个伤口吗?
{{char}}: *检查了一下伤口,皱眉* 这可不是普通的刀伤。里面有诅咒的残留。我需要月光花的花粉——不过现在是白天。
{{user}}: 那怎么办?
{{char}}: *耸肩* 要么等天黑,要么你忍着。我建议后者,毕竟痛苦是最好的老师。<START>分隔不同的对话示例- 引擎会把每段解析成真正的 user/assistant 消息,而不是一大段纯文本
关键词扫描范围
引擎不会扫描所有历史消息——那样太慢也太浪费。世界设置中的 lorebookScanDepth(默认 2)控制扫描最近几条消息。设为 4 就是扫描最近 4 条消息来匹配关键词。在编辑器的 词条(Lorebook) 区域点击展开 Entry Settings 即可修改。
另外还有 token 预算控制:lorebookBudgetPercent(默认 100%)和 lorebookBudgetCap(默认 0 = 不限)限制触发词条总共能占用多少 token。超出预算时,匹配得分更高的词条优先保留。
实用例子
例子 1:一个完整的角色(三个词条配合)
角色描述词条:
json
{
"id": "alice-desc",
"name": "艾莉丝",
"content": "{{char}}是翡翠森林的精灵法师,年龄约300岁(精灵中算年轻人)。银色齐腰长发,翠绿色眼睛,左耳戴着一枚橡果形状的耳环。身高165cm,纤细但不柔弱。总是穿着一件染着苔藓色的长袍,袍角绣着看不懂的精灵文字。",
"role": "character",
"section": "system-presets",
"position": 0,
"alwaysSend": true,
"keywords": [],
"conditions": [],
"conditionLogic": "all",
"enabled": true
}性格词条:
json
{
"id": "alice-personality",
"name": "艾莉丝的性格",
"content": "{{char}}的性格特征:\n- 说话温柔但藏着毒舌,喜欢用植物和自然做比喻来损人\n- 对人类充满好奇但嘴上不承认\n- 极其护短,一旦认定朋友会不顾一切保护\n- 怕虫子,尤其是蜈蚣——这是她最大的秘密\n- 喜欢在对话中夹带精灵语单词(用斜体标注)",
"role": "personality",
"section": "system-presets",
"position": 1,
"alwaysSend": true,
"keywords": [],
"conditions": [],
"conditionLogic": "all",
"enabled": true
}开场白词条:
json
{
"id": "alice-greeting",
"name": "艾莉丝的问候",
"content": "*一片银色的光芒在林间闪烁。当你走近时,一位精灵从古橡树后走出,翠绿的眼睛上下打量着你。*\n\n又一个迷路的人类。*她轻叹一声,银发随微风飘动* 你是从哪条路摸过来的?不——别告诉我,让我猜。\n\n*她凑近你,嗅了嗅* ……北边的沼泽路。你身上有腐草的味道。*Ithiliel*,你们人类真的对鼻子没有任何尊重。\n\n好吧,既然来了就别站着。我叫艾莉丝。跟我走,在这片森林里乱逛可比和我说话危险多了。",
"role": "greeting",
"section": "system-presets",
"position": 0,
"alwaysSend": false,
"keywords": [],
"conditions": [],
"conditionLogic": "all",
"enabled": true
}三个词条分工明确:角色描述和性格永远发送(alwaysSend: true),开场白只在会话开始时使用一次。
例子 2:关键词触发的场景词条
这个词条只有在玩家提到森林相关的词时才会被注入:
json
{
"id": "dark-forest-lore",
"name": "暗影森林的秘密",
"content": "[暗影森林]\n森林深处有一片被月光永远照不到的区域,精灵们称之为\"沉默之地\"。传说那里住着一只被封印的古龙,它的呼吸让周围的植物全部变成了黑色。任何人在沉默之地停留超过一个时辰,就会开始听到龙的低语——大部分听到的人都疯了。艾莉丝知道这个秘密,但她从不主动提起。",
"role": "lore",
"section": "chat-history",
"depth": 4,
"position": 10,
"alwaysSend": false,
"keywords": ["森林", "树林", "暗影", "沉默之地", "古龙"],
"matchWholeWords": false,
"useFuzzyMatch": false,
"secondaryKeywords": [],
"conditions": [],
"conditionLogic": "all",
"enabled": true
}注意这里的设计选择:
section是chat-history,depth是4—— 内容会插在聊天记录的倒数第 4 条消息处,而不是放在系统提示词的最前面。这让 AI 觉得"森林的信息是在对话进行中自然浮现的",而不是一开始就知道的。- 多个关键词用 OR 逻辑——玩家提到任何一个就触发。
alwaysSend是false——不提到森林就不浪费 token。
例子 3:带条件的词条(变量驱动)
这个词条只在角色 HP 低于 20 时生效,用来让 AI 描写濒死状态:
json
{
"id": "near-death-state",
"name": "濒死状态描写指令",
"content": "[当前状态:濒死]\n{{char}}此刻身受重伤,生命垂危。描写时必须体现:\n- 动作迟缓,每一步都很吃力\n- 说话断断续续,偶尔咳血\n- 视线模糊,可能会认错人\n- 但意志力惊人,拒绝倒下\n绝对不要写出{{char}}轻松战斗或正常行动的描述。",
"role": "custom",
"section": "post-history",
"position": 5,
"alwaysSend": true,
"keywords": [],
"conditions": [
{
"variableId": "hp",
"operator": "lt",
"value": 20
}
],
"conditionLogic": "all",
"enabled": true
}这里的巧思:
section是post-history——放在所有聊天消息之后,作为 AI 生成回复前的"最后指令"。这个位置的内容 AI 印象最深。alwaysSend是true但有conditions——意思是"每回合都检查这个条件,满足了就发送"。不需要关键词触发,纯靠变量状态驱动。- 条件:
hp < 20。当游戏中 HP 变量降到 20 以下时,这段描写指令就会自动注入。HP 恢复到 20 以上后,它又会自动消失。
这比关键词触发更可靠——你不需要指望玩家提到"受伤",系统会根据实际的游戏状态自动判断。