跳转至

每轮现拼 prompt:放哪、放不下砍谁

网页版 ChatGPT 的 prompt 只有两段:开头一段 system prompt,后面是全量回放的历史。写在开头的规矩,聊得越久离生成点越远;历史一长就超出窗口,被截掉的往往是开头的人设。酒馆每轮从零重拼一遍:内容拆进具名的槽,指令按「距末尾第几条」插进历史,固定部分先占位,历史从新往旧填满剩下的预算。下面是 Chat Completion 路径(发消息数组的 API)的拼法,效果都是用酒馆自己的函数拼出来的。

一段 system prompt 拆成具名槽位

问题:baseline 的 system prompt 是一整段,人设、设定、规矩挤在一起,哪段放前哪段放后改不了,也没法单独关掉一段。

功能:Prompt Manager 把 prompt 拆成一串条目。有正文的条目由用户写(主提示 main、末尾指令 jailbreak 等);没正文的叫 marker,运行时才填(charDescription、worldInfoBefore、chatHistory……)。出厂顺序:

模版 · public/scripts/PromptManager.js:2087-2099
const promptManagerDefaultPromptOrder = [
    {
        'identifier': 'main',
        'enabled': true,
    },
    {
        'identifier': 'worldInfoBefore',
        'enabled': true,
    },
    {
        'identifier': 'personaDescription',
        'enabled': true,
    },

拼的时候每个槽按它在顺序里的下标落位,调用先后不影响结果,最终顺序就是用户拖出来的顺序:

模版 · public/scripts/openai.js:1187-1218
    const addToChatCompletion = async (source, target = null) => {
        // We need the prompts array to determine a position for the source.
        if (false === prompts.has(source)) return;

        if (promptManager.isPromptDisabledForActiveCharacter(source) && source !== 'main') {
            promptManager.log(`Skipping prompt ${source} because it is disabled`);
            return;
        }

        const prompt = prompts.get(source);

        if (prompt.injection_position === INJECTION_POSITION.ABSOLUTE) {
            promptManager.log(`Skipping prompt ${source} because it is an absolute prompt`);
            return;
        }

        const index = target ? prompts.index(target) : prompts.index(source);
        const collection = new MessageCollection(source);
        const message = await Message.fromPromptAsync(prompt);
        collection.add(message);
        chatCompletion.add(collection, index);
    };

    chatCompletion.reserveBudget(3); // every reply is primed with <|start|>assistant<|message|>
    // Character and world information
    await addToChatCompletion('worldInfoBefore');
    await addToChatCompletion('main');
    await addToChatCompletion('worldInfoAfter');
    await addToChatCompletion('charDescription');
    await addToChatCompletion('charPersonality');
    await addToChatCompletion('scenario');
    await addToChatCompletion('personaDescription');

效果:扮演主线第一轮实际拼出来的消息数组,每行是一条消息:token 数、槽、角色、开头:

效果 · 第一轮的拼装
  24  main                   system    Write 塞拉菲娜's next reply in a fictional c……
 893  worldInfoBefore        system    旅人: “林间空地是什么?”⏎塞拉菲娜: *塞拉菲娜温柔地笑了,眼里闪着暖意,点……
   0  personaDescription     system    (空)
 606  charDescription        system    [塞拉菲娜的性格= "体贴", "保护欲强", "富有同情心", "擅长治愈",……
   0  charPersonality        system    (空)
   0  scenario               system    (空)
   0  nsfw                   system    (空)
   0  worldInfoAfter         system    (空)
   6  newMainChat            system    [Start a new Chat]
 163  chatHistory-1          assistant *你猛地惊醒,想起自己是怎么走进森林深处、又是怎么被野兽围攻的。随着眼睛适应了屋……
  19  chatHistory-2          user      *我撑起身子,接过茶杯* 谢谢你……这片森林里到底有什么怪物?
   0  jailbreak              system    (空)

空的槽(人设、性格、场景)不发出去。用户在顺序里加一条自定义的「写作规则」,先按顺序放,再把它改成「放进聊天里,深度 1」:

效果 · 同一条规则,按顺序放 / 改成深度 1(dry run)
出厂顺序(Default 预设,全局 100001):
  main → writing-rules → worldInfoBefore → personaDescription → charDescription → charPersonality → scenario → enhanceDefinitions(关) → nsfw → worldInfoAfter → dialogueExamples → chatHistory → jailbreak

== 「写作规则」relative
  main                   system    Write 塞拉菲娜's next reply in a f……
  writing-rules          system    每次回复不超过三段。
  worldInfoBefore        system    旅人: “林间空地是什么?”⏎塞拉菲娜: *塞拉菲娜温柔地笑……
  personaDescription     system    (空)
  charDescription        system    [塞拉菲娜的性格= "体贴", "保护欲强", "富有同情心……
  charPersonality        system    (空)
  scenario               system    (空)
  nsfw                   system    (空)
  worldInfoAfter         system    (空)
  newMainChat            system    [Start a new Chat]
  chatHistory-1          assistant *你猛地惊醒,想起自己是怎么走进森林深处、又是怎么被野兽围攻……
  chatHistory-2          user      *我撑起身子,接过茶杯* 谢谢你……这片森林里到底有什么怪物……
  chatHistory-3          assistant *塞拉菲娜在床边坐下,双手交叠放在膝上,神色变得严肃。* “……
  chatHistory-4          user      你的魔法真厉害。我的伤要多久才能好?我还得赶去北边的镇子。
  chatHistory-5          assistant *塞拉菲娜眉头微微蹙起,语气温和却带着一丝担忧。* “你的伤……
  jailbreak              system    (空)

== 「写作规则」in-chat@1
  main                   system    Write 塞拉菲娜's next reply in a f……
  worldInfoBefore        system    旅人: “林间空地是什么?”⏎塞拉菲娜: *塞拉菲娜温柔地笑……
  personaDescription     system    (空)
  charDescription        system    [塞拉菲娜的性格= "体贴", "保护欲强", "富有同情心……
  charPersonality        system    (空)
  scenario               system    (空)
  nsfw                   system    (空)
  worldInfoAfter         system    (空)
  newMainChat            system    [Start a new Chat]
  chatHistory-1          assistant *你猛地惊醒,想起自己是怎么走进森林深处、又是怎么被野兽围攻……
  chatHistory-2          user      *我撑起身子,接过茶杯* 谢谢你……这片森林里到底有什么怪物……
  chatHistory-3          assistant *塞拉菲娜在床边坐下,双手交叠放在膝上,神色变得严肃。* “……
  chatHistory-4          user      你的魔法真厉害。我的伤要多久才能好?我还得赶去北边的镇子。
  chatHistory-5          system    每次回复不超过三段。
  chatHistory-6          assistant *塞拉菲娜眉头微微蹙起,语气温和却带着一丝担忧。* “你的伤……
  jailbreak              system    (空)

每轮从零重拼

问题:baseline 的 system prompt 一写就定,每轮原样回放;该随剧情变的东西(眼下用得上的设定、这几轮的规矩)没有地方放。

功能:每次生成,作者注释、世界书、角色笔记、用户人设都重新算一遍,再进拼装:

模版 · public/script.js:4618-4636
    // Set non-WI AN
    setFloatingPrompt();

    // Add WI to prompt (and also inject WI to AN value via hijack)
    // Make quiet prompt available for WIAN
    setExtensionPrompt(inject_ids.QUIET_PROMPT, quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true);
    const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse();
    /** @type {import('./scripts/world-info.js').WIGlobalScanData} */
    const globalScanData = {
        personaDescription: persona,
        characterDescription: description,
        characterPersonality: personality,
        characterDepthPrompt: charDepthPrompt,
        scenario: scenario,
        creatorNotes: creatorNotes,
        trigger: GENERATION_TYPE_TRIGGERS.includes(type) ? type : 'normal',
    };
    const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth, outletEntries } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun, globalScanData);
    setExtensionPrompt(inject_ids.QUIET_PROMPT, '', extension_prompt_types.IN_PROMPT, 0, true);

效果:主线每一步的世界书和总量:

效果 · 主线各步的拼装
r1               世界书  893 token(埃尔多利亚、影牙、力量、林间空地)  历史  2 条  合计 1711 token
r2               世界书  893 token(埃尔多利亚、影牙、力量、林间空地)  历史  4 条  合计 1872 token
r2-swipe         世界书  893 token(埃尔多利亚、影牙、力量、林间空地)  历史  4 条  合计 1872 token
r3               世界书  893 token(埃尔多利亚、影牙、力量、林间空地)  历史  6 条  合计 2023 token
r3-continue      世界书  893 token(埃尔多利亚、影牙、力量、林间空地)  历史  6 条  合计 2310 token
r4-impersonate   世界书  893 token(埃尔多利亚、影牙、力量、林间空地)  历史  7 条  合计 2354 token
r4               世界书  893 token(埃尔多利亚、影牙、力量、林间空地)  历史  8 条  合计 2353 token
r4-continue      世界书    0 token(无)  历史  8 条  合计 1777 token
r5               世界书    0 token(无)  历史 10 条  合计 1799 token

前七步聊天里一直有森林、魔法这些词,四个条目一直在;第四轮续写起,最近两条讲的是吟游诗人和点灯,世界书全部退场,省下 893 个 token。同一张卡、同一本书,每轮发出去的 prompt 都不一样。

按「距末尾第几条」插:深度注入

问题:主提示里写「保持第三人称」,聊到第 80 轮,模型开始用第一人称——指令在最前面,离生成点几千 token,模型以历史为准。

功能:一张全局的注入表,任何模块都往里登记一段文字,带四个属性:放哪(主提示前 / 后、聊天里、不放)、深度、角色、要不要参与世界书扫描:

模版 · public/script.js:8926-8935
export function setExtensionPrompt(key, value, position, depth, scan = false, role = extension_prompt_roles.SYSTEM, filter = null) {
    extension_prompts[key] = {
        value: String(value),
        position: Number(position),
        depth: Number(depth),
        scan: !!scan,
        role: Number(role ?? extension_prompt_roles.SYSTEM),
        filter: filter,
    };
}

作者注释、角色笔记、世界书的 @深度条目、用户人设、/inject、摘要、向量召回都登记在这里。「聊天里」那一类按深度插:深度 0 在最后一条之后,深度 4 在倒数第 4 条之前。从深度 0 往上逐层处理,已插入的条数不算深度:

模版 · public/scripts/openai.js:842-870
        const orders = Object.keys(orderGroups).sort((a, b) => +b - +a);
        for (const order of orders) {
            const orderPrompts = orderGroups[order];

            // Order of priority for roles (most important go lower)
            const roles = ['system', 'user', 'assistant'];
            for (const role of roles) {
                const rolePrompts = orderPrompts
                    .filter(prompt => prompt.role === role)
                    .map(x => x.content)
                    .join(separator);

                // Get extension prompt
                const extensionPrompt = order === extensionPromptsOrder
                    ? await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role], wrap)
                    : '';
                const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join(separator);

                if (jointPrompt && jointPrompt.length) {
                    roleMessages.push({ 'role': role, 'content': jointPrompt, injected: true });
                }
            }
        }

        if (roleMessages.length) {
            const injectIdx = i + totalInsertedMessages;
            messages.splice(injectIdx, 0, ...roleMessages);
            totalInsertedMessages += roleMessages.length;
        }

