预设兼容:按真实抓包对齐装配与包装层,补上声明式工具与预设正则。

- 装配只认 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:真假上游抓包对比工具、三份预设的机制结论与实测报告
This commit is contained in:
2026-10-23 00:13:08 +08:00
parent ed09e5c831
commit 9dc927282f
94 changed files with 11558 additions and 413 deletions
+120
View File
@@ -0,0 +1,120 @@
import { describe, expect, it } from "vitest";
import { importSillyTavernPreset } from "../src/preset/importer.js";
import {
declarePresetTools,
extractDeclaredPayload,
} from "../src/preset/tool-declaration.js";
function importRaw(raw: unknown, name: string) {
return importSillyTavernPreset(raw, { name }).preset;
}
describe("声明式工具(预设数据 → 真 tools)", () => {
it("从 ToolBindings 的 form 生成 tools", () => {
const preset = importRaw(
{
prompts: [{ identifier: "main", name: "main", role: "system", content: "MAIN" }],
prompt_order: [
{ character_id: 100001, order: [{ identifier: "main", enabled: true }] },
],
extensions: {
SPreset: {
ToolBindings: {
aaa: {
enabled: true,
resolvedName: "game_content",
form: {
name: "game_content",
description: "# 你的全部输出应当在此工具内。",
parameters: [{ name: "content", type: "string", required: true }],
},
},
},
},
},
},
"bindings",
);
const declared = declarePresetTools(preset);
expect(declared?.source).toBe("bindings");
expect(declared?.tools).toHaveLength(1);
const fn = declared!.tools[0]!.function;
expect(fn.name).toBe("game_content");
expect(fn.description).toContain("全部输出");
expect(fn.parameters).toEqual({
type: "object",
properties: { content: { type: "string" } },
required: ["content"],
});
expect(declared?.payloadFields).toEqual(["content"]);
});
it("只有伪工具协议条目时从正文取工具名与字段", () => {
const preset = importRaw(
{
prompts: [
{
identifier: "guide-sys",
name: "【ToolCall引导】系统声明",
role: "system",
content:
"调用 %TOOL_NAME% 才能输出正文;执行宿 dispatch_stream_payload_34d98628087f3798。",
},
{
identifier: "guide-field",
name: "【ToolCall引导】字段说明",
role: "system",
content: "stream_target = \"display_stream\"\n正文写在这里。",
},
],
prompt_order: [
{
character_id: 100001,
order: [
{ identifier: "guide-sys", enabled: true },
{ identifier: "guide-field", enabled: true },
],
},
],
},
"protocol",
);
const declared = declarePresetTools(preset);
expect(declared?.source).toBe("protocol");
const fn = declared!.tools[0]!.function;
expect(fn.name).toBe("dispatch_stream_payload_34d98628087f3798");
expect(declared?.payloadFields).toContain("display_stream");
const params = fn.parameters as { properties: Record<string, unknown>; required: string[] };
expect(Object.keys(params.properties)).toContain("display_stream");
expect(params.required).toContain("display_stream");
});
it("没有工具协议就返回 null", () => {
const preset = importRaw(
{
prompts: [{ identifier: "main", name: "main", role: "system", content: "MAIN" }],
prompt_order: [
{ character_id: 100001, order: [{ identifier: "main", enabled: true }] },
],
},
"plain",
);
expect(declarePresetTools(preset)).toBeNull();
});
});
describe("extractDeclaredPayload", () => {
it("按字段优先级取正文", () => {
expect(
extractDeclaredPayload(JSON.stringify({ display_stream: "正文" }), ["display_stream"]),
).toBe("正文");
});
it("字段不在时退回任意字符串字段", () => {
expect(extractDeclaredPayload(JSON.stringify({ other: "兜底" }), ["content"])).toBe("兜底");
});
it("坏 JSON 返回 null", () => {
expect(extractDeclaredPayload("{oops", ["content"])).toBeNull();
});
});