跳转至

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

摘要

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

关键词

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

术语

词 出处 意思
角色卡 领域通用 一张内嵌 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

引言

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

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

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

一、角色卡:一段人设按寿命拆成几格

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

细讲:角色卡:一段人设按寿命拆成几格 —— 卡的存法、字段落进哪个槽、宏

二、世界书:设定按关键词进场

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

细讲:世界书:设定按关键词进场 —— 每步判定取自酒馆自己的调试日志

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

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

细讲:每轮现拼 prompt:放哪、放不下砍谁 —— 出厂槽位的真实账本、注入表、三档上下文各留下什么

四、一份内容喂各种模型

  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,模板序列兼作停止串

细讲:一份内容喂各种模型 —— 同一份消息数组过各家转换、续写、ChatML 拼法

五、重来、分叉、改历史

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

细讲:重来、分叉、改历史 —— 候选的存法、隐藏、用原样的 createBranch 开分支

六、聊久了

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

细讲:聊久了 —— 两次真实摘要和它落在哪

七、多角色同场

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

细讲:多角色同场 —— 调度函数实跑、两轮真实群聊的完整请求

八、用户自己加规则

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

细讲:用户自己加规则 —— 正则三层对照、事件监听原地改 prompt

局限

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

结论:可以搬走的设计

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

维护记录:读的哪个版本、素材从哪来、实验怎么跑的。正常阅读不用看。


  1. SillyTavern/SillyTavern,2026-10-20 读。 ↩

  2. SillyTavern 文档首页,2026-10-20 读。 ↩