---
description: "酒馆 SillyTavern 拆解：领域是 AI 角色扮演，它把网页版 ChatGPT 的一段 system prompt 换成每轮重拼的上下文；按角色卡、世界书、现拼 prompt、喂各种模型、重来分叉、聊久了、群聊、自加规则八条功能线讲问题和功能，每步配源码模版和实跑效果，结论是能搬走的设计"
---

# 酒馆 SillyTavern：把一段 system prompt 换成每轮重拼的上下文

## 摘要 { #abstract }

酒馆是一个 AI 角色扮演前端：自己不带模型，接用户的 API，管理角色卡、世界书、预设，每轮把它们拼成 prompt[^repo]。拿网页版 ChatGPT 当 baseline——一段 system prompt 写人设，历史全量回放，每次生成一条——它在角色扮演里会撞上八类毛病：人设一刀切、设定塞不下、指令被历史淹没、换模型就换规矩、说错改不了、聊久了忘、多角色串台、规则写死。酒馆的回答是每轮从零重拼：世界书只扫最近 2 条消息决定哪些设定进场，作者注释这类指令按「距末尾第几条」插进历史，固定部分先占位，历史从新往旧填满剩下的预算。设计重心是规则全在拼装这一步，由用户和作者配置，模型只管接着写。

## 关键词 { #keywords }

AI 角色扮演（LLM roleplay，RP）· 角色扮演前端（RP frontend）· role-playing language agents · 角色卡（character card）· 世界书（world info / lorebook）· 预设（preset）· 作者注释（Author's Note）· prompt 组装（prompt assembly）· 上下文工程（context engineering）· Chat Completion · Text Completion

## 术语 { #terms }

| 词 | 出处 | 意思 |
|---|---|---|
| 角色卡 | 领域通用 | 一张内嵌 JSON 的 PNG，装一个角色的描述、开场白、示范对话等字段 |
| 世界书 | 领域通用，酒馆叫 World Info，也叫 Lorebook | 设定拆成的条目集，每条带关键词，命中才注入 |
| 蓝灯、绿灯 | 酒馆中文社区 | 常驻的世界书条目、关键词触发的条目 |
| 预设 | 领域通用 | 采样参数加全部提示词条目和它们的顺序，打成一个 JSON |
| 槽位 | 本文起，酒馆叫 marker | Prompt Manager 里没有正文、运行时才填内容的条目，如 `charDescription`、`chatHistory` |
| 深度注入 | 本文起，深度（depth）是酒馆的叫法 | 按「距末尾第几条」把一段文字插进聊天历史；深度 0 在最后一条之后 |
| 作者注释 | 领域通用，Author's Note | 用户写给当前聊天的一段指令，出厂插在深度 4 |
| 角色笔记 | 酒馆自造，Character's Note | 卡里随卡分发的一段深度注入 |
| 末尾指令 | 本文起，酒馆叫 Post-History Instructions，旧称 jailbreak | 排在整个聊天历史之后的指令 |
| 用户人设 | 本文起，酒馆叫 Persona | 用户扮演的那个人的名字和描述 |
| swipe | 领域通用 | 同一位置的多个候选回复，左右滑动切换 |
| 文件头 | 本文起，酒馆叫 chat_metadata | 聊天 JSONL 的第一行，存这一局的状态 |
| Chat Completion、Text Completion | 领域通用 | 发消息数组的 API、发一整条字符串的 API |

## 引言 { #intro }

**领域是 AI 角色扮演**：不是问答，用户扮演故事里的一个角色，和模型一起把故事演下去。

- **软件形态**叫「角色扮演前端」：自己不带模型，接用户的 API，管理角色卡、世界书、预设，再把它们拼成 prompt。酒馆自称「LLM Frontend for Power Users」，2023 年 2 月从 TavernAI 1.2.8 分出来[^docs]，同类还有 RisuAI、Agnai
- **研究界**多叫 role-playing language agents；商业产品那边是 Character.AI 这类「AI 角色 / AI 陪伴」

**baseline 是网页版 ChatGPT**：一段 system prompt 写人设，历史消息全量回放，每次生成一条回复。下面八条功能线，各从它在角色扮演里撞上的一个具体毛病讲起，只写现状；每条线一篇细讲，每步贴源码模版和用酒馆自己的代码跑出来的效果。实跑用随包的角色卡 Seraphina 和世界书 Eldoria 的中译（塞拉菲娜、埃尔多利亚），用户名「旅人」，模型接 DeepSeek，其余是出厂设置。

### 一、角色卡：一段人设按寿命拆成几格 { #card }

