Skip to content

世界书

读取、写入、扫描世界书(lorebook)条目的相关 API。所有函数均通过 Luker.getContext() 暴露;原始 HTTP 路由参见底层端点

读取世界书

loadWorldInfo

ts
loadWorldInfo(name: string): Promise<WorldInfoData | null>

按名称读取单个世界书文件。名称查找忽略大小写和重音符。无匹配文件时返回 null。内部带缓存——重复读取同一文件直接走内存。

js
const ctx = Luker.getContext();
const book = await ctx.loadWorldInfo('My Lorebook');
console.log(Object.keys(book.entries).length);

loadWorldInfoBatch

ts
loadWorldInfoBatch(names: string[]): Promise<Map<string, WorldInfoData | null>>

一次 HTTP 往返读取多个世界书文件。返回以解析后名称为键的 Map;不存在的条目映射为 null。已缓存的文件和 in-flight 请求会自动合并。

js
const books = await ctx.loadWorldInfoBatch(['Book A', 'Book B']);
for (const [name, data] of books) {
    if (data) console.log(name, Object.keys(data.entries).length);
}

getWorldInfoNames

ts
getWorldInfoNames(): string[]

返回编辑器已知的所有世界书文件名的快照副本。可用于填充 UI 下拉框。

getWorldInfoPrompt

ts
getWorldInfoPrompt(
    chat: string[],
    maxContext: number,
    isDryRun: boolean,
    globalScanData: WIGlobalScanData,
): Promise<WIPromptResult>

底层扫描器,针对给定的聊天切片产出世界书 prompt 片段。大多数插件应优先使用 resolveWorldInfoForMessages——它在此之上封装了归一化和激活后钩子。

WIPromptResult 结构:

字段类型说明
worldInfoStringstringbeforeafter 用换行连接
worldInfoBeforeEntriesstring[]注入到聊天前的条目
worldInfoAfterEntriesstring[]注入到聊天后的条目
worldInfoExamplesArray<{position, content}>对话示例条目
worldInfoDepthArray<{depth, role, entries}>depth 注入条目
anBefore / anAfterstring[]Author's note 注入
outletEntriesRecord<string, string[]>自定义 outlet 内容

isDryRunfalse 时,会触发 event_types.WORLD_INFO_ACTIVATED 事件,携带激活的条目。

写入世界书

saveWorldInfo

ts
saveWorldInfo(
    name: string,
    data: WorldInfoData,
    immediately?: boolean,
    options?: object,
): Promise<void>

将世界书文件持久化到磁盘。缓存同步更新;网络写入默认走 debounce 队列。

