- 装配只认 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:真假上游抓包对比工具、三份预设的机制结论与实测报告
203 lines
6.6 KiB
TypeScript
203 lines
6.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
applyRegexScripts,
|
||
hasRegexFor,
|
||
readRegexScripts,
|
||
runRegexScript,
|
||
type PresetRegexScript,
|
||
} from "../src/preset/regex-scripts.js";
|
||
|
||
const base: PresetRegexScript = {
|
||
name: "t",
|
||
findRegex: "/<choices>[\\s\\S]*?<\\/choices>/g",
|
||
replaceString: "",
|
||
trimStrings: [],
|
||
placement: [2],
|
||
promptOnly: true,
|
||
markdownOnly: false,
|
||
disabled: false,
|
||
runOnEdit: false,
|
||
substituteRegex: 0,
|
||
minDepth: null,
|
||
maxDepth: null,
|
||
approximateSubstitute: false,
|
||
};
|
||
|
||
describe("readRegexScripts", () => {
|
||
it("从 raw.regex_scripts / extensions.regex_scripts / SPreset.RegexBinding 读并去重", () => {
|
||
const raw = {
|
||
regex_scripts: [
|
||
{ id: "a", scriptName: "A", findRegex: "/x/g", placement: [1], promptOnly: true },
|
||
],
|
||
extensions: {
|
||
regex_scripts: [
|
||
{ id: "a", scriptName: "A", findRegex: "/x/g", placement: [1], promptOnly: true },
|
||
{ id: "b", scriptName: "B", findRegex: "/y/g", placement: [2], markdownOnly: true },
|
||
],
|
||
SPreset: {
|
||
RegexBinding: {
|
||
regexes: [
|
||
{ id: "c", scriptName: "C", findRegex: "/z/g", placement: [2] },
|
||
],
|
||
},
|
||
},
|
||
},
|
||
};
|
||
const scripts = readRegexScripts(raw);
|
||
expect(scripts.map((s) => s.name)).toEqual(["A", "B", "C"]);
|
||
expect(scripts[1]?.markdownOnly).toBe(true);
|
||
expect(scripts[2]?.promptOnly).toBe(false);
|
||
});
|
||
|
||
it("没有 findRegex 的条目跳过", () => {
|
||
expect(readRegexScripts({ regex_scripts: [{ id: "x" }] })).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe("runRegexScript(对齐酒馆 runRegexScript)", () => {
|
||
it("支持 {{match}} 与 $1 / 命名组", () => {
|
||
expect(
|
||
runRegexScript("前言<choices>a</choices>后记", {
|
||
...base,
|
||
replaceString: "[$1]",
|
||
findRegex: "/<choices>(.*?)<\\/choices>/g",
|
||
}),
|
||
).toBe("前言[a]后记");
|
||
expect(
|
||
runRegexScript("abc", {
|
||
...base,
|
||
replaceString: "<<$<x>>>",
|
||
findRegex: "/(?<x>b)/g",
|
||
}),
|
||
).toBe("a<<b>>c");
|
||
expect(
|
||
runRegexScript("打勾", { ...base, replaceString: "✓{{match}}", findRegex: "/打/g" }),
|
||
).toBe("✓打勾");
|
||
});
|
||
|
||
it("trimStrings 从匹配片段里剔掉", () => {
|
||
expect(
|
||
runRegexScript("前((去掉))后", {
|
||
...base,
|
||
findRegex: "/\\(\\((.*?)\\)\\)/g",
|
||
replaceString: "$1",
|
||
trimStrings: ["去掉"],
|
||
}),
|
||
).toBe("前后");
|
||
});
|
||
|
||
it("坏正则原样返回", () => {
|
||
expect(runRegexScript("x", { ...base, findRegex: "/([/g" })).toBe("x");
|
||
});
|
||
});
|
||
|
||
describe("applyRegexScripts(方向 / placement / depth)", () => {
|
||
it("prompt 只跑 promptOnly,display 只跑 markdownOnly,raw 只跑两者都不是的", () => {
|
||
const scripts: PresetRegexScript[] = [
|
||
{ ...base, name: "p", promptOnly: true, findRegex: "/A/g", replaceString: "P" },
|
||
{ ...base, name: "d", promptOnly: false, markdownOnly: true, findRegex: "/A/g", replaceString: "D" },
|
||
{ ...base, name: "r", promptOnly: false, markdownOnly: false, findRegex: "/A/g", replaceString: "R" },
|
||
];
|
||
expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt" })).toBe("P");
|
||
expect(applyRegexScripts("A", scripts, { placement: 2, direction: "display" })).toBe("D");
|
||
expect(applyRegexScripts("A", scripts, { placement: 2, direction: "raw" })).toBe("R");
|
||
});
|
||
|
||
it("placement 不匹配就不跑", () => {
|
||
const scripts: PresetRegexScript[] = [{ ...base, findRegex: "/A/g", replaceString: "P" }];
|
||
expect(applyRegexScripts("A", scripts, { placement: 1, direction: "prompt" })).toBe("A");
|
||
});
|
||
|
||
it("depth 超出 min/max 就跳过", () => {
|
||
const scripts: PresetRegexScript[] = [
|
||
{ ...base, findRegex: "/A/g", replaceString: "P", minDepth: 2 },
|
||
];
|
||
expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt", depth: 1 })).toBe("A");
|
||
expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt", depth: 3 })).toBe("P");
|
||
});
|
||
|
||
it("disabled 的脚本不跑", () => {
|
||
const scripts: PresetRegexScript[] = [{ ...base, disabled: true, findRegex: "/A/g", replaceString: "P" }];
|
||
expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt" })).toBe("A");
|
||
});
|
||
|
||
it("substituteRegex=RAW 时先展开 findRegex 里的宏", () => {
|
||
const scripts: PresetRegexScript[] = [
|
||
{ ...base, findRegex: "/{{char}}/g", replaceString: "X", substituteRegex: 1 },
|
||
];
|
||
expect(
|
||
applyRegexScripts("角色名", scripts, {
|
||
placement: 2,
|
||
direction: "prompt",
|
||
expand: (text) => text.replaceAll("{{char}}", "角色名"),
|
||
}),
|
||
).toBe("X");
|
||
});
|
||
|
||
it("hasRegexFor 快速判断", () => {
|
||
const scripts: PresetRegexScript[] = [{ ...base }];
|
||
expect(hasRegexFor(scripts, "prompt", 2)).toBe(true);
|
||
expect(hasRegexFor(scripts, "display", 2)).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe("装配时对对话历史跑发送前正则", () => {
|
||
it("user/assistant 分别按 placement 1/2 处理,depth 生效", async () => {
|
||
const { importSillyTavernPreset } = await import("../src/preset/importer.js");
|
||
const { assemblePlayWorkerMessages } = await import("../src/preset/play-frame.js");
|
||
const report = importSillyTavernPreset(
|
||
{
|
||
prompts: [
|
||
{
|
||
identifier: "chatHistory",
|
||
name: "History",
|
||
role: "system",
|
||
marker: true,
|
||
content: "",
|
||
},
|
||
],
|
||
prompt_order: [
|
||
{ character_id: 100001, order: [{ identifier: "chatHistory", enabled: true }] },
|
||
],
|
||
regex_scripts: [
|
||
{
|
||
id: "strip",
|
||
scriptName: "去选项",
|
||
findRegex: "/<choices>[\\s\\S]*?<\\/choices>/g",
|
||
replaceString: "",
|
||
placement: [2],
|
||
promptOnly: true,
|
||
},
|
||
{
|
||
id: "user-only",
|
||
scriptName: "改用户输入",
|
||
findRegex: "/你好/g",
|
||
replaceString: "您好",
|
||
placement: [1],
|
||
promptOnly: true,
|
||
},
|
||
],
|
||
},
|
||
{ name: "regex-history" },
|
||
);
|
||
const messages = assemblePlayWorkerMessages({
|
||
systemPrompt: "",
|
||
preset: report.preset,
|
||
pack: {
|
||
worldBookBefore: [],
|
||
history: [
|
||
{ role: "user", content: "你好" },
|
||
{ role: "assistant", content: "回答<choices>ABC</choices>" },
|
||
],
|
||
worldBookAfter: [],
|
||
turn: "",
|
||
postTurn: [],
|
||
},
|
||
persona: null,
|
||
charName: null,
|
||
});
|
||
expect(messages.map((m) => m.content)).toEqual(["您好", "回答"]);
|
||
expect(messages.map((m) => m.role)).toEqual(["user", "assistant"]);
|
||
});
|
||
});
|