fix(rectification): inherit target year for partial dates
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user