参数说明
name文件名(不含 .json 扩展名)
data完整世界书对象({ entries: Record<number, WIEntry> }
immediatelytrue 时等待真正写入完成而非 debounce

namedata 为 falsy 时直接返回。

updateWorldInfoList

ts
updateWorldInfoList(): Promise<void>

从服务器刷新全局 world_names 列表。在编辑器 UI 之外创建、删除、重命名文件后调用。

createWorldBook

ts
createWorldBook(name: string, options?: { interactive?: boolean }): Promise<boolean>

创建一个新的空世界书文件。成功返回 true,失败返回 false(例如 interactive 为 false 时遇到重名)。interactive: false(默认)会跳过重名确认弹窗,适合程序化创建。新建文件没有任何条目,通过 saveWorldInfo 或编辑器写入条目。

importEmbeddedWorldInfo

ts
importEmbeddedWorldInfo(skipPopup?: boolean): Promise<void>

把 V2/V3 PNG 卡内嵌的 data.character_book 导入为独立的世界书文件并绑定为该角色的 primary world。要导入哪张卡通过 #import_character_info 元素的 chid data 属性读取(角色编辑器在打开含内嵌书的卡时会设置这个值)。skipPopup: true 用来跳过确认弹窗直接导入,适合已经获取过明确确认的工具调用。导入完成后,characters[chid].data.extensions.world 指向新文件,内嵌书不再被提示重新导入。

charUpdatePrimaryWorld

ts
charUpdatePrimaryWorld(name: string): Promise<void>

通过名字绑定角色的主世界书(传 '' 解绑)。走角色编辑器的保存路径,因此需要当前有活跃的角色上下文。

getCharacterEmbeddedWorld

ts
getCharacterEmbeddedWorld(charId: number | string): {
    present: boolean,
    name: string | null,
    entryCount: number,
    bound: boolean,
}

只读地查询某张卡的 V2/V3 内嵌 data.character_book 状态:

  • present — 卡是否携带内嵌书。
  • name — 内嵌书的 name 字段。
  • entryCount — 内嵌书包含的条目数。
  • bound — 卡是否已经绑定了一个真实的世界书文件(即 data.extensions.world 解析得到一个已知的世界书)。present && !bound 表示内嵌书还没通过 importEmbeddedWorldInfo 导入。present && bound 表示内嵌书只是绑定世界的过期镜像(导出时留下的良性副产物),运行时应忽略。

reloadWorldInfoEditor

ts
reloadWorldInfoEditor(file: string, loadIfNotSelected?: boolean): void

重新渲染 file 对应的世界书编辑器。当 file 不是当前打开的文件时默认无操作;将 loadIfNotSelected 设为 true 可切换到该文件。

激活扫描

simulateWorldInfoActivation

ts
simulateWorldInfoActivation(request: {
    coreChat?: ChatMessage[],
    maxContext?: number,
    dryRun?: boolean,
    type?: string,
    chatForWI?: string[],
    includeNames?: boolean,
    globalScanData?: WIGlobalScanData,
}): Promise<WIPromptResult & {
    chatForWI: string[],
    maxContext: number,
    globalScanData: WIGlobalScanData,
}>

针对提供的消息执行一次世界书激活扫描,返回激活结果。这是 resolveWorldInfoForMessages 背后的原语;只在需要更精细地控制扫描输入时直接调用。

参数说明
coreChat用于扫描的消息列表({ name, mes, is_user, is_system }
maxContexttoken 预算;省略或 <= 0 时回退到 getMaxPromptTokens()
dryRuntrue 时抑制 WORLD_INFO_ACTIVATED 事件
type生成触发标签('normal''quiet''regenerate' 等)
chatForWI预先构建的扫描输入;提供时会跳过 buildWorldInfoChatInput
includeNames是否在每条扫描行前加上 name:
globalScanData角色卡派生扫描字段的覆盖值

返回对象会回显实际使用的 chatForWImaxContextglobalScanData,便于调用方检视解析后的扫描输入。

buildWorldInfoChatInput

ts
buildWorldInfoChatInput(messages: ChatMessage[], includeNames?: boolean): string[]

构建 WI 扫描器期望的反转扫描字符串数组。includeNames 为真时每行格式为 "name: mes",否则只是 mes。当你想给 simulateWorldInfoActivation 喂一份预格式化的切片时有用。

buildWorldInfoGlobalScanData

ts
buildWorldInfoGlobalScanData(type: string, overrides?: Partial<WIGlobalScanData>): WIGlobalScanData

为当前角色构建角色卡派生的扫描字段(personaDescriptioncharacterDescriptioncharacterPersonalitycharacterDepthPromptscenariocreatorNotestrigger)。传入 overrides 可在不重建整个对象的情况下替换单个字段。

getActiveWorldInfoPromptFields

ts
getActiveWorldInfoPromptFields(): {
    worldInfoBeforeEntries: string[],
    worldInfoAfterEntries: string[],
}

返回最近一次实时生成流水线中捕获的世界书 before/after 片段。无活动聊天或快照属于其他聊天时返回空数组。当你想读取上次注入的内容、又不想重跑一次扫描时使用。

角色辅助世界书

角色卡可以在主 world 字段之外声明额外的世界书绑定——例如 CardApp 可以为角色内置工具绑定额外的参考书。

getCharaAuxWorlds

ts
getCharaAuxWorlds(charaFilename: string): string[]

返回绑定到角色的辅助世界书名称去重列表。解析规则:

  • 角色创建期间(menu_type === 'create'),从 in-flight 的新角色缓冲区读取。
  • 否则从持久化的 world_info.charLore 中匹配 charaFilename 的条目读取。

charaFilename 为 falsy 或没有绑定时返回 []。配合 getCharaFilename 解析当前角色:

js
const ctx = Luker.getContext();
const auxBooks = ctx.getCharaAuxWorlds(ctx.getCharaFilename());
const datas = await ctx.loadWorldInfoBatch(auxBooks);

格式转换

convertCharacterBook

ts
convertCharacterBook(characterBook: V2CharacterBook): {
    entries: Record<number, WIEntry>,
    originalData: V2CharacterBook,
}

把 V2 角色卡的 character_book 负载转换成内部世界书结构。导入角色卡或将 V2 lore 投影到可编辑条目时使用。originalData 保留以便往返序列化。

实战模式

插件读取并编辑一个世界书

js
const ctx = Luker.getContext();

const book = await ctx.loadWorldInfo('Setting Bible');
if (!book) {
    console.warn('Lorebook not found');
    return;
}

const newUid = Math.max(0, ...Object.keys(book.entries).map(Number)) + 1;
book.entries[newUid] = {
    uid: newUid,
    key: ['npc:Tavernkeeper'],
    keysecondary: [],
    content: 'A burly man with a scar across his cheek.',
    comment: 'Auto-added by my-plugin',
    constant: false,
    selective: true,
    order: 100,
    position: 0,
    disable: false,
    excludeRecursion: false,
    probability: 100,
    useProbability: true,
};

await ctx.saveWorldInfo('Setting Bible', book);
ctx.reloadWorldInfoEditor('Setting Bible', true);

插件扫描 WI 而不影响主聊天

js
const wi = await ctx.simulateWorldInfoActivation({
    coreChat: [
        { name: 'User', mes: 'Tell me about the tavernkeeper.', is_user: true },
    ],
    dryRun: true,
});

console.log(wi.worldInfoBeforeEntries);

如果想得到能直接传给 buildPresetAwarePromptMessages 的结果,请改用 resolveWorldInfoForMessages

基于 SillyTavern 构建