简介
这是什么?
TavernHeadless 是一个没有内置界面的 AI 角色扮演后端引擎。你可以把它理解为「SillyTavern 的引擎层」——核心能力都在后端,前端可以自由替换:
- 所有功能通过 HTTP 接口和事件系统提供,而不是页面驱动。
- 角色、会话、分支、记忆等数据用工程化方式管理。
- 可以接入任意前端(网页、桌面客户端、自动化脚本),也可以完全不用前端。
当前状态
后端已进入 Beta3(v0.2.0-beta.3),核心链路完整可用,真实大模型回归测试通过。项目整体仍处于 Alpha 阶段。
已完成的部分:
- 会话管理、分支治理、重试、编辑再生成、时间线查询。
- SillyTavern 生态导入:预设、世界书、正则、角色卡。
- 流式输出(SSE)、提示词调试(dry-run)、Prompt Runtime 单段文本 preview、接口文档(OpenAPI)、类型化 SDK。
- 官方集成层两个包:
@tavern/sdk和@tavern/client-helpers,覆盖会话、内容结构、Prompt Runtime、Session State 公共 API、变量、记忆、导出、工具调用、MCP 等主要功能,并同步暴露结构化 Prompt Runtime memory truth。 - 核心资产备份 v1:支持
characters、worldbooks、sessions的.thbackup导出、restore preview、异步恢复与作业查询。 - 三种认证模式(
off/api_key/jwt)、多账号隔离、LLM 密钥加密存储。 - 记忆系统(摘要提取、衰减排序、自动维护)、变量系统(五级级联)。
- LLM 多模型配置(Profile + 实例槽位)、模型发现与连通性测试。
- 批量操作接口(变量、记忆、消息、会话、用户等)。
当前重点:部署文档完善、正式发布准备。
设计思路
传统的 AI RP 工具(比如 SillyTavern)是以「角色卡」为中心的前端应用。TavernHeadless 走了一条不同的路:
- 后端优先:核心逻辑全部跑在服务端,前端只是一个可选的管理界面。
- 项目即角色卡:不再需要一张 PNG 角色卡文件。一个 TavernHeadless 项目本身就包含了角色设定、世界观、预设、正则规则等所有内容。
- 兼容但不受限:可以导入酒馆的预设和世界书直接用,但也提供了更强大的原生能力。
核心概念
下面介绍在 TavernHeadless 中经常出现的几个概念。理解它们有助于理解后续的 API 和架构设计。
三层消息结构
系统把一次聊天拆成三层:
| 层级 | 含义 | 说明 |
|---|---|---|
| 会话(Session) | 一次完整的聊天 | 创建时绑定角色、预设、世界书等配置 |
| 楼层(Floor) | 一次回合 | 用户发一条消息、AI 回一条消息,构成一个楼层 |
| 消息页(MessagePage) | 楼层内的一个版本 | 重新生成时会在同一楼层创建新的消息页,旧版本保留 |
消息页里面是具体的消息(Message)——最小的数据单位。每条消息有角色(user / assistant / system / narrator)和内容。
这个结构的好处是:分支和版本管理是原生支持的。从任意楼层可以新开分支,切换消息页相当于在同一回合内切换 AI 的不同回复。
Session
└── Floor 1 (committed)
│ ├── MessagePage v1 (inactive) ← 第一次生成
│ │ └── Message: assistant "你好呀"
│ └── MessagePage v2 (active) ← 重新生成后的版本
│ └── Message: assistant "嗨!很高兴见到你"
└── Floor 2 (generating)
└── MessagePage v1 (active)
└── Message: assistant "从前有座山..." ← 正在流式生成楼层状态
每个楼层有状态,按固定方向流转:
draft → generating → committed
→ faileddraft:已创建,等待生成。generating:正在调用 LLM。committed:生成完成,内容锁定。已提交的楼层不可修改。failed:生成失败,保留现场方便排查。可以原地重试。
分支(Branch)
从任意已提交的楼层可以创建分支。分支是一条独立的故事线,不影响原来的内容。每个会话默认有一个 main 分支。
角色与用户
- 角色(Character):AI 扮演的角色。支持版本化管理,会话绑定角色时冻结一份快照。
- 用户(User):对话中人类一方的身份。同样支持快照冻结。
角色和用户的快照存储在会话中,保证即使原始数据被修改,已有会话的行为也不会变。
提示词模式
系统提供两种提示词编排路径:
| 模式 | 说明 |
|---|---|
compat_strict | 严格复刻 SillyTavern 的提示词拼接行为,导入预设即用 |
compat_plus | 在兼容基础上加入少量已声明的增强能力,当前主要用于记忆注入 |
native | 使用原生编排路径;当前实现主要是一条固定流水线,后续再演进为声明式图模型 |
会话创建时通过 prompt_mode 字段选择。
运行中如果要显式查看或改写它,推荐使用 Prompt Runtime 的独立 mode 控制面:
GET /sessions/:id/prompt-runtime/modePATCH /sessions/:id/prompt-runtime/mode
无论你走 Sessions 通用更新面,还是走 Prompt Runtime /mode 子路由,底层持久化真相都只有一份:sessions.prompt_mode。
导入 ST preset 时,建议默认使用 compat_strict。native 当前不承诺 ST preset 的保真执行。
主要系统
变量系统
变量用来存储叙事状态,比如好感度、是否触发过某事件、某物品的位置等。系统把变量分成五个层级,每一层解决不同的隔离问题。
读取时按从小到大的顺序查找,找到就停;写入时默认写到最小范围(page),起沙箱保护作用:
page → floor → branch → chat → global为什么需要五层?
- page(页级):每次生成尝试的沙箱。假设 AI 写了
mood = happy,你不满意点了重试,新一次生成写了mood = sad。没有页级隔离,两次生成会互相覆盖。有了 page,每次生成各自独立,只有你选定的版本才会提升到更高层。 - floor(楼层级):一个回合的结果。楼层提交后冻结,后续回合不再修改。
- branch(分支级):RP 中经常从某个楼层开始分叉,走出不同的故事线。如果没有分支级变量,两条分支共用同一份会话级状态——一条分支里"角色 A 受伤了",另一条里"角色 A 安然无恙",变量互相冲突。有了 branch,每条故事线有自己的状态空间,互不干扰。
- chat(会话级):整次聊天共享的状态,比如好感度、已解锁的剧情标记。
- global(全局级):跨会话的世界观设定或全局开关。
楼层提交时,页级变量按策略提升到更高层级。提升到哪一层由系统编排器控制,不会自动发生。
ST 宏兼容层
TavernHeadless 在 compat_strict 和 compat_plus 提示词路径中提供 SillyTavern 宏兼容层。
当前可稳定使用的范围是 ST Macro Compatibility (Core Profile)。它的目标是兼容常见预设、角色卡、世界书和变量写法,不追求复刻全部历史细节。
当前文档层面应当明确以下边界:
支持基础宏语法:{{name}}、{{name arg}}、{{name::arg1::arg2}}
支持常见 legacy 角括号别名:`<USER>`、`<BOT>`、`<CHAR>` 等
支持常见只读宏:{{user}}、{{char}}、{{userName}}、{{assistantName}}、{{runKind}}、{{promptMode}}、{{isodate}}、{{isotime}}、{{description}}、{{scenario}}、{{systemPrompt}} 等
支持 ST 变量读取与写入宏:`getvar`、`getglobalvar`、`setvar`、`setglobalvar`、`addvar`、`incvar`、`deletevar`、`hasvar`、`hasglobalvar`
支持变量宏 alias:`varexists`、`globalvarexists`、`flushvar`、`flushglobalvar`
支持结构化变量路径访问:`{{getvar::资产.金币}}`、`{{.资产.金币}}`、`{{setvar::资产.金币::3}}`、`{{.资产.金币=3}}`
支持 shorthand 写入子集:`{{.name=value}}`、`{{$name=value}}`、`{{.name++}}`、`{{.name--}}`
支持 `if` 条件块子集:truthy / falsy、`==`、`!=`、`>`、`<`、`>=`、`<=`、`and`、`or`、`not`、`contains`、`startsWith`、括号分组当前实现还明确限制如下:
if的>、<、>=、<=只做数值比较if的contains、startsWith按区分大小写的字符串谓词处理if的and、or按短路语义求值,短路未求值一侧不会执行写宏- 遇到不支持语法、结构解析失败或数值比较类型不合法时,运行时会保留原始 block,并返回对应 warning,而不是把它当作普通 truthy 文本
- 带副作用的写宏不会在导入、预览或 dry-run 阶段直接写库,只会进入 preview 或 staged mutation 缓冲
- 对外宏诊断统一走
runtime_trace.macro;POST /sessions/:id/prompt-runtime/preview继续复用主线宏求值链,只做单段文本 preview,不会创建 floor,也不会调用 LLM - 当前仍不支持完整 shorthand 运算符族,例如
NaN、NaN、||=、??=、==
如果你需要查看接口返回中的宏调试字段,请参考:
提示词编排
提示词系统负责把预设、世界书、变量、聊天记录、记忆等拼成一份完整的提示词,发给大模型。
不管走哪种模式,最终都会编译成一个统一的中间格式,再交给大模型。兼容模式和原生模式共享同一个渲染器。
原生模式的流水线由以下节点组成:
| 节点 | 作用 |
|---|---|
template | 渲染模板,填入变量 |
condition | 按条件选择不同路径 |
worldbook_resolve | 检查世界书触发条件,注入命中条目 |
transform | 正则替换、文本清洗 |
memory_inject | 注入记忆摘要和关键事实 |
token_budget | 按 token 预算裁剪历史 |
pack_messages | 最终拼装成 LLM 要求的 messages 数组 |
LLM 配置:预设、实例与 Profile
在角色扮演场景中,"用哪个大模型、怎么调用它"涉及三层配置:
| 概念 | 解决什么问题 | 举例 |
|---|---|---|
| 预设 | 提示词怎么拼、生成参数怎么设 | 温度 0.9、top_p 0.95、酒馆格式模板 |
| 实例槽位 | 谁来调用大模型、负责什么 | 叙述者写故事,记忆员整理记忆 |
| LLM Profile(凭证配置) | 调用哪个大模型、用什么密钥 | OpenAI gpt-4o + 对应 API Key |
三者的关系:会话绑定一个预设(决定提示词怎么拼)→ 每个实例槽位绑定一个 Profile(决定调用哪个大模型)→ 实例还可以用自己的预设覆盖会话级别的预设。
系统定义了四个实例槽位:
| 实例 | 职责 | 何时使用 |
|---|---|---|
| 叙述者(Narrator) | 生成角色扮演文本 | 每个回合 |
| 记忆员(Memory) | 整理摘要、提取事实 | 回合结束后 |
| 导演(Director) | 规划剧情走向 | 可选 |
| 校验员(Verifier) | 检查内容一致性 | 可选 |
一次回合中,实例按 导演 → 记忆检索 → 叙述者生成 → 校验员检查 → 记忆整理 的顺序执行。叙述者被禁用时请求会返回错误;导演、校验员、记忆员被禁用时对应子流程跳过。
Profile 支持按作用域(全局或会话)和槽位粒度绑定。你可以让叙述者用高质量模型,记忆员用便宜模型,也可以按会话单独切换。不配置 Profile 时使用环境变量作为默认值。每次回合启动时,当前的 Profile 配置会被冻结成快照,运行中不会被后续修改影响。
记忆系统
聊天越来越长之后,大模型的上下文窗口装不下所有内容。记忆系统把重要信息提炼出来,在需要时注入提示词,让大模型能"记住"之前发生过的事。
记忆有两个来源:
- 从 AI 回复中提取:很多预设会引导 AI 在回复末尾写摘要标签(如
<summary>角色 A 和角色 B 达成了协议</summary>)。系统自动识别并提取这些内容。 - 后台整理:回合提交后,系统在后台启动作业,读取最近的对话内容,输出结构化的记忆操作——新增事实、更新事实、标记过时事实。
每条记忆有类型和层级:
- 类型:事实(fact)、摘要(summary)、开放剧情线(open_loop)。摘要进一步分为短摘要(micro,覆盖一两个回合)和长摘要(macro,由多条短摘要压缩而来)。
- 层级:全局、会话、分支或楼层,决定可见范围。
page不是新的 live 记忆层级,它只在 proposal / promotion 链路里作为候选容器出现。 - 状态:活跃(active)、已压缩(compacted)、已弃用(deprecated)。已弃用的记忆不再注入提示词。
记忆之间可以有六种关系:支持、矛盾、更新、派生、压缩、消解。
组装提示词时,编排器按 token 预算和重要度选取记忆条目注入。系统也在后台自动维护:按半衰期衰减排序、弃用过期摘要、当短摘要积累到一定数量时触发长摘要压缩。这些维护任务不阻塞对话。
Prompt Runtime 的 preview、inspect 和 historical explain 现在会在兼容字段 memory_summary 旁边,额外返回结构化 memory 真相。 这组结构会说明当前回合使用了哪条记忆运行模式、命中了哪些记忆条目、scope 是如何解析的,以及 proposal / promotion 的状态。 这样做的目的,是让 explain、回放和分支排查都能直接看到记忆链路,而不必再把真相压成一个字符串。
事件系统
系统内部通过事件总线来解耦各模块。当某件事发生时(比如楼层创建、生成完成、变量变更),系统会广播一个事件,其他模块可以监听并做出反应。主要事件包括:
- 楼层生命周期:
floor.created/floor.committed/floor.failed - 生成过程:
generation.started/generation.chunk/generation.completed/generation.failed - 记忆操作:
memory.created/memory.updated/memory.deprecated/memory.deleted - 记忆关系边:
memory.edge.created/memory.edge.deleted - 记忆整理汇总:
memory.consolidated - 变量变更:
variable.set/variable.promoted/variable.deleted
这些事件可以通过 WebSocket 转发给前端用于实时显示,也可以用来记录日志或触发自定义逻辑。
项目模块
项目使用 pnpm 多包仓库结构,分为以下几个包:
packages/core
核心引擎。包含所有与具体框架无关的业务逻辑:
- 楼层状态机:管理楼层从创建到提交的状态流转,以及变量提升。
- 变量系统:五级级联读取和沙箱写入。
- 提示词基础设施:模板渲染、token 预算裁剪、消息拼装。
- 原生编排流水线:由 7 种节点组成的可组合提示词编排流水线。
- LLM 接入层:基于 Vercel AI SDK,支持多个模型提供商的调度。
- 生成流水线:前处理 → 大模型调用 → 摘要提取 → 后处理。
- 记忆系统:记忆存储、整理和注入。
- 回合编排器:按 导演 → 记忆检索 → 叙述者 → 校验员 → 记忆整理 的顺序串联调度。
- 数据库抽象接口:定义了楼层、变量、记忆等数据操作的接口,由 API 层负责实现。
核心引擎只依赖 shared,不依赖任何 API 或框架代码。
packages/adapters-sillytavern
SillyTavern 兼容适配层。负责:
- 解析器:把酒馆格式的预设、世界书、正则脚本、角色卡解析为内部格式。全部基于 Zod 做运行时校验。
- 世界书引擎:处理关键词触发、正则触发、选择性逻辑、扫描深度、常驻条目等世界书机制。
- 正则引擎:执行查找替换、捕获组、放置位置等正则脚本规则。
- 兼容编排器:按酒馆的方式拼接提示词,分严格兼容和增强兼容两种模式。
packages/shared
项目内部共享的类型和工具包:
- 楼层状态、变量作用域、记忆类型等枚举常量。
- 事件名称常量。
- 自动生成的 OpenAPI 类型和类型化 API 客户端。
packages/official-integration-kit/sdk
官方 SDK。面向需要接入 TavernHeadless 的开发者,提供:
- 类型安全的 HTTP 客户端,自动处理请求头和错误格式。
- SSE 事件流读取,支持流式生成场景。
- 覆盖会话、角色、预设、世界书、正则、变量、记忆、Prompt Runtime 结构化 memory truth、导出、工具调用、MCP、LLM 配置等主要功能的资源方法。
- 保留底层 HTTP 方法,供需要直接操作接口的开发者使用。
packages/official-integration-kit/client-helpers
官方客户端辅助层。提供与框架无关的高层工具:
- token 用量统计的格式归一化。
- 时间线构建(把楼层和消息页组装成完整的对话时间线)。
- 流式生成的状态管理。
- 当前生效页选择和错误映射。
这两个包是当前唯一官方公开接入面。
shared 是内部包,不对外公开。
当后端接口、事件结构或接口文档发生变化时,应同步检查这两个官方包和对应文档。
apps/api
后端服务,基于 Fastify 框架。负责:
- 数据库层:SQLite + Drizzle ORM,启动时自动迁移。
- 路由层:14 个资源路由模块 + 6 个聊天端点,接口字段使用
snake_case风格。 - 服务层:聊天业务、提示词组装、Profile 管理、实例配置、记忆维护等。
- 插件:认证、跨域、接口文档(Swagger)、请求日志。
- 运维脚本:性能测试、接口文档导出、记忆维护命令行、回归测试、冒烟测试。
apps/web
管理前端(可选)。基于 Vue 3 + Pinia + TailwindCSS,提供可视化管理界面。
系统分层
┌─────────────────────────────────────────────────┐
│ apps/web │
│ 管理前端(可选) │
└────────────────────────────────────────────┘
│ HTTP / WebSocket
┌──────────────────────▼──────────────────────────┐
│ apps/api │
│ Fastify 后端服务 │
│ ┌───────────────────────────────────────────┐ │
│ │ packages/core │ │
│ │ 消息管理 · 变量系统 · 提示词编排 │ │
│ │ LLM 调度 · 记忆系统 · 事件总线 │ │
│ └───────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────┐ │
│ │ packages/adapters-sillytavern │ │
│ │ 预设导入 · 正则导入 · 世界书导入 │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘依赖方向
apps/api ──→ packages/core ──→ packages/shared
│ │
└──→ packages/adapters-sillytavern ──→ packages/shared
apps/web ──→ packages/shared
└──→ packages/official-integration-kit
├──→ sdk
└──→ client-helpers依赖方向永远是 apps → packages,不能反过来。核心引擎不知道后端服务的存在,后端通过实现核心引擎定义的数据库抽象接口来对接。