Skip to content

简介

这是什么?

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:支持 charactersworldbookssessions.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 的不同回复。

text
Session
 └── Floor 1 (committed)
 │   ├── MessagePage v1 (inactive)    ← 第一次生成
 │   │   └── Message: assistant "你好呀"
 │   └── MessagePage v2 (active)      ← 重新生成后的版本
 │       └── Message: assistant "嗨!很高兴见到你"
 └── Floor 2 (generating)
     └── MessagePage v1 (active)
         └── Message: assistant "从前有座山..."  ← 正在流式生成

楼层状态

每个楼层有状态,按固定方向流转:

text
draft → generating → committed
                   → failed
  • draft:已创建,等待生成。
  • 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/mode
  • PATCH /sessions/:id/prompt-runtime/mode

无论你走 Sessions 通用更新面,还是走 Prompt Runtime /mode 子路由,底层持久化真相都只有一份:sessions.prompt_mode

导入 ST preset 时,建议默认使用 compat_strictnative 当前不承诺 ST preset 的保真执行。


主要系统

变量系统

变量用来存储叙事状态,比如好感度、是否触发过某事件、某物品的位置等。系统把变量分成五个层级,每一层解决不同的隔离问题。

读取时按从小到大的顺序查找,找到就停;写入时默认写到最小范围(page),起沙箱保护作用:

text
page → floor → branch → chat → global

为什么需要五层?

  • page(页级):每次生成尝试的沙箱。假设 AI 写了 mood = happy,你不满意点了重试,新一次生成写了 mood = sad。没有页级隔离,两次生成会互相覆盖。有了 page,每次生成各自独立,只有你选定的版本才会提升到更高层。
  • floor(楼层级):一个回合的结果。楼层提交后冻结,后续回合不再修改。
  • branch(分支级):RP 中经常从某个楼层开始分叉,走出不同的故事线。如果没有分支级变量,两条分支共用同一份会话级状态——一条分支里"角色 A 受伤了",另一条里"角色 A 安然无恙",变量互相冲突。有了 branch,每条故事线有自己的状态空间,互不干扰。
  • chat(会话级):整次聊天共享的状态,比如好感度、已解锁的剧情标记。
  • global(全局级):跨会话的世界观设定或全局开关。

楼层提交时,页级变量按策略提升到更高层级。提升到哪一层由系统编排器控制,不会自动发生。

ST 宏兼容层

TavernHeadless 在 compat_strictcompat_plus 提示词路径中提供 SillyTavern 宏兼容层。

当前可稳定使用的范围是 ST Macro Compatibility (Core Profile)。它的目标是兼容常见预设、角色卡、世界书和变量写法,不追求复刻全部历史细节。

当前文档层面应当明确以下边界:

text
支持基础宏语法:{{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><>=<= 只做数值比较
  • ifcontainsstartsWith 按区分大小写的字符串谓词处理
  • ifandor 按短路语义求值,短路未求值一侧不会执行写宏
  • 遇到不支持语法、结构解析失败或数值比较类型不合法时,运行时会保留原始 block,并返回对应 warning,而不是把它当作普通 truthy 文本
  • 带副作用的写宏不会在导入、预览或 dry-run 阶段直接写库,只会进入 preview 或 staged mutation 缓冲
  • 对外宏诊断统一走 runtime_trace.macroPOST /sessions/:id/prompt-runtime/preview 继续复用主线宏求值链,只做单段文本 preview,不会创建 floor,也不会调用 LLM
  • 当前仍不支持完整 shorthand 运算符族,例如 NaNNaN||=??===

如果你需要查看接口返回中的宏调试字段,请参考:

提示词编排

提示词系统负责把预设、世界书、变量、聊天记录、记忆等拼成一份完整的提示词,发给大模型。

不管走哪种模式,最终都会编译成一个统一的中间格式,再交给大模型。兼容模式和原生模式共享同一个渲染器。

原生模式的流水线由以下节点组成:

节点作用
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 配置会被冻结成快照,运行中不会被后续修改影响。

记忆系统

聊天越来越长之后,大模型的上下文窗口装不下所有内容。记忆系统把重要信息提炼出来,在需要时注入提示词,让大模型能"记住"之前发生过的事。

记忆有两个来源:

  1. 从 AI 回复中提取:很多预设会引导 AI 在回复末尾写摘要标签(如 <summary>角色 A 和角色 B 达成了协议</summary>)。系统自动识别并提取这些内容。
  2. 后台整理:回合提交后,系统在后台启动作业,读取最近的对话内容,输出结构化的记忆操作——新增事实、更新事实、标记过时事实。

每条记忆有类型和层级:

  • 类型:事实(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,提供可视化管理界面。


系统分层

text
┌─────────────────────────────────────────────────┐
│                  apps/web                       │
│              管理前端(可选)                      │
└────────────────────────────────────────────┘
                       │ HTTP / WebSocket
┌──────────────────────▼──────────────────────────┐
│                  apps/api                       │
│              Fastify 后端服务                     │
│  ┌───────────────────────────────────────────┐   │
│  │            packages/core                  │   │
│  │  消息管理 · 变量系统 · 提示词编排            │   │
│  │  LLM 调度 · 记忆系统 · 事件总线             │   │
│  └───────────────────────────────────────────┘   │
│  ┌───────────────────────────────────────────┐   │
│  │      packages/adapters-sillytavern        │   │
│  │  预设导入 · 正则导入 · 世界书导入            │   │
│  └───────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

依赖方向

text
apps/api  ──→  packages/core  ──→  packages/shared
   │               │
   └──→  packages/adapters-sillytavern ──→  packages/shared

apps/web  ──→  packages/shared
         └──→  packages/official-integration-kit
                    ├──→  sdk
                    └──→  client-helpers

依赖方向永远是 apps → packages,不能反过来。核心引擎不知道后端服务的存在,后端通过实现核心引擎定义的数据库抽象接口来对接。

下一步