一条一个角色下拉加一整句正文,换行原样保留;旧盘里的纯文本在读盘时迁移,切得出轮次的转成条目,切不出的挪进史前设定免得悄悄丢掉。历史编辑器做成共用控件,预设工作台的用例与假历史也用同一套。
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { importSillyTavernPreset } from "../src/preset/importer.js";
|
|
import {
|
|
EMPTY_SHELL_SLOTS,
|
|
assembleDirectChatMessages,
|
|
createShell,
|
|
getDirectChatsDir,
|
|
getShell,
|
|
worldInfoPackFromDirectChat,
|
|
} from "../src/direct-chat/index.js";
|
|
|
|
function samplePreset() {
|
|
return importSillyTavernPreset({
|
|
prompts: [
|
|
{ identifier: "main", name: "Main", role: "system", content: "MAIN" },
|
|
{ identifier: "chatHistory", name: "History", role: "system", marker: true },
|
|
],
|
|
prompt_order: [
|
|
{
|
|
character_id: 1,
|
|
order: [
|
|
{ identifier: "main", enabled: true },
|
|
{ identifier: "chatHistory", enabled: true },
|
|
],
|
|
},
|
|
],
|
|
}).preset;
|
|
}
|
|
|
|
const slots = (extra: Record<string, unknown> = {}) => ({
|
|
...EMPTY_SHELL_SLOTS,
|
|
...extra,
|
|
});
|
|
|
|
describe("直聊种子历史:结构化真多轮", () => {
|
|
beforeEach(() => {
|
|
process.env.WRITING_AGENT_DATA_DIR = mkdtempSync(
|
|
path.join(tmpdir(), "wa-seed-history-"),
|
|
);
|
|
});
|
|
|
|
it("seedMessages 每条一个 message,排在已有轮次之前", () => {
|
|
const messages = assembleDirectChatMessages(samplePreset(), {
|
|
message: "继续",
|
|
slots: slots({
|
|
seedMessages: [
|
|
{ role: "user", content: "前情-问" },
|
|
{ role: "assistant", content: "前情-答" },
|
|
],
|
|
}),
|
|
priorMessages: [{ role: "user", 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("真-问"));
|
|
expect(at("真-问")).toBeLessThan(at("继续"));
|
|
// 真轮次,不是塞进 system 的 few-shot
|
|
expect(messages.some((m) => m.name)).toBe(false);
|
|
});
|
|
|
|
it("seedMessages 优先于旧版 seedHistory 文本", () => {
|
|
const pack = worldInfoPackFromDirectChat({
|
|
message: "继续",
|
|
slots: slots({
|
|
seedHistory: "## 用户\n\n旧文本",
|
|
seedMessages: [{ role: "assistant", content: "新结构" }],
|
|
}),
|
|
priorMessages: [],
|
|
});
|
|
expect(pack.history).toEqual([{ role: "assistant", content: "新结构" }]);
|
|
});
|
|
|
|
it("脏条目丢掉,全空时回落旧文本", () => {
|
|
const pack = worldInfoPackFromDirectChat({
|
|
message: "继续",
|
|
slots: slots({
|
|
seedHistory: "## 用户\n\n旧文本",
|
|
seedMessages: [
|
|
{ role: "system", content: "不是历史角色" },
|
|
{ role: "user", content: " " },
|
|
],
|
|
}),
|
|
priorMessages: [],
|
|
});
|
|
expect(pack.history).toEqual([{ role: "user", content: "旧文本" }]);
|
|
});
|
|
|
|
it("读旧盘时把种子历史文本迁成结构化条目并落盘", () => {
|
|
const shell = createShell({ name: "旧壳", slots: slots() });
|
|
const file = path.join(getDirectChatsDir(), "shells", `${shell.id}.json`);
|
|
writeFileSync(
|
|
file,
|
|
JSON.stringify({
|
|
...shell,
|
|
seedHistory: "【用户】\n我推开门\n\n【助手】\n屋里没有人。\n\n【系统】\n旁白一句",
|
|
}),
|
|
"utf8",
|
|
);
|
|
|
|
const loaded = getShell(shell.id);
|
|
expect(loaded?.seedHistory).toBe("");
|
|
expect(loaded?.seedMessages).toEqual([
|
|
{ role: "user", content: "我推开门" },
|
|
{ role: "assistant", content: "屋里没有人。" },
|
|
{ role: "assistant", content: "旁白一句" },
|
|
]);
|
|
// 迁移要写回盘,下次读到的就是新形状
|
|
const onDisk = JSON.parse(readFileSync(file, "utf8")) as { seedMessages?: unknown };
|
|
expect(Array.isArray(onDisk.seedMessages)).toBe(true);
|
|
});
|
|
|
|
it("切不出轮次的旧文本挪进前情,避免变成看不见的发送内容", () => {
|
|
const shell = createShell({ name: "读料壳", slots: slots({ loreBefore: "设定" }) });
|
|
const file = path.join(getDirectChatsDir(), "shells", `${shell.id}.json`);
|
|
writeFileSync(file, JSON.stringify({ ...shell, seedHistory: "一段没有块头的读料" }), "utf8");
|
|
|
|
const loaded = getShell(shell.id);
|
|
expect(loaded?.seedHistory).toBe("");
|
|
expect(loaded?.seedMessages).toEqual([]);
|
|
expect(loaded?.loreBefore).toBe("设定\n\n一段没有块头的读料");
|
|
});
|
|
});
|