- 装配只认 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:真假上游抓包对比工具、三份预设的机制结论与实测报告
101 lines
7.8 KiB
Markdown
101 lines
7.8 KiB
Markdown
# 真实请求对比(酒馆 vs 本仓库)
|
||
|
||
目的:让**本机真酒馆**和**本仓库**用同一个预设发一次真实请求,把两边真正发到上游的 JSON 落盘,再逐字段、逐条消息对齐,弄清"预设兼容"还差在哪。
|
||
|
||
不猜、不看内部对象,只看**线上真实请求体**。
|
||
|
||
## 前提
|
||
|
||
- 酒馆源码在 `D:/game/sillytavern`,能 `node server.js` 起来(版本 1.18)。
|
||
- Node 22+(用到内置 `WebSocket`)、本机有 Edge 或 Chrome(headless 也行)。
|
||
- 不改你 `D:/game/sillytavern/data` 里的任何东西:两边都用独立数据目录
|
||
(酒馆 `--dataRoot ./.st-data`,本仓库 `WRITING_AGENT_DATA_DIR=./.wa-data`)。
|
||
|
||
## 链路(两边都走"纯导入 json")
|
||
|
||
1. 假上游 `capture-server.mjs`:实现够用的 OpenAI 兼容端点,把收到的**原始请求体**原样写进 `captures/`,并回一句固定文本。
|
||
2. 酒馆侧:起 ST → 起 headless 浏览器 → CDP 进页面 → 用页面上的「导入预设」文件输入把 json 丢进去(等于人手点导入)→ 选预设 → 连接指向假上游 → 选探针角色 `ProbeCard` → 清空历史 → `ctx.generate("normal")` 真发一轮。
|
||
3. 本仓库侧:起 `src/server/web-server.ts`(独立数据目录)→ `POST /api/presets/import` → `POST /api/direct-chat/ephemeral/complete` 真发一轮。
|
||
4. `diff.mjs` 对齐两份请求体,出 `captures/report-<预设名>.md`。
|
||
|
||
## 用法
|
||
|
||
全自动(两个预设、两边、报告):
|
||
|
||
node run-all.mjs
|
||
node run-all.mjs --only st --preset "C:/Users/zed/Downloads/linux_1.3.json"
|
||
node run-all.mjs --only wa|diff
|
||
|
||
手动挡(我起环境、你点发送、我只监控):
|
||
|
||
node manual-st.mjs --preset "C:/Users/zed/Downloads/NVWA-Galaxia-ST.json"
|
||
# 浏览器窗口打开后,你在里面写内容点发送即可;抓到会打出来
|
||
|
||
配套小工具(都对着已开着的页面,不动浏览器):
|
||
|
||
node attach-preset.mjs --preset <json> # 往活页面里再导入一个预设并选中
|
||
node attach-clear.mjs --preset <名字> # 切预设 + 清空历史 + 打印当前生效参数
|
||
node attach-send.mjs --preset <名字> --text 你好
|
||
node inspect-capture.mjs captures/st-001-xxx.json [--full 3]
|
||
node inspect-prompt-order.mjs <preset.json> # 看 order/开关/marker/system_prompt/注入位置
|
||
node peek-preset-flags.mjs <preset.json> # 看 function_calling / squash / extensions
|
||
node peek-gen-fields.mjs <preset.json> # 看采样参数字段类型
|
||
node inspect-wa-assembly.mjs <preset.json> # 用本仓库的装配代码看会拼出什么(不发请求)
|
||
|
||
## 已确认的机制差异(2026-10 实测)
|
||
|
||
| 预设 | 酒馆侧机制 | 证据 |
|
||
| --- | --- | --- |
|
||
| `linux_1.3` | 预设里是纯文本的伪工具协议(6 条 `【ToolCall引导】…`),运行期被 `tavern_helper` 脚本抽 description/fields **动态注册成真 tool** | 4 条消息 + `tools(1)=dispatch_stream_payload_xxx`、`tool_choice:auto` |
|
||
| `Reborn2.4` | 预设自带 `function_calling:true`、`tool_call_recurse_limit:5`、`extensions.ToolBindings.game_content`,提示词要求必须调用 `game_content` → 设计上就是真 function calling | 两次抓包都是 33 条、**无 tools** → 绑定没注册上,退化成纯文本 |
|
||
| `NVWA-Galaxia-ST` | 无任何工具痕迹(无 tool 条目、无 `function_calling`) | 14 条、无 tools,纯文本 |
|
||
|
||
## 已确认的差距(按优先级)
|
||
|
||
1. **脚本/正则层**(最贵):本仓库导入时明确不执行 ST 正则与前端脚本;linux_1.3 的 tools 升格、Reborn2.4 的 `game_content` 都在这层。
|
||
2. **`prompts[].enabled` 双闸**(已修:装配只认 `prompt_order.enabled`):酒馆只用 `prompt_order.enabled`(`openai.js` 从不读 `prompts[].enabled`),本仓库要求两者都开 → linux_1.3 少发 `<世界设定与背景>`、`作者简介 古典武侠`、`结尾` 共约 1 万字。
|
||
3. **`squash_system_messages` 语义**(已修:新增 `system` 档,只合并相邻 system):酒馆只合并**相邻 system**(跳过带 `name` 的、跳过 `newMainChat/newChat/groupNudge`),本仓库按"同角色合并"把 `结尾(框架闭合)`、`/历史记录`、`你好` 三条 user 合成一条。
|
||
4. **角色卡插槽 + 格式模板**:`charDescription/charPersonality/scenario` 及 `personality_format/scenario_format` 包装(如 `<character_personality>…</character_personality>`)。
|
||
5. **示例对话**:`new_example_chat_prompt` 打头,`mes_example` 按 `<START>` 切段成多条 system,并带 `name=example_user/example_assistant`。
|
||
6. **`new_chat_prompt`**:空聊天时插一条 `[Start a new Chat]` / `[NVWA chat history begins below.]`。
|
||
7. 本仓库多发的:`姓名:玩家`(persona 无描述时的兜底)、`### 任务契约 …`、`stream_options:{include_usage:true}`。
|
||
|
||
## 工具协议:声明式映射(不跑脚本)
|
||
|
||
预设里的「伪工具协议」有两种数据形态,`src/preset/tool-declaration.ts` 都直接读数据翻译成真 `tools`:
|
||
|
||
1. `extensions.SPreset.ToolBindings[].form` → name / description / parameters(Reborn2.4 的 `game_content`);
|
||
2. 只有协议条目、没有 ToolBindings 时 → 从条目正文取工具名(`dispatch_stream_payload_…`)、
|
||
「工具说明」条目当 description、「字段说明」条目当参数的 description(linux_1.3 的 `display_stream`)。
|
||
|
||
开关:设置页「预设工具协议」= `preset`(只发预设要求的工具)。默认仍是 `adapt`,不动现有行为。
|
||
命中后 `complete` / `completeStream` 会改用带 tools 的请求,并把工具调用里的正文按声明字段折回正文。
|
||
|
||
实测(linux_1.3):酒馆与我们的 `tool_choice` 都是 `auto`、都只有 1 个工具、参数都是 `display_stream`;
|
||
差别只剩工具名后缀(酒馆每次导入随机生成 hash)与 description 覆盖范围(酒馆把「系统声明」也并进去了)。
|
||
|
||
## 正则脚本:三类,只做数据驱动的三类
|
||
|
||
酒馆 `regex_scripts` 的语义(`extensions/regex/engine.js` + `getRegexedString` 调用点):
|
||
|
||
| 类别 | 条件 | 作用对象 | 我们做了吗 |
|
||
| --- | --- | --- | --- |
|
||
| ① 发送前 | `promptOnly === true` | **对话历史**(user=placement 1、assistant=2、思维链=6),带 `minDepth/maxDepth` | ✅ `src/preset/regex-scripts.ts` + `play-frame` 的 chatHistory 装配 |
|
||
| ② 展示 | `markdownOnly === true` | 只影响界面渲染 | ❌ 暂不做(纯 UI) |
|
||
| ③ 写入时 | 两个 flag 都不是 true | 消息进 chat 那一刻改写文本 | ✅ 回复落盘前(`direct-chat/turn.ts`) |
|
||
|
||
支持的脚本字段:`findRegex`、`replaceString`(含 `{{match}}`、`$1`、`$<name>`)、`trimStrings`、
|
||
`placement`、`promptOnly`/`markdownOnly`、`minDepth`/`maxDepth`、`disabled`、`substituteRegex`(0/1;
|
||
2 按 RAW 处理并标记)。
|
||
|
||
实测(linux_1.3,两轮对话):上一轮助手回复 `回答正文<choices>A、B、C</choices>` →
|
||
发出的请求里变成 `回答正文`(预设的「去<choices>」promptOnly 正则生效);
|
||
上一轮用户消息被预设的「userinput」正则改写成 `【HISTORICAL_USER_INPUT】【LAST_USER_INPUT】…`。
|
||
|
||
## 决策记录
|
||
|
||
- **采样参数跟默认走**:预设缺 `temperature/top_p/max_tokens` 时,本仓库回落到 API 配置默认即可(酒馆是"缺就不改,沿用上次的值",不做对齐)。所以 `NVWA-Galaxia-ST` 报告里那行参数差异算已知取舍。
|
||
- `names_behavior`(`NONE:-1/DEFAULT:0/COMPLETION:1/CONTENT:2`)只影响**聊天历史**是否加 `名字: ` 前缀,三个预设都是 0,单角色下无效果 → 暂不实现。
|
||
- `system_prompt:true` 标的是 ST 的**内置槽位**(不可删/不在用户列表/不参与深度注入,落位由 `openai.js` 里 `systemPrompts=['nsfw','jailbreak']` 等硬编码决定),不是位置规则;当前三份抓包里两边位置一致 → 不是当前核心差距。
|
||
|