- 装配只认 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:真假上游抓包对比工具、三份预设的机制结论与实测报告
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { importSillyTavernPreset } from "../src/preset/importer.js";
|
|
import {
|
|
declarePresetTools,
|
|
extractDeclaredPayload,
|
|
} from "../src/preset/tool-declaration.js";
|
|
|
|
function importRaw(raw: unknown, name: string) {
|
|
return importSillyTavernPreset(raw, { name }).preset;
|
|
}
|
|
|
|
describe("声明式工具(预设数据 → 真 tools)", () => {
|
|
it("从 ToolBindings 的 form 生成 tools", () => {
|
|
const preset = importRaw(
|
|
{
|
|
prompts: [{ identifier: "main", name: "main", role: "system", content: "MAIN" }],
|
|
prompt_order: [
|
|
{ character_id: 100001, order: [{ identifier: "main", enabled: true }] },
|
|
],
|
|
extensions: {
|
|
SPreset: {
|
|
ToolBindings: {
|
|
aaa: {
|
|
enabled: true,
|
|
resolvedName: "game_content",
|
|
form: {
|
|
name: "game_content",
|
|
description: "# 你的全部输出应当在此工具内。",
|
|
parameters: [{ name: "content", type: "string", required: true }],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"bindings",
|
|
);
|
|
const declared = declarePresetTools(preset);
|
|
expect(declared?.source).toBe("bindings");
|
|
expect(declared?.tools).toHaveLength(1);
|
|
const fn = declared!.tools[0]!.function;
|
|
expect(fn.name).toBe("game_content");
|
|
expect(fn.description).toContain("全部输出");
|
|
expect(fn.parameters).toEqual({
|
|
type: "object",
|
|
properties: { content: { type: "string" } },
|
|
required: ["content"],
|
|
});
|
|
expect(declared?.payloadFields).toEqual(["content"]);
|
|
});
|
|
|
|
it("只有伪工具协议条目时从正文取工具名与字段", () => {
|
|
const preset = importRaw(
|
|
{
|
|
prompts: [
|
|
{
|
|
identifier: "guide-sys",
|
|
name: "【ToolCall引导】系统声明",
|
|
role: "system",
|
|
content:
|
|
"调用 %TOOL_NAME% 才能输出正文;执行宿 dispatch_stream_payload_34d98628087f3798。",
|
|
},
|
|
{
|
|
identifier: "guide-field",
|
|
name: "【ToolCall引导】字段说明",
|
|
role: "system",
|
|
content: "stream_target = \"display_stream\"\n正文写在这里。",
|
|
},
|
|
],
|
|
prompt_order: [
|
|
{
|
|
character_id: 100001,
|
|
order: [
|
|
{ identifier: "guide-sys", enabled: true },
|
|
{ identifier: "guide-field", enabled: true },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
"protocol",
|
|
);
|
|
const declared = declarePresetTools(preset);
|
|
expect(declared?.source).toBe("protocol");
|
|
const fn = declared!.tools[0]!.function;
|
|
expect(fn.name).toBe("dispatch_stream_payload_34d98628087f3798");
|
|
expect(declared?.payloadFields).toContain("display_stream");
|
|
const params = fn.parameters as { properties: Record<string, unknown>; required: string[] };
|
|
expect(Object.keys(params.properties)).toContain("display_stream");
|
|
expect(params.required).toContain("display_stream");
|
|
});
|
|
|
|
it("没有工具协议就返回 null", () => {
|
|
const preset = importRaw(
|
|
{
|
|
prompts: [{ identifier: "main", name: "main", role: "system", content: "MAIN" }],
|
|
prompt_order: [
|
|
{ character_id: 100001, order: [{ identifier: "main", enabled: true }] },
|
|
],
|
|
},
|
|
"plain",
|
|
);
|
|
expect(declarePresetTools(preset)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("extractDeclaredPayload", () => {
|
|
it("按字段优先级取正文", () => {
|
|
expect(
|
|
extractDeclaredPayload(JSON.stringify({ display_stream: "正文" }), ["display_stream"]),
|
|
).toBe("正文");
|
|
});
|
|
|
|
it("字段不在时退回任意字符串字段", () => {
|
|
expect(extractDeclaredPayload(JSON.stringify({ other: "兜底" }), ["content"])).toBe("兜底");
|
|
});
|
|
|
|
it("坏 JSON 返回 null", () => {
|
|
expect(extractDeclaredPayload("{oops", ["content"])).toBeNull();
|
|
});
|
|
});
|