Files
confidence-engine/app/api/focused-investigation/deconstruct/route.js
T

164 lines
5.3 KiB
JavaScript

import { getProvider, getProviderModelName } from "@/lib/llm/provider";
import {
buildFocusedDeconstructPrompt,
focusedDeconstructJsonSchema,
validateFocusedDeconstructSchema,
} from "@/lib/graph/focused-investigation";
import { withAuthenticatedApi } from "@/lib/supabase/api-auth.js";
async function post(request) {
let targetNodeId = null;
let startedAt = null;
try {
const body = await request.json();
targetNodeId = body.targetNodeId ?? null;
if (!body.targetNodeId || typeof body.targetNodeId !== "string") {
return Response.json(
{ error: "Request must include a 'targetNodeId' string field" },
{ status: 400 },
);
}
if (!body.targetLabel || typeof body.targetLabel !== "string") {
return Response.json(
{ error: "Request must include a 'targetLabel' string field" },
{ status: 400 },
);
}
if (!body.targetDescription || typeof body.targetDescription !== "string") {
return Response.json(
{ error: "Request must include a 'targetDescription' string field" },
{ status: 400 },
);
}
if (!body.centralStatement || typeof body.centralStatement !== "string") {
return Response.json(
{ error: "Request must include a 'centralStatement' string field" },
{ status: 400 },
);
}
if (!body.question || typeof body.question !== "string") {
return Response.json(
{ error: "Request must include a 'question' string field" },
{ status: 400 },
);
}
if (!body.answer || typeof body.answer !== "string") {
return Response.json(
{ error: "Request must include an 'answer' string field" },
{ status: 400 },
);
}
const prompt = buildFocusedDeconstructPrompt({
targetLabel: body.targetLabel,
targetDescription: body.targetDescription,
centralStatement: body.centralStatement,
question: body.question,
answer: body.answer,
});
const provider = getProvider();
const modelName = getProviderModelName();
startedAt = Date.now();
console.info("[api/focused-investigation/deconstruct] start", {
targetNodeId,
modelName,
providerMode: process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER ?? "ollama",
startedAt,
});
const wrapper = await provider.generateReconstruction(
prompt,
modelName,
focusedDeconstructJsonSchema,
);
const elapsedMs = Date.now() - startedAt;
// Unwrap the semantic deconstruction from the provider envelope.
const deconstruction = wrapper.response;
console.info("[api/focused-investigation/deconstruct] provider success", {
targetNodeId,
elapsedMs,
providerApiPath: wrapper.providerApiPath ?? null,
responsePresent: Boolean(deconstruction),
responseKeys: deconstruction && typeof deconstruction === "object"
? Object.keys(deconstruction)
: [],
});
// Validate schema (required fields present, no graph-mutation fields)
const validationErrors = validateFocusedDeconstructSchema(deconstruction);
console.info("[api/focused-investigation/deconstruct] validation", {
targetNodeId,
schemaValid: validationErrors.length === 0,
responseKeys: deconstruction && typeof deconstruction === "object"
? Object.keys(deconstruction)
: [],
validationErrors,
});
if (validationErrors.length > 0) {
console.info("[api/focused-investigation/deconstruct] end", {
targetNodeId,
status: 502,
elapsedMs,
});
return Response.json(
{
success: false,
error: "Focused deconstruction result did not match expected schema",
validationErrors,
targetNodeId: body.targetNodeId,
elapsedMs,
},
{ status: 502 },
);
}
const response = Response.json({
success: true,
targetNodeId: body.targetNodeId,
observations: deconstruction.observations,
uncertainties: deconstruction.uncertainties,
assumptions: deconstruction.assumptions,
relationships: deconstruction.relationships,
possibleFollowUpQuestions: deconstruction.possibleFollowUpQuestions,
elapsedMs,
});
console.info("[api/focused-investigation/deconstruct] end", {
targetNodeId,
status: 200,
elapsedMs,
});
return response;
} catch (e) {
const elapsedMs = startedAt == null ? null : Date.now() - startedAt;
console.error("[api/focused-investigation/deconstruct] provider failure", {
targetNodeId,
elapsedMs,
errorName: e?.name ?? "Error",
errorMessage: e?.message ?? "Unknown server error",
statusCode: e?.statusCode ?? e?.status ?? null,
providerApiPath: e?.providerApiPath ?? null,
errorCode: e?.code ?? null,
errorParam: e?.param ?? null,
});
console.info("[api/focused-investigation/deconstruct] end", {
targetNodeId,
status: e?.code === "PROVIDER_UNAVAILABLE" ? 503 : 500,
elapsedMs,
});
if (e?.code === "PROVIDER_UNAVAILABLE") {
return Response.json(
{ error: "Reasoning service is temporarily unavailable." },
{ status: 503 },
);
}
return Response.json(
{ error: "Reasoning request could not be completed." },
{ status: 500 },
);
}
}
export const POST = withAuthenticatedApi(post);