fix(confidence-engine): retain successful provider path

This commit is contained in:
2026-09-05 17:12:31 +01:00
parent dabd9e2245
commit 46b9bd8b03
5 changed files with 34 additions and 3 deletions
+5
View File
@@ -73,6 +73,11 @@
- The detector fix remains unverified live; a subsequent HTTP 500 left the actually attempted provider path unobservable. - The detector fix remains unverified live; a subsequent HTTP 500 left the actually attempted provider path unobservable.
- Case-start failure diagnostics now preserve `providerApiPath` only when the provider reports an endpoint actually attempted during that request. The next step remains one production call. - Case-start failure diagnostics now preserve `providerApiPath` only when the provider reports an endpoint actually attempted during that request. The next step remains one production call.
## Successful provider-path observability
- The 502 live run at `dabd9e2` was PATH NOT OBSERVABLE ON FAILURE: provider path survived provider errors but not successful-provider/later-validation failures.
- Successful provider results now retain their factual attempted path through later reconstruction-validation failure diagnostics. This corrects only that observability seam; the next unknown remains one real `/api/cases/start` call establishing `/api/chat` versus `/api/generate`.
## Current product architecture ## Current product architecture
Three distinct routes, not a single page: Three distinct routes, not a single page:
+16 -1
View File
@@ -72,11 +72,23 @@ export async function analyseScenario(scenario, opts = {}) {
// ── Call provider ────────────────────────────────── // ── Call provider ──────────────────────────────────
const provider = getProvider(); const provider = getProvider();
let rawResponse; let rawResponse;
let providerApiPath;
try { try {
rawResponse = await provider.generateReconstruction( const providerResult = await provider.generateReconstruction(
promptObj.prompt, promptObj.prompt,
OLLAMA_MODEL, OLLAMA_MODEL,
); );
if (
providerResult &&
typeof providerResult === "object" &&
"response" in providerResult &&
"providerApiPath" in providerResult
) {
rawResponse = providerResult.response;
providerApiPath = providerResult.providerApiPath;
} else {
rawResponse = providerResult;
}
} catch (e) { } catch (e) {
return buildErrorResponse( return buildErrorResponse(
e.message || "Provider error during analysis", e.message || "Provider error during analysis",
@@ -137,6 +149,7 @@ export async function analyseScenario(scenario, opts = {}) {
duration, duration,
promptVersion, promptVersion,
compatibility, compatibility,
providerApiPath,
); );
} }
@@ -219,6 +232,7 @@ function buildPartialResult(
duration, duration,
version, version,
compatibility, compatibility,
providerApiPath,
) { ) {
let errors = []; let errors = [];
const validationIssues = error?.issues ?? []; const validationIssues = error?.issues ?? [];
@@ -245,6 +259,7 @@ function buildPartialResult(
nextQuestion: undefined, nextQuestion: undefined,
errors, errors,
validationIssues, validationIssues,
providerApiPath,
...buildCompatibilityDiagnostics(compatibility), ...buildCompatibilityDiagnostics(compatibility),
}; };
} }
+4 -1
View File
@@ -247,7 +247,10 @@ class OllamaLlmProvider {
// Step 5: Parse and return // Step 5: Parse and return
// ================================================================ // ================================================================
try { try {
return recoverJson(rawResponse); return {
response: recoverJson(rawResponse),
providerApiPath: apiUsed,
};
} catch (e) { } catch (e) {
if (e instanceof SyntaxError) { if (e instanceof SyntaxError) {
const error = new Error( const error = new Error(
+5 -1
View File
@@ -14,7 +14,7 @@ describe("OllamaLlmProvider chat capability detection", () => {
try { try {
const { getProvider } = await import("@/lib/llm/provider.js"); const { getProvider } = await import("@/lib/llm/provider.js");
await getProvider().generateReconstruction("prompt", "configured-model"); const result = await getProvider().generateReconstruction("prompt", "configured-model");
expect(JSON.parse(fetchSpy.mock.calls[0][1].body)).toMatchObject({ expect(JSON.parse(fetchSpy.mock.calls[0][1].body)).toMatchObject({
model: "configured-model", model: "configured-model",
@@ -23,6 +23,10 @@ describe("OllamaLlmProvider chat capability detection", () => {
expect(fetchSpy.mock.calls[0][0]).toBe("http://ollama.test/api/chat"); expect(fetchSpy.mock.calls[0][0]).toBe("http://ollama.test/api/chat");
expect(fetchSpy.mock.calls[1][0]).toBe("http://ollama.test/api/chat"); expect(fetchSpy.mock.calls[1][0]).toBe("http://ollama.test/api/chat");
expect(fetchSpy.mock.calls[1][0]).not.toContain("/api/generate"); expect(fetchSpy.mock.calls[1][0]).not.toContain("/api/generate");
expect(result).toMatchObject({
response: {},
providerApiPath: "/api/chat",
});
} finally { } finally {
vi.unstubAllGlobals(); vi.unstubAllGlobals();
if (originalBaseUrl === undefined) delete process.env.OLLAMA_BASE_URL; if (originalBaseUrl === undefined) delete process.env.OLLAMA_BASE_URL;
@@ -210,6 +210,8 @@ describe("analyseScenario compatibility", () => {
it("preserves nested validation issues and complete raw output on reconstruction failure", async () => { it("preserves nested validation issues and complete raw output on reconstruction failure", async () => {
mockGenerateReconstruction.mockResolvedValue({ mockGenerateReconstruction.mockResolvedValue({
providerApiPath: "/api/chat",
response: {
inputClassification: { inputClassification: {
primaryType: "unexplained_change", primaryType: "unexplained_change",
secondaryTypes: [], secondaryTypes: [],
@@ -239,6 +241,7 @@ describe("analyseScenario compatibility", () => {
reason: "reason", reason: "reason",
expectedInformationValue: "high", expectedInformationValue: "high",
}, },
},
}); });
const { analyseScenario } = await import("@/lib/analysis.js"); const { analyseScenario } = await import("@/lib/analysis.js");
@@ -253,6 +256,7 @@ describe("analyseScenario compatibility", () => {
code: "invalid_type", code: "invalid_type",
message: "Required", message: "Required",
})); }));
expect(result.providerApiPath).toBe("/api/chat");
expect(result.errors).toContain("reconstruction: Required"); expect(result.errors).toContain("reconstruction: Required");
}); });