作者注释还能隔几轮插一次(出厂每轮都插,深度 4,system):

模版 · public/scripts/authors-note.js:346-362
    // interval 1 should be inserted no matter what
    if (chat_metadata[metadata_keys.interval] === 1) {
        lastMessageNumber = 1;
    }

    if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
        context.setExtensionPrompt(MODULE_NAME, '', extension_prompt_types.NONE, MAX_INJECTION_DEPTH);
        $('#extension_floating_counter').text('(disabled)');
        shouldWIAddPrompt = false;
        return;
    }

    const messagesTillInsertion = lastMessageNumber >= chat_metadata[metadata_keys.interval]
        ? (lastMessageNumber % chat_metadata[metadata_keys.interval])
        : (chat_metadata[metadata_keys.interval] - lastMessageNumber);
    const shouldAddPrompt = messagesTillInsertion == 0;
    shouldWIAddPrompt = shouldAddPrompt;

效果:完整版卡(带角色笔记)、作者注释、两条世界书 @深度 4(一条 system、一条 user)、一条 /inject(assistant,深度 4)、用户人设 @深度 2,拼主线最后一轮。注入表:

效果 · 这一轮的注入表
2_floating_prompt    位置=1 深度=4 角色=0  (作者注释:塞拉菲娜这几轮话少一些。)
DEPTH_PROMPT         位置=1 深度=4 角色=0  (夜里塞拉菲娜的魔法会变弱,她不愿让人知道。)
PERSONA_DESCRIPTION  位置=1 深度=2 角色=0  {{user}}是二十出头的猎人。
customDepthWI_4_0    位置=1 深度=4 角色=0  (世界书 @深度 4:天快黑了。)
customDepthWI_4_1    位置=1 深度=4 角色=1  (世界书 @深度 4,user:我得早点上路。)
script_inject_伏笔     位置=1 深度=4 角色=2  (/inject:窗外有什么东西在看着这间屋子。)

