- 装配只认 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:真假上游抓包对比工具、三份预设的机制结论与实测报告
196 lines
6.0 KiB
TypeScript
196 lines
6.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { importSillyTavernPreset } from "../src/preset/importer.js";
|
|
import { assemblePlayWorkerMessages } from "../src/preset/play-frame.js";
|
|
import { parseExampleDialogue } from "../src/preset/examples.js";
|
|
|
|
const rawPreset = {
|
|
new_chat_prompt: "[Start a new Chat]",
|
|
new_example_chat_prompt: "[Example Chat]",
|
|
prompts: [
|
|
{ identifier: "main", name: "main", role: "system", content: "MAIN" },
|
|
{
|
|
identifier: "dialogueExamples",
|
|
name: "examples",
|
|
role: "system",
|
|
marker: true,
|
|
content: "",
|
|
},
|
|
{
|
|
identifier: "chatHistory",
|
|
name: "history",
|
|
role: "system",
|
|
marker: true,
|
|
content: "",
|
|
},
|
|
],
|
|
prompt_order: [
|
|
{
|
|
character_id: 100001,
|
|
order: [
|
|
{ identifier: "main", enabled: true },
|
|
{ identifier: "dialogueExamples", enabled: true },
|
|
{ identifier: "chatHistory", enabled: true },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const emptyPack = {
|
|
worldBookBefore: [],
|
|
history: [],
|
|
worldBookAfter: [],
|
|
turn: "",
|
|
postTurn: [],
|
|
};
|
|
|
|
function assemble(overrides: Record<string, unknown> = {}) {
|
|
const report = importSillyTavernPreset(rawPreset, { name: "t" });
|
|
return assemblePlayWorkerMessages({
|
|
systemPrompt: "",
|
|
preset: report.preset,
|
|
pack: { ...emptyPack, ...overrides },
|
|
persona: null,
|
|
charName: "ProbeCard",
|
|
});
|
|
}
|
|
|
|
describe("酒馆包装层:new_chat_prompt 与示例对话", () => {
|
|
it("chatHistory 空时插一条 new_chat_prompt", () => {
|
|
const messages = assemble();
|
|
const history = messages.filter((m) => m.content === "[Start a new Chat]");
|
|
expect(history).toHaveLength(1);
|
|
expect(history[0]?.role).toBe("system");
|
|
});
|
|
|
|
it("有历史时 new_chat_prompt 仍排在历史最前", () => {
|
|
const messages = assemble({
|
|
history: [
|
|
{ role: "user", content: "第一句" },
|
|
{ role: "assistant", content: "第二句" },
|
|
],
|
|
});
|
|
const texts = messages.map((m) => m.content);
|
|
const head = texts.indexOf("[Start a new Chat]");
|
|
expect(head).toBeGreaterThanOrEqual(0);
|
|
expect(texts.indexOf("第一句")).toBe(head + 1);
|
|
expect(texts.indexOf("第二句")).toBe(head + 2);
|
|
});
|
|
|
|
it("示例对话按 <START> 切段,带 example_user / example_assistant", () => {
|
|
const messages = assemble({
|
|
exampleDialogue:
|
|
"<START>\n{{user}}: 今天做什么?\n{{char}}: 读书。\n更多解释",
|
|
});
|
|
expect(messages.map((m) => m.content)).toContain("[Example Chat]");
|
|
const user = messages.find((m) => m.name === "example_user");
|
|
const assistant = messages.find((m) => m.name === "example_assistant");
|
|
expect(user?.role).toBe("system");
|
|
expect(user?.content).toBe("今天做什么?");
|
|
expect(assistant?.role).toBe("system");
|
|
expect(assistant?.content).toBe("读书。\n更多解释");
|
|
});
|
|
|
|
it("没有示例对话就不发 new_example_chat_prompt", () => {
|
|
const texts = assemble().map((m) => m.content);
|
|
expect(texts).not.toContain("[Example Chat]");
|
|
});
|
|
|
|
it("人设描述为空时不塞「姓名:X」", () => {
|
|
const report = importSillyTavernPreset(
|
|
{
|
|
prompts: [
|
|
{
|
|
identifier: "personaDescription",
|
|
name: "persona",
|
|
role: "system",
|
|
marker: true,
|
|
content: "",
|
|
},
|
|
],
|
|
prompt_order: [
|
|
{ character_id: 100001, order: [{ identifier: "personaDescription", enabled: true }] },
|
|
],
|
|
},
|
|
{ name: "t2" },
|
|
);
|
|
const messages = assemblePlayWorkerMessages({
|
|
systemPrompt: "",
|
|
preset: report.preset,
|
|
pack: emptyPack,
|
|
persona: { name: "User", description: "" },
|
|
charName: "ProbeCard",
|
|
});
|
|
expect(messages.some((m) => String(m.content).startsWith("姓名:"))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("parseExampleDialogue", () => {
|
|
it("多块示例各自成组", () => {
|
|
const blocks = parseExampleDialogue({
|
|
text: "<START>\nA: 一\nB: 二\n<START>\nA: 三\nB: 四",
|
|
userName: "A",
|
|
charName: "B",
|
|
});
|
|
expect(blocks.map((block) => block.length)).toEqual([2, 2]);
|
|
expect(blocks[1]?.map((m) => m.content)).toEqual(["三", "四"]);
|
|
});
|
|
|
|
it("续行并入上一条", () => {
|
|
const blocks = parseExampleDialogue({
|
|
text: "A: 第一行\n第二行\nB: 回复",
|
|
userName: "A",
|
|
charName: "B",
|
|
});
|
|
expect(blocks[0]?.map((m) => m.content)).toEqual(["第一行\n第二行", "回复"]);
|
|
});
|
|
});
|
|
|
|
describe("角色卡三块(槽位填)", () => {
|
|
const rawCardPreset = {
|
|
personality_format: "<character_personality>\n{{personality}}\n</character_personality>",
|
|
scenario_format: "<scenario>\n{{scenario}}\n</scenario>",
|
|
prompts: [
|
|
{ identifier: "charDescription", name: "desc", role: "system", marker: true, content: "" },
|
|
{ identifier: "charPersonality", name: "pers", role: "system", marker: true, content: "" },
|
|
{ identifier: "scenario", name: "scen", role: "system", marker: true, content: "" },
|
|
],
|
|
prompt_order: [
|
|
{
|
|
character_id: 100001,
|
|
order: [
|
|
{ identifier: "charDescription", enabled: true },
|
|
{ identifier: "charPersonality", enabled: true },
|
|
{ identifier: "scenario", enabled: true },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
function build(card: unknown) {
|
|
const report = importSillyTavernPreset(rawCardPreset, { name: "card" });
|
|
return assemblePlayWorkerMessages({
|
|
systemPrompt: "",
|
|
preset: report.preset,
|
|
pack: { ...emptyPack, card },
|
|
persona: null,
|
|
charName: "ProbeCard",
|
|
});
|
|
}
|
|
|
|
it("描述原样发,性格与场景套预设模板", () => {
|
|
const messages = build({
|
|
description: "DESC",
|
|
personality: "PERS",
|
|
scenario: "SCEN",
|
|
});
|
|
const texts = messages.map((m) => m.content);
|
|
expect(texts).toContain("DESC");
|
|
expect(texts).toContain("<character_personality>\nPERS\n</character_personality>");
|
|
expect(texts).toContain("<scenario>\nSCEN\n</scenario>");
|
|
});
|
|
|
|
it("槽位为空就整条不发", () => {
|
|
expect(build(undefined)).toEqual([]);
|
|
});
|
|
});
|