1. 人设是一段字，换个前端、发给别人就得整段复制 → **卡是一张 PNG**：JSON 写进图片的文本块，转发图片就是转发角色
2. 示范对话写在 system prompt 里，聊到第 80 轮还占着位置 → **字段分槽**：描述常驻；示范对话只拿剩余预算，最先被挤掉；开场白是聊天的第 0 条；末尾指令贴在最后；角色笔记固定在深度 4
3. 每局开头都一样 → **备选开场白**：和开场白一起成为第 0 条的候选
4. 卡想在全局指令上补一句，而不是整段替换 → **`{{original}}`**：展开成被覆盖的原文
5. 「我是谁」只能和角色写在一起 → **用户人设**：独立一份，可以放在深度 2 跟着对话前沿走
6. 名字写死，卡没法给别人用；随机值每轮重算就每轮变 → **晚绑定宏**：`{{char}}`、`{{user}}` 每轮代入；`{{pick}}` 以聊天 ID 为种子，同一局不变

细讲：[角色卡：一段人设按寿命拆成几格](card.md) —— 卡的存法、字段落进哪个槽、宏

### 二、世界书：设定按关键词进场 { #lorebook }

1. 几万字的设定塞不进 system prompt → **世界书**：每轮只扫最近 2 条消息，关键词命中才注入
2. 同一个词在不同话题里指不同的东西 → **次关键词**：四种逻辑消歧
3. 设定互相引用，聊天里没提到的名字模型不认识 → **递归**：激活条目的内容再扫一轮
4. 世界书一扩张就把历史挤掉 → **预算**：上下文的 25% 封顶，第一次溢出就停
5. 输入一样 prompt 就一样，随机事件做不出来；互斥的变体同时命中 → **概率和互斥组**
6. 没有跨轮状态：地牢规则只在提到「地牢」的那两条里有效 → **定时效果**：sticky、cooldown、delay，以聊天条数为时钟
7. 设定放在开头影响弱 → **8 个插入位置**，含按深度插进聊天

细讲：[世界书：设定按关键词进场](lorebook.md) —— 每步判定取自酒馆自己的调试日志

### 三、每轮现拼 prompt：放哪、放不下砍谁 { #assembly }

1. system prompt 是一整段，顺序改不了、也没法单独关一段 → **具名槽位**：Prompt Manager 把 prompt 拆成可排序、可开关的条目，运行时填内容
2. 该随剧情变的东西没地方放 → **每轮从零重拼**：作者注释、世界书、角色笔记每轮重算；实跑后几轮世界书全部退场，省下近九百个 token
3. 「保持第三人称」写在最前面，聊到第 80 轮被历史淹没 → **深度注入**：一张注入表，所有模块登记文字和深度；同一深度按角色、键名排序，规则写死
4. 全量回放，聊长了就溢出 → **预算**：固定部分必放，历史从新往旧连续填，示范对话最后拿余量
5. 用户不知道模型看不见哪些消息 → **截断线**标在界面上

细讲：[每轮现拼 prompt：放哪、放不下砍谁](assembly.md) —— 出厂槽位的真实账本、注入表、三档上下文各留下什么

### 四、一份内容喂各种模型 { #adapt }

1. Claude 要 system 单独放、严格交替，Gemini 的 assistant 叫 model → **服务端按供应商改写**
2. 自搭的端点各有怪规矩 → **几档通用后处理**：merge、semi、strict、single；DeepSeek 固定走 semi
3. 回复被截断，再发「继续」会重起一段 → **continue**：被截断的那条拿到最后，用续写提示或预填接着写
4. 想照文风让模型替自己起草一句 → **impersonate**
5. 调好的 prompt 和参数带不走 → **预设**：参数、条目、顺序打成一个 JSON
6. 本地模型只收一整条字符串 → **Text Completion**：前端按 instruct 模板套 chat template，模板序列兼作停止串

细讲：[一份内容喂各种模型](adapt.md) —— 同一份消息数组过各家转换、续写、ChatML 拼法

### 五、重来、分叉、改历史 { #branch }

1. 重新生成会覆盖上一版 → **swipe**：同一位置留多个候选，只有选中的进下一轮
2. 模型说错的一句被一直回放 → **编辑**：任意消息都能改，改的就是下一轮的输入
3. 只有删和留两种选择 → **隐藏**：人看得见，模型看不见
4. 想试另一个走向，又不想丢掉现在这条线 → **分支**：复制前缀，带上文件头另存；变量、世界书计时跟着走
5. 整文件覆盖写，删错没法撤销 → **备份**

