feat(confidence-engine): add completed episode reasoning seam
This commit is contained in:
@@ -31,6 +31,8 @@ import {
|
||||
validateGraphReferences,
|
||||
} from "./utils.js";
|
||||
import { validateFindings, produceFindingInformedSummary } from "./finding-helpers.js";
|
||||
import { prepareCompletedEpisode } from "./episode-preparation.js";
|
||||
import { buildEpisodeAwareGraphPrompt } from "./prompt-builder-episode.js";
|
||||
|
||||
function toValidationErrors(error) {
|
||||
return (
|
||||
@@ -1106,3 +1108,107 @@ async function updateCaseWithDependencies(body, dependencies = {}) {
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Episode-aware reconsideration seam ─────────────────────
|
||||
|
||||
/**
|
||||
* Reason over a completed focused episode and return a parsed GraphUpdateProposal.
|
||||
*
|
||||
* This is the smallest sibling to updateCase that reuses existing provider,
|
||||
* model, and parsing mechanics — only the prompt path differs.
|
||||
*/
|
||||
export async function reconsiderCompletedEpisode(epiParams, deps = {}) {
|
||||
const { situationGraph: epiGraph, targetNodeId, contributions, findings, episode: rawEpisode } = epiParams;
|
||||
|
||||
// Accept prepared episode or produce it deterministically
|
||||
|
||||
let episode;
|
||||
|
||||
if (rawEpisode) {
|
||||
episode = rawEpisode;
|
||||
} else if (epiParams?.turns || epiParams?.eligibleCanonicalFindings) {
|
||||
// Already a prepared episode passed as first positional arg
|
||||
episode = epiParams;
|
||||
} else {
|
||||
episode = prepareCompletedEpisode(epiParams);
|
||||
}
|
||||
|
||||
if (!episode?.turns && !episode?.eligibleCanonicalFindings) {
|
||||
return {
|
||||
success: false,
|
||||
stage: "input_validation",
|
||||
error: "Invalid prepared episode — requires turns or findings",
|
||||
statusCode: 400,
|
||||
};
|
||||
}
|
||||
|
||||
const getModelName = deps.getModelName;
|
||||
const buildEpisodePrompt = deps.buildEpisodePrompt;
|
||||
const parseProposal = deps.parseProposal;
|
||||
const provider = epiParams.provider ?? deps.provider;
|
||||
|
||||
let modelName = null;
|
||||
let rawResponse;
|
||||
const startedAt = Date.now();
|
||||
|
||||
try {
|
||||
modelName = deps.modelName ?? (deps.config ? deps.config.OLLAMA_MODEL : null) ?? getModelName?.() ?? null;
|
||||
|
||||
const promptBuilder = buildEpisodePrompt ?? buildEpisodeAwareGraphPrompt;
|
||||
const prompt = promptBuilder({ episode });
|
||||
|
||||
const prov = provider ?? getProvider();
|
||||
rawResponse = await prov.generateReconstruction(prompt, modelName);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
stage: "provider",
|
||||
error: "Graph update proposal generation failed",
|
||||
providerErrors: [
|
||||
sanitiseErrorMessage(
|
||||
error,
|
||||
"Provider failed to generate graph update proposal from episode",
|
||||
),
|
||||
],
|
||||
diagnostics: {
|
||||
promptVersion: null,
|
||||
modelName,
|
||||
responseDurationMs: Date.now() - startedAt,
|
||||
normalisationsApplied: [],
|
||||
},
|
||||
statusCode: 502,
|
||||
};
|
||||
}
|
||||
|
||||
const parser = parseProposal ?? parseGraphUpdateProposal;
|
||||
const parsedProposal = parser(rawResponse);
|
||||
const responseDurationMs = Date.now() - startedAt;
|
||||
|
||||
if (!parsedProposal.success) {
|
||||
return {
|
||||
success: false,
|
||||
stage: "proposal_validation",
|
||||
error: "Invalid graph update proposal",
|
||||
proposalErrors: parsedProposal.errors,
|
||||
diagnostics: {
|
||||
promptVersion: null,
|
||||
modelName,
|
||||
responseDurationMs,
|
||||
normalisationsApplied: parsedProposal.normalisationsApplied,
|
||||
},
|
||||
statusCode: 502,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
stage: "proposal_ready",
|
||||
proposal: parsedProposal.proposal,
|
||||
diagnostics: {
|
||||
promptVersion: null,
|
||||
modelName,
|
||||
responseDurationMs,
|
||||
normalisationsApplied: parsedProposal.normalisationsApplied,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user