Files
writing-agent/tests/preset-regex.test.ts
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

203 lines
6.6 KiB
TypeScript
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.
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"]);
});
});