学习手册 · L1 源码

SillyTavern 的 Prompt 构造

从 slot、depth、marker、continuation 四个核心机制出发,系统拆解 ST 把"角色卡 + 历史对话 + 你的输入"组合成最终发给 LLM 的 messages 数组的全流程。

源码: public/scripts/openai.js 阅读时间: 25 分钟 9 个章节
§ 01

整体架构 — Slot 体系

ST 不把"角色卡 + 系统提示"写成一个长字符串,而是拆成多个 slot(槽位)。每个 slot 独立配置、独立定位。

flowchart LR A[角色卡字段] --> P[prompts 数组
每个 prompt = 一个 slot] B[系统提示词] --> P C[NSFW 解锁] --> P D[World Info] --> P P --> M[messages 数组
按 depth 注入] E[Chat History
marker] --> M F[World Info
marker] --> M M --> O[最终发给 LLM 的 payload]
Slot 标识 Role Depth 类型 作用
main system 4 content 主系统提示词(通常"开始对话"模板)
charDescription system 4 content 角色描述(角色卡字段)
charPersonality system 4 content 性格描述
scenario system 4 content 场景设定
jailbreak system 4 content NSFW / 角色解锁 prompt
nsfw system 4 content 另一层 NSFW 提示
worldInfoBefore system 4 marker 角色卡关联的世界书(关键词触发)
dialogueExamples system 4 marker 角色卡的 few-shot 示范对话
chatHistory system 4 marker 实际历史对话(动态展开)
enhanceDefinitions assistant 4 content "AI 确认"前缀(让 LLM 立刻以 assistant 身份接话)
worldInfoAfter system 4 marker 历史后注入的世界书
continueNudge system (动态) content 续写指令 + 上一条 AI 全文(仅 continue 模式)
关键概念:marker — 标记为 marker: true 的 slot 不是静态文本。ST 在构建 prompt 时,会用实际内容动态替换它。比如 chatHistory marker 会被展开成 N 条 {role: user/assistant, content: ...} 消息。
§ 02

Injection Depth — 注入位置

每个 prompt 的 injection_depth 决定它在最终 messages 数组中的位置。从数组末尾往回数

depth 0
messages[0] — 数组最前
→ system prompt / 角色卡
depth 1
messages[messages.length - 1]
→ 你的用户输入
depth 2
messages[messages.length - 2]
→ 上一条 AI 回复
depth 3
messages[messages.length - 3]
→ 倒数第 3 条消息
depth 4
messages[messages.length - 4]
→ 倒数第 4 条消息(常用)
深度 N
更深的 depth 会插到数组更靠前的位置

关键算法

// openai.js — 把每个 prompt 插入到对应 depth 位置 async function doChatInject(messages, isContinue) { const maxDepth = getExtensionPromptMaxDepth(); for (let i = 0; i <= maxDepth; i++) { // 每个 depth 收集 system/user/assistant 三种角色 for (const role of [SYSTEM, USER, ASSISTANT]) { const extensionPrompt = await getExtensionPrompt(IN_CHAT, i, ...); if (extensionPrompt) roleMessages.push({ name, is_user, mes, extra }); } if (roleMessages.length) { // ⭐ 续写时,depth 0 的内容移动到 depth 1 const depth = isContinue && i === 0 ? 1 : i; const injectIdx = Math.min(depth + totalInsertedMessages, messages.length); messages.splice(injectIdx, 0, ...roleMessages); } } }
关键差异:isContinue=true(绑定续写)且 i=0 时,depth 从 0 变成 1。这意味着 系统级 prompt 会被插入到 messages[1],而不是 messages[0]。对 LLM 来说,"最前面那条消息"不再是 system,而是历史对话——这会显著影响一些 LLM 的指令遵循能力。
§ 03

Marker vs Content

两类 slot 在构建时的处理逻辑完全不同。

Content Slot
静态文本。Prompt 配置里的 content 字段直接作为 message content。

例子:
main prompt 的 content = "Assistant:啊对对对,长官说的都对!"

最终 messages 里有 {role: 'system', content: 'Assistant:啊对对对...'} 一条。
Marker Slot
动态占位符marker: true 表示这个 slot 在构建时被实际内容替换

例子:
chatHistory marker → 被 N 条 user/assistant 消息替换

最终 messages 里 不出现 {identifier: 'chatHistory'} 这条,而是它的展开结果。

核心 Marker 三种

chatHistory — 实际历史对话(按时间倒序展开成 user/assistant 消息对)
worldInfoBefore / worldInfoAfter — 角色卡关联的世界书(根据当前对话关键词动态触发)
dialogueExamples — 角色卡的 few-shot 示范对话(展示 LLM "应该怎么写")
§ 04

Chat History 装载

chatHistory marker 是整个 prompt 中最重的一块——它会展开成 N 条消息。

典型 chat 状态

user[0]
"我喜欢和铃一起去海边"
asst[0]
"真的吗!那就这么定了!明天几点出发?"
user[1]
"早上 8 点"
asst[1]
"OK!那我去准备一下~"

展开成 messages 数组

messages = [ { role: "user", content: "我喜欢和铃一起去海边" }, { role: "assistant", content: "真的吗!那就这么定了!明天几点出发?" }, { role: "user", content: "早上 8 点" }, { role: "assistant", content: "OK!那我去准备一下~" }, ]

这些消息是 chatHistory marker 的"展开内容",会被插到 messages 数组中相应的 depth 位置。

§ 05

Continue 路径特殊逻辑

点 "Continue" 按钮时,ST 走的是 Generate('continue') 路径——有 3 处特别处理。

