fix: close rectification input bypasses
This commit is contained in:
@@ -102,13 +102,34 @@ function evidenceId(input: ExtractLifeEventEvidenceInput, index: number, summary
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
||||
}
|
||||
|
||||
function splitSentenceFragments(sentence: string): string[] {
|
||||
const fragments: string[] = [];
|
||||
const separators = /\s*(并且|并|以及|同时|然后|后来又|又|,|,)\s*/g;
|
||||
let cursor = 0;
|
||||
let prefixForNext = "";
|
||||
for (const match of sentence.matchAll(separators)) {
|
||||
const index = match.index ?? cursor;
|
||||
const fragment = sentence.slice(cursor, index).trim();
|
||||
if (fragment) {
|
||||
fragments.push(`${prefixForNext}${fragment}`);
|
||||
prefixForNext = "";
|
||||
}
|
||||
const separator = match[1] ?? "";
|
||||
if (separator === "然后" || separator === "后来又") {
|
||||
prefixForNext += separator;
|
||||
}
|
||||
cursor = index + match[0].length;
|
||||
}
|
||||
const tail = sentence.slice(cursor).trim();
|
||||
if (tail) fragments.push(`${prefixForNext}${tail}`);
|
||||
return fragments;
|
||||
}
|
||||
|
||||
function splitSentences(value: string): string[][] {
|
||||
const sentences = value.split(/[。!?!?;;]/)
|
||||
.map((sentence) => sentence.trim())
|
||||
.filter(Boolean)
|
||||
.map((sentence) => sentence.split(/\s*(?:并且|并|以及|同时|然后|后来又|又|,|,)\s*/)
|
||||
.map((fragment) => fragment.trim())
|
||||
.filter(Boolean));
|
||||
.map(splitSentenceFragments);
|
||||
return sentences.length > 0 ? sentences : [[value.trim()]];
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ const sensitiveSemanticsPattern = /(?:敏感|变化|差异|切换|不同|sensiti
|
||||
const discriminationSemanticsPattern = /(?:区分|辨别|判别|验证|差异|变化|discriminat|distinguish)/i;
|
||||
const broadYearRangePattern = /(?:19|20)\d{2}\s*年?\s*(?:[-–—~~至到\/]|\.\.)\s*(?:19|20)\d{2}\s*年?/i;
|
||||
const explicitYearPattern = /(?:19|20)\d{2}\s*年?/g;
|
||||
const choiceQuestionPattern = /(?:哪(?:一|个)?(?:年份|年代|时间段|区间|时期)|哪个时间段|选择|选项|更符合|更匹配|A\s*[.、::)]|B\s*[.、::)]|which\s+(?:year|period|range)|options?)/i;
|
||||
const proposedYearAlternativesPattern = /(?:19|20)\d{2}\s*年?\s*(?:还是|或者|或是|或|、|,|,)\s*(?:19|20)\d{2}\s*年?/i;
|
||||
const choiceQuestionPattern = /(?:哪(?:一|个)?(?:年|年份|年代|时间段|区间|时期)|哪个时间段|还是|选择|选项|更符合|更匹配|A\s*[.、::)]|B\s*[.、::)]|which\s+(?:year|period|range)|options?)/i;
|
||||
const domainSemantics = {
|
||||
career: /(?:事业|工作|职业|career)/i,
|
||||
education: /(?:教育|学业|学校|education)/i,
|
||||
@@ -120,8 +121,9 @@ function narrativeReferences(value: string): string[] {
|
||||
function isGenericBroadYearChoiceQuestionnaire(value: string): boolean {
|
||||
const distinctYears = unique((value.match(explicitYearPattern) ?? [])
|
||||
.map((year) => year.replace(/\s*年$/, "")));
|
||||
return choiceQuestionPattern.test(value)
|
||||
&& (broadYearRangePattern.test(value) || distinctYears.length >= 2);
|
||||
return proposedYearAlternativesPattern.test(value)
|
||||
|| (choiceQuestionPattern.test(value)
|
||||
&& (broadYearRangePattern.test(value) || distinctYears.length >= 2));
|
||||
}
|
||||
|
||||
function proseFields(output: RectificationNarrativeModelOutput): readonly {
|
||||
|
||||
@@ -80,6 +80,8 @@ for (const rawText of [
|
||||
"2021年7月毕业并后来工作",
|
||||
"2021年7月毕业并第二年工作",
|
||||
"2021年7月毕业并此前工作",
|
||||
"2021年7月毕业然后工作",
|
||||
"2021年7月毕业后来又工作",
|
||||
]) {
|
||||
test(`does not propagate a shared date through an unresolved relative clause: ${rawText}`, () => {
|
||||
const evidence = extractLifeEventEvidence({ rawText, sourceTurnId, asOfDate: "2026-07-20" });
|
||||
|
||||
@@ -217,6 +217,42 @@ test("rejects generic individual-year options even without a written range", ()
|
||||
assert.ok(result.issues.some((issue) => issue.includes("broad-year choice questionnaire")));
|
||||
});
|
||||
|
||||
for (const prompt of [
|
||||
"请提供已经发生的真实事件:2018年还是2021年?也请说明哪一月以及发生了什么。",
|
||||
"请提供已经发生的真实事件,并说明哪一年:2018年、2021年;也请说明哪一月以及发生了什么。",
|
||||
"请提供已经发生的真实事件:2018年或2021年,也请说明哪一月以及发生了什么。",
|
||||
]) {
|
||||
test(`rejects a rich first turn that proposes multiple years: ${prompt}`, () => {
|
||||
const output = richOutput();
|
||||
assert.ok(output.evidenceRequest);
|
||||
const invalid = {
|
||||
...output,
|
||||
evidenceRequest: { ...output.evidenceRequest, prompt },
|
||||
} satisfies RectificationNarrativeModelOutput;
|
||||
const result = validateNarrativeAgainstPacket(invalid, syntheticTechnicalPacket(), "first");
|
||||
|
||||
assert.equal(result.valid, false);
|
||||
assert.ok(result.issues.some((issue) => issue.includes("broad-year choice questionnaire")));
|
||||
});
|
||||
}
|
||||
|
||||
test("accepts a request for one real past event's year and month without proposed years", () => {
|
||||
const output = richOutput();
|
||||
assert.ok(output.evidenceRequest);
|
||||
const legitimate = {
|
||||
...output,
|
||||
evidenceRequest: {
|
||||
...output.evidenceRequest,
|
||||
prompt: "请提供一件已经发生的真实事件,并说明是哪一年、哪一月以及发生了什么。",
|
||||
},
|
||||
} satisfies RectificationNarrativeModelOutput;
|
||||
|
||||
assert.deepEqual(
|
||||
validateNarrativeAgainstPacket(legitimate, syntheticTechnicalPacket(), "first"),
|
||||
{ valid: true, issues: [] },
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects ungrounded layers and references nested in a domain reason", () => {
|
||||
const output = richOutput();
|
||||
const firstReason = output.domainReasons[0];
|
||||
|
||||
Reference in New Issue
Block a user