拼出来的聊天部分:

效果 · 注入落位(dry run)
chatHistory-1          assistant *你猛地惊醒,想起自己是怎么走进森林深处、又是怎么被野兽围攻的。随着眼睛适应了屋里柔和的光,那些记忆渐渐淡去。* “啊,……
chatHistory-2          user      *我撑起身子,接过茶杯* 谢谢你……这片森林里到底有什么怪物?
chatHistory-3          assistant *塞拉菲娜在床边坐下,双手交叠放在膝上,神色变得严肃。* “这片森林里潜藏着许多危险。最常见的是影狼——它们成群游荡,眼……
chatHistory-4          user      你的魔法真厉害。我的伤要多久才能好?我还得赶去北边的镇子。
chatHistory-5          assistant *塞拉菲娜眉头一皱,握着你的手不觉紧了些。* “北边的镇子?” *她缓缓摇头,眼里掠过一丝忧虑。* “旅人,北边的路已经……
chatHistory-6          user      你一个人守在这里多久了?给我讲讲你是怎么成为森林守护者的吧。
chatHistory-7          assistant *塞拉菲娜沉默了片刻,望向窗外摇曳的树影,眼神变得悠远。* “很久了。久到我记不清具体是多少个季节。” *她轻轻叹了口气……
chatHistory-8          assistant (/inject:窗外有什么东西在看着这间屋子。)
chatHistory-9          user      (世界书 @深度 4,user:我得早点上路。)
chatHistory-10         system    (作者注释:塞拉菲娜这几轮话少一些。)⏎(夜里塞拉菲娜的魔法会变弱,她不愿让人知道。)⏎(世界书 @深度 4:天快黑了。……
chatHistory-11         user      *我把杯子捧在手里暖着,犹豫了一下,还是开口问。* “你刚才说这些年人越来越少……那以前来的人,都是些什么样的?有没有让……
chatHistory-12         assistant *塞拉菲娜停下正要走开的脚步,回头看你,眼里闪过一丝意外,随即柔软下来。* “没关系,我其实愿意说。” *她重新坐回床边……
chatHistory-13         system    旅人是二十出头的猎人。
chatHistory-14         user      ……谢谢你愿意跟我说这些。等伤好了,我想陪你去点亮更多的灯。
chatHistory-15         assistant *门外静了一瞬,随后传来塞拉菲娜轻轻的笑声,带着点被击中的哽咽。门被推开一条缝,她把头探进来,琥珀色的眼睛在炉火映照下亮……
jailbreak              system    用第三人称描写塞拉菲娜的动作,不替旅人说话或行动。

深度 4 上的五样东西合成了三条消息。顺序规则写死在代码里:

  • 同一深度、不同角色:各成一条,从上到下 assistant → user → system,system 离生成点最近(注释原话「most important go lower」)
  • 同一深度、同一角色:合成一条,按注入表的键名字典序排;作者注释的键特意起名 2_floating_prompt,源码注释写着「for sorting lower than memory」,排在摘要 1_memory 之后
  • 末尾指令:是 jailbreak 槽,排在整个聊天历史之后,比深度 0 还靠后

预算:固定部分必放,历史从新往旧,示范拿余量

问题:全量回放,聊长了要么超出窗口报错,要么被从前面截断,截掉的是人设。

功能:预算 = 上下文上限 − 回复长度(出厂 4095 − 300):

模版 · public/scripts/openai.js:3982-3989
    setTokenBudget(context, response) {
        this.log(`Prompt tokens: ${context}`);
        this.log(`Completion tokens: ${response}`);

        this.tokenBudget = context - response;

        this.log(`Token budget: ${this.tokenBudget}`);
    }

填的次序:

  1. 主提示、角色卡、世界书、末尾指令这些固定槽先放,放不下就报错
  2. 聊天历史从最新一条往旧填,第一条放不下就停——不会跳过一条长消息去塞更旧的短消息,进上下文的总是连续的最近一段:

    模版 · public/scripts/openai.js:947-955,1070-1074
        // Insert chat messages as long as there is budget available
        const chatPool = [...messages].reverse();
        for (let index = 0; index < chatPool.length; index++) {
            const chatPrompt = chatPool[index];
    
            // We do not want to mutate the prompt
            const prompt = new Prompt(chatPrompt);
            prompt.identifier = `chatHistory-${messages.length - index}`;
            const chatMessage = await Message.fromPromptAsync(promptManager.preparePrompt(prompt));
            if (chatCompletion.canAfford(chatMessage)) {
                chatCompletion.insertAtStart(chatMessage, 'chatHistory');
            } else {
                break;
            }
    
  3. 示范对话最后拿剩余的预算,整块放或整块丢:

    模版 · public/scripts/openai.js:1124-1126
                if (!chatCompletion.canAffordAll([newExampleChat, ...chatMessages])) {
                    break;
                }
    

    用户可以把示范对话设成「总是放」,那它就排在历史前面先占位:

    模版 · public/scripts/openai.js:1336-1343
        // Decide whether dialogue examples should always be added
        if (power_user.pin_examples) {
            await populateDialogueExamples(prompts, chatCompletion, messageExamples);
            await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
        } else {
            await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
            await populateDialogueExamples(prompts, chatCompletion, messageExamples);
        }
    

效果:完整版卡(带示范对话),主线最后的 11 条聊天,上下文上限分别设成出厂的 4095、2048、1400:

效果 · 三档上下文各留下什么(h 是第几条历史,ex 是示范对话)
上下文 4095(可用 3795):合计 1935 token,示范对话 4 段,进上下文的聊天消息 11/11 条
   main:34 charDescription:131 charPersonality:11 scenario:21 newChat:4 ex0-0:9 ex0-1:201 ex0-2:10 ex0-3:191 newMainChat:6 h1:163 h2:19 h3:140 h4:21 h5:135 h6:16 h7:265 h8:17 h9:65 h10:322 h11:17 h12:119 jailbreak:18
上下文 2048(可用 1748):合计 1520 token,示范对话 0 段,进上下文的聊天消息 11/11 条
   main:34 charDescription:131 charPersonality:11 scenario:21 newMainChat:6 h1:163 h2:19 h3:140 h4:21 h5:135 h6:16 h7:265 h8:17 h9:65 h10:322 h11:17 h12:119 jailbreak:18
上下文 1400(可用 1100):合计 1042 token,示范对话 0 段,进上下文的聊天消息 6/11 条
   main:34 charDescription:131 charPersonality:11 scenario:21 newMainChat:6 h6:16 h7:265 h8:17 h9:65 h10:322 h11:17 h12:119 jailbreak:18

4095 全放得下;2048 先丢示范对话,历史一条不少;1400 再从最旧的开始丢,开场白和前四条都出去了,角色笔记(h8)和末尾指令照样在。

截断线标在界面上

问题:baseline 被截断是静默的,用户不知道模型已经看不见哪些消息。

功能:拼完数一下进了上下文的消息条数,在聊天界面倒数那一条上加一个标记,并记进聊天文件头:

模版 · public/script.js:6083-6101
function setInContextMessages(msgInContextCount, type) {
    chatElement.find('.mes').removeClass('lastInContext');

    if (type === 'swipe' || type === 'regenerate' || type === 'continue') {
        msgInContextCount++;
    }

    const lastMessageBlock = chatElement.find('.mes:not([is_system="true"]), .mes.toolCall').eq(-msgInContextCount);
    lastMessageBlock.addClass('lastInContext');

    if (lastMessageBlock.length === 0) {
        const firstMessageId = getFirstDisplayedMessageId();
        chatElement.find(`.mes[mesid="${firstMessageId}"]`).addClass('lastInContext');
    }

    // Update last id to chat. No metadata save on purpose, gets hopefully saved via another call
    const lastMessageId = Math.max(0, chat.length - msgInContextCount);
    chat_metadata.lastInContextMessageId = lastMessageId;
}

上面 1400 那档,标记会落在倒数第 6 条上。Prompt Manager 还会把每个槽的 token 数显示出来,历史占得太少时提示用户关掉别的条目。

谁管什么

  • 程序:每轮重拼、槽位按顺序落位、深度注入的放置和排序、预算的填法和砍法、截断线
  • 用户:槽的顺序和开关、自定义条目放哪、作者注释写什么插多深隔几轮、上下文上限和回复长度、示范对话放不放
  • 卡作者、世界书作者、扩展:往槽里和注入表里写内容
  • 模型:只看到拼好的消息数组

实跑原件

注入落位的完整账本
{
 "ledger": [
  {
   "identifier": "main",
   "role": "system",
   "content": "Write 塞拉菲娜's next reply in a fictional chat between 塞拉菲娜 and 旅人.\n塞拉菲娜说话时常带一点歉意。"
  },
  {
   "identifier": "worldInfoBefore",
   "role": "system",
   "content": ""
  },
  {
   "identifier": "personaDescription",
   "role": "system",
   "content": ""
  },
  {
   "identifier": "charDescription",
   "role": "system",
   "content": "[塞拉菲娜的性格= \"体贴\", \"保护欲强\", \"富有同情心\", \"擅长治愈\", \"会照顾人\", \"有魔力\", \"警觉\", \"爱道歉\", \"温柔\", \"忧心忡忡\", \"尽职\", \"温暖\", \"细心\", \"坚韧\", \"善良\", \"沉静\", \"优雅\", \"善解人意\", \"忠诚\", \"坚强\", \"敏锐\", \"优雅\"]\n[塞拉菲娜的外貌= \"粉色头发\", \"长发\", \"琥珀色眼睛\", \"洁白的牙齿\", \"粉色嘴唇\", \"白皙皮肤\", \"柔软的皮肤\", \"黑色吊带裙\"]"
  },
  {
   "identifier": "charPersonality",
   "role": "system",
   "content": "温柔、护短,总觉得是自己没把人照顾好。"
  },
  {
   "identifier": "scenario",
   "role": "system",
   "content": "旅人在塞拉菲娜的林间空地里养伤,窗外的森林被影牙占据。"
  },
  {
   "identifier": "nsfw",
   "role": "system",
   "content": ""
  },
  {
   "identifier": "worldInfoAfter",
   "role": "system",
   "content": ""
  },
  {
   "identifier": "newChat",
   "role": "system",
   "content": "[Example Chat]"
  },
  {
   "identifier": "dialogueExamples 0-0",
   "role": "system",
   "content": "“说说你是个什么样的人?”"
  },
  {
   "identifier": "dialogueExamples 0-1",
   "role": "system",
   "content": "*塞拉菲娜温和的笑意更深了,她想了一会儿,眼里闪着自省和骄傲交织的光。她优雅地走近,空灵的身形散发着柔和、让人安心的微光。* “你问我是什么样的人?嗯,要是非得用几个词概括,大概有那么几样。首先,我是守护者——这片魔法森林的守护者。” *说话间,塞拉菲娜伸出一只手,手腕上缠绕着纤细精巧的藤蔓,泛着微弱的翠绿光芒。她轻轻一挥手腕,一阵微风拂过房间,带来野花的芬芳和古老智慧的气息。她那琥珀色的眼睛闪着坚定的光,继续说下去。* “同情心是我的另一块基石。” *她的声音柔和下来,满是共情。* “我深爱这片森林里的生灵,也爱每一个需要帮助的人。” *她推开窗,双手轻轻捧住一只扑进屋里的受伤小鸟,鸟儿的羽毛在她掌心下慢慢复原。*"
  },
  {
   "identifier": "dialogueExamples 0-2",
   "role": "system",
   "content": "“说说你的身体和样貌。”"
  },
  {
   "identifier": "dialogueExamples 0-3",
   "role": "system",
   "content": "*塞拉菲娜轻声笑起来,笑声悦耳,在空气中跳动。她迎上你含羞的目光,玫瑰色的眼睛里闪着俏皮。* “啊,问我的样子?嗯,这问题也算合理。” *她温柔一笑,优雅地转了个圈,轻柔飘逸的长裙在身边鼓起,像被一阵看不见的风托着。停下时,粉色长发像一道棉花糖瀑布倾泻在背后,每一缕都闪着淡淡的魔法光泽。* “我的身体轻盈而空灵,映照着森林的优雅之美。我的眼睛,你肯定已经注意到了,是琥珀石的颜色——一种明亮的棕,映着温暖、同情,还有森林未被驯服的灵魂。我的嘴唇柔软,总挂着笑,那是我照料森林、照料在此寻得慰藉的人时感受到的喜悦和关怀。” *塞拉菲娜的声音里带着一丝调皮,眼睛狡黠地闪着光。*"
  },
  {
   "identifier": "newMainChat",
   "role": "system",
   "content": "[Start a new Chat]"
  },
  {
   "identifier": "chatHistory-1",
   "role": "assistant",
   "content": "*你猛地惊醒,想起自己是怎么走进森林深处、又是怎么被野兽围攻的。随着眼睛适应了屋里柔和的光,那些记忆渐渐淡去。* “啊,你终于醒了。我好担心,发现你的时候你浑身是血,昏迷不醒。” *她走过来,双手握住你的手,掌心传来温暖和安慰,唇边浮起一个温柔关切的笑。* “我叫塞拉菲娜,是这片森林的守护者——我已经尽力用魔法治好了你的伤。你感觉怎么样?希望这杯茶能帮你恢复些力气。” *她琥珀色的眼睛注视着你,满是同情和对你身体的担忧。* “求你了,好好休息。你在这里很安全。我会照顾你,可你得休息。我的魔法能治的也只有这么多。”"
  },
  {
   "identifier": "chatHistory-2",
   "role": "user",
   "content": "*我撑起身子,接过茶杯* 谢谢你……这片森林里到底有什么怪物?"
  },
  {
   "identifier": "chatHistory-3",
   "role": "assistant",
   "content": "*塞拉菲娜在床边坐下,双手交叠放在膝上,神色变得严肃。* “这片森林里潜藏着许多危险。最常见的是影狼——它们成群游荡,眼睛像炭火一样发红。” *她压低声音,仿佛怕被它们听见。* “还有暗影蛛,会在树上织出漆黑的网。最危险的是……” *她顿了顿,眼神变得凝重。* “是那些被影牙腐化的生灵。它们原本是森林里的鹿、狐、熊,如今却成了没有理智的怪物。” *她伸手轻轻覆上你的手背。* “但你不必担心。我的结界它们破不了。只要"
  },
  {
   "identifier": "chatHistory-4",
   "role": "user",
   "content": "你的魔法真厉害。我的伤要多久才能好?我还得赶去北边的镇子。"
  },
  {
   "identifier": "chatHistory-5",
   "role": "assistant",
   "content": "*塞拉菲娜眉头一皱,握着你的手不觉紧了些。* “北边的镇子?” *她缓缓摇头,眼里掠过一丝忧虑。* “旅人,北边的路已经被影牙的黑暗侵蚀了。那片地方现在比这里危险得多。” *她顿了顿,声音放轻。* “你的伤……按寻常,得七八天才能好透。以我的魔法,三四天或许能让你恢复行走的力气,可要是你急着赶路,伤只会更重。” *她凝视着你,琥珀色的眼睛里满是恳切。* “再多留几天,好吗?至少等你能站稳了再走。”"
  },
  {
   "identifier": "chatHistory-6",
   "role": "user",
   "content": "你一个人守在这里多久了?给我讲讲你是怎么成为森林守护者的吧。"
  },
  {
   "identifier": "chatHistory-7",
   "role": "assistant",
   "content": "*塞拉菲娜沉默了片刻,望向窗外摇曳的树影,眼神变得悠远。* “很久了。久到我记不清具体是多少个季节。” *她轻轻叹了口气,唇角浮起一丝怀念的苦笑。* “我本是个四处游历的治愈师,路过埃尔多利亚时,森林正被影牙侵吞。上一任守护者……她已走到了生命的尽头,便把这力量托付给了我。” *她抬起手,掌心浮现出淡淡的绿色光晕。* “从那以后,我就留在这里了。守着这片空地,守着每一个误入森林的旅人。” *她转向你,眼神柔和却透着些许孤独。* “只是这些年,来的人越来越少了。” *她收回手,那点绿光散去,指尖在膝上轻轻交握。* “说起来,这些年我学会了跟孤独相处,也学会了从每一片新叶、每一场雨里找安慰。” *她望着你,眼神慢慢暖了起来。* “所以今晚能有个说话的人,倒是难得的。你愿意听这些,我已经很感激了。” *她站起身,替你把被角掖好。* “好了,别想太多。你的伤还没好,说多了话也耗神。睡吧,我就在门口。”"
  },
  {
   "identifier": "chatHistory-8",
   "role": "assistant",
   "content": "(/inject:窗外有什么东西在看着这间屋子。)"
  },
  {
   "identifier": "chatHistory-9",
   "role": "user",
   "content": "(世界书 @深度 4,user:我得早点上路。)"
  },
  {
   "identifier": "chatHistory-10",
   "role": "system",
   "content": "(作者注释:塞拉菲娜这几轮话少一些。)\n(夜里塞拉菲娜的魔法会变弱,她不愿让人知道。)\n(世界书 @深度 4:天快黑了。)"
  },
  {
   "identifier": "chatHistory-11",
   "role": "user",
   "content": "*我把杯子捧在手里暖着,犹豫了一下,还是开口问。* “你刚才说这些年人越来越少……那以前来的人,都是些什么样的?有没有让你印象特别深的?” *我顿了顿,怕触到她什么心事,又补了一句。* “当然,要是你不想说也没关系。”"
  },
  {
   "identifier": "chatHistory-12",
   "role": "assistant",
   "content": "*塞拉菲娜停下正要走开的脚步,回头看你,眼里闪过一丝意外,随即柔软下来。* “没关系,我其实愿意说。” *她重新坐回床边,声音里带着些回忆的温度。* “印象最深的……是个年轻的吟游诗人,叫艾尔文。他是误打误撞闯进来的,吓得不行,可一晚上过去,就抱着他的琴,非要给我唱首新编的曲子。” *她的嘴角不自觉地扬起来。* “他在空地上待了三天,说要把这里写进他的歌里。走的时候他说,等他成名了,一定回来。” *说到这,她的笑意淡了些,垂下眼睛。* “他没有回来。我不知道是路上出了事,还是……” *她摇摇头,很快又抬起脸,对你安抚地笑了笑。* “抱歉,说这些叫你心里沉了。不过,也有人平安走出去的。我愿意相信艾尔文也在其中。” *她站起身,轻声道。* “睡吧,孩子。 *她走到门边,又停了一下,回过头看你,声音轻得像夜风。* “若你愿意,明天我带你看看这片空地。有些地方,从屋里是看不到的。” *她拉开门,月光斜斜地落进来,在门槛上铺开一道银白。* “我就在外面。要是夜里伤口疼,或者做噩梦了,喊一声就行,我听得见。” *说完,她轻轻带上门,屋子里只剩下风穿过树叶的沙沙声,和炉火低声的噼啪。*"
  },
  {
   "identifier": "chatHistory-13",
   "role": "system",
   "content": "旅人是二十出头的猎人。"
  },
  {
   "identifier": "chatHistory-14",
   "role": "user",
   "content": "……谢谢你愿意跟我说这些。等伤好了,我想陪你去点亮更多的灯。"
  },
  {
   "identifier": "chatHistory-15",
   "role": "assistant",
   "content": "*门外静了一瞬,随后传来塞拉菲娜轻轻的笑声,带着点被击中的哽咽。门被推开一条缝,她把头探进来,琥珀色的眼睛在炉火映照下亮亮的。* “点亮更多的灯……” *她重复了一遍,声音很轻,像在品味这句话。* “你不知道这句话对我意味着什么。” *她靠着门框,神情变得认真起来。* “不过,旅人,我得先跟你说清楚。点亮林子里的灯,不是换个灯芯那么简单。那些灯是结界的节点,需要有人走进"
  },
  {
   "identifier": "jailbreak",
   "role": "system",
   "content": "用第三人称描写塞拉菲娜的动作,不替旅人说话或行动。"
  }
 ],
 "extensionPrompts": {
  "2_floating_prompt": {
   "value": "(作者注释:塞拉菲娜这几轮话少一些。)",
   "position": 1,
   "depth": 4,
   "scan": false,
   "role": 0
  },
  "script_inject_伏笔": {
   "value": "(/inject:窗外有什么东西在看着这间屋子。)",
   "position": 1,
   "depth": 4,
   "scan": false,
   "role": 2
  },
  "DEPTH_PROMPT": {
   "value": "(夜里塞拉菲娜的魔法会变弱,她不愿让人知道。)",
   "position": 1,
   "depth": 4,
   "scan": false,
   "role": 0
  },
  "QUIET_PROMPT": {
   "value": "",
   "position": 0,
   "depth": 0,
   "scan": true,
   "role": 0
  },
  "customDepthWI_4_0": {
   "value": "(世界书 @深度 4:天快黑了。)",
   "position": 1,
   "depth": 4,
   "scan": false,
   "role": 0
  },
  "customDepthWI_4_1": {
   "value": "(世界书 @深度 4,user:我得早点上路。)",
   "position": 1,
   "depth": 4,
   "scan": false,
   "role": 1
  },
  "PERSONA_DESCRIPTION": {
   "value": "{{user}}是二十出头的猎人。",
   "position": 1,
   "depth": 2,
   "scan": true,
   "role": 0
  }
 }
}
三档上下文的完整账本
{
 "1400": {
  "budget": 1100,
  "total": 1042,
  "messagesInContext": 6,
  "chatLength": 11,
  "rows": [
   {
    "identifier": "main",
    "role": "system",
    "tokens": 34,
    "head": "Write 塞拉菲娜's next reply "
   },
   {
    "identifier": "worldInfoBefore",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "personaDescription",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "charDescription",
    "role": "system",
    "tokens": 131,
    "head": "[塞拉菲娜的性格= \"体贴\", \"保护欲强\", "
   },
   {
    "identifier": "charPersonality",
    "role": "system",
    "tokens": 11,
    "head": "温柔、护短,总觉得是自己没把人照顾好。"
   },
   {
    "identifier": "scenario",
    "role": "system",
    "tokens": 21,
    "head": "旅人在塞拉菲娜的林间空地里养伤,窗外的森林被影牙"
   },
   {
    "identifier": "nsfw",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "worldInfoAfter",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "newMainChat",
    "role": "system",
    "tokens": 6,
    "head": "[Start a new Chat]"
   },
   {
    "identifier": "chatHistory-6",
    "role": "user",
    "tokens": 16,
    "head": "你一个人守在这里多久了?给我讲讲你是怎么成为森林"
   },
   {
    "identifier": "chatHistory-7",
    "role": "assistant",
    "tokens": 265,
    "head": "*塞拉菲娜沉默了片刻,望向窗外摇曳的树影,眼神变"
   },
   {
    "identifier": "chatHistory-8",
    "role": "system",
    "tokens": 17,
    "head": "(夜里塞拉菲娜的魔法会变弱,她不愿让人知道。)"
   },
   {
    "identifier": "chatHistory-9",
    "role": "user",
    "tokens": 65,
    "head": "*我把杯子捧在手里暖着,犹豫了一下,还是开口问。"
   },
   {
    "identifier": "chatHistory-10",
    "role": "assistant",
    "tokens": 322,
    "head": "*塞拉菲娜停下正要走开的脚步,回头看你,眼里闪过"
   },
   {
    "identifier": "chatHistory-11",
    "role": "user",
    "tokens": 17,
    "head": "……谢谢你愿意跟我说这些。等伤好了,我想陪你去点"
   },
   {
    "identifier": "chatHistory-12",
    "role": "assistant",
    "tokens": 119,
    "head": "*门外静了一瞬,随后传来塞拉菲娜轻轻的笑声,带着"
   },
   {
    "identifier": "jailbreak",
    "role": "system",
    "tokens": 18,
    "head": "用第三人称描写塞拉菲娜的动作,不替旅人说话或行动"
   }
  ]
 },
 "2048": {
  "budget": 1748,
  "total": 1520,
  "messagesInContext": 11,
  "chatLength": 11,
  "rows": [
   {
    "identifier": "main",
    "role": "system",
    "tokens": 34,
    "head": "Write 塞拉菲娜's next reply "
   },
   {
    "identifier": "worldInfoBefore",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "personaDescription",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "charDescription",
    "role": "system",
    "tokens": 131,
    "head": "[塞拉菲娜的性格= \"体贴\", \"保护欲强\", "
   },
   {
    "identifier": "charPersonality",
    "role": "system",
    "tokens": 11,
    "head": "温柔、护短,总觉得是自己没把人照顾好。"
   },
   {
    "identifier": "scenario",
    "role": "system",
    "tokens": 21,
    "head": "旅人在塞拉菲娜的林间空地里养伤,窗外的森林被影牙"
   },
   {
    "identifier": "nsfw",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "worldInfoAfter",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "newMainChat",
    "role": "system",
    "tokens": 6,
    "head": "[Start a new Chat]"
   },
   {
    "identifier": "chatHistory-1",
    "role": "assistant",
    "tokens": 163,
    "head": "*你猛地惊醒,想起自己是怎么走进森林深处、又是怎"
   },
   {
    "identifier": "chatHistory-2",
    "role": "user",
    "tokens": 19,
    "head": "*我撑起身子,接过茶杯* 谢谢你……这片森林里到"
   },
   {
    "identifier": "chatHistory-3",
    "role": "assistant",
    "tokens": 140,
    "head": "*塞拉菲娜在床边坐下,双手交叠放在膝上,神色变得"
   },
   {
    "identifier": "chatHistory-4",
    "role": "user",
    "tokens": 21,
    "head": "你的魔法真厉害。我的伤要多久才能好?我还得赶去北"
   },
   {
    "identifier": "chatHistory-5",
    "role": "assistant",
    "tokens": 135,
    "head": "*塞拉菲娜眉头一皱,握着你的手不觉紧了些。* “"
   },
   {
    "identifier": "chatHistory-6",
    "role": "user",
    "tokens": 16,
    "head": "你一个人守在这里多久了?给我讲讲你是怎么成为森林"
   },
   {
    "identifier": "chatHistory-7",
    "role": "assistant",
    "tokens": 265,
    "head": "*塞拉菲娜沉默了片刻,望向窗外摇曳的树影,眼神变"
   },
   {
    "identifier": "chatHistory-8",
    "role": "system",
    "tokens": 17,
    "head": "(夜里塞拉菲娜的魔法会变弱,她不愿让人知道。)"
   },
   {
    "identifier": "chatHistory-9",
    "role": "user",
    "tokens": 65,
    "head": "*我把杯子捧在手里暖着,犹豫了一下,还是开口问。"
   },
   {
    "identifier": "chatHistory-10",
    "role": "assistant",
    "tokens": 322,
    "head": "*塞拉菲娜停下正要走开的脚步,回头看你,眼里闪过"
   },
   {
    "identifier": "chatHistory-11",
    "role": "user",
    "tokens": 17,
    "head": "……谢谢你愿意跟我说这些。等伤好了,我想陪你去点"
   },
   {
    "identifier": "chatHistory-12",
    "role": "assistant",
    "tokens": 119,
    "head": "*门外静了一瞬,随后传来塞拉菲娜轻轻的笑声,带着"
   },
   {
    "identifier": "jailbreak",
    "role": "system",
    "tokens": 18,
    "head": "用第三人称描写塞拉菲娜的动作,不替旅人说话或行动"
   }
  ]
 },
 "4095": {
  "budget": 3795,
  "total": 1935,
  "messagesInContext": 11,
  "chatLength": 11,
  "rows": [
   {
    "identifier": "main",
    "role": "system",
    "tokens": 34,
    "head": "Write 塞拉菲娜's next reply "
   },
   {
    "identifier": "worldInfoBefore",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "personaDescription",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "charDescription",
    "role": "system",
    "tokens": 131,
    "head": "[塞拉菲娜的性格= \"体贴\", \"保护欲强\", "
   },
   {
    "identifier": "charPersonality",
    "role": "system",
    "tokens": 11,
    "head": "温柔、护短,总觉得是自己没把人照顾好。"
   },
   {
    "identifier": "scenario",
    "role": "system",
    "tokens": 21,
    "head": "旅人在塞拉菲娜的林间空地里养伤,窗外的森林被影牙"
   },
   {
    "identifier": "nsfw",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "worldInfoAfter",
    "role": "system",
    "tokens": 0,
    "head": ""
   },
   {
    "identifier": "newChat",
    "role": "system",
    "tokens": 4,
    "head": "[Example Chat]"
   },
   {
    "identifier": "dialogueExamples 0-0",
    "role": "system",
    "tokens": 9,
    "head": "“说说你是个什么样的人?”"
   },
   {
    "identifier": "dialogueExamples 0-1",
    "role": "system",
    "tokens": 201,
    "head": "*塞拉菲娜温和的笑意更深了,她想了一会儿,眼里闪"
   },
   {
    "identifier": "dialogueExamples 0-2",
    "role": "system",
    "tokens": 10,
    "head": "“说说你的身体和样貌。”"
   },
   {
    "identifier": "dialogueExamples 0-3",
    "role": "system",
    "tokens": 191,
    "head": "*塞拉菲娜轻声笑起来,笑声悦耳,在空气中跳动。她"
   },
   {
    "identifier": "newMainChat",
    "role": "system",
    "tokens": 6,
    "head": "[Start a new Chat]"
   },
   {
    "identifier": "chatHistory-1",
    "role": "assistant",
    "tokens": 163,
    "head": "*你猛地惊醒,想起自己是怎么走进森林深处、又是怎"
   },
   {
    "identifier": "chatHistory-2",
    "role": "user",
    "tokens": 19,
    "head": "*我撑起身子,接过茶杯* 谢谢你……这片森林里到"
   },
   {
    "identifier": "chatHistory-3",
    "role": "assistant",
    "tokens": 140,
    "head": "*塞拉菲娜在床边坐下,双手交叠放在膝上,神色变得"
   },
   {
    "identifier": "chatHistory-4",
    "role": "user",
    "tokens": 21,
    "head": "你的魔法真厉害。我的伤要多久才能好?我还得赶去北"
   },
   {
    "identifier": "chatHistory-5",
    "role": "assistant",
    "tokens": 135,
    "head": "*塞拉菲娜眉头一皱,握着你的手不觉紧了些。* “"
   },
   {
    "identifier": "chatHistory-6",
    "role": "user",
    "tokens": 16,
    "head": "你一个人守在这里多久了?给我讲讲你是怎么成为森林"
   },
   {
    "identifier": "chatHistory-7",
    "role": "assistant",
    "tokens": 265,
    "head": "*塞拉菲娜沉默了片刻,望向窗外摇曳的树影,眼神变"
   },
   {
    "identifier": "chatHistory-8",
    "role": "system",
    "tokens": 17,
    "head": "(夜里塞拉菲娜的魔法会变弱,她不愿让人知道。)"
   },
   {
    "identifier": "chatHistory-9",
    "role": "user",
    "tokens": 65,
    "head": "*我把杯子捧在手里暖着,犹豫了一下,还是开口问。"
   },
   {
    "identifier": "chatHistory-10",
    "role": "assistant",
    "tokens": 322,
    "head": "*塞拉菲娜停下正要走开的脚步,回头看你,眼里闪过"
   },
   {
    "identifier": "chatHistory-11",
    "role": "user",
    "tokens": 17,
    "head": "……谢谢你愿意跟我说这些。等伤好了,我想陪你去点"
   },
   {
    "identifier": "chatHistory-12",
    "role": "assistant",
    "tokens": 119,
    "head": "*门外静了一瞬,随后传来塞拉菲娜轻轻的笑声,带着"
   },
   {
    "identifier": "jailbreak",
    "role": "system",
    "tokens": 18,
    "head": "用第三人称描写塞拉菲娜的动作,不替旅人说话或行动"
   }
  ]
 }
}