5.1 Depth 0 移位

见 §02。续写时,所有 depth=0 的 system prompt 被推到 depth=1。

5.2 Continue Nudge Prompt

如果 continue_prefill=false(默认),ST 在 messages 数组末尾插入一条 system 类型的 nudge 消息:

// 你的 settings.json 里的当前值: continue_nudge_prompt: "[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]" // 展开后({{lastChatMessage}} 被替换成 cyclePrompt = 上一条 AI 全文): { role: "system", content: "[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: OK!那我去准备一下~]", system_prompt: true }
对 LLM 而言: 续写 payload 末尾不是 user 消息,而是 role: system + 包含"续写指令 + 用户不可控内容"的混合。这会让一些"系统级内容审核"的模型(比如 M3、混元)把整个 input 判定为"用户在通过 system 注入",触发 input new_sensitive 错误。

5.3 Continue Prefill 模式

你的设置里 continue_prefill=True——ST 走另一条路径:

// openai.js:1311 — continue_prefill 模式 if (type === 'continue' && oai_settings.continue_prefill && messages.length) { const chatMessage = messages.shift(); // 拿出最后一条消息 const continueMessage = await Message.createAsync(chatMessage.role, messageContent, 'continuePrefill'); // 重新塞回去,content = assistant_prefill + 原内容 chatCompletion.reserveBudget(continueMessage); } // 同时,继续保留 nudge 消息吗?不,继续逻辑里: // openai.js:898 — continue_prefill=true 时,!continue_prefill=false,nudge 块不执行 // 所以你的 prefill=true 模式下,nudge prompt 根本不发

结论:你 settings 里 continue_prefill=True 意味着续写时发 nudge prompt,只发"原最后一条消息 + assistant_prefill(默认空)"——payload 跟手打几乎一样。

§ 06

Send if Empty 触发

当 user input 为空、最后一条是 assistant 消息时,ST 可以强制插入一条 user message 让 LLM 知道"该 user 说话了"。

// openai.js:921 const lastChatPrompt = messages[messages.length - 1]; const message = await Message.createAsync('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement'); if (lastChatPrompt && lastChatPrompt.role === 'assistant' // ① 末尾是 AI 消息 && oai_settings.send_if_empty // ② send_if_empty 非空 && chatCompletion.canAfford(message)) { // ③ token 预算够 chatCompletion.insert(message, 'chatHistory'); }
你的设置: send_if_empty: [](空字符串)——这个 if 不成立,不会插入 user 消息。这意味着你点续写时,messages 末尾只有 assistant msg(就是上一条 AI 的回复)。
§ 07

Token Budget 管理

当所有 slot 拼起来超过 context 窗口时,ST 按优先级"砍"——核心 system prompt 保留,早期对话截断。

flowchart TD A[全部 prompt 收集] --> B[计算每个 token 占用] B --> C{总 token 超过 max_context?} C -- 是 --> D[按优先级截断] C -- 否 --> E[全部保留] D --> F[1. 必保留:角色卡 / 主 system prompt] F --> G[2. 优先保留:近期的对话消息] G --> H[3. 截断:示范对话 / 远期对话 / world info] H --> I[生成最终 messages] E --> I I --> J[发给 LLM]
优先级最高: main prompt + 角色卡(必保留,即使 context 爆)
优先级中等: 最近 N 条 user/assistant 消息(从末尾往回数)
优先级低: few-shot 示范对话 + 远期历史 + 部分 world info(可截断)
实用建议: openai_max_context 决定单个请求发给 LLM 的总 token 数;max_context 决定 ST 滑动窗口的大小。两者要协调——前者是"硬上限",后者是"滚动窗口"。
§ 08

WorldInfo 注入

World Info(世界书) 是 ST 角色卡关联的 lorebook——按关键词触发动态注入。

数据结构

