fix(rectification): keep evidence explanations non-conclusive

This commit is contained in:
Jesse_Chen
2026-07-31 21:00:51 +08:00
parent 6faa0a6be3
commit 9e7431ea19
2 changed files with 17 additions and 5 deletions
@@ -21,7 +21,7 @@ const genericAcknowledgementPattern = /^(?:好的|明白了|知道了|收到|已
const methodMappingPattern = /(?:(?:)?|(?:|)|(?:|||)|(?:)?|||||)/u;
const observedResultClaimPattern = /(?:(?:(?:)?||||||||||||||)[^\n]{0,48}(?:|(?:|)|||||||)|(?:|(?:|)|||||||)[^\n]{0,48}(?:|||||||||||))/u;
const candidateConclusionPattern = /(?:|||||||||||||||||)/u;
const groundedPublicReplyRequirement = "When the latest answer adds or refines a concrete event, the final public reply must: acknowledge the exact event and its date precision; summarize one to three evidence signals found in the user wording; use evidenceExplanation to map those signals to public method layers from dossier.capabilities.publicTechniqueCapabilities; distinguish a general method mapping from calculations actually present in tool observations; explain why the next question helps; and ask at most one question. Public method names such as D4, D24, Vimshottari, Narayana, UL, and A10 are allowed. Never expose internal ids, raw scores, weights, contribution matrices, raw tool traces, candidate minutes, or hidden reasoning. Never claim a numeric structural fact such as a division switching N times unless the dossier contains the matching window_sensitivity observation and publicExplanationGrounding cites its fact key.";
const groundedPublicReplyRequirement = "When the latest answer adds or refines a concrete event, the final public reply must: acknowledge the exact event and its date precision; summarize one to three evidence signals found in the user wording; use evidenceExplanation to map those signals to public method layers from dossier.capabilities.publicTechniqueCapabilities; keep evidenceExplanation as a general method mapping rather than a calculated candidate conclusion; put only server-verifiable candidate updates in candidateCommentary; explain why the next question helps; and ask at most one question. Public method names such as D4, D24, Vimshottari, Narayana, UL, and A10 are allowed. Never expose internal ids, raw scores, weights, contribution matrices, raw tool traces, candidate minutes, or hidden reasoning. Never claim a numeric structural fact such as a division switching N times unless the dossier contains the matching window_sensitivity observation and publicExplanationGrounding cites its fact key.";
function containsExactMinute(value: string): boolean {
return exactMinutePattern.test(value);
@@ -192,13 +192,11 @@ export function validateRectificationTurnPlan(input: Readonly<{ plan: unknown; d
if (!observationFacts.get(grounding.source)?.has(grounding.factKey)) issues.push("public_grounding_invalid");
});
const hasCapabilityGrounding = plan.publicExplanationGrounding.some((grounding) => grounding.source === "capability_matrix");
const hasObservationGrounding = plan.publicExplanationGrounding.some((grounding) => grounding.source !== "capability_matrix");
const evidenceExplanation = plan.publicReply.evidenceExplanation;
if (evidenceExplanation && !hasObservationGrounding
&& (observedResultClaimPattern.test(evidenceExplanation) || candidateConclusionPattern.test(evidenceExplanation))) {
if (evidenceExplanation && (observedResultClaimPattern.test(evidenceExplanation) || candidateConclusionPattern.test(evidenceExplanation))) {
issues.push("ungrounded_candidate_claim");
}
if (hasCapabilityGrounding && !hasObservationGrounding && evidenceExplanation && !methodMappingPattern.test(evidenceExplanation)) {
if (hasCapabilityGrounding && evidenceExplanation && !methodMappingPattern.test(evidenceExplanation)) {
issues.push("capability_claim_not_mapping");
}
if (groundedEvent && plan.publicReply.evidenceExplanation) {
@@ -556,6 +556,20 @@ test("public reply allows method names but rejects private internals and ungroun
const ungroundedCandidateClaim = plan({ publicReply: { acknowledgement: "已记录。", evidenceExplanation: "D10 已经显示较早候选更符合。", candidateCommentary: null, limitation: null } });
assert.ok(validateRectificationTurnPlan({ plan: ungroundedCandidateClaim, dossier: dossier(), latestAnswer: "", phase: "final" }).issues.includes("ungrounded_candidate_claim"));
const emptyCandidateObservation = {
...dossier(),
runtime: {
revision: 1,
hypotheses: [],
observations: [{ round: 1, tool: "candidate_scan" as const, diagnostic: null, outcome: "succeeded" as const, result: { candidateState: { hasSnapshot: false, publicRangeAllowed: false, topClusters: [], contrasts: [] } }, dossierRevision: 1, errorCode: null }],
},
};
const falselyObservedClaim = plan({
publicReply: { acknowledgement: "已记录。", evidenceExplanation: "D10 已经显示较早候选更符合。", candidateCommentary: null, limitation: null },
publicExplanationGrounding: [{ source: "candidate_scan", factKey: "candidateState" }],
});
assert.ok(validateRectificationTurnPlan({ plan: falselyObservedClaim, dossier: emptyCandidateObservation, latestAnswer: "", phase: "final" }).issues.includes("ungrounded_candidate_claim"));
const multiple = plan({ action: { type: "ask_question", focus: { mode: "collect_independent_event", targetEventId: null, domain: null, requestedFacts: ["independent_event"], rationaleCodes: ["test"] }, question: "你记得它发生在哪一年。那时发生了什么。", optionalQuickReplies: [] } });
assert.ok(validateRectificationTurnPlan({ plan: multiple, dossier: dossier(), latestAnswer: "", phase: "final" }).issues.includes("multiple_questions"));
const single = plan({ action: { type: "ask_question", focus: { mode: "collect_independent_event", targetEventId: null, domain: null, requestedFacts: ["independent_event"], rationaleCodes: ["test"] }, question: "你还记得一件时间大致确定的重要经历吗?", optionalQuickReplies: [] } });