🧩 EJS 模板
Since v0.87.0
📖 概述
EJS 模板让你在生成提示词的所有字段——角色卡(描述、性格、场景、开场白等)、预设、世界书、正则——里嵌入 <% %> 语法,写条件判断、循环、读写变量,做宏(Macros)做不到的动态逻辑。
- 和宏的关系:两者作用于相同的字段。宏(
{{char}}、{{setvar}}等)擅长简单注入;EJS 擅长逻辑、变量运算、循环。 - 渲染顺序:先渲染 EJS,再把结果交给
{{}}宏。也就是说 EJS 的输出里还能包含宏,会被后续宏引擎继续处理。 - 语法基于 EJS:Tavo 内置了一套 EJS 引擎,支持其中最常用的子集(标签语法 + 变量/逻辑),但不含
include/partial/ 自定义分隔符等高级特性。
⚙️ 需要先开启
EJS 默认开启。如未生效,检查 设置 → 聊天设置 → 兼容性 → 启用 EJS 模板(位于该分组第一项)。
🚀 快速上手
在任意提示词字段(最直观的是角色开场白)粘贴:
<% if (getvar("vip", "no") === "no") { %>欢迎,访客。<% } else { %>欢迎回来,尊贵的会员!<% } %>未设置 vip 变量时渲染为:欢迎,访客。
🏷️ 标签语法
| 写法 | 作用 | 例子 → 输出 |
|---|---|---|
<%- 表达式 %> | 输出(原样,不转义) | <%- "1<2" %> → 1<2 |
<%= 表达式 %> | 输出(HTML 转义) | <%= "1<2" %> → 1<2 |
<% 语句 %> | 执行逻辑,不输出 | <% var n = 1 %> → (无) |
<%# 注释 %> | 注释,不执行不输出 | a<%# 备注 %>b → ab |
print(x) | 在逻辑块里输出 | <% print("hi") %> → hi |
<%% %%> | 输出字面量标签 | <%% raw %%> → <% raw %> |
-%> | 删除结束标签后紧跟的换行 | 合并上下两行 |
<#escape-ejs>…</#escape-ejs> | 范围内的标签当字面文本 | <#escape-ejs><% x %></#escape-ejs> → <% x %> |
循环示例:
<% for (var i = 1; i <= 3; i++) { %><%= i %><% } %>输出:123
🔣 变量读写
Tavo 提供一组变量函数,桥接到内置的两层变量存储。
| 函数 | 说明 |
|---|---|
getvar(key) | 读变量,不存在返回空串 |
getvar(key, 默认值) | 不存在时返回默认值 |
getvar(key, {scope, defaults}) | 指定层级读取 / 默认值 |
setvar(key, value, {scope}) | 写变量(原样存,不解析类型) |
incvar(key, n=1, {scope}) | 自增(默认 +1) |
decvar(key, n=1, {scope}) | 自减(默认 -1) |
delvar(key, {scope}) | 删除变量 |
<% setvar("hp", 100) %>HP:<%- getvar("hp") %> → HP:100
<% setvar("code", "007") %>编号:<%- getvar("code") %> → 编号:007(保留前导零)
<% incvar("n") %><% incvar("n") %>n=<%- getvar("n") %> → n=2
<%- getvar("missing", "缺省值") %> → 缺省值键支持点路径,可直接读写嵌套(无需先取出对象):
<% setvar("o.a.b", 42) %><%- getvar("o.a.b") %> → 42作用域 scope:
chat(默认):当前会话变量,随会话保存。(兼容local)。global:全局变量,写入即时持久化。- 不带
scope时,getvar会先找chat然后global。 getvar使用cache与不带一致;message/initial与chat一致setvar如果不为global统一做chat处理。
<% setvar("g", 1, {scope: "global"}) %><%- getvar("g", {scope: "global"}) %>lodash 风格的 _:_.get(obj, path, 默认值)、_.has(obj, path)、_.set(obj, path, value)、_.unset(obj, path)、_.cloneDeep(obj)。路径支持 a.b[0].c 与 a.b.0.c 两种写法。
<% setvar("o", {a: {b: 42}}) %><%- _.get(getvar("o"), "a.b") %> → 42🧩 内置常量
以下常量已注入为全局变量,直接使用即可。
| 常量 | 含义 |
|---|---|
charName | 当前角色名 |
userName | 当前 persona 名 |
lastUserMessage | 最近一条非隐藏用户消息的原文 |
lastCharMessage | 最近一条非隐藏角色消息的原文 |
characterId | 当前角色 ID |
你好,我是 <%- charName %>。 → 你好,我是 <角色名>。常见用法:
characterId:群聊里按角色分支,或做「每角色独立变量」命名空间,如getvar("affinity_" + characterId)。lastUserMessage:依据用户刚说的话做条件注入(检测到关键词就追加提示),或喂给后续逻辑 / 正则。lastCharMessage:基于角色上一条回复做衔接、状态更新、或避免重复。
📍 能用在哪里
EJS 在提示词组装时统一渲染,覆盖:
- 角色卡:描述 / 性格 / 场景 / 开场白(含备用开场白)/ 对话示例 / 系统提示等
- 世界书:条目内容、扫描关键词
- 预设:各 prompt 段
- 正则:匹配 / 替换 / 裁剪字符串
- 其它:翻译、图像描述注入、群聊等
⚠️ 注意事项
整模板级兜底
同一字段内任意一个 EJS 标签报错(语法错误、调用了不存在的方法等),整段会原样回退、不渲染,以保证不崩、不丢内容。因此调试时若发现「标签没生效、显示的全是原文」,多半是该字段里某处 EJS 写错了。也因此,不要把「故意写错 / 演示用」的坏标签和正常标签混在同一字段。
- 与宏的顺序:先 EJS、后
{{}}宏,EJS 的输出可继续包含宏。例如<%- "{{char}}" %>先被 EJS 输出为{{char}},再由宏引擎替换成角色名。 - 转义显示:
<%= %>产出 HTML 实体(如<);若聊天开启了高级前端渲染,实体可能被二次渲染。要核对原文请查看上下文日志。 - 与官方 EJS 的差异:仅支持常用子集,不含
include/partial/ 自定义分隔符等。