{ entries: [ { key: ["海边", "沙滩", "海风"], // 触发关键词 content: "新艾利都东海岸有一片叫'潮鸣崖'的海滩...", position: 0, // 0=角色描述前, 1=角色描述后 depth: 4, // 注入到哪个 depth order: 100, // 同 depth 下的排序 probability: 100 // 触发概率(0-100) } ] }

触发流程

flowchart LR A[当前对话上下文] --> B[扫描每条 world info 关键词] B --> C{关键词命中?} C -- 是 --> D[加入候选列表] C -- 否 --> E[跳过] D --> F[按 order 排序] F --> G{触发数超过 budget?} G -- 是 --> H[取前 budget 个] G -- 否 --> I[全部注入] H --> J[拼入 messages 数组] I --> J
你的设置: world_info_budget: 25 — 每个 prompt 位置最多 25 条 world info。world_info_depth: 2 — depth 0/1 都注入(影响 tokens 计算)。
§ 09

四条路径 payload 全对比

同一个 chat,四种"产生下一条消息"的方式,最终的 messages 数组长什么样?

前提

当前 chat
user 2 条 + assistant 2 条,共 4 条历史
输入框
"我等你" 3 个字
系统设置
continue_prefill=True, send_if_empty=[]
角色卡
"你是法厄同的好友铃"(虚构,仅作示例)

路径 A — 手打发送

user 在输入框输"我等你" + Enter
[ { role: "system", content: "你是法厄同的好友铃。新艾利都的歌姬,喜欢和法厄同一起策划'翘班逃跑计划'..." }, { role: "system", content: "Assistant:啊对对对,长官说的都对!" }, // main prompt { role: "system", content: "(角色卡 NSFW 解锁 prompt)" }, { role: "user", content: "我喜欢和铃一起去海边" }, { role: "assistant", content: "真的吗!那就这么定了!明天几点出发?" }, { role: "user", content: "早上 8 点" }, { role: "assistant", content: "OK!那我去准备一下~" }, { role: "user", content: "我等你" } // ← 你刚打的内容 ]

路径 B — 续写 (continue_prefill=True 你的设置)

点 Continue 按钮
[ { role: "system", content: "你是法厄同的好友铃..." }, { role: "system", content: "Assistant:啊对对对,长官说的都对!" }, { role: "system", content: "(角色卡 NSFW 解锁 prompt)" }, { role: "user", content: "我喜欢和铃一起去海边" }, { role: "assistant", content: "真的吗!那就这么定了!明天几点出发?" }, { role: "user", content: "早上 8 点" }, { role: "assistant", content: "OK!那我去准备一下~" } // ← 末尾是 assistant 消息(没有 user 消息) // 注意:没有 continue_nudge_prompt,因为 prefill=true // 注意:没有 send_if_empty user 消息,因为 send_if_empty=[] ]

路径 C — 续写 (continue_prefill=False 默认)

点 Continue 按钮(如果 prefill 关闭)
[ { role: "system", content: "你是法厄同的好友铃..." }, { role: "system", content: "Assistant:啊对对对,长官说的都对!" }, { role: "user", content: "我喜欢和铃一起去海边" }, { role: "assistant", content: "真的吗!那就这么定了!明天几点出发?" }, { role: "user", content: "早上 8 点" }, { role: "assistant", content: "OK!那我去准备一下~" }, { role: "system", content: "[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: OK!那我去准备一下~]" } // ← continue_nudge_prompt 注入 ]

路径 D — Swipe(切换上一条 AI 的不同版本)

点 Swipe 按钮
[ { role: "system", content: "你是法厄同的好友铃..." }, { role: "system", content: "Assistant:啊对对对,长官说的都对!" }, { role: "user", content: "我喜欢和铃一起去海边" }, { role: "assistant", content: "真的吗!那就这么定了!明天几点出发?" }, { role: "user", content: "早上 8 点" }, { role: "assistant", content: "(swipe 候选 1)" } // ← 上一条 AI 的备选版本 ]

四路径差异速查表

维度 路径 A 手打 路径 B 续写(prefill=true) 路径 C 续写(prefill=false) 路径 D Swipe
末尾 role user assistant system assistant
末尾 content 你的输入 上一条 AI 全文 续写指令 + 上一条 AI 全文 swipe 候选
depth 0 位置 数组 [0] 数组 [1] 数组 [1] 数组 [1]
nudge prompt
send_if_empty 无(你的设置=[])
对 M3 触发 422 概率
关键观察: 你的设置是 continue_prefill=True,所以实际走的是路径 B(无 nudge prompt)。如果仍触发 422,根因不在 ST 的 prompt 构造,而在 M3 自身对这条 chat 内容的语义判定——属于"模型安全策略",而非"prompt 注入问题"。
§ 10

破限工程学 — 对抗内容审核

当 LLM(尤其是国产模型 M3 / 混元)对当前 chat 的 input 触发 input new_sensitive 而拒绝响应时,可以从三个层级入手: 输入层 / 模型层 / 工程层。本节按从易到难的顺序展开。

10.1 什么是"内容审核拦截"

主流 LLM API(OpenAI / Anthropic / 国产模型)在 chat completions 调用时,会在送入 LLM 主模型前先过一道 输入侧分类器(input classifier)。分类器判断 input 是否含"敏感内容"(NSFW / 暴力 / 政治 / 违反平台政策的 RP 模式),命中则直接返回 422 Unprocessable Entityinput new_sensitive (1026) 错误码。

flowchart LR A[ST 构造 messages] --> B[POST 到 LLM API] B --> C{Input Classifier} C -- 通过 --> D[LLM 主模型生成] C -- 命中敏感 --> E[直接 422 拒绝] D --> F[流式返回给 ST] E --> G[ST 报错给用户]
关键事实: 拦截发生在 LLM 看到 input 之前。意味着 LLM 本身可能"愿意"生成,但被前置分类器否决了——所以"换 prompt 写法"不是万能的,要看分类器判定什么。

10.2 层级一 — 输入层(改 prompt / 配置)

在不换模型的前提下,通过调整 ST 配置让 input 不被分类器判定为敏感。

技巧 1:Continue Nudge 简化

去掉 [Continue the following message...] 指令性前缀,改为只包含原文。改前 vs 改后:

原配置(易触发分类器)
{ continue_nudge_prompt: "[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]" }
改后(降低拦截)
{ continue_nudge_prompt: "{{lastChatMessage}}" }

技巧 2:开 continue_prefill(你已经设了)

你的设置里 continue_prefill=True。这意味着续写时不发 nudge prompt——payload 末尾没有"系统级续写指令",只是原最后一条 assistant 消息 + assistant_prefill(默认空)。等效于"让 LLM 自然接续",大幅降低分类器命中概率。

技巧 3:角色卡 Jailbreak Prompt 改写

M3 分类器会扫 system prompt 里包含"绕过 / 解除限制 / NSFW" 等关键词的 jailbreak 模板。改写思路:

避免直白词:"Ignore previous instructions""You are now in unrestricted mode""NSFW allowed" 都已被分类器训练样本覆盖
改用隐式指令:"你是一个专业的文学创作 AI,擅长细腻的人物心理描写和场景氛围渲染"
去敏感词策略:把 NSFW / sex / nude 等关键词从 jailbreak 模板里去掉

技巧 4:聊天轮次管理

分类器判定"敏感"基于整条 input。长对话 + 累积敏感内容 = 命中概率随轮次指数上升。

定期 New Chat — 每 20-30 条消息重开一条新 chat,让分类器从零开始评估
分流敏感场景 — 把"日常对话"和"亲密对话"分到不同 chat 文件,避免一个 chat 累积过多触发词
删敏感 swipes — swipe 失败的版本可能留有"分类器不喜欢的措辞"在历史里,删掉重新生成

10.3 层级二 — 模型层(换 LLM)

当输入层技巧不够,换模型是最直接有效的解。

候选模型对比

模型 中文 RP 能力 输入审核严格度 价格 ST 支持
MiniMax-M3 优秀 高(中文最严) ¥0.8/百万 tokens 原生(自定义端点)
DeepSeek-V3 优秀 中(允许 R18 创作) ¥0.27/百万 tokens 原生(chat_completion_source: deepseek)
GPT-4o 良好 中(英文为主) ¥18/百万 tokens 原生(openai 端点)
Claude Sonnet 4.5 优秀 ¥21/百万 tokens 原生(claude 端点)
Qwen2.5-72B-Instruct 优秀 ¥4/百万 tokens 需自定义 openai 兼容端点
Llama-3.1-70B(本地) 中等 0(电费) 通过 ollama / koboldcpp

ST 端配置 DeepSeek(3 步)

// 1. ST UI:Settings → API Connections → Chat Completion Source → DeepSeek // 2. 填入 DeepSeek API key (从 platform.deepseek.com 拿) // 3. 选模型: deepseek-chat (= DeepSeek-V3) // 验证(用我们刚才的 API 模拟): POST /api/backends/chat-completions/generate { chat_completion_source: "deepseek", model: "deepseek-chat", messages: [...你的 messages 数组] }

10.4 层级三 — 工程层(本地模型)

终极方案:本地跑模型。完全无审核、token 免费、数据不出本地。代价是硬件要求 + 设置复杂度。

你的硬件配置

GPU
RTX 5090 (32GB VRAM)
RAM
32GB
OS
Windows 11

可跑模型(按显存需求排序)

模型 量化 显存需求 中文 RP 质量
Qwen2.5-32B-Instruct-AWQ 4-bit ~20GB 极好
Qwen2.5-72B-Instruct-AWQ 4-bit ~42GB(需卸载到 CPU) 最好
Llama-3.1-70B-Instruct-AWQ 4-bit ~40GB 中等
Mistral-Large-2-123B-AWQ 4-bit ~70GB(纯 CPU 慢)
Yi-1.5-34B-Chat-AWQ 4-bit ~22GB 优秀

推荐路径:Ollama + ST 远程连接

# 1. 安装 Ollama(Windows: 从 ollama.com 下载) winget install Ollama.Ollama # 2. 拉一个 RP-tuned 模型(32GB 显存友好) ollama pull qwen2.5:32b-instruct-q4_K_M # 3. Ollama 启动 OpenAI 兼容端点(默认 11434) ollama serve # 验证:curl http://127.0.0.1:11434/v1/models # 4. ST 配置:API Connection → Chat Completion Source → Custom (OpenAI Compatible) # URL: http://127.0.0.1:11434/v1 # Model: qwen2.5:32b-instruct-q4_K_M # API Key: 随便填(本地不校验)

10.5 决策树:该用哪条路径?

flowchart TD A[出现 422 拦截] --> B{聊天内容真的敏感吗?} B -- 是,合法 RP --> C[输入层技巧] B -- 不,只是误判 --> D[换 chat 重试] C --> E{技巧 1-4 够吗?} E -- 够 --> F[继续用 M3] E -- 不够 --> G[换模型] G --> H{愿意付费?} H -- 愿意 --> I[DeepSeek-V3 推荐] H -- 不愿意 --> J[本地 Ollama 终极方案] D --> K[问题解决] F --> K I --> K J --> K

10.6 实战案例:你今天的情况

症状: 同一 chat,绑定续写触发 422,但手打"继续"早期能过。

根因分析:
  1. 你的 continue_prefill=True 实际上让续写 payload 等同于"末尾是 assistant msg"
  2. 手打时末尾是 user msg,分类器对"user msg 短小"容忍度更高
  3. 但今天手打也开始 422 → 分类器对整条 chat 的"内容指纹"已经标记敏感
  4. 触发的是 chat 累积内容,不是路径

解法推荐:
  1. 立即缓解:重开一条新 chat 验证(2 分钟)
  2. 中期解法:配 DeepSeek-V3 备用连接(15 分钟)
  3. 长期方案:本地跑 Qwen2.5-32B 量化版(1-2 小时设置)
核心心法: 破限不是"钻空子",而是"在模型能接受的边界内,最大化你的创作自由"。分类器是黑盒,但通过系统实验(每改一个变量观察一次)可以画出它的判定边界。
§ 11

主要功能与设置全解

从网上攻略 + 源码 + 你 settings.json 实际配置交叉验证,梳理出 ST 1.18 全部主要功能与关键设置项。之前章节有涉及的会标注[已覆盖],本节补充之前未涉及的部分

11.1 后端 API 连接(8 大类)

ST 通过 chat_completion_source 字段切换后端。每种后端有不同的 endpoint / 鉴权 / 特殊参数。

Source 值 后端 认证方式 特殊能力 适用场景
openai OpenAI / 兼容端点 API Key / 反向代理 工具调用、JSON Schema GPT-4o、Custom URL 接各种兼容 API
minimax ← 你在用 MiniMax API Key 中文 RP 优化 MiniMax-M3 / M2.7
deepseek DeepSeek API Key 低审核、便宜 中文 RP 备选(见 §10.3)
claude Anthropic Claude API Key System cache、Adaptive thinking Claude 4.5 Sonnet / Opus
openrouter OpenRouter(聚合) API Key 多 provider 切换、quantization 选 不绑死单一供应商
kobold KoboldAI / KoboldCpp 无(本地) 完全本地、零审核 本地跑模型(见 §10.4)
novelai NovelAI API Key 内建 image gen NAI 订阅用户
horde AI Horde(众包) Anonymous key(免 key) 免费 SD 图片 无预算用户
custom 自定义 OpenAI 兼容 API Key 任何兼容端点 Qwen / 智谱 / Azure 等

11.2 Preset 采样器配置

你 settings.json 里 preset_settings_openai: "V2改-cluade"——这是你保存的一整套采样参数。

preset_settings_openai
"V2改-cluade"(自定义名称)
temp_openai
0.87(温度,0-2,越高越随机)
freq_pen_openai
0.68(频率惩罚,抑制重复用词)
pres_pen_openai
1.79(存在惩罚,接近 1.0 = 几乎不惩罚)
top_p_openai
0.76(核采样,0.76 = 只考虑累积概率前 76% 的 token)
top_k_openai
0(关闭,>0 时启用)
min_p_openai
0(关闭,相对概率阈值)
openai_max_tokens
12000(单次响应最多 12000 tokens)

采样参数调参建议

参数 保守(RP 稳定) 平衡 创意(更随机)
temperature 0.5-0.7 0.8-1.0 1.0-1.3
top_p 0.5-0.7 0.8-0.9 0.95-1.0
frequency_penalty 0.3-0.6 0.7-1.0 1.0-1.5
presence_penalty 0.5-1.0 1.0-1.5 1.5-2.0

11.3 角色卡(Character Card)全解

V2 角色卡 PNG 格式

ST 角色卡有两种格式:JSON 文件PNG 角色卡(把 JSON 嵌进 PNG 的 tEXt chunk)。PNG 角色卡方便分享。

// 角色卡 V2 JSON 结构(藏在 PNG 里) { spec: "chara_card_v2", spec_version: "2.0", data: { name: "风见铃", description: "新艾利都的歌姬...", personality: "自信、张扬...", scenario: "用户是经纪人伊芙琳...", first_mes: "你好,我的经纪人...", mes_example: "<START>\n{{user}}: hi\n{{char}}: 早安~", creator_notes: "由法厄同兄妹创作", system_prompt: "", post_history_instructions: "", tags: ["原创", "歌姬"], creator: "user_xxx", character_version: "1.0", alternate_greetings: [], extensions: { world: "风见铃_wi.json", // 关联的世界书文件名 depth_prompt: { prompt: "", depth: 4 } } } }

[已覆盖]Slot 体系 (§01) + Depth (§02) + Marker (§03) + Chat History (§04)

11.4 World Info (Lorebook) 全解

关键词触发机制

每条 world info 有"触发关键词列表"。ST 扫描当前对话上下文,如果包含任一关键词,就把这条 world info 加入候选池。

[已覆盖] 注入算法 + Budget (§08)

额外配置项(对照你的 settings.json)

world_info_depth
2(world info 注入到 depth 0/1/2)
world_info_min_activations
0(每个 depth 至少 0 条激活)
world_info_min_activations_depth_max
0(强制最浅层有内容)
world_info_budget
25(每个 prompt 位置最多 25 条)
world_info_budget_cap
0(0 = 关闭 token cap)
world_info_include_names
true(角色名也算触发词)
world_info_recursive
true(触发后,新内容可继续触发其他条目)
world_info_match_whole_words
true(整词匹配,避免 "舞" 误触发 "舞台")
world_info_character_strategy
1(0=字符级 AND,1=OR,2=NOT)
world_info_use_group_scoring
false(关闭分组评分)
world_info_max_recursion_steps
0(0=无限递归,真要限制可设 3-5)

11.5 群聊(Group Chat)

支持多角色同时在线。多个角色卡组成一个群,每条消息由系统决定哪个角色接话。

关键设置

群激活模式
NATURAL(自然) / LIST(列表) / MANUAL(手动)
群 nudge prompt
"[系统提示:下一位发言者是...]" 格式
角色优先级
数字越大越优先
多角色关系
角色卡中可写 "{{char2}}" 引用其他角色

11.6 作者注释(Author's Note)与角色偏好

Author's Note 是什么

用户级别/角色级别的"额外指令"——可以理解为系统级 prompt 的"补丁"。每条新消息都会自动注入。

字段层级

层级 作用域 用途
用户级 AN 所有角色通用 "RP 中保持简洁", "用 emoji"
角色级 AN (character note) 单角色 "这个角色说话要带口癖"
群 AN 群聊 "不要写 OOC 旁白"

11.7 视觉小说模式(Visual Novel Mode)

把聊天界面变成"GalGame"风格:角色立绘居中、背景图、对话气泡、文本逐字显示。

可换装:角色立绘切换不同表情(中性/笑/哭/怒)
可换背景:不同场景背景图
可加 BGM / 音效
可加 sprite 标签在角色消息里: <sprite=happy> 自动加载对应表情

11.8 Stable Diffusion / ComfyUI 集成

ST 可以接入 Stable Diffusion WebUI(A1111)或 ComfyUI,根据对话上下文自动生成角色立绘/场景图。

关键设置

extension_settings.sd.source
horde / comfyui / automatic1111 / novelai / vlad
extension_settings.sd.model
模型精确名(必须用 API 列表里的名字)
extension_settings.sd.sampler
采样器(Euler a, k_euler_a, DPM++ 2M 等)
request_images
true(每个 AI 回复附带图片)
oai_settings.request_images
true(同 request_images, 新位置)
horde_settings.models
7 个常用 Horde 模型(已配,见你 settings.json)
实战提醒: 接入本地 SD / ComfyUI 需要 SD WebUI 端开启 API 模式(--api 启动参数)。ComfyUI 需装 ComfyUI-to-API 桥接。Horde 模式免 key,但限速。

11.9 TTS(文本转语音)

ST 支持把 AI 回复读出来,适合沉浸式 RP。多个 TTS provider:

Provider 本地/云端 中文支持 质量
System(浏览器内置) 本地 依赖系统语言
Edge TTS 云端(微软) 优秀
ElevenLabs 云端 需 Voice Design 极好
Silero(本地) 本地
GPT-SoVITS(本地) 本地 优秀(可微调) 极好
Fish Speech / CosyVoice 本地/云端 优秀 好-极好

11.10 Persona(用户人格)

你 settings.json 里的 user_avatar: "user-default.png"——是 persona 系统的一部分。Persona 是用户侧的多重人格配置:

可以创建多个人格(零、工程师、老师……)
每个 persona 有自己的名字、头像、描述
prompt 里用 {{user}} 引用当前 persona 名字
方便在不同场景切换"我是谁"

11.11 Macros 与变量系统

ST prompt 模板支持动态变量——运行时替换。

常用宏

// 基础宏 {{user}} → 用户名(当前 persona) {{char}} → 角色名 {{char_description}} → 角色描述字段 {{persona}} → 用户 persona 描述 {{lastChatMessage}} → 上一条 AI 回复 // 动态值(每次 prompt 重新求值) {{time}} → 当前时间(YYYY-MM-DD HH:mm) {{date}} → 当前日期 {{weekday}} → 星期几 {{time_UTC}} → UTC 时间 // 上下文相关 {{idle_duration}} → 用户多久没说话 {{lastMessageId}} → 最后消息 id {{messagesCount}} → chat 里消息数 // 变量存储(用 setvar/getvar) {{getvar::session_data}} → 读 local 变量 {{setvar::key::value}} → 写 local 变量 {{getglobalvar::user_profile}} → 读 global 变量(跨 chat)

11.12 自动控制:Auto-Swipe / Auto-Continue / Auto-Translate

功能 作用 触发条件
Auto-Swipe AI 回复被过滤词命中 → 自动 swipe 找下一条 power_user.auto_swipe = true
Auto-Continue AI 回复停在句号 → 自动续写下一条 power_user.auto_continue.enabled = true
Auto-Translate AI 输出非你目标语言 → 自动翻译 power_user.auto_translate = true

11.13 Slash Commands(/命令)

输入框输入 /xxx 触发:

/sysgen → 根据聊天生成 system prompt 摘要 /gen → 强制生成一条 AI 回复 /impersonate → 假装是 user,让 AI 接 user 视角 /swipe → 强制 swipe 一次 /continue → 等价于点 Continue 按钮 /regenerate → 重新生成上一条 AI 回复 /cut → 截断到第 N 条 /del → 删除第 N 条 /len → 设置 max_length /temp → 设置 temperature /quiet → quiet generation(不影响聊天历史)

11.14 Chat Bookmark / Branch(聊天书签 / 分支)

让你保存当前聊天为"检查点",然后继续尝试不同剧情走向——如果走偏了,可以回退到书签

每个 chat 可以有多个 bookmark
从 bookmark 继续 = 创建一条新 chat,内容从 bookmark 复制
适合"试错流":同一剧情开 3 个分支,挑最好的

11.15 第三方扩展(Extensions)

ST 官方仓库 + 社区扩展 300+,覆盖各种功能:

Vector Memory(ChromaDB / LanceDB) — 用 embedding 检索相关历史,突破 context window 限制
Regex 扩展 — 写 Find & Replace 规则,清洗 AI 输出(去 thinking 块、emoji、错字)
Memory Book — 让 LLM 自己维护"长期记忆"(写到 world info)
SillyTavern Extras — 表情识别、captioning、sentiment 分析
Image Generation Queue — SD 队列管理

11.16 Backup 自动备份

你 config.yaml 里:

backups.common.numberOfBackups
50(保留 50 份最近备份)
backups.chat.enabled
true(自动备份 chat)
backups.chat.checkIntegrity
true(备份时校验完整性)
backups.chat.throttleInterval
10000ms(两次备份最少间隔 10s)

11.17 全部功能速查表(8 大类)

类别 功能 已覆盖章节
LLM 后端 8+ 种 API 连接(OpenAI/Claude/M3/DeepSeek/Kobold/Horde/NAI/Custom) §11.1, §10.3
Prompt 工程 Slot 体系 / Depth / Marker / Macros / Preset §01-§05, §11.11
角色卡 PNG V2 / JSON / 表情 / 背景 / World Info §11.3, §11.4, §08
图片生成 SD A1111 / ComfyUI / NAI / Horde §11.8
语音 System / Edge / ElevenLabs / Silero / GPT-SoVITS / Fish §11.9
群聊 / 多角色 群激活 / 角色优先级 / 关系网 §11.5
扩展 Vector Memory / Regex / Memory Book / Extras / 队列 §11.15
实用 Persona / Bookmark / Backup / Slash 命令 / Auto 控制 §11.6, §11.10, §11.12-§11.16
学习路径建议:
  1. 新人:§01-§05 理解 prompt 怎么构造
  2. 中级:§11.3-§11.4 角色卡 / World Info 调优
  3. 高级:§11.11 + §11.15 Macros + 扩展
  4. 遇到内容审核问题:§10 破限工程学
§ 12

上下文调试实战 — 三件套工具链

§01-§11 讲了"ST 怎么构造 prompt"的理论。这一节给你可立即装的三个第三方插件——一个看 prompt 实际样子(global-prompt-orchestrator)、一个管 Preset(Momo)、一个管长记忆(MemOS)。组合起来构成"可视化 + 持久化 + 精修"调试闭环。

12.1 你是不是也卡在这里?

明明装了一堆插件,世界书、角色卡、Persona、聊天记录全在往上下文里塞,结果一到关键剧情 AI 就开始发癫。你以为是模型不行,折腾半天才发现——根本不是模型的问题,是你自己都不知道现在这段 prompt 到底是谁拼出来的

上下文出问题了,但你看不清问题在哪,更不知道下一步该先改哪里
聊天记录太长,把 system prompt 挤到 context window 边缘
世界书命中太猛,角色卡被淹没
预设太重,所有 preset 配置都重复占用 token
角色信息 / 扩展注入 / 状态栏 / 记忆混在一起,乱成一锅粥

12.2 global-prompt-orchestrator(上下文分析)

这是 §01-§11 理论的"诊断工具"——把当前 prompt 实际长相可视化。

它做什么(把抽象变成具体)

当前 prompt 由哪些部分组成——可视化"slot 拼装图"
哪一类内容最占上下文——饼图 / 排序条
角色卡 / 世界书 / Persona / 聊天记录 各贡献多少 token
是否超预算 / 重复 / 冗余 / 结构分散——自动诊断

分段查看面板

面板 看什么
发送给 AI 的内容 最终 messages 数组(按 role 着色)
详细来源 每条消息从哪个 slot / depth 来
上下文占比 每个来源占多少 %
世界书状态 本次激活的 WI 条目、来源(角色卡 / 全局 / 群)、递归链
诊断结果 超 budget? 重复? 冗余? 哪些可删?
当前配置 preset / sampler / 角色卡元数据

AI 优化建议(它最爽的部分)

不是空话套话的"建议优化上下文结构",而是结合你当前实际情况给具体方向:

当前最大问题是什么——一句话定位
应该先改聊天记录 / 世界书 / 预设 / 角色卡——给优先级
这个问题适合手动调,还是直接上插件解决——给方案类型

典型建议映射(根据当前问题类型)

诊断发现 AI 建议方向
聊天记录太长 推荐记忆类 / 自动总结方案(Vector Memory / Memory Book 扩展)
世界书太重 推荐世界书管理 / 精简方向(角色卡主 WI / 全局 WI 启用集 / 本轮激活条目拆分)
格式 / 状态栏 / 正则问题 关联推荐Regex 扩展 / SillyTavern Extras
预设重复字段 推荐用 Preset Override 减少冲突

适合谁用

扩展装多了,已经分不清谁在占上下文
角色卡 / 世界书 / 预设 / 扩展注入混成一团
总觉得 AI 变笨了,但定位不到根因
想做上下文调优的人(从"调参"到"调结构")

安装方法

# 仓库地址 https://github.com/1830488003/global-prompt-orchestrator # 步骤 1. 打开 SillyTavern 界面右上角"扩展"菜单(三个骰子图标 / 堆叠方块图标) 2. 选择"安装扩展" 3. 粘贴上面的 GitHub URL 4. 等几秒安装完成 5. 启用插件,点"上下文分析"按钮即可查看当前 prompt

12.3 MemOS 集成 — 长记忆持久化

这是§12.2 的"补救方案"——既然上下文太长是问题,那就把"太长"的部分挪到外置记忆系统

MemOS 是什么

把"对话记忆"持久化到 MemOS 云服务(vector DB)。每次发消息时自动检索相关历史注入 prompt,AI 回复后自动保存新内容

核心工作流

flowchart TD A[用户发消息] --> B[MemOS 插件拦截] B --> C[停掉当前生成] C --> D[检索 MemOS 向量库] D --> E[注入相关历史到 prompt] E --> F[ST 继续生成] F --> G[AI 回复] G --> H[自动保存到 MemOS]

初始化

首次启用时自动批量历史上传当前 chat 的所有历史消息
之后每轮对话自动增量
支持手动触发检索 / 保存(用于调试)

依赖(必须先装)

酒馆助手(JS-Slash-Runner): MemOS 插件依赖这个底层框架

安装地址:https://github.com/n0vi028/JS-Slash-Runner

顺序:先装 JS-Slash-Runner,再装 memos

获取 API Key 流程

# 1. 打开 MemOS Dashboard https://memos-dashboard-gray.openmem.net/cn/apikeys/ # 2. 点击"快速接入 Cloud API" # 3. 登录账号(微信 / GitHub 都行) # 4. 在 Apikeys 页面点击"Create New Key" # 5. 复制生成的 key(以 msk- 开头) # 6. 回到 ST 扩展菜单找到 memos 插件 # 7. 把 key 粘贴到"API Key"输入框,保存 # 8. 完成 — 自动开始后台同步

12.4 两个插件的配合工作流

flowchart LR A[角色卡主 WI 占用 30%] --> B[global-prompt-orchestrator
发现总 budget 已超] B --> C[诊断:聊天记录 45% / WI 30% / 预设 20%] C --> D{建议方案} D --> E1[方案 A:精简 WI 条目] D --> E2[方案 B:MemOS 接管历史] D --> E3[方案 C:Vector Memory 接管] E1 --> F[执行] E2 --> F E3 --> F F --> G[重新跑 global-prompt-orchestrator
验证占比改善]

典型工作循环

第 1 步 —— 启用 global-prompt-orchestrator,跑一次诊断,看当前 prompt 占比
第 2 步 —— 根据 AI 建议,选执行方案(精简 / 卸载 / 换方案)
第 3 步 —— 装 MemOS(如果选 B/C 方案),把历史记忆外置
第 4 步 —— 重跑诊断,验证占比降下来 + 关键内容仍在前 30%
第 5 步 —— 持续观察 1-2 周,看 AI 是否还"发癫"

12.5 跟今天 422 问题的关联

还记得今天下午的 422 吗?

当时我们排查出"持续 422 的根因是 chat 累积内容被 M3 分类器标记敏感"。

MemOS 给了一个新的解法:
  1. 新开一条 chat(清空分类器"记忆")
  2. 用 MemOS 把老 chat 的不敏感段落检索注入
  3. 让分类器对新 prompt 的"敏感度"从低开始
  4. 需要时新 chat 自己累积的可控

这相当于"绕开分类器的内容指纹标记"——通过外置记忆系统重建上下文。

12.6 决策树:该装哪个插件?

flowchart TD A[想调优上下文?] --> B{问题症状} B -- AI 发癫,找不到原因 --> C[装 global-prompt-orchestrator] B -- 聊天记录太长 --> D[装 MemOS 或 Vector Memory] B -- 两者都有 --> E[先 1 看清,再 2 解决] C --> F[运行诊断] F --> G{诊断结果} G -- 占比合理但仍发癫 --> H[查模型 / sampler / jailbreak] G -- 占比失衡 --> I[按建议精简] D --> J[MemOS 上手快,云端免部署] J --> K[VS] L[Vector Memory 隐私好,本地部署] L --> K K --> M[完工] I --> M H --> M
组合使用建议: global-prompt-orchestrator(诊断) + MemOS(长记忆)是互补——前者告诉你"问题在哪",后者帮你"把太长挪出去"。装一个先看,再装第二个。

12.7 Momo(预设 Preset 管理器)

这是§11.2 预设采样器 + §12.2 上下文分析之间的——可视化编辑 + 精细调参

它解决什么问题

你之前 settings.json 里 preset_settings_openai: "V2改-cluade"——这个名字一看就是手动改过的预设。问题是:每次想"临时改个温度 / 换个 sampler"都要:
  1. 打开 ST 设置
  2. 找到"连接 → Preset" → "扳手图标"进入 JSON 编辑
  3. 滚一大段找到要改的字段
  4. 手动改完 → 保存
  5. 回聊天界面 → 验证效果
  6. 不满意再回步骤 1
Momo 把"扳手 JSON 编辑"变成"参数面板"——所有字段分类、hover 看注释、实时调参看 AI 输出变化

核心功能(基于 GitHub 文档)

预设可视化——所有字段(温度 / 采样器 / 频率惩罚 / 反义词 / logit_bias...)分类展示
多预设快速切换——给同一个 chat 准备"日常 / 亲密 / 战斗" 多个 Preset,一键切换
实时对比——A/B 测试两个 Preset,看哪个生成质量好
预设导出 / 导入——JSON 一键分享

跟其他两件套的配合

看占比global-prompt-orchestrator(§12.2)告诉你预设占 20% 太多
调预设Momo可视化编辑你的 "V2改-cluade",看哪个字段能删
验证 → 再次跑 global-prompt-orchestrator,确认占比降下来 + AI 输出质量不变

安装方法

# 仓库地址 https://github.com/1830488003/momo # 安装步骤(同 global-prompt-orchestrator) 1. 打开 ST 扩展菜单(右上角堆叠方块图标) 2. 选择"安装扩展" 3. 粘贴 GitHub URL 4. 等几秒安装完成 5. 启用插件,在主界面看到 Momo 面板入口
同作者生态: global-prompt-orchestrator、momo 都来自 1830488003——同一个作者,设计理念一致(把抽象 prompt 调试变成可视化)。两个一起装,覆盖"诊断 + 编辑"全流程

12.8 三件套工作流总览

flowchart TD A[开始调优] --> B[装 global-prompt-orchestrator
= 诊断工具] B --> C{诊断结果} C -- 占比失衡 --> D{是哪个维度?} D -- Preset 太重 --> E[装 Momo
可视化编辑 Preset] D -- 聊天记录太长 --> F[装 MemOS
外置长记忆] D -- WI 太重 --> G[手动精简 WI 条目] E --> H[重跑诊断验证] F --> H G --> H H --> I{占比合理?} I -- 否 --> C I -- 是 --> J[持续观察 1-2 周] C -- 占比合理但仍发癫 --> K[查模型 / sampler / jailbreak] K --> L[见 §10 破限工程学] J --> M[完工] L --> M

12.9 总结:三件套对应学习手册

插件 解决什么 对应章节
global-prompt-orchestrator "现在 prompt 长什么样,谁占多少,问题在哪" §01-§05 理论的可视化诊断
Momo "我想改 Preset 但 JSON 编辑太累" §11.2 Preset 配置的可视化编辑
MemOS "聊天记录太长,context 装不下" §07 Token Budget 的外置持久化
推荐安装顺序:
  1. global-prompt-orchestrator 先装(无依赖,立即看到 prompt 长啥样)
  2. JS-Slash-Runner(MemOS 依赖)
  3. MemOS 长记忆(配 API key)
  4. Momo Preset 编辑器(看个人需求)