fix(rectification): unify discriminator question contract and rank by information gain
Independent Staging Quality Gate / validate (push) Failing after 11m51s
Independent Staging Quality Gate / publish (push) Has been skipped

Python and TypeScript now share a four-option probe contract, persist Focus before asking, and pick the highest-value renderable probe instead of preferring low-gain career events over D24.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-28 00:25:35 +08:00
parent 85db4591d3
commit 7f82428b44
20 changed files with 1175 additions and 141 deletions
@@ -21,12 +21,16 @@ import { resolveSessionLanguageModel } from "@/lib/model-catalog";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import { defaultMessageOrigin, isRectificationMessageOrigin } from "@/lib/rectification-agentic/v9/message-origin";
import { previousInferenceFromReceipt } from "@/lib/rectification-agentic/v9/inference-adapter";
import { previousInferenceFromReceipt, askedDiscriminatorKeys } from "@/lib/rectification-agentic/v9/inference-adapter";
import { parseAgentChoiceCopy } from "@/lib/rectification-agentic/v9/choice-card";
import {
classifyRectificationTurnIntent,
optionIdForAnswerClass,
} from "@/lib/rectification-agentic/v9/turn-intent-classifier";
import { decideFromDossier, contrastPacketFromDossier } from "@/lib/rectification-agentic/v9/decision-from-dossier";
import { persistServerOwnedFocus, openQuestionFromPersistedFocus } from "@/lib/rectification-agentic/v9/server-focus";
import { buildMethodFollowupPlan } from "@/lib/rectification-agentic/v9/method-followup";
import { refinementFromDecisionReceipt } from "@/lib/rectification-agentic/v9/refinement-packet";
export const runtime = "nodejs";
export const maxDuration = 240;
@@ -308,11 +312,8 @@ export async function POST(request: Request) {
try {
const dossier = await loadV9CaseDossier(accounting, userId, caseId);
const focus = dossier.conversationSummary.activeFocus;
if (focus) {
const choice = parseAgentChoiceCopy(focus.expectedAnswerSchema);
if (!choice) {
return completedMessageResponse("当前问题已更新,请刷新后重新作答。", requestId, caseId);
}
const choice = focus ? parseAgentChoiceCopy(focus.expectedAnswerSchema) : null;
if (focus && choice) {
let classified = null;
try {
classified = await classifyRectificationTurnIntent(selectedModel, {
@@ -379,6 +380,81 @@ export async function POST(request: Request) {
await transitionV9CaseStatus(accounting, userId, caseId, "paused");
return completedMessageResponse(applied.narration, requestId, caseId);
}
} else {
const decision = decideFromDossier(dossier);
if (decision.nextAction === "ask_candidate_discriminator") {
const refinement = refinementFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
const plan = buildMethodFollowupPlan({
evidence: dossier.evidence,
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
sessionOutcome: "discriminate_candidates",
eventProbes: refinement.discriminating_event_probes,
askedProbeKeys: askedDiscriminatorKeys(
dossier.latestResult?.decisionReceipt,
dossier.evidence,
),
contrastPacket: contrastPacketFromDossier(dossier),
candidatesSeparated: false,
});
const persisted = await persistServerOwnedFocus({
accounting,
userId,
caseId,
activeFocus: focus,
decisionReceipt: dossier.latestResult?.decisionReceipt,
followup: plan.next_followup,
});
const open = openQuestionFromPersistedFocus(persisted);
if (open && persisted.focus) {
let classified = null;
try {
classified = await classifyRectificationTurnIntent(selectedModel, {
focus: persisted.focus,
userMessage: parsed.data.message ?? "",
caseStatus,
signal: request.signal,
});
} catch {
classified = null;
}
if (classified?.intent === "answer_current_focus" && classified.answer_class) {
const optionId = optionIdForAnswerClass(persisted.focus, classified.answer_class);
if (optionId) {
const previous = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
const applied = await applyRectificationChoice(accounting, {
userId,
caseId,
sessionId,
actionId: requestId,
action: CHOICE_ACTION,
focusId: persisted.focus.id,
questionId: persisted.focus.questionId,
probeId: typeof persisted.focus.expectedAnswerSchema.probe_id === "string"
? persisted.focus.expectedAnswerSchema.probe_id
: null,
optionId,
expectedRevision: previous?.revision ?? 0,
userDisplay: parsed.data.message ?? null,
});
return completedMessageResponse(applied.narration, requestId, caseId);
}
}
const narration = "请点选下面的选项作答。点选和一句话回答会按同一规则计入当前区分题。";
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
assistantMessage: narration,
});
return completedMessageResponse(narration, requestId, caseId);
}
const narration = "目前没有可继续区分的问题。当前几个候选构成可信区间,不再泛问已经覆盖过的经历。";
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
assistantMessage: narration,
});
return completedMessageResponse(narration, requestId, caseId);
}
}
} catch (error) {
if (error instanceof RectificationToolServiceError) {