预设可以带一段手写的假历史,按真多轮发出去。
它进对话历史区、占真实轮次,跟酒馆示例对话(只做示范、不占轮次)不是一回事,所以要分开存:有它时排在真实历史之前,宏照样展开。
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { importSillyTavernPreset } from "../src/preset/importer.js";
|
||||
import { PRESET_HISTORY_MARK } from "../src/preset/assembler.js";
|
||||
import {
|
||||
assemblePlayWorkerMessages,
|
||||
shapeFramedMessages,
|
||||
} from "../src/preset/play-frame.js";
|
||||
import { normalizePseudoHistory } from "../src/preset/pseudo-history.js";
|
||||
import { buildCaseMessages } from "../src/preset/test-run.js";
|
||||
import type { PresetChatTurn } from "../src/types/preset.js";
|
||||
|
||||
const rawPreset = {
|
||||
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 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function assemble(input: {
|
||||
pseudoHistory?: PresetChatTurn[];
|
||||
history?: Array<{ role: "user" | "assistant"; content: string }>;
|
||||
exampleDialogue?: string;
|
||||
}) {
|
||||
const report = importSillyTavernPreset(rawPreset, { name: "t" });
|
||||
const preset = {
|
||||
...report.preset,
|
||||
...(input.pseudoHistory ? { pseudoHistory: input.pseudoHistory } : {}),
|
||||
};
|
||||
return assemblePlayWorkerMessages({
|
||||
systemPrompt: "",
|
||||
preset,
|
||||
pack: {
|
||||
worldBookBefore: [],
|
||||
history: input.history ?? [],
|
||||
worldBookAfter: [],
|
||||
turn: "",
|
||||
postTurn: [],
|
||||
...(input.exampleDialogue ? { exampleDialogue: input.exampleDialogue } : {}),
|
||||
},
|
||||
persona: null,
|
||||
charName: "林昭",
|
||||
});
|
||||
}
|
||||
|
||||
type Marked = { role: string; content: string };
|
||||
|
||||
/** 只取历史区(打了 PRESET_HISTORY_MARK 的那一段)。 */
|
||||
function historyTurns(messages: Array<Record<string, unknown>>): Marked[] {
|
||||
return messages
|
||||
.filter((m) => Boolean((m as Record<symbol, unknown>)[PRESET_HISTORY_MARK]))
|
||||
.map((m) => ({ role: String(m.role), content: String(m.content) }));
|
||||
}
|
||||
|
||||
describe("预设的假历史:真多轮,不与示例对话混用", () => {
|
||||
it("每条假历史各占一条 user / assistant 消息", () => {
|
||||
const messages = assemble({
|
||||
pseudoHistory: [
|
||||
{ role: "user", content: "我推开门" },
|
||||
{ role: "assistant", content: "屋里没有人。" },
|
||||
],
|
||||
});
|
||||
expect(historyTurns(messages as never)).toEqual([
|
||||
{ role: "user", content: "我推开门" },
|
||||
{ role: "assistant", content: "屋里没有人。" },
|
||||
]);
|
||||
// 不能伪装成示例对话(system + example_* 的 few-shot)
|
||||
expect(messages.some((m) => m.name)).toBe(false);
|
||||
});
|
||||
|
||||
it("假历史排在真实历史之前,真实轮次原样保留", () => {
|
||||
const messages = assemble({
|
||||
pseudoHistory: [
|
||||
{ role: "user", content: "前情-问" },
|
||||
{ role: "assistant", content: "前情-答" },
|
||||
],
|
||||
history: [
|
||||
{ role: "user", content: "真-问" },
|
||||
{ role: "assistant", content: "真-答" },
|
||||
],
|
||||
});
|
||||
expect(historyTurns(messages as never).map((m) => m.content)).toEqual([
|
||||
"前情-问",
|
||||
"前情-答",
|
||||
"真-问",
|
||||
"真-答",
|
||||
]);
|
||||
});
|
||||
|
||||
it("正文里的换行是真换行,不会被当成新的一条", () => {
|
||||
const messages = assemble({
|
||||
pseudoHistory: [
|
||||
{ role: "assistant", content: "第一段\n\n第二段\nassistant: 这不是新的一条" },
|
||||
],
|
||||
});
|
||||
const turns = historyTurns(messages as never);
|
||||
expect(turns).toHaveLength(1);
|
||||
expect(turns[0]!.content).toContain("\n\n");
|
||||
expect(turns[0]!.content).toContain("assistant: 这不是新的一条");
|
||||
});
|
||||
|
||||
it("合体后的历史区不被 squash 揉成一段", () => {
|
||||
const shaped = shapeFramedMessages(
|
||||
assemble({
|
||||
pseudoHistory: [
|
||||
{ role: "user", content: "前情-问" },
|
||||
{ role: "assistant", content: "前情-答" },
|
||||
],
|
||||
history: [{ role: "user", content: "真-问" }],
|
||||
}),
|
||||
"strict",
|
||||
);
|
||||
const contents = historyTurns(shaped as never).map((m) => m.content);
|
||||
expect(contents).toEqual(["前情-问", "前情-答", "真-问"]);
|
||||
});
|
||||
|
||||
it("示例对话仍是酒馆的 system + example_* 形状,与假历史并存", () => {
|
||||
const messages = assemble({
|
||||
pseudoHistory: [{ role: "user", content: "前情" }],
|
||||
exampleDialogue: "<START>\nUser: 你好\n林昭: 你也好",
|
||||
});
|
||||
const examples = messages.filter((m) => m.name?.startsWith("example_"));
|
||||
expect(examples.map((m) => m.name)).toEqual([
|
||||
"example_user",
|
||||
"example_assistant",
|
||||
]);
|
||||
expect(examples.every((m) => m.role === "system")).toBe(true);
|
||||
// 假历史照旧是真多轮
|
||||
expect(historyTurns(messages as never)).toEqual([
|
||||
{ role: "user", content: "前情" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("工作台用例预览走同一条路:预设假历史 + 用例历史 + 本轮输入", () => {
|
||||
const report = importSillyTavernPreset(rawPreset, { name: "t" });
|
||||
const preset = { ...report.preset, pseudoHistory: [{ role: "user" as const, content: "预设前情" }] };
|
||||
const messages = buildCaseMessages(preset, {
|
||||
id: "c1",
|
||||
name: "c1",
|
||||
charName: "林昭",
|
||||
charDescription: "",
|
||||
worldBook: "",
|
||||
userInput: "本轮输入",
|
||||
history: [{ role: "assistant", content: "用例前情" }],
|
||||
});
|
||||
const contents = messages.map((m) => String(m.content));
|
||||
const at = (text: string) => contents.findIndex((c) => c.includes(text));
|
||||
expect(at("预设前情")).toBeGreaterThanOrEqual(0);
|
||||
expect(at("预设前情")).toBeLessThan(at("用例前情"));
|
||||
expect(at("用例前情")).toBeLessThan(at("本轮输入"));
|
||||
});
|
||||
|
||||
it("非法条目被丢掉,不会变成空消息", () => {
|
||||
expect(
|
||||
normalizePseudoHistory([
|
||||
{ role: "user", content: "留着" },
|
||||
{ role: "system", content: "丢掉" },
|
||||
{ role: "assistant", content: " " },
|
||||
{ role: "assistant" },
|
||||
"垃圾",
|
||||
null,
|
||||
]),
|
||||
).toEqual([{ role: "user", content: "留着" }]);
|
||||
expect(normalizePseudoHistory("user: 我")).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user