Files
writing-agent/tools/st-request-diff/README.md
T
chenmoranzhi 9dc927282f 预设兼容:按真实抓包对齐装配与包装层,补上声明式工具与预设正则。
- 装配只认 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:真假上游抓包对比工具、三份预设的机制结论与实测报告
2026-10-23 00:13:08 +08:00

101 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 真实请求对比(酒馆 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']` 等硬编码决定),不是位置规则;当前三份抓包里两边位置一致 → 不是当前核心差距。