fix(confidence-engine): constrain understanding to supported evidence
This commit is contained in:
@@ -52,31 +52,66 @@ export function filterEligibleFindings(findings) {
|
||||
* The prompt instructs the model to produce one coherent Current Understanding narrative
|
||||
* from the provided inputs, without append semantics.
|
||||
*/
|
||||
export function buildSynthesisPrompt(situationGraph, findings) {
|
||||
// Build structured graph representation for the prompt
|
||||
const graphInfo = {
|
||||
centralStatement: situationGraph.centralStatement ?? "",
|
||||
nodes: (situationGraph.nodes ?? []).map((n) => ({
|
||||
id: n.id,
|
||||
proposition: n.proposition ?? "",
|
||||
description: n.description ?? "",
|
||||
status: n.status ?? null,
|
||||
confidence: n.confidence ?? null,
|
||||
})),
|
||||
edges: (situationGraph.edges ?? []).map((e) => ({
|
||||
from: e.from ?? null,
|
||||
to: e.to ?? null,
|
||||
type: e.type ?? "",
|
||||
context: e.context ?? "",
|
||||
})),
|
||||
};
|
||||
|
||||
// Build a structured representation of eligible Findings for the prompt
|
||||
const KNOWN_SUPPORTED_STATUSES = new Set(["known", "supported"]);
|
||||
|
||||
function safeDesc(value) {
|
||||
return (value && typeof value === "string") ? value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evidence-authority projection for CU synthesis.
|
||||
*
|
||||
* Includes only:
|
||||
* - centralStatement (framing context — not independent evidence)
|
||||
* - known nodes (provider-active graph evidence)
|
||||
* - supported nodes (provider-active graph evidence)
|
||||
* - eligible Findings (from the findings parameter)
|
||||
*
|
||||
* Explicitly excludes from provider-active synthesis input:
|
||||
* provisional nodes, unknown nodes, resolved nodes, edges,
|
||||
* activeUnknownNodeId, resolvedNodeIds, currentSummary,
|
||||
* reasoningState, confidence/control metadata, dependency/control fields.
|
||||
*/
|
||||
function buildGraphEvidenceProjection(graphInfo) {
|
||||
const knownNodes = (graphInfo.nodes ?? [])
|
||||
.filter((n) => KNOWN_SUPPORTED_STATUSES.has(n.status))
|
||||
.map((n) => ({
|
||||
kind: n.kind ?? null,
|
||||
label: safeDesc(n.label),
|
||||
description: safeDesc(n.description),
|
||||
value: n.value ?? null,
|
||||
unit: n.unit ?? null,
|
||||
status: n.status ?? null,
|
||||
}));
|
||||
|
||||
const supportedNodes = (graphInfo.nodes ?? [])
|
||||
.filter((n) => KNOWN_SUPPORTED_STATUSES.has(n.status))
|
||||
.filter((n) => n.status !== "known")
|
||||
.map((n) => ({
|
||||
kind: n.kind ?? null,
|
||||
label: safeDesc(n.label),
|
||||
description: safeDesc(n.description),
|
||||
value: n.value ?? null,
|
||||
unit: n.unit ?? null,
|
||||
status: n.status ?? null,
|
||||
}));
|
||||
|
||||
const centralStatement = safeDesc(graphInfo.centralStatement) || "";
|
||||
|
||||
return { centralStatement, knownNodes, supportedNodes };
|
||||
}
|
||||
|
||||
export function buildSynthesisPrompt(situationGraph, findings) {
|
||||
// Structured evidence projection for the model
|
||||
const evidence = buildGraphEvidenceProjection(situationGraph);
|
||||
|
||||
// Eligible Finding sections (preserve existing agree vs null distinction)
|
||||
const findingsSections = [];
|
||||
|
||||
if (findings.length > 0) {
|
||||
const agreed = findings.filter((f) => f.userDisposition === "agree");
|
||||
const provisional = findings.filter(
|
||||
const working = findings.filter(
|
||||
(f) => f.userDisposition === null
|
||||
);
|
||||
|
||||
@@ -90,10 +125,10 @@ export function buildSynthesisPrompt(situationGraph, findings) {
|
||||
});
|
||||
}
|
||||
|
||||
if (provisional.length > 0) {
|
||||
if (working.length > 0) {
|
||||
findingsSections.push({
|
||||
label: "Provisional Findings (working interpretation)",
|
||||
items: provisional.map((f) => ({
|
||||
label: "Working Premises",
|
||||
items: working.map((f) => ({
|
||||
proposition: f.proposition,
|
||||
id: f.id ?? null,
|
||||
})),
|
||||
@@ -101,14 +136,26 @@ export function buildSynthesisPrompt(situationGraph, findings) {
|
||||
}
|
||||
}
|
||||
|
||||
// Human-readable node and edge representations for the prompt
|
||||
const nodesSection = graphInfo.nodes.length > 0 ? `\nNodes:\n${graphInfo.nodes.map((n) => ` Node(${n.id}): ${n.proposition}${n.description ? ` — ${n.description}` : ""}${n.status ? ` [${n.status}]` : ""}`).join("\n")}` : "";
|
||||
const edgesSection = graphInfo.edges.length > 0 ? `\nEdges:\n${graphInfo.edges.map((e) => ` Edge(${e.from} → ${e.to}, type=${e.type}): ${e.context || "(no context)"}`).join("\n")}` : "";
|
||||
// Human-readable node display for the prompt
|
||||
function formatNodeSection(title, nodes) {
|
||||
if (!nodes || nodes.length === 0) return "";
|
||||
const items = nodes.map(
|
||||
(n) => ` ${title}: kind=${n.kind}, label="${n.label}", value=${n.value ? n.value + (n.unit ? " (" + n.unit + ")" : "") : null} — ${n.description ?? "(no description)"} [${n.status}]`
|
||||
);
|
||||
return "\n" + items.join("\n");
|
||||
}
|
||||
|
||||
const knownSection = formatNodeSection("Known", evidence.knownNodes);
|
||||
const supportedItem = formatNodeSection("Supported", evidence.supportedNodes);
|
||||
|
||||
const prompt = `You are producing a Current Understanding narrative from investigation evidence.
|
||||
|
||||
Canonical Situation Graph:
|
||||
${JSON.stringify(graphInfo, null, 2)}${nodesSection}${edgesSection}
|
||||
Situation Framing:
|
||||
${evidence.centralStatement ? " Central Statement: " + evidence.centralStatement : "(none)"}
|
||||
|
||||
Provider-Active Evidence:
|
||||
Known Facts:${knownSection}
|
||||
Supported Inferences:${supportedItem}
|
||||
|
||||
Eligible Findings:
|
||||
${findingsSections.length > 0
|
||||
@@ -116,14 +163,15 @@ ${findingsSections.length > 0
|
||||
: "(none)"}
|
||||
|
||||
Rules for this synthesis:
|
||||
1. Produce exactly ONE coherent narrative paragraph (or short multi-sentence paragraph) that represents the Current Understanding of the situation.
|
||||
2. Synthesize all provided evidence into a unified understanding — do not list or append findings. The result should read as a natural summary, not a bullet list.
|
||||
3. This is a FRESH synthesis from the complete set of inputs above. Do NOT treat any previous Current Understanding as input or authority. Do NOT append to prior summaries.
|
||||
4. Use only information present in the Situation context and Eligible Findings above.
|
||||
5. If no eligible Findings are provided, synthesize from the Situation context alone.
|
||||
6. Return ONLY a JSON object with this exact structure:
|
||||
1. Current Understanding describes only established or supported understanding from the evidence supplied here.
|
||||
2. Do not introduce or describe open questions, unresolved uncertainties, assumptions, provisional hypotheses, speculative explanations, or future investigation needs.
|
||||
3. Produce exactly ONE coherent narrative paragraph (or short multi-sentence paragraph) that represents the Current Understanding of the situation.
|
||||
4. Synthesize all provided evidence into a unified understanding — do not list or append findings. The result should read as a natural summary, not a bullet list.
|
||||
5. This is a FRESH synthesis from the complete set of inputs above. Do NOT treat any previous Current Understanding as input or authority. Do NOT append to prior summaries.
|
||||
6. Use only information present in the evidence above. The centralStatement is framing context, not independent evidence.
|
||||
7. Return ONLY a JSON object with this exact structure:
|
||||
{"currentUnderstanding": "your narrative here"}
|
||||
7. The currentUnderstanding value must be a non-empty string.
|
||||
8. The currentUnderstanding value must be a non-empty string.
|
||||
|
||||
Return ONLY the JSON object. No markdown, no explanation, no preamble.`;
|
||||
|
||||
@@ -204,7 +252,7 @@ export async function synthesizeCurrentUnderstanding(
|
||||
// 2. Eligibility normalization (domain seam responsibility)
|
||||
const eligibleFindings = filterEligibleFindings(allFindings);
|
||||
|
||||
// 3. Build synthesis prompt (uses full canonical graph, not just centralStatement)
|
||||
// 3. Build synthesis prompt (uses evidence-authority projection, not raw graph)
|
||||
const prompt = buildSynthesisPrompt(situationGraph, eligibleFindings);
|
||||
|
||||
// 4. Resolve provider — DI fallback to configured default
|
||||
|
||||
Reference in New Issue
Block a user