- 装配只认 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:真假上游抓包对比工具、三份预设的机制结论与实测报告
204 lines
6.8 KiB
TypeScript
204 lines
6.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
appendDialogueHistoryTurn,
|
||
parseHistoryMessages,
|
||
projectDialogueHistory,
|
||
buildHistoryFallback,
|
||
isDialogueHistoryRef,
|
||
} from "../src/skills/dialogue-history.js";
|
||
import { assembleWorkerContext } from "../src/skills/context-segments.js";
|
||
import { Blackboard } from "../src/blackboard/blackboard.js";
|
||
import {
|
||
contextOrderToSegments,
|
||
parseContextOrder,
|
||
applyContextOrderEdit,
|
||
synthesizeContextOrderFromWorkers,
|
||
} from "../src/skills/context-order.js";
|
||
|
||
describe("parseHistoryMessages", () => {
|
||
it("turns ## 用户 / ## 助手 blocks into real turns", () => {
|
||
const raw = appendDialogueHistoryTurn(
|
||
appendDialogueHistoryTurn("", { role: "助手", text: "门开了。" }),
|
||
{ role: "用户", text: "我推门。" },
|
||
);
|
||
expect(parseHistoryMessages(raw)).toEqual([
|
||
{ role: "assistant", content: "门开了。" },
|
||
{ role: "user", content: "我推门。" },
|
||
]);
|
||
});
|
||
|
||
it("accepts 【用户】 and 用户:single-line forms", () => {
|
||
expect(parseHistoryMessages("【用户】\n你好\n\n【助手】\n在。")).toEqual([
|
||
{ role: "user", content: "你好" },
|
||
{ role: "assistant", content: "在。" },
|
||
]);
|
||
expect(parseHistoryMessages("用户:你好\n助手:在。")).toEqual([
|
||
{ role: "user", content: "你好" },
|
||
{ role: "assistant", content: "在。" },
|
||
]);
|
||
});
|
||
|
||
it("keeps the projection note on the first turn", () => {
|
||
const turns = parseHistoryMessages(
|
||
"(仅最近 20 段,更早已省略)\n\n## 用户\n\n你好",
|
||
);
|
||
expect(turns).toHaveLength(1);
|
||
expect(turns[0]!.content).toContain("仅最近");
|
||
expect(turns[0]!.content).toContain("你好");
|
||
});
|
||
|
||
it("returns [] for text without role markers", () => {
|
||
expect(parseHistoryMessages("一段无结构的前情提要")).toEqual([]);
|
||
});
|
||
|
||
it("keeps a body line that looks like markdown heading", () => {
|
||
const raw = "## 用户\n\n我说## 这不是块头\n\n## 助手\n\n嗯";
|
||
const turns = parseHistoryMessages(raw);
|
||
expect(turns).toHaveLength(2);
|
||
expect(turns[0]!.content).toContain("这不是块头");
|
||
});
|
||
});
|
||
|
||
describe("dialogue history projection", () => {
|
||
it("recognizes history refs and projects by level", () => {
|
||
expect(isDialogueHistoryRef("对话.历史")).toBe(true);
|
||
expect(isDialogueHistoryRef("history")).toBe(true);
|
||
const long = Array.from({ length: 60 }, (_, i) => `## 用户\n\n第${i}句`).join(
|
||
"\n\n",
|
||
);
|
||
const summary = projectDialogueHistory(long, "summary");
|
||
expect(summary).toContain("仅最近");
|
||
// 裁到 n 段(summary = 20)
|
||
expect(summary.split("## 用户").length - 1).toBe(20);
|
||
expect(projectDialogueHistory(long, "fixed")).toContain("不注入");
|
||
});
|
||
|
||
it("keeps the whole window until it doubles(滞回,不每轮位移)", () => {
|
||
const turn = (n: number) =>
|
||
Array.from({ length: n }, (_, i) => `## 用户\n\n第${i}句`).join("\n\n");
|
||
// 20~40 段(summary 的 n=20,高水位=40)原样全给,前缀稳定
|
||
expect(projectDialogueHistory(turn(30), "summary")).not.toContain("仅最近");
|
||
expect(projectDialogueHistory(turn(30), "summary").split("## 用户").length - 1).toBe(
|
||
30,
|
||
);
|
||
// 超过高水位才裁,且一次裁到 20
|
||
expect(projectDialogueHistory(turn(41), "summary").split("## 用户").length - 1).toBe(
|
||
20,
|
||
);
|
||
});
|
||
|
||
it("assembles history tag in flat segment order", () => {
|
||
const bb = new Blackboard();
|
||
bb.write({
|
||
tag: "对话.历史",
|
||
content: appendDialogueHistoryTurn("", { role: "用户", text: "推门" }),
|
||
source: "test",
|
||
});
|
||
bb.write({ tag: "用户.最新输入", content: "再看一眼", source: "test" });
|
||
const text = assembleWorkerContext({
|
||
inputs: {},
|
||
segments: [
|
||
{
|
||
id: "p",
|
||
tier: "static",
|
||
tags: [],
|
||
label: "## persona",
|
||
inline: "GM",
|
||
projection: "fixed",
|
||
},
|
||
{
|
||
id: "h",
|
||
tier: "static",
|
||
tags: ["对话.历史"],
|
||
label: "## 对话.历史",
|
||
projection: "full",
|
||
},
|
||
{
|
||
id: "u",
|
||
tier: "dynamic",
|
||
tags: ["用户.最新输入"],
|
||
label: "## 用户.最新输入",
|
||
projection: "full",
|
||
},
|
||
],
|
||
blackboard: bb,
|
||
workerId: "world-simulator",
|
||
workerName: "主世界层",
|
||
outputTags: [],
|
||
});
|
||
const iPersona = text.indexOf("GM");
|
||
const iHist = text.indexOf("推门");
|
||
const iUser = text.indexOf("再看一眼");
|
||
expect(iPersona).toBeGreaterThanOrEqual(0);
|
||
expect(iHist).toBeGreaterThan(iPersona);
|
||
expect(iUser).toBeGreaterThan(iHist);
|
||
});
|
||
|
||
it("falls back to event stream when history empty", () => {
|
||
expect(
|
||
buildHistoryFallback({
|
||
eventStream: "门开了",
|
||
latestUser: "走进去",
|
||
}),
|
||
).toContain("门开了");
|
||
});
|
||
});
|
||
|
||
describe("flat context order", () => {
|
||
it("synthesizes history tag and moves across it", () => {
|
||
const doc = synthesizeContextOrderFromWorkers(
|
||
[
|
||
{
|
||
ref: "world-simulator",
|
||
name: "主世界层",
|
||
context: {
|
||
static: ["设计.叙事指南"],
|
||
dynamic: ["用户.最新输入"],
|
||
},
|
||
},
|
||
],
|
||
{ gm: true, narrator: true, perspective: false },
|
||
)!;
|
||
const gmSlot = doc.slots.find((s) => s.ref === "world-simulator")!;
|
||
expect(gmSlot.inserts.some((i) => i.ref === "对话.历史")).toBe(true);
|
||
|
||
const histIdx = gmSlot.inserts.findIndex((i) => i.ref === "对话.历史");
|
||
const moved = applyContextOrderEdit(doc, {
|
||
action: "set_anchor",
|
||
slotRef: "world-simulator",
|
||
index: gmSlot.inserts.length - 1,
|
||
anchor: "pre_history",
|
||
});
|
||
const movedGm = moved.slots.find((s) => s.ref === "world-simulator")!;
|
||
const newHist = movedGm.inserts.findIndex((i) => i.ref === "对话.历史");
|
||
const lastUser = movedGm.inserts.findIndex(
|
||
(i) => i.ref === "用户.最新输入",
|
||
);
|
||
expect(lastUser).toBeLessThan(newHist);
|
||
expect(histIdx).toBeGreaterThanOrEqual(0);
|
||
});
|
||
|
||
it("maps to segments in list order including history", () => {
|
||
const doc = parseContextOrder({
|
||
schema: "context-order.v1",
|
||
slots: [
|
||
{
|
||
ref: "world-simulator",
|
||
inserts: [
|
||
{ order: 0, ref: "worker.persona", projection: "fixed" },
|
||
{ order: 1, ref: "对话.历史", projection: "summary" },
|
||
{ order: 2, ref: "用户.最新输入", projection: "full" },
|
||
],
|
||
},
|
||
],
|
||
})!;
|
||
const segs = contextOrderToSegments({
|
||
slot: doc.slots[0]!,
|
||
personaText: "裁决",
|
||
});
|
||
expect(segs[0]!.inline).toContain("裁决");
|
||
expect(segs[1]!.tags).toEqual(["对话.历史"]);
|
||
expect(segs[2]!.tags).toEqual(["用户.最新输入"]);
|
||
});
|
||
});
|