fix: reselect after reasoning pattern filtering

This commit is contained in:
2026-08-03 12:39:01 +01:00
parent c27320984c
commit 34c25fcb43
4 changed files with 317 additions and 46 deletions
+170 -42
View File
@@ -2038,6 +2038,137 @@ function selectDecompositionChildCandidate(
};
}
function buildSelectedQuestionResult({
updatedSituationGraph,
deterministicSelection,
}) {
const selectedNode =
deterministicSelection?.status === "selected"
? findNodeById(updatedSituationGraph, deterministicSelection.nodeId)
: null;
const formulatedQuestion = selectedNode
? formulateQuestion({
node: selectedNode,
graph: updatedSituationGraph,
context: { selectionState: deterministicSelection },
})
: null;
const selectedQuestion =
deterministicSelection?.status === "ambiguous"
? {
id: "q_tie_resolution",
...formulateTieResolutionQuestion({ graph: updatedSituationGraph }),
nodeId: null,
tiedCandidateIds: deterministicSelection.tiedCandidateIds,
}
: deterministicSelection?.status === "selected" && formulatedQuestion
? {
nodeId: deterministicSelection.nodeId,
question:
formulatedQuestion.question || deterministicSelection.question,
reason: formulatedQuestion.reason,
strategy: formulatedQuestion.strategy,
investigationStrategy: formulatedQuestion.investigationStrategy,
reasoningPattern: formulatedQuestion.reasoningPattern,
reasoningPatternReason: formulatedQuestion.reasoningPatternReason,
questionFamily: formulatedQuestion.questionFamily,
allowedQuestionFamilies: formulatedQuestion.allowedQuestionFamilies,
rejectedQuestionFamilies:
formulatedQuestion.rejectedQuestionFamilies,
selectedQuestionTemplate:
formulatedQuestion.selectedQuestionTemplate,
questionComplexity: formulatedQuestion.questionComplexity,
plainLanguageNormalisations:
formulatedQuestion.plainLanguageNormalisations,
}
: null;
return {
selectedNode,
formulatedQuestion,
selectedQuestion,
};
}
function resolveAmbiguousGraphBackedSelection({
graphSnapshot,
updatedSituationGraph,
deterministicSelection,
}) {
const orderedCandidateIds =
deterministicSelection?.displayOrder ||
deterministicSelection?.tiedCandidateIds ||
[];
for (const candidateNodeId of orderedCandidateIds) {
if (
!isSelectableUnresolvedUnknown(updatedSituationGraph, candidateNodeId)
) {
continue;
}
const candidateResult = runDeterministicDecomposition({
graphSnapshot,
proposalSnapshot: {
addedNodes: [],
updatedNodes: [],
addedEdges: [],
removedEdgeIds: [],
resolvedUnknownNodeIds: [],
affectedNodeIds: [],
selectedQuestion: null,
},
updatedSituationGraph: cloneJsonSafe(updatedSituationGraph),
reasoningResolution: { reasoningStateOverride: {} },
deterministicSelection: {
status: "selected",
nodeId: candidateNodeId,
reason:
"Selected this tied candidate for deterministic decomposition-based reselection.",
},
});
if (!candidateResult.success) {
continue;
}
const nextGraph = candidateResult.updatedSituationGraph;
nextGraph.reasoningState = buildReasoningState(nextGraph);
const nextSelection = isSelectableUnresolvedUnknown(
nextGraph,
candidateResult.selectedChildNodeId,
)
? {
status: "selected",
nodeId: candidateResult.selectedChildNodeId,
reason:
"Selected the preserved decomposition child after resolving an initial tie.",
}
: candidateResult.deterministicSelection;
const questionResult = buildSelectedQuestionResult({
updatedSituationGraph: nextGraph,
deterministicSelection: nextSelection,
});
if (questionResult.selectedQuestion?.question) {
nextGraph.activeUnknownNodeId =
nextSelection?.status === "selected" ? nextSelection.nodeId : null;
nextGraph.currentSummary = describeGraph(nextGraph);
return {
...candidateResult,
updatedSituationGraph: nextGraph,
deterministicSelection: nextSelection,
...questionResult,
};
}
}
return null;
}
export function determineGraphBackedQuestion({ situationGraph }) {
const graphSnapshot = cloneJsonSafe(situationGraph);
let updatedSituationGraph = cloneJsonSafe(situationGraph);
@@ -2090,52 +2221,48 @@ export function determineGraphBackedQuestion({ situationGraph }) {
: null;
updatedSituationGraph.currentSummary = describeGraph(updatedSituationGraph);
const selectedNode =
deterministicSelection?.status === "selected"
? findNodeById(updatedSituationGraph, deterministicSelection.nodeId)
: null;
const formulatedQuestion = selectedNode
? formulateQuestion({
node: selectedNode,
graph: updatedSituationGraph,
context: { selectionState: deterministicSelection },
})
: null;
let questionResult = buildSelectedQuestionResult({
updatedSituationGraph,
deterministicSelection,
});
if (
deterministicSelection?.status === "ambiguous" &&
!questionResult.selectedQuestion?.question
) {
const reselectionResult = resolveAmbiguousGraphBackedSelection({
graphSnapshot,
updatedSituationGraph,
deterministicSelection,
});
if (reselectionResult) {
updatedSituationGraph = reselectionResult.updatedSituationGraph;
deterministicSelection = reselectionResult.deterministicSelection;
questionResult = reselectionResult;
}
}
const noQuestionReason = questionResult.selectedQuestion?.question
? null
: deterministicSelection?.status === "ambiguous"
? questionResult.selectedQuestion?.questionSuppressedReason ||
questionResult.selectedQuestion?.reason ||
"Eligible unresolved candidates remain tied after initial graph-backed selection."
: (updatedSituationGraph.nodes || []).some(
(node) =>
node.kind === "unknown" &&
!["resolved", "contradicted"].includes(node.status) &&
!(updatedSituationGraph.resolvedNodeIds || []).includes(node.id),
)
? "Compatible unresolved candidates remain, but none produced a valid graph-backed question."
: "No unresolved unknown candidates remain after initial graph construction.";
return {
success: true,
updatedSituationGraph,
deterministicSelection,
selectedQuestion:
deterministicSelection?.status === "ambiguous"
? {
id: "q_tie_resolution",
...formulateTieResolutionQuestion({ graph: updatedSituationGraph }),
nodeId: null,
tiedCandidateIds: deterministicSelection.tiedCandidateIds,
}
: deterministicSelection?.status === "selected" && formulatedQuestion
? {
nodeId: deterministicSelection.nodeId,
question:
formulatedQuestion.question || deterministicSelection.question,
reason: formulatedQuestion.reason,
strategy: formulatedQuestion.strategy,
investigationStrategy: formulatedQuestion.investigationStrategy,
reasoningPattern: formulatedQuestion.reasoningPattern,
reasoningPatternReason: formulatedQuestion.reasoningPatternReason,
questionFamily: formulatedQuestion.questionFamily,
allowedQuestionFamilies:
formulatedQuestion.allowedQuestionFamilies,
rejectedQuestionFamilies:
formulatedQuestion.rejectedQuestionFamilies,
selectedQuestionTemplate:
formulatedQuestion.selectedQuestionTemplate,
questionComplexity: formulatedQuestion.questionComplexity,
plainLanguageNormalisations:
formulatedQuestion.plainLanguageNormalisations,
}
: null,
selectedQuestion: questionResult.selectedQuestion,
atomicityAssessment: decompositionResult.atomicityAssessment,
answerabilityAssessment: decompositionResult.answerabilityAssessment,
independentlyAnswerable:
@@ -2160,7 +2287,8 @@ export function determineGraphBackedQuestion({ situationGraph }) {
selectedUnknownBefore: decompositionResult.selectedUnknownBefore,
selectedUnknownAfter: deterministicSelection?.nodeId ?? null,
questionComplexityAssessment:
formulatedQuestion?.questionComplexity ?? null,
questionResult.formulatedQuestion?.questionComplexity ?? null,
noQuestionReason,
};
}
+33 -4
View File
@@ -71,6 +71,7 @@ function buildDiagnostics({
compatibilityFailures,
replacementActions,
graphReasoningIntegrity,
noQuestionReason,
}) {
return {
promptVersion: analysis?.promptVersion ?? null,
@@ -112,6 +113,28 @@ function buildDiagnostics({
compatibilityFailures: compatibilityFailures ?? [],
replacementActions: replacementActions ?? [],
graphReasoningIntegrity: graphReasoningIntegrity ?? null,
noQuestionReason: noQuestionReason ?? null,
};
}
function fallbackStartCaseReasoningPatternValidation(
selectedQuestion,
existingValidation,
) {
if (existingValidation) {
return existingValidation;
}
if (!selectedQuestion?.reasoningPattern) {
return null;
}
return {
activePattern: selectedQuestion.reasoningPattern,
valid: Boolean(selectedQuestion.question),
reason: selectedQuestion.question
? "Initial graph-backed selection produced a reasoning-pattern-compatible question."
: "Initial graph-backed selection did not produce a valid question for the inferred reasoning pattern.",
};
}
@@ -443,8 +466,10 @@ export async function startCase(body) {
reasoningPatternReason:
initialQuestionResult.selectedQuestion?.reasoningPatternReason ??
null,
reasoningPatternValidation:
initialQuestionResult.reasoningPatternValidation ?? null,
reasoningPatternValidation: fallbackStartCaseReasoningPatternValidation(
initialQuestionResult.selectedQuestion,
initialQuestionResult.reasoningPatternValidation,
),
patternCompatibleNodeCount:
initialQuestionResult.patternCompatibleNodeCount ?? 0,
incompatibleNodeIds: initialQuestionResult.incompatibleNodeIds ?? [],
@@ -453,6 +478,7 @@ export async function startCase(body) {
replacementActions: initialQuestionResult.replacementActions ?? [],
graphReasoningIntegrity:
initialQuestionResult.graphReasoningIntegrity ?? null,
noQuestionReason: initialQuestionResult.noQuestionReason ?? null,
}),
validationErrors: graphReferenceValidation.errors,
statusCode: 500,
@@ -509,8 +535,10 @@ export async function startCase(body) {
null,
reasoningPatternReason:
initialQuestionResult.selectedQuestion?.reasoningPatternReason ?? null,
reasoningPatternValidation:
initialQuestionResult.reasoningPatternValidation ?? null,
reasoningPatternValidation: fallbackStartCaseReasoningPatternValidation(
initialQuestionResult.selectedQuestion,
initialQuestionResult.reasoningPatternValidation,
),
patternCompatibleNodeCount:
initialQuestionResult.patternCompatibleNodeCount ?? 0,
incompatibleNodeIds: initialQuestionResult.incompatibleNodeIds ?? [],
@@ -518,6 +546,7 @@ export async function startCase(body) {
replacementActions: initialQuestionResult.replacementActions ?? [],
graphReasoningIntegrity:
initialQuestionResult.graphReasoningIntegrity ?? null,
noQuestionReason: initialQuestionResult.noQuestionReason ?? null,
}),
};
}