182 lines
7.3 KiB
TypeScript
182 lines
7.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
GENERAL_NO_BIRTH_TIME_REFUSAL,
|
|
guardGeneralNoBirthTimeOutput,
|
|
guardPreciseTimingOutput,
|
|
} from "../src/lib/timing-output-guard.ts";
|
|
import { streamTextResponse } from "../src/lib/stream-text-response.ts";
|
|
import { parseAgentReply } from "../src/lib/agent-reply.ts";
|
|
import { createBirthTimeModeOutputGuard } from "../src/lib/consultation-birth-time-mode.ts";
|
|
|
|
test("removes exact dates and months when precise timing is blocked", () => {
|
|
const guarded = guardPreciseTimingOutput(
|
|
"你会在2027年3月15日结婚,事业将在11月转折。",
|
|
);
|
|
|
|
assert.doesNotMatch(guarded, /2027年3月15日|11月/);
|
|
assert.match(guarded, /具体时间已省略/);
|
|
});
|
|
|
|
test("removes guarantee conclusions when the evidence contract is incomplete", () => {
|
|
const guarded = guardPreciseTimingOutput("我保证你一定会升职。");
|
|
|
|
assert.doesNotMatch(guarded, /保证你一定会升职/);
|
|
assert.match(guarded, /保证性结论已省略/);
|
|
assert.equal(
|
|
guardPreciseTimingOutput("You will definitely get promoted."),
|
|
"[保证性结论已省略].",
|
|
);
|
|
});
|
|
|
|
test("guards a date that crosses streamed chunks", async () => {
|
|
async function* reply() {
|
|
yield "应期是2027年";
|
|
yield "3月15日,但我保证你一定会升职。";
|
|
}
|
|
|
|
const response = streamTextResponse(reply(), {
|
|
mode: "mastra",
|
|
requestId: "00000000-0000-4000-8000-000000000098",
|
|
transformText: guardPreciseTimingOutput,
|
|
});
|
|
const text = await response.text();
|
|
|
|
assert.doesNotMatch(text, /2027年3月15日|保证你一定会升职/);
|
|
assert.match(text, /保证性结论已省略/);
|
|
});
|
|
|
|
test("guards only visible prose and preserves AYANAM blocks across arbitrary chunk boundaries", async () => {
|
|
const suggestions = '<!--AYANAM_SUGGESTIONS:["你一定会升职吗?","D9 是什么?","先完成生时校正"]-->';
|
|
const title = "<!--AYANAM_TITLE:一般占星咨询-->";
|
|
const longVisiblePrefix = "一般知识不依赖个人星盘。".repeat(100);
|
|
async function* reply() {
|
|
yield `${longVisiblePrefix}正文说你将在2027年`;
|
|
yield "3月15日一定会升职。\n<!";
|
|
yield "--AYANAM_SUGGESTIONS:[\"你一定会升职吗?\",\"D9 是什么?\",\"先完成生时校正\"]--";
|
|
yield ">\n<!--AYANAM_TIT";
|
|
yield "LE:一般占星咨询-->";
|
|
}
|
|
|
|
const response = streamTextResponse(reply(), {
|
|
mode: "mastra",
|
|
requestId: "00000000-0000-4000-8000-000000000099",
|
|
transformText: createBirthTimeModeOutputGuard("general_no_birth_time", false),
|
|
});
|
|
const text = await response.text();
|
|
const parsed = parseAgentReply(text, "general");
|
|
|
|
assert.doesNotMatch(text, /2027年3月15日|正文说你将在.*一定会升职/);
|
|
assert.match(text, /^一般知识不依赖个人星盘/);
|
|
assert.match(text, /具体时间已省略|保证性结论已省略/);
|
|
assert.equal(text.includes(suggestions), true);
|
|
assert.equal(text.includes(title), true);
|
|
assert.doesNotMatch(text, /<!\[/);
|
|
assert.deepEqual(parsed.suggestions, ["你一定会升职吗?", "D9 是什么?", "先完成生时校正"]);
|
|
assert.equal(parsed.title, "一般占星咨询");
|
|
});
|
|
|
|
test("general mode structurally rejects personalized chart placements in Chinese and English", () => {
|
|
const unsafeClaims = [
|
|
"你的七宫落入摩羯。",
|
|
"盘面显示你的事业宫很强。",
|
|
"你的金星落第七宫。",
|
|
"你的上升落在巨蟹座。",
|
|
"你的 D9 显示婚姻会晚一些。",
|
|
"Your Venus is in the 7th house.",
|
|
"Your ascendant falls in Cancer.",
|
|
"Your D9 chart shows a strong marriage house.",
|
|
];
|
|
|
|
for (const claim of unsafeClaims) {
|
|
const guarded = guardGeneralNoBirthTimeOutput(claim);
|
|
assert.equal(guarded.includes(GENERAL_NO_BIRTH_TIME_REFUSAL), true, claim);
|
|
assert.equal(guarded.includes(claim.replace(/[。.]$/, "")), false, claim);
|
|
}
|
|
|
|
assert.equal(
|
|
guardGeneralNoBirthTimeOutput("第七宫在占星概念中常与关系相关。"),
|
|
"第七宫在占星概念中常与关系相关。",
|
|
);
|
|
assert.equal(
|
|
guardGeneralNoBirthTimeOutput("Venus is generally associated with relating and values."),
|
|
"Venus is generally associated with relating and values.",
|
|
);
|
|
assert.equal(
|
|
guardGeneralNoBirthTimeOutput("你问的第七宫,在占星概念中常与关系相关。"),
|
|
"你问的第七宫,在占星概念中常与关系相关。",
|
|
);
|
|
});
|
|
|
|
test("general mode rejects personalized chart predicates beyond possessive placements", () => {
|
|
const unsafeClaims = [
|
|
"您的上升星座为巨蟹。",
|
|
"你是巨蟹上升。",
|
|
"您有一个巨蟹上升。",
|
|
"You have Cancer rising.",
|
|
"You are Cancer rising.",
|
|
"You have a Cancer ascendant.",
|
|
"你拥有巨蟹上升。",
|
|
"In your chart, Venus occupies the seventh house.",
|
|
"For you, Venus falls in the seventh house.",
|
|
];
|
|
|
|
for (const claim of unsafeClaims) {
|
|
const guarded = guardGeneralNoBirthTimeOutput(claim);
|
|
const punctuation = claim.endsWith("。") ? "。" : ".";
|
|
assert.equal(guarded, `${GENERAL_NO_BIRTH_TIME_REFUSAL}${punctuation}`, claim);
|
|
}
|
|
});
|
|
|
|
test("general mode preserves educational chart questions and general knowledge", () => {
|
|
const safeStatements = [
|
|
"你的问题是第七宫的一般含义。",
|
|
"你问的是第七宫的一般含义。",
|
|
"你问的第七宫在占星概念中常与关系相关。",
|
|
"关于你的第七宫的一般含义。",
|
|
"你是一个正在研究第七宫的学生。",
|
|
"你可以先了解第七宫的一般概念。",
|
|
"你有第七宫的一般问题。",
|
|
"你有金星方面的问题,可以继续问。",
|
|
"你是第七宫问题的提问者。",
|
|
"第七宫在占星概念中常与关系相关。",
|
|
"Your question is the general meaning of the seventh house.",
|
|
"You asked about the seventh house.",
|
|
"You have a question about the seventh house.",
|
|
"The seventh house is associated with relationships.",
|
|
];
|
|
|
|
for (const statement of safeStatements) {
|
|
assert.equal(guardGeneralNoBirthTimeOutput(statement), statement);
|
|
}
|
|
});
|
|
|
|
test("hidden AYANAM comments cannot split a personalized claim around the guard", async () => {
|
|
const title = "<!--AYANAM_TITLE:一般占星咨询-->";
|
|
const suggestions = '<!--AYANAM_SUGGESTIONS:["了解第七宫的一般概念","先完成生时校正","改问一般知识"]-->';
|
|
async function* reply() {
|
|
yield "一般知识可以说明概念。你的<!";
|
|
yield "--AYANAM_TITLE:一般占星咨询--";
|
|
yield ">金星落";
|
|
yield "第七宫。\n<!--AYANAM_SUGGEST";
|
|
yield 'IONS:["了解第七宫的一般概念","先完成生时校正","改问一般知识"]-->';
|
|
}
|
|
|
|
const response = streamTextResponse(reply(), {
|
|
mode: "mastra",
|
|
requestId: "00000000-0000-4000-8000-000000000097",
|
|
transformText: createBirthTimeModeOutputGuard("general_no_birth_time", false),
|
|
});
|
|
const text = await response.text();
|
|
const parsed = parseAgentReply(text, "general");
|
|
|
|
assert.match(text, /一般知识可以说明概念/);
|
|
assert.match(text, new RegExp(GENERAL_NO_BIRTH_TIME_REFUSAL));
|
|
assert.doesNotMatch(text, /你的\s*金星落第七宫/);
|
|
assert.equal(text.includes(title), true);
|
|
assert.equal(text.includes(suggestions), true);
|
|
assert.equal(parsed.title, "一般占星咨询");
|
|
assert.deepEqual(parsed.suggestions, ["了解第七宫的一般概念", "先完成生时校正", "改问一般知识"]);
|
|
});
|