- 装配只认 prompt_order.enabled;squash_system_messages 只合并相邻 system(新增 system 档,连接符按酒馆用单换行)
- 包装层:new_chat_prompt、示例对话(new_example_chat_prompt + <START> 切段 + name)、角色卡三块与 personality_format/scenario_format、{{user}}/{{char}} 取值、任务契约并进预设条目
- 工具协议:按 ToolBindings / 伪工具协议数据声明真 tools(presetToolPolicy=preset),工具描述用到的条目不再重复作为消息发出
- 正则:promptOnly 作用于对话历史(含 depth 过滤)、写入时脚本改写回复;{{//}} 注释按配平扫描
- tools/st-request-diff:真假上游抓包对比工具、三份预设的机制结论与实测报告
498 lines
20 KiB
Markdown
498 lines
20 KiB
Markdown
# 预设格式
|
||
|
||
## 1. 定位
|
||
|
||
Preset 是一次 LLM 请求的上下文编排资产。它负责描述 prompt manager 中有哪些条目、这些条目的启用状态与排列顺序,以及本次生成使用哪些模型参数。
|
||
|
||
第一版的目标是兼容 SillyTavern 类预设文件中的核心部分,而不是完整复刻 SillyTavern 的所有扩展能力。
|
||
|
||
> 实现状态(v2):
|
||
> 已实现 —— prompt manager 条目与顺序、生成参数、双括号宏、消息整形(squash)、工具协议适配、导入诊断。
|
||
> 仍未实现 —— ST 正则引擎、预设内 JS 脚本、injection_position=1 的深度插入、世界书关键词/概率/sticky。
|
||
|
||
需要支持的内容:
|
||
|
||
```text
|
||
prompts
|
||
prompt manager 条目列表。每个条目描述一段可插入上下文的 prompt。
|
||
|
||
prompt_order
|
||
条目顺序与启用关系。它决定本次请求实际按什么顺序装配 prompt。
|
||
|
||
generation parameters
|
||
生成参数,例如 temperature、top_p、top_k、min_p、frequency_penalty、presence_penalty、max tokens 等。
|
||
```
|
||
|
||
第一版不支持的内容:
|
||
|
||
```text
|
||
regex_scripts
|
||
正则隐藏、正文提取、格式美化等后处理脚本(导入时只做诊断,不执行)。
|
||
|
||
extension scripts
|
||
预设内携带的前端脚本、按钮、插件配置(一律不执行,导入报告点名)。
|
||
|
||
UI-only fields
|
||
只影响 SillyTavern 界面展示、不影响 LLM 请求组装的字段。
|
||
```
|
||
|
||
## 2. 从 SillyTavern 预设中读取什么
|
||
|
||
一个真实的 SillyTavern 预设通常同时包含 prompt manager 条目、顺序关系、生成参数和扩展配置。我们的导入器只读取前三类。
|
||
|
||
### 2.1 Prompt 条目
|
||
|
||
从 `prompts` 数组读取 prompt manager 条目。
|
||
|
||
典型字段:
|
||
|
||
```ts
|
||
type ImportedPromptEntry = {
|
||
identifier: string;
|
||
name: string;
|
||
enabled: boolean;
|
||
role: "system" | "user" | "assistant";
|
||
content?: string;
|
||
injection_position?: number;
|
||
injection_depth?: number;
|
||
injection_order?: number;
|
||
system_prompt?: boolean;
|
||
marker?: boolean;
|
||
forbid_overrides?: boolean;
|
||
};
|
||
```
|
||
|
||
字段含义:
|
||
|
||
```text
|
||
identifier
|
||
条目唯一标识。prompt_order 通过它引用条目。
|
||
|
||
name
|
||
给用户看的条目名称。
|
||
|
||
enabled
|
||
条目自身默认启用状态。最终是否启用还需要结合 prompt_order。
|
||
|
||
role
|
||
条目插入请求时使用的消息角色。
|
||
|
||
content
|
||
条目文本。部分内置 marker 条目可能没有 content,需要运行时从 session 或角色数据中补齐。
|
||
|
||
injection_position / injection_depth / injection_order
|
||
SillyTavern 的插入位置、深度和顺序信息。第一版先保留,不完全模拟深度插入语义。
|
||
|
||
system_prompt / marker
|
||
标识这个条目是不是内置占位条目,例如角色描述、世界书、聊天历史等。
|
||
|
||
forbid_overrides
|
||
标识条目是否禁止被覆盖。第一版先保留字段,不实现复杂覆盖策略。
|
||
```
|
||
|
||
### 2.2 顺序与启用关系
|
||
|
||
从 `prompt_order` 读取实际装配顺序。
|
||
|
||
典型字段:
|
||
|
||
```ts
|
||
type ImportedPromptOrder = Array<{
|
||
character_id: number;
|
||
order: Array<{
|
||
identifier: string;
|
||
enabled: boolean;
|
||
}>;
|
||
}>;
|
||
```
|
||
|
||
第一版采用的规则:
|
||
|
||
```text
|
||
1. prompt_order 可能有多套(按 character_id 分)。优先 character_id=100001(酒馆里通用于所有角色的那套);
|
||
它不存在时才按「启用内容最多」打分。
|
||
2. 按 order 数组顺序遍历 identifier。
|
||
3. 找到对应 prompts 条目。
|
||
4. 只有 order.enabled 和 prompt.enabled 都为 true 时,条目才参与本轮上下文装配。
|
||
5. 如果 prompt_order 引用了不存在的 identifier,导入时记录 warning,但不阻断导入。
|
||
6. 如果 prompts 中存在但 prompt_order 未引用,默认不参与请求,但保留在 preset 中。
|
||
7. 选中的那一套若缺 main / chatHistory / jailbreak,导入时告警(框架可能不完整)。
|
||
```
|
||
|
||
`prompt_order` 比 `prompts` 中的排列更重要。`prompts` 是条目仓库,`prompt_order` 才是实际启用的编排表。
|
||
|
||
### 2.3 生成参数
|
||
|
||
从预设顶层读取生成参数。
|
||
|
||
第一版优先支持这些字段:
|
||
|
||
```ts
|
||
type ImportedGenerationParameters = {
|
||
temperature?: number;
|
||
top_p?: number;
|
||
top_k?: number;
|
||
min_p?: number;
|
||
frequency_penalty?: number;
|
||
presence_penalty?: number;
|
||
repetition_penalty?: number;
|
||
openai_max_context?: number;
|
||
openai_max_tokens?: number;
|
||
stream_openai?: boolean;
|
||
verbosity?: string;
|
||
seed?: number;
|
||
n?: number;
|
||
};
|
||
```
|
||
|
||
字段分为两类:
|
||
|
||
```text
|
||
通用采样参数
|
||
temperature、top_p、top_k、min_p、frequency_penalty、presence_penalty、repetition_penalty。
|
||
|
||
请求容量与行为参数
|
||
openai_max_context、openai_max_tokens、stream_openai、verbosity、seed、n。
|
||
思考强度不进预设,只在 API 配置里设置。
|
||
```
|
||
|
||
不同供应商不一定支持全部字段。导入后应先保存原始字段,再由 LLM adapter 决定哪些字段可以发送。
|
||
|
||
## 3. 内部归一化格式
|
||
|
||
导入 SillyTavern preset 后,不应该直接在业务层使用原始 JSON。需要归一化成我们自己的结构。
|
||
|
||
```ts
|
||
type PresetPackage = {
|
||
id: string;
|
||
name: string;
|
||
source: "native" | "sillytavern";
|
||
prompts: PresetPromptEntry[];
|
||
promptOrder: PresetPromptOrderItem[];
|
||
generation: GenerationParameters;
|
||
unsupported: UnsupportedPresetSection[];
|
||
raw?: unknown;
|
||
};
|
||
|
||
type PresetPromptEntry = {
|
||
id: string;
|
||
name: string;
|
||
enabled: boolean;
|
||
role: "system" | "user" | "assistant";
|
||
content: string;
|
||
marker: boolean;
|
||
sourceIdentifier: string;
|
||
injection?: {
|
||
position?: number;
|
||
depth?: number;
|
||
order?: number;
|
||
};
|
||
};
|
||
|
||
type PresetPromptOrderItem = {
|
||
promptId: string;
|
||
enabled: boolean;
|
||
orderIndex: number;
|
||
};
|
||
|
||
type GenerationParameters = {
|
||
temperature?: number;
|
||
topP?: number;
|
||
topK?: number;
|
||
minP?: number;
|
||
frequencyPenalty?: number;
|
||
presencePenalty?: number;
|
||
repetitionPenalty?: number;
|
||
maxContextTokens?: number;
|
||
maxOutputTokens?: number;
|
||
stream?: boolean;
|
||
verbosity?: string;
|
||
seed?: number;
|
||
variants?: number;
|
||
};
|
||
|
||
type UnsupportedPresetSection = {
|
||
path: string;
|
||
reason: string;
|
||
};
|
||
```
|
||
|
||
归一化后的 `PresetPackage` 是系统内部唯一使用的 preset 结构。原始 SillyTavern JSON 只作为导入来源和调试证据保留。
|
||
|
||
## 4. 上下文装配规则
|
||
|
||
运行时只走一份 `promptOrder`:遇到普通条目原样插入,遇到 marker 就填洞。空的酒馆角色卡 / 世界书 marker **不填我们的正文**(跳过)。
|
||
|
||
### 4.1 Marker 词汇表
|
||
|
||
酒馆原有(导入后保留位置,空洞跳过):
|
||
|
||
```text
|
||
worldInfoBefore personaDescription charDescription charPersonality
|
||
scenario worldInfoAfter dialogueExamples chatHistory
|
||
```
|
||
|
||
我们新增(由 context-order 填值;生成内容按 **世界书条目** 看待,不是角色卡字段):
|
||
|
||
| identifier | 填什么 |
|
||
|---|---|
|
||
| `worldBookBefore` | `对话.历史` 之前的 inserts,每条为标题+正文 |
|
||
| `chatHistory` | 共用酒馆 id;填 `对话.历史`——**展开成真多轮 message[]**,不是一段文本 |
|
||
| `worldBookAfter` | 历史之后、本轮之前(变量、工单等)。**不是** `worldInfoAfter` |
|
||
| `currentTurn` | `用户.最新输入`,role=user |
|
||
| `postTurn` | 本轮输入之后的 inserts(可空) |
|
||
|
||
`worldInfoBefore` / `worldInfoAfter` 都在酒馆历史 **前**,不能承接「我们写的、历史后」,因此不要复用那两个 id。
|
||
|
||
### 4.2 导入时插入四个洞
|
||
|
||
导入 SillyTavern `prompt_order` 时,以 `chatHistory` 为锚点切开并插入:
|
||
|
||
```text
|
||
[...ST 历史前条目...]
|
||
worldBookBefore
|
||
chatHistory
|
||
worldBookAfter
|
||
currentTurn
|
||
[...ST 历史后条目...]
|
||
postTurn
|
||
```
|
||
|
||
若原文没有 `chatHistory`,把它补在历史前条目之后,再接后四个洞。原生预设也可以直接按这份扩展 order 来写。无选用预设时,play worker 仍按现有 `assembleWorkerContext` 拼装(不走 prompt_order)。
|
||
|
||
社区预设里包着 WI 洞的装饰分隔会留在历史前段,中间可能是空的。第一版接受。
|
||
|
||
### 4.3 六段夹心(扫 order 后的效果)
|
||
```text
|
||
1. 酒馆内、历史前(ST 普通条目)
|
||
2. 我们写的、历史前(worldBookBefore)
|
||
3. 对话历史(chatHistory)
|
||
4. 我们写的、历史后本轮前(worldBookAfter)
|
||
5. 本轮对话(currentTurn)
|
||
6. 本轮对话后(ST 历史后条目 + postTurn)
|
||
```
|
||
|
||
### 4.4 装配步骤
|
||
|
||
```text
|
||
1. 读取(已扩展过的)PresetPackage.promptOrder。
|
||
2. 过滤 enabled=false 的顺序项。
|
||
3. 找到对应 PresetPromptEntry。
|
||
4. 再过滤 entry.enabled=false 的条目。
|
||
5. 对 marker:用 WorldInfoPack 解析我们的四个洞 + chatHistory;酒馆角色卡/WI 恒为空。
|
||
6. 普通 content 条目原样插入。
|
||
7. 按条目 role 合成 LLM messages(chatHistory 洞展开成多条)。
|
||
8. injection_position=1 的条目插进历史区(见 4.4.1)。
|
||
9. 应用 generation 参数。
|
||
```
|
||
|
||
不实现:世界书关键词、概率、sticky、outlet、ST 正则引擎、插件脚本。
|
||
|
||
#### 4.4.1 深度插入
|
||
|
||
`injection_position = 1` 的条目**不进线性位置**,而是按 `injection_depth` 从历史底部往上插:
|
||
|
||
```text
|
||
depth 0 历史最后一条之后(紧贴 currentTurn 之前)
|
||
depth N 倒数第 N 条之前
|
||
```
|
||
|
||
同深度按 `injection_order` 升序排列(数值大的更靠后)。插进去的 message 与历史同等待遇:
|
||
`injection_order` 默认 0,`role` 取自条目自身。无历史区时(单包请求)按收集顺序附在末尾。
|
||
|
||
### 4.5 双括号宏
|
||
|
||
预设正文在装配前过一遍宏层(`src/directives/macros.ts`),与 `@` 指令共用 persona 与骰子引擎。
|
||
|
||
| 宏 | 行为 |
|
||
|---|---|
|
||
| `{{// … }}` | 注释,展开为空(多行也吃) |
|
||
| `{{trim}}` | 展开为空,并让本条首尾去空白 |
|
||
| `{{user}}` | 当前用户角色名 |
|
||
| `{{persona}}` | 当前用户角色(`@人设`)的详细人设正文;未设 = 空 |
|
||
| `{{char}}` | 预设角色名;取当前作品名(对话落盘时的快照名),没绑作品时用「作品」 |
|
||
| `{{lastUserMessage}}` | 最近一条用户输入 |
|
||
| `{{getvar::k}}` | 变量值;未设 = 空 |
|
||
| `{{setvar::k::v}}` / `{{addvar::k::v}}` | 写入变量,展开为空 |
|
||
| `{{roll:1d6}}` / `{{roll N}}` | 掷骰 / 1..N 随机 |
|
||
| 其它(如 `{{charPrompt}}`、`{{description}}`) | **原样保留**,导入报告点名 |
|
||
|
||
变量作用域是**单次装配**一份 Map,不落盘、不跨轮。依据:6 份真实预设里所有 `setvar` 要么在每次请求都执行的条目里,要么在关闭的条目里 —— 单次作用域与原行为等价。
|
||
|
||
展开后变空的条目不入 messages(只写了 `{{setvar}}` 或纯注释的条目会被跳过)。
|
||
|
||
### 4.6 消息整形(squash)
|
||
|
||
对应酒馆的 Squash System Messages,在夹心完成后、发请求前执行(`src/llm/message-squash.ts`)。
|
||
|
||
#### 酒馆到底提供了什么(核过源码)
|
||
|
||
```text
|
||
squash_system_messages bool,默认 false
|
||
true = 把相邻的、没有 name 的 system 消息合并成一条
|
||
(exception: newMainChat / newChat / groupNudge)
|
||
出处:public/scripts/openai.js → ChatCompletion.squashSystemMessages()
|
||
|
||
custom_prompt_post_processing none / merge / strict(claude 已弃用 → merge)
|
||
管的是「自定义 system prompt 那几段是合成一条,还是分开发」,
|
||
与 squash 是两个设置,不碰 user/assistant 轮次。
|
||
```
|
||
|
||
所以酒馆侧只有两档真实语义:**不合并** / **合并相邻 system**。
|
||
我们多出的 `semi` / `strict` / `single-user-turn` 是为「上游拒收 system 或连续同角色」自造的降级档。
|
||
|
||
#### 我们的五档
|
||
|
||
| 档 | 行为 | 对历史区 |
|
||
|---|---|---|
|
||
| `none` | 原样(= 酒馆默认) | 不动 |
|
||
| `merge` | 只合并相邻同角色(= 酒馆勾上 squash) | 全序列合并 |
|
||
| `semi` | 合并后把 system 折进相邻普通消息,强制 user/assistant 交替 | 只合并相邻同角色 |
|
||
| `strict` | 同 semi,并保证首条为 user | 只合并相邻同角色 |
|
||
| `single-user-turn` | 全部合成一条 user | 原样保留 |
|
||
|
||
约束:
|
||
|
||
```text
|
||
工具调用组(assistant(tool_calls) + tool)永不参与合并与重排。
|
||
请求里出现工具组时,semi / strict / single-user-turn 一律退化为 merge。
|
||
|
||
历史区(真多轮)最多只做「合并相邻同角色」:
|
||
用户连发两条(上一轮助手没出正文)→ 合成一条,结构不破;
|
||
injection_depth 插进历史的 system → 不被折掉;
|
||
single-user-turn 不会把历史堆成一条。
|
||
```
|
||
|
||
来源:预设的 `squash_system_messages`(bool 或枚举)→ `PresetPackage.messagePolicy.squash`;
|
||
字段缺失时不写 `messagePolicy`,运行时按 **`strict`** 处理(`DEFAULT_PRESET_SQUASH`)——
|
||
兼容性优先:各家上游对「首条 system + 连续同角色」的容忍度不一,默认取最保守的一档。
|
||
想只合并 system(更贴酒馆)选 `merge`,想原样发出选 `none`;单个预设可在预设里自己写。
|
||
|
||
### 4.7 工具协议适配
|
||
|
||
两份真实预设把破限写在工具协议里,形态有两种:
|
||
|
||
```text
|
||
SPreset.ToolBindings 明确声明(含结构化 form.name / form.parameters)
|
||
条目正文占位符 %TOOL_NAME% + stream_target = display_stream 之类
|
||
```
|
||
|
||
导入时只做诊断(`src/preset/diagnostics.ts`):列出伪工具协议条目、待映射占位符、已声明工具。
|
||
运行时按 `toolPolicy` 处理(`src/preset/tool-policy.ts`):
|
||
|
||
| 模式 | 行为 |
|
||
|---|---|
|
||
| `adapt`(默认) | 把预设要求的工具折算到已有工具:给宿主工具追加描述与兼容字段;工具名对不上时另挂一个同名载荷工具,收到调用再折算回宿主 |
|
||
| `app` | 只发本应用工具,并把伪工具协议条目从装配中剔除 |
|
||
| `preset` | 只发预设声明的那个工具(直聊/纯 RP) |
|
||
|
||
正文字段从预设里反推:只认「被赋值」的名字(`stream_target = display_stream`)与 ToolBindings 的参数名,
|
||
不把正文里偶然出现的词当字段名。工具描述只取 ToolBindings 里作者写的那句,
|
||
**不把条目正文抄进工具描述**(那会把 prompt 体积翻倍)。
|
||
|
||
### 4.8 截断保留
|
||
|
||
正文走工具参数,`maxOutputTokens` 到顶时 `arguments` 是半截 JSON,
|
||
直接 parse 会把已经写好的正文整段丢掉。`src/llm/truncation.ts` 在参数无法解析时
|
||
把目标字段已生成的部分拨出来(只比截断处少一截),并记一行日志。
|
||
|
||
它在工具调用、消息整形、宏层全部完成后才执行,所以只救正文,不改变协议。
|
||
|
||
## 5. 与创作流程的关系
|
||
|
||
Preset 不决定创作流程,也不决定验收模式。
|
||
|
||
```text
|
||
Preset
|
||
决定本轮请求如何拼接 prompt、哪些条目启用、使用哪些生成参数。
|
||
|
||
Execution Flow
|
||
决定阶段顺序、每阶段使用哪个 worker、每阶段采用哪种验收模式。
|
||
|
||
Worker
|
||
决定某个创作能力如何执行,例如大纲、剧情线、文风、正文草稿。
|
||
|
||
Runtime Session
|
||
记录当前事实、历史事件、产物和审批结果。
|
||
```
|
||
|
||
因此,同一个 preset 可以用于多个创作流程;同一个创作流程也可以切换不同 preset。二者是正交关系。
|
||
|
||
生成参数与 **prompt 条目 + marker 填洞** 均在 `wrapLlmForSession`(`PresetLlmProvider`)统一做:凡 LLM 请求先夹心再发。调用方只交任务 messages;任务 system / 创作上下文进 `worldBookBefore` 等洞,**禁止**在 `prompt_order` 之外再前置一条 system。无选用预设时原样转发。
|
||
|
||
## 6. 第一版导入策略
|
||
|
||
第一版导入器只做保守转换:
|
||
|
||
```text
|
||
保留 prompts。
|
||
保留 prompt_order(多套时优先 100001)。
|
||
保留生成参数。
|
||
读取 squash_system_messages → messagePolicy。
|
||
记录但忽略 regex_scripts(只做诊断)。
|
||
记录但忽略 extension scripts(只做诊断)。
|
||
记录未知字段,不丢弃原始 JSON。
|
||
```
|
||
|
||
导入结果应该给用户可读的报告(`PresetImportReport`):
|
||
|
||
```text
|
||
prompt 条目数量
|
||
启用条目数量
|
||
未被 prompt_order 引用的条目数量
|
||
缺失 identifier 的 order 项
|
||
选中的 order 属于哪个 character_id;缺哪些核心位置(main / chatHistory / jailbreak)
|
||
已读取的生成参数
|
||
诊断五项:
|
||
· 伪工具协议条目 + 待映射占位符 + 已声明的工具
|
||
· 跨条目标签配对(合并消息会打散的那些)
|
||
· 宏清单:哪些会展开、哪些会原样外发
|
||
· 随机宏相对 chatHistory 的位置(在历史前会每轮击穿前缀缓存)
|
||
· 静默失效:injection_position=1 的启用条目、脚本里没被替代的能力
|
||
```
|
||
|
||
这个报告比静默导入更重要。预设文件经常很大,且混有脚本、正则、UI 配置和模型参数,必须让用户知道哪些内容真正进入了我们的运行时。
|
||
|
||
## 7. 保真度矩阵(预设到底实现了没有)
|
||
|
||
对应测试:`tests/preset-conformance.test.ts`(预设声明什么,发出去的 `messages[]` 就得是什么)。
|
||
|
||
### 7.1 已实现
|
||
|
||
| 预设里的东西 | 行为 | 实现 |
|
||
|---|---|---|
|
||
| `prompts[].identifier / content` | 原文入装配(宏展开后) | `src/preset/importer.ts` → `assembler.ts` |
|
||
| `prompts[].role` | 逐条保留(squash 可能按预设要求改角色) | `assembler.ts` |
|
||
| `prompts[].enabled` × `prompt_order[].enabled` | 两级过滤,两者都 true 才发 | `assembler.ts` |
|
||
| `prompt_order[].order` | 权威顺序(`prompts` 数组顺序无效) | `assembler.ts` |
|
||
| 多套 `prompt_order` | 优先 `character_id=100001`,否则按启用内容最多打分 | `importer.ts` |
|
||
| 未被 order 引用的条目 | 不发(仍存于预设) | `assembler.ts` |
|
||
| `injection_position=1` | 按 `injection_depth` 从历史底部插入;`injection_order` 排同深度次序 | `assembler.ts` |
|
||
| `chatHistory` | 展开成真多轮 `message[]`(role 来自 `## 用户/## 助手` 块) | `dialogue-history.ts` |
|
||
| `personaDescription` | 填当前用户角色人设 | `play-frame.ts` |
|
||
| 双括号宏 | `{{//}} {{trim}} {{user}} {{persona}} {{char}} {{lastUserMessage}} {{getvar}} {{setvar}} {{addvar}} {{roll}}` | `directives/macros.ts` |
|
||
| 生成参数 | `temperature / top_p / top_k / min_p / frequency_penalty / presence_penalty / repetition_penalty / openai_max_context / openai_max_tokens / stream_openai / verbosity / seed / n` | `importer.ts` → `generation-compat.ts` |
|
||
| `squash_system_messages` | `true → merge`、`false → none`、枚举/序号直读;缺失 → `strict` | `importer.ts` / `types/preset.ts` |
|
||
| 伪工具协议 | `toolPolicy` 三档(`adapt` / `app` / `preset`)折算到已有工具 | `preset/tool-policy.ts` |
|
||
|
||
### 7.2 不实现(会静默变弱,导入报告点名)
|
||
|
||
| 预设里的东西 | 为什么 | 影响 |
|
||
|---|---|---|
|
||
| `regex_scripts` | 不执行 ST 正则引擎 | 正文抽取/隐藏/美化那层没有;只在诊断里列出 |
|
||
| `extensions` / 插件脚本 / 按钮 | 不执行前端脚本 | 脚本类功能全无 |
|
||
| 世界书关键词 / 概率 / sticky / outlet | 我们用自己的黑板 tag | 用世界书关键词触发的条目不会自动出现 |
|
||
| `charDescription` / `charPersonality` / `scenario` / `dialogueExamples` / `worldInfoBefore` / `worldInfoAfter` | 这些洞恒空(跳过) | 靠角色卡占位的预设会变薄;角色卡主要靠 `personaDescription` + 我们的 `worldBook*` 洞 |
|
||
| `forbid_overrides` / `system_prompt` 等 UI 字段 | 保留字段,不参与装配 | 无影响 |
|
||
| 未列在宏表里的 `{{...}}` | 原样外发 | 模型会看到宏原文(`{{charPrompt}}` 等) |
|
||
| `injection_position` 非 0/1 | 按普通位置发 | 少见 |
|
||
| 预设里的 `reasoning_effort` | 读入但发送前剥离 | 思考强度只跟 API 配置 |
|
||
|
||
### 7.3 怎么自己验
|
||
|
||
```text
|
||
1. 单元:npx vitest run tests/preset-conformance.test.ts
|
||
2. 试跑:设置页 → 预设 → 试跑,看「实际发出」的 messages 列表(system/user/assistant 逐条)
|
||
3. 请求痕迹:每条回复上挂的 contextTrace.messages 就是发出去的那一份
|
||
4. 成本:npm run stats:cache —— 组内首次 vs 组内后续的 cached/prompt
|
||
``` |