细讲：[重来、分叉、改历史](branch.md) —— 候选的存法、隐藏、用原样的 createBranch 开分支

### 六、聊久了 { #memory }

1. 开头的约定被挤出窗口 → **摘要**：每 10 条带着完整上下文写一次，作为一段注入紧跟主提示；实跑出厂设置下，摘要被 300 token 的回复长度截断
2. 删了消息，摘要还写着不存在的情节 → **摘要挂在消息上**，随删改回退
3. 摘要不删原文，token 没省下来 → **配合 /hide**
4. 摘要丢细节 → **向量召回**：按语义把相关的旧原文挪回本轮 prompt

细讲：[聊久了](memory.md) —— 两次真实摘要和它落在哪

### 七、多角色同场 { #group }

1. 群里说一句，该谁回 → **调度器**：点名的一定说，其余按话痨度掷骰
2. 模型一次替所有人说话 → **一次只让一个角色生成**，后说的看得到先说的
3. 几个 NPC 都是 assistant，分不清谁说的 → **名字前缀、「只写某某的下一句」、截掉别人的台词**
4. 换人就换整段人设，本地缓存全失效 → **Swap 和 Join** 两种拼卡

细讲：[多角色同场](group.md) —— 调度函数实跑、两轮真实群聊的完整请求

### 八、用户自己加规则 { #rules }

1. 显示的、存下的、发给模型的是同一个字符串，状态栏被一直回放 → **正则三层分离**，可按深度只作用于旧消息
2. 好感度只能靠模型记 → **变量**存在文件头，宏读写
3. 「每轮后做点什么」没地方挂 → **STscript 和 Quick Reply**，世界书条目激活也能触发脚本
4. 扩展要在发出前改 prompt → **串行可改的事件总线**、**生成拦截器**
5. 模型没法掷骰子、查东西再接着讲 → **工具调用**：递归生成，最多 5 轮

细讲：[用户自己加规则](rules.md) —— 正则三层对照、事件监听原地改 prompt

## 局限 { #limits }

- **规则都按字面算**：世界书按关键词字面命中，点名按 `\w` 切词，摘要按条数触发，程序不理解内容；中文点名点不中就是这么来的。向量匹配能补同义说法，但文档自己建议要可预期就用关键词
- **每轮重拼和前缀缓存相冲**：世界书、深度注入、向量召回每轮在变，前缀跟着变，缓存就失效。文档自己提醒向量召回和缓存冲突；群聊的 Join 就是为本地后端的前缀缓存做的让步，代价是角色混淆自己和别人
- **状态由用户维护**：模型只写文本，好感、物品这些状态要用户自己用正则、变量、脚本抽取维护，程序不会从回复里更新结构化状态
- **分支只靠文件名互链**：没有会话树，分支是一份复制出来的新文件，文档原话改名「will break links to that chat from checkpoints」

## 结论：可以搬走的设计 { #takeaways }

- **按「距末尾第几条」注入**：指令、状态、临时设定都用深度定位，跟着对话前沿走，不随历史变长被推远；同一深度按角色、键名排序，结果可预期
- **关键词进场、按比例封顶、溢出即停**：外部知识按最近几条消息的字面命中注入，条目内容还能再触发别的条目，适合做 RAG 前面那层确定性的检索
- **预算分三档**：必放的先占位，历史从新往旧连续填（不留空洞），可有可无的示范拿余量
- **定时效果拿消息条数当时钟，存在会话头**：sticky、cooldown、delay 只需要一个起点和一个长度；重来时聊天没前进，计时就作废，不用写回滚代码
- **显示、存储、发给模型三层分离**：同一条消息三处可以是三个样子，按深度只改旧消息
- **前端只产一种格式，供应商差异收进一个纯函数模块**：再给未知端点几档通用后处理，接新供应商只动服务端一处
- **分支 = 复制前缀 + 继承会话头**：状态都放在会话文件头，开分支就是带着它另存一份，不需要专门的状态迁移
- **晚绑定宏，随机值以会话 ID 为种子**：名字每轮代入，一张卡给谁都能用；要随机又要同一局稳定的地方，用会话 ID 和位置做种子

[^repo]: [SillyTavern/SillyTavern](https://github.com/SillyTavern/SillyTavern)，2026-10-20 读。
[^docs]: [SillyTavern 文档首页](https://docs.sillytavern.app/)，2026-10-20 读。

<div class="zx-no-copy" markdown>

---

维护记录：[读的哪个版本、素材从哪来、实验怎么跑的](../../maintenance/projects/sillytavern.md)。正常阅读不用看。

</div>
