fix(rectification): inherit target year for partial dates

This commit is contained in:
Jesse_Chen
2026-08-01 01:04:33 +08:00
parent 32aa0ecbac
commit b5232e7903
8 changed files with 179 additions and 25 deletions
@@ -494,7 +494,7 @@ export const agentRunSchema = z.object({
decision: rectificationDecisionSchema.nullable(),
validatedDecision: validatedDecisionSchema,
toolCalls: z.array(toolCallTraceSchema).max(10),
fallbackReason: nonblank(120).nullable(),
fallbackReason: nonblank(240).nullable(),
inputTokenCount: z.number().int().nonnegative().nullable(),
outputTokenCount: z.number().int().nonnegative().nullable(),
latencyMs: z.number().int().nonnegative().max(300_000),
@@ -211,6 +211,7 @@ export function validateRectificationTurnPlan(input: Readonly<{ plan: unknown; d
if (plan.targetDisposition === "resolved" && !revisedCurrentTarget) issues.push("resolved_target_not_revised");
if (plan.targetDisposition === "answered_other_event" && !createdOtherEvent) issues.push("other_event_not_proposed");
}
if (input.phase === "evidence") return { plan: issues.length ? null : plan, issues };
const groundedEvent = latestGroundedEvent(input.dossier, input.latestAnswer);
const capabilityFacts = new Map(input.dossier.capabilities.publicTechniqueCapabilities.map((item) => [`domain:${item.domain}`, item]));
const observationFacts = new Map<"window_sensitivity" | "candidate_scan" | "diagnostic", Set<string>>([
@@ -425,6 +426,9 @@ export async function runRectificationDirector(input: Readonly<{ caseValue: Rect
publicReply: { acknowledgement: "本轮信息已经保留。", evidenceExplanation: null, candidateCommentary: null, limitation: "当前无法安全生成新的不重复问题,先暂停在这里。" },
publicExplanationGrounding: [],
};
return { plan: safePlan, dossier, mode: "deterministic_fallback" as const, fallbackReason: error instanceof Error ? error.message.slice(0, 120) : "director_failed", toolCalls, inputTokenCount: usageObserved ? inputTokens : null, outputTokenCount: usageObserved ? outputTokens : null, latencyMs: Date.now() - started };
const primaryReason = error instanceof Error ? error.message : "director_failed";
const fallbackValidationReason = validatedFallback.plan ? null : `fallback_rejected:${validatedFallback.issues.join(",")}`;
const fallbackReason = [primaryReason, fallbackValidationReason].filter((value): value is string => Boolean(value)).join(";").slice(0, 240);
return { plan: safePlan, dossier, mode: "deterministic_fallback" as const, fallbackReason, toolCalls, inputTokenCount: usageObserved ? inputTokens : null, outputTokenCount: usageObserved ? outputTokens : null, latencyMs: Date.now() - started };
}
}
@@ -4,7 +4,7 @@ import { buildCandidateClusters } from "../rectification-v4/candidate-clusters.t
import type { CandidateSnapshot, RectificationAnalysisTrace, RectificationV4Question } from "../rectification-v4/contracts.ts";
import { evaluateDecisionGate } from "../rectification-v4/decision-gate.ts";
import { reconcileV4Evidence, stageAgentEvidenceProposals, type ReconciledV4Evidence, type TargetDisposition } from "../rectification-v4/extraction.ts";
import { buildRectificationCaseDossier, runRectificationDirector } from "./director-agent.ts";
import { buildRectificationCaseDossier, runRectificationDirector, type RectificationDirectorGenerator } from "./director-agent.ts";
import { candidateUpdateFor } from "./renderer-agent.ts";
import { extractEventWithModel } from "./event-extractor-agent.ts";
import { evidenceSetHash } from "../rectification-v4/fingerprints.ts";
@@ -138,6 +138,7 @@ export async function processRectificationAgentTurn(input: Readonly<{
engine: RectificationV4CandidateEngine;
now: Date;
onPhase?: (phase: "extracting_evidence" | "scoring_candidates" | "checking_robustness" | "planning_question" | "reasoning" | "rendering") => Promise<void>;
generateDirectorPlan?: RectificationDirectorGenerator;
}>): Promise<Readonly<{
newEventRevisions: ClaimedRectificationV4Job["events"];
pendingEvidence: import("../rectification-v4/contracts.ts").PendingEvidence[];
@@ -190,7 +191,7 @@ export async function processRectificationAgentTurn(input: Readonly<{
currentTargetEventId: claimed.turn.questionTargetEventId,
});
evidenceDirector = await runRectificationDirector({
caseValue: claimed.case, dossier, latestAnswer: claimed.turn.answer, phase: "evidence", diagnostics: provisionalDiagnostics,
caseValue: claimed.case, dossier, latestAnswer: claimed.turn.answer, phase: "evidence", diagnostics: provisionalDiagnostics, generatePlan: input.generateDirectorPlan,
});
const serverReconciliation = reconcileV4Evidence({ caseId: claimed.case.id, answer: claimed.turn.answer, sourceTurnId: claimed.turn.id, asOfDate, existing: claimed.events, targetEventId: claimed.turn.questionTargetEventId, now });
reconciliation = claimed.case.deploymentMode === "v5_agent" && evidenceDirector.mode === "agent" && evidenceDirector.plan.evidenceProposals.length
@@ -395,7 +396,7 @@ export async function processRectificationAgentTurn(input: Readonly<{
});
await enterPhase("reasoning");
const directed = await runRectificationDirector({
caseValue: claimed.case, dossier, latestAnswer: claimed.turn.answer, phase: "final", diagnostics: safeDiagnostics,
caseValue: claimed.case, dossier, latestAnswer: claimed.turn.answer, phase: "final", diagnostics: safeDiagnostics, generatePlan: input.generateDirectorPlan,
});
const plan = directed.plan;
const action = plan.action;
@@ -447,7 +448,7 @@ export async function processRectificationAgentTurn(input: Readonly<{
: "awaiting_answer" as const;
const totalInput = [evidenceDirector?.inputTokenCount, directed.inputTokenCount].filter((value): value is number => value !== null && value !== undefined).reduce((sum, value) => sum + value, 0);
const totalOutput = [evidenceDirector?.outputTokenCount, directed.outputTokenCount].filter((value): value is number => value !== null && value !== undefined).reduce((sum, value) => sum + value, 0);
const fallbackReason = [evidenceDirector?.fallbackReason, directed.fallbackReason].filter(Boolean).join(";").slice(0, 120) || null;
const fallbackReason = [evidenceDirector?.fallbackReason, directed.fallbackReason].filter(Boolean).join(";").slice(0, 240) || null;
const agentRun: AgentRun = {
id: randomUUID(), caseId: claimed.case.id, jobId: claimed.job.id, caseVersion: claimed.case.version,
modelId: claimed.case.orchestrationModelId, skillVersion: claimed.case.skillVersion, promptVersion: claimed.case.promptVersion,
+37 -19
View File
@@ -242,24 +242,42 @@ export function reconcileV4Evidence(input: {
revisions.push(clarified);
targetResolved = true;
} else {
const targetAnswer = extracted.find((event) => event.dateValue && event.datePrecision !== "unknown" && describesTarget(event, target));
if (targetAnswer?.dateValue && targetAnswer.datePrecision !== "unknown") {
const dateRange = dateRangeFromDeclared(targetAnswer.dateValue, targetAnswer.datePrecision);
if (dateRange.start <= input.asOfDate) {
revisions.push(appendEventRevision(input.existing, {
eventId: target.eventId,
domain: target.domain,
eventKind: target.eventKind,
subject: target.subject,
relatedPerson: target.relatedPerson,
summary: target.summary,
rawText: input.answer,
dateRange,
...eventDateProvenance(target),
scoreability: target.scoreability,
}, { id: targetAnswer.id, now: input.now }));
consumed.add(targetAnswer.id);
targetResolved = true;
const inheritedDateRange = inferredTargetDateRange(input.answer, target, input.asOfDate);
if (inheritedDateRange && inheritedDateRange.start <= input.asOfDate) {
revisions.push(appendEventRevision(input.existing, {
eventId: target.eventId,
domain: target.domain,
eventKind: target.eventKind,
subject: target.subject,
relatedPerson: target.relatedPerson,
summary: target.summary,
rawText: input.answer,
dateRange: inheritedDateRange,
...eventDateProvenance(target),
scoreability: target.scoreability,
}, { now: input.now }));
extracted.forEach((event) => consumed.add(event.id));
targetResolved = true;
} else {
const targetAnswer = extracted.find((event) => event.dateValue && event.datePrecision !== "unknown" && describesTarget(event, target));
if (targetAnswer?.dateValue && targetAnswer.datePrecision !== "unknown") {
const dateRange = dateRangeFromDeclared(targetAnswer.dateValue, targetAnswer.datePrecision);
if (dateRange.start <= input.asOfDate) {
revisions.push(appendEventRevision(input.existing, {
eventId: target.eventId,
domain: target.domain,
eventKind: target.eventKind,
subject: target.subject,
relatedPerson: target.relatedPerson,
summary: target.summary,
rawText: input.answer,
dateRange,
...eventDateProvenance(target),
scoreability: target.scoreability,
}, { id: targetAnswer.id, now: input.now }));
consumed.add(targetAnswer.id);
targetResolved = true;
}
}
}
}
@@ -285,7 +303,7 @@ export function reconcileV4Evidence(input: {
const suppressPending = targetDisposition === "unknown"
|| targetDisposition === "declined"
|| targetDisposition === "direction_change";
if (extracted.length === 0 && !suppressPending) unresolvedReason = "event_unparsed";
if (extracted.length === 0 && !suppressPending && !targetResolved) unresolvedReason = "event_unparsed";
const pending = unresolvedReason && !suppressPending ? [
pendingEvidence({
caseId: input.caseId,