fix: harden general consultation output guard
This commit is contained in:
@@ -11,30 +11,78 @@ const guaranteeConclusionPatterns = [
|
||||
/(?:^|[.?!\n])[^.?!\n]*\b(?:will definitely|guaranteed? to|certain to|without doubt)\b[^.?!\n]*/gi,
|
||||
];
|
||||
|
||||
// The boundary is structural: first require a personalized subject, then a
|
||||
// chart object and a placement/conclusion predicate. Planet names are a finite
|
||||
// vocabulary supplement, not the primary detection mechanism.
|
||||
const chineseChartObjectPattern = /(?:盘面?|星盘|命盘|出生盘|本命盘|D\s*\d+|上升(?:星座)?|[\p{Script=Han}A-Za-z0-9]{0,8}宫|行星|星体|太阳|月亮|火星|水星|木星|金星|土星|罗喉|凯图|Rahu|Ketu)/iu;
|
||||
const chineseChartConclusionPattern = /(?:落(?:在|入|座)?|位于|进入|是|在|显示|表明|说明|意味着|主宰|很?强|很?弱|旺|受克|有力|无力|[::])/iu;
|
||||
const englishChartObjectPattern = /\b(?:natal\s+chart|birth\s+chart|chart|ascendant|rising\s+sign|D\s*\d+|(?:\d+(?:st|nd|rd|th)|[a-z]+)\s+house|house|planet|sun|moon|mars|mercury|jupiter|venus|saturn|rahu|ketu)\b/iu;
|
||||
const englishChartConclusionPattern = /(?:\b(?:is|are|falls?|lands?|sits?|placed?|located?|shows?|indicates?|means?|rules?|strong|weak)\b|[::])/iu;
|
||||
const chinesePossessiveChartSubjectPattern = /(?:你|您)\s*(?:的|个人(?:的)?)\s*(?:盘面?|星盘|命盘|出生盘|本命盘|D\s*\d+|上升(?:星座)?|[\p{Script=Han}A-Za-z0-9]{0,8}宫|行星|星体|太阳|月亮|火星|水星|木星|金星|土星|罗喉|凯图|Rahu|Ketu)/iu;
|
||||
const chineseBareChartSubjectPattern = /(?:你|您)\s*(?:盘面?|星盘|命盘|出生盘|本命盘|D\s*\d+|上升(?:星座)?|(?:第\s*)?[一二三四五六七八九十百0-9]+\s*宫|行星|星体|太阳|月亮|火星|水星|木星|金星|土星|罗喉|凯图|Rahu|Ketu)/iu;
|
||||
const chineseChartAddressesUserPattern = /(?:盘面?|星盘|命盘|出生盘|本命盘|D\s*\d+|上升(?:星座)?)\s*(?:显示|表明|说明|意味着)\s*(?:你|您)/iu;
|
||||
const englishPersonalChartSubjectPattern = /\b(?:your|the\s+user(?:'s)?)\s+(?:personal\s+)?(?:natal\s+chart|birth\s+chart|chart|ascendant|rising\s+sign|D\s*\d+|(?:\d+(?:st|nd|rd|th)|[a-z]+)\s+house|house|planet|sun|moon|mars|mercury|jupiter|venus|saturn|rahu|ketu)\b/iu;
|
||||
const englishChartAddressesUserPattern = /\b(?:the\s+)?(?:natal\s+|birth\s+)?chart\s+(?:shows?|indicates?|means?)\s+(?:that\s+)?you\b/iu;
|
||||
// The boundary is structural: an educational question frame is removed first,
|
||||
// then a personal subject/context, chart object, and chart predicate must agree.
|
||||
// This avoids treating every sentence that merely contains `你` and `宫` as a
|
||||
// personal chart claim.
|
||||
const chineseZodiacSignSource = String.raw`(?:白羊|金牛|双子|巨蟹|狮子|处女|天秤|天蝎|射手|摩羯|水瓶|双鱼)`;
|
||||
const chineseNamedHouseSource = String.raw`(?:命|财帛|兄弟|田宅|子女|夫妻|婚姻|疾厄|迁移|事业|官禄|交友|仆役|福德|父母)宫`;
|
||||
const chineseChartObjectSource = String.raw`(?:盘面?|星盘|命盘|出生盘|本命盘|D\s*\d+|上升(?:星座)?|星座|${chineseZodiacSignSource}(?:座|上升)|(?:第\s*)?[一二三四五六七八九十百0-9]+\s*宫|${chineseNamedHouseSource}|宫位|行星|星体|太阳|月亮|火星|水星|木星|金星|土星|罗喉|凯图|Rahu|Ketu)`;
|
||||
const chineseChartObjectPattern = new RegExp(chineseChartObjectSource, "iu");
|
||||
const chineseChartPredicatePattern = /(?:落(?:在|入|座)?|位于|进入|为|是|有|显示|表明|说明|意味着|主宰|很?强|很?弱|旺|受克|有力|无力|[::])/iu;
|
||||
const chineseOwnedChartSubjectPattern = new RegExp(
|
||||
String.raw`(?:你|您)\s*(?:的|个人(?:的)?)?\s*${chineseChartObjectSource}`,
|
||||
"iu",
|
||||
);
|
||||
const chineseUserHasChartPattern = new RegExp(
|
||||
String.raw`(?:你|您)\s*(?:为|是|有)\s*(?:一(?:个|颗)\s*)?${chineseChartObjectSource}`,
|
||||
"iu",
|
||||
);
|
||||
const chinesePersonalChartContextPattern = /(?:对(?:你|您)而言|基于(?:你|您)(?:的)?(?:盘面?|星盘|命盘|出生盘|本命盘)|在(?:你|您)(?:的)?(?:盘面?|星盘|命盘|出生盘|本命盘)(?:中|里)?)/iu;
|
||||
const chineseChartAddressesUserPattern = new RegExp(
|
||||
String.raw`${chineseChartObjectSource}\s*(?:显示|表明|说明|意味着)[^。!?.!?\n]{0,24}(?:你|您)`,
|
||||
"iu",
|
||||
);
|
||||
|
||||
const englishZodiacSignSource = String.raw`(?:aries|taurus|gemini|cancer|leo|virgo|libra|scorpio|sagittarius|capricorn|aquarius|pisces)`;
|
||||
const englishChartObjectSource = String.raw`(?:natal\s+chart|birth\s+chart|chart|ascendant|rising(?:\s+sign)?|D\s*\d+(?:\s+chart)?|placement|(?:\d+(?:st|nd|rd|th)|[a-z]+)\s+house|house|planet|sun|moon|mars|mercury|jupiter|venus|saturn|rahu|ketu)`;
|
||||
const englishChartObjectPattern = new RegExp(String.raw`\b${englishChartObjectSource}\b`, "iu");
|
||||
const englishChartPredicatePattern = /(?:\b(?:has|have|is|are|occup(?:y|ies)|falls?|lands?|sits?|placed?|located?|shows?|indicates?|means?|rules?|strong|weak)\b|[::])/iu;
|
||||
const englishOwnedChartSubjectPattern = new RegExp(
|
||||
String.raw`\b(?:your|the\s+user(?:'s)?)\s+(?:personal\s+)?${englishChartObjectSource}\b`,
|
||||
"iu",
|
||||
);
|
||||
const englishUserHasChartPattern = new RegExp(
|
||||
String.raw`\byou\s+have\s+(?:an?\s+)?(?:${englishZodiacSignSource}\s+rising|${englishChartObjectSource})\b`,
|
||||
"iu",
|
||||
);
|
||||
const englishPersonalChartContextPattern = /\b(?:for\s+you|(?:in|from|based\s+on|according\s+to)\s+your\s+(?:natal\s+|birth\s+)?chart)\b/iu;
|
||||
const englishChartAddressesUserPattern = new RegExp(
|
||||
String.raw`\b${englishChartObjectSource}\s+(?:shows?|indicates?|means?)\s+(?:that\s+)?you\b`,
|
||||
"iu",
|
||||
);
|
||||
|
||||
const chineseEducationalFramePattern = /^(?:(?:你|您)(?:的)?问题(?:是|为)|(?:你|您)(?:所)?问(?:的)?(?:是)?)[\s,,::]*/iu;
|
||||
const chineseAboutGeneralMeaningPattern = /^关于\s*(?:你|您)(?:的)?\s*(.+?(?:一般含义|一般意义))$/iu;
|
||||
const englishEducationalFramePattern = /^(?:your\s+question\s+is|you\s+asked\s+about)\s*/iu;
|
||||
|
||||
function normalizeClaimClause(clause: string) {
|
||||
let normalized = clause
|
||||
.normalize("NFKC")
|
||||
.replace(/[*_`~]+/gu, "")
|
||||
.replace(/\s+/gu, " ")
|
||||
.trim();
|
||||
normalized = normalized
|
||||
.replace(chineseEducationalFramePattern, "")
|
||||
.replace(englishEducationalFramePattern, "");
|
||||
const aboutGeneralMeaning = chineseAboutGeneralMeaningPattern.exec(normalized);
|
||||
return aboutGeneralMeaning?.[1] ?? normalized;
|
||||
}
|
||||
|
||||
function isPersonalChartConclusion(clause: string) {
|
||||
const normalized = clause.normalize("NFKC");
|
||||
const chineseStructure = (chinesePossessiveChartSubjectPattern.test(normalized)
|
||||
|| chineseBareChartSubjectPattern.test(normalized)
|
||||
const normalized = normalizeClaimClause(clause);
|
||||
const chineseStructure = (chineseOwnedChartSubjectPattern.test(normalized)
|
||||
|| chineseUserHasChartPattern.test(normalized)
|
||||
|| chinesePersonalChartContextPattern.test(normalized)
|
||||
|| chineseChartAddressesUserPattern.test(normalized))
|
||||
&& chineseChartObjectPattern.test(normalized)
|
||||
&& chineseChartConclusionPattern.test(normalized);
|
||||
const englishStructure = (englishPersonalChartSubjectPattern.test(normalized)
|
||||
&& chineseChartPredicatePattern.test(normalized);
|
||||
const englishStructure = (englishOwnedChartSubjectPattern.test(normalized)
|
||||
|| englishUserHasChartPattern.test(normalized)
|
||||
|| englishPersonalChartContextPattern.test(normalized)
|
||||
|| englishChartAddressesUserPattern.test(normalized))
|
||||
&& englishChartObjectPattern.test(normalized)
|
||||
&& englishChartConclusionPattern.test(normalized);
|
||||
&& englishChartPredicatePattern.test(normalized);
|
||||
return chineseStructure || englishStructure;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,43 @@ test("general mode structurally rejects personalized chart placements in Chinese
|
||||
);
|
||||
});
|
||||
|
||||
test("general mode rejects personalized chart predicates beyond possessive placements", () => {
|
||||
const unsafeClaims = [
|
||||
"您的上升星座为巨蟹。",
|
||||
"你是巨蟹上升。",
|
||||
"您有一个巨蟹上升。",
|
||||
"You have Cancer rising.",
|
||||
"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:["了解第七宫的一般概念","先完成生时校正","改问一般知识"]-->';
|
||||
|
||||
Reference in New Issue
Block a user