experiment(confidence-engine): isolate reconstruction observation
This commit is contained in:
@@ -145,6 +145,12 @@
|
|||||||
- OpenAI strict transport projection now derives structure from canonical Zod-generated JSON Schema and canonical input optionality from the Zod contract, including `.optional().default(...)`. Canonically omittable fields are required-but-nullable for transport; null placeholders are omitted only for those fields before canonical Zod validation, while genuinely required fields remain non-nullable.
|
- OpenAI strict transport projection now derives structure from canonical Zod-generated JSON Schema and canonical input optionality from the Zod contract, including `.optional().default(...)`. Canonically omittable fields are required-but-nullable for transport; null placeholders are omitted only for those fields before canonical Zod validation, while genuinely required fields remain non-nullable.
|
||||||
- `reconstructionV2Schema` and Ollama behavior remain unchanged. `START_CASE_EXPERIMENT_PROVIDER=openai` is experiment-only selection; default behavior remains Ollama/Qwen. Deterministic closeout passed with zero live calls; next boundary is exactly one live GPT-5.6 Terra reconstruction.
|
- `reconstructionV2Schema` and Ollama behavior remain unchanged. `START_CASE_EXPERIMENT_PROVIDER=openai` is experiment-only selection; default behavior remains Ollama/Qwen. Deterministic closeout passed with zero live calls; next boundary is exactly one live GPT-5.6 Terra reconstruction.
|
||||||
|
|
||||||
|
## Reconstruction-only OpenAI observation seam
|
||||||
|
|
||||||
|
- The first Terra observation was blocked before execution because normal `startCase()` would continue into Ollama-backed question generation. No Terra inference occurred.
|
||||||
|
- An experiment-only reconstruction-only seam now returns after initial reconstruction and graph construction, before `determineGraphBackedQuestion`. Normal production/default `startCase()` behavior remains unchanged; explicit OpenAI helper selection guarantees one selected reconstruction provider call and zero downstream question-generation calls.
|
||||||
|
- Zero live calls occurred while implementing and verifying this seam. Next boundary: exactly one live Terra reconstruction with no retry.
|
||||||
|
|
||||||
## Current product architecture
|
## Current product architecture
|
||||||
|
|
||||||
Three distinct routes, not a single page:
|
Three distinct routes, not a single page:
|
||||||
|
|||||||
@@ -412,6 +412,26 @@ export async function startCase(body, dependencies = {}) {
|
|||||||
const graphReferenceValidation = validateGraphReferences(
|
const graphReferenceValidation = validateGraphReferences(
|
||||||
initialSituationGraph,
|
initialSituationGraph,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (dependencies.reconstructionOnly) {
|
||||||
|
return {
|
||||||
|
success: graphReferenceValidation.valid,
|
||||||
|
summary: analysis.reconstruction?.summary ?? null,
|
||||||
|
reconstruction: analysis.reconstruction,
|
||||||
|
situationGraph: initialSituationGraph,
|
||||||
|
selectedQuestion: null,
|
||||||
|
diagnostics: buildDiagnostics({
|
||||||
|
analysis,
|
||||||
|
graph: initialSituationGraph,
|
||||||
|
graphReferenceValidation,
|
||||||
|
}),
|
||||||
|
validationErrors: graphReferenceValidation.valid
|
||||||
|
? undefined
|
||||||
|
: graphReferenceValidation.errors,
|
||||||
|
statusCode: graphReferenceValidation.valid ? 200 : 500,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const provider = dependencies.provider ?? getProvider();
|
const provider = dependencies.provider ?? getProvider();
|
||||||
const modelName = dependencies.modelName ?? analysis?.modelName ?? null;
|
const modelName = dependencies.modelName ?? analysis?.modelName ?? null;
|
||||||
const initialQuestionResult = await determineGraphBackedQuestion({
|
const initialQuestionResult = await determineGraphBackedQuestion({
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ async function runStartCaseExperiment(scenarioInput, experimentInstruction, opti
|
|||||||
let startCase = options.startCase;
|
let startCase = options.startCase;
|
||||||
let reconstructionProvider = options.reconstructionProvider;
|
let reconstructionProvider = options.reconstructionProvider;
|
||||||
let reconstructionModelName = options.reconstructionModelName;
|
let reconstructionModelName = options.reconstructionModelName;
|
||||||
|
let reconstructionOnly = options.reconstructionOnly;
|
||||||
const isMock = process.env.START_CASE_EXPERIMENT_HELPER_MOCK === "1";
|
const isMock = process.env.START_CASE_EXPERIMENT_HELPER_MOCK === "1";
|
||||||
|
|
||||||
if (process.env.START_CASE_EXPERIMENT_PROVIDER === "openai") {
|
if (process.env.START_CASE_EXPERIMENT_PROVIDER === "openai") {
|
||||||
@@ -80,6 +81,7 @@ async function runStartCaseExperiment(scenarioInput, experimentInstruction, opti
|
|||||||
fetchImpl: fetch,
|
fetchImpl: fetch,
|
||||||
});
|
});
|
||||||
reconstructionModelName = "gpt-5.6-terra";
|
reconstructionModelName = "gpt-5.6-terra";
|
||||||
|
reconstructionOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!startCase && isMock) {
|
if (!startCase && isMock) {
|
||||||
@@ -120,6 +122,7 @@ async function runStartCaseExperiment(scenarioInput, experimentInstruction, opti
|
|||||||
result = await startCase(scenarioInput, {
|
result = await startCase(scenarioInput, {
|
||||||
reconstructionProvider,
|
reconstructionProvider,
|
||||||
reconstructionModelName,
|
reconstructionModelName,
|
||||||
|
reconstructionOnly,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up experiment env var after execution regardless of outcome
|
// Clean up experiment env var after execution regardless of outcome
|
||||||
@@ -141,6 +144,7 @@ async function runStartCaseExperiment(scenarioInput, experimentInstruction, opti
|
|||||||
return {
|
return {
|
||||||
success: result.success,
|
success: result.success,
|
||||||
summary: result.summary ?? null,
|
summary: result.summary ?? null,
|
||||||
|
reconstruction: result.reconstruction ?? null,
|
||||||
situationGraphNodeCount: result.situationGraph?.nodes?.length ?? 0,
|
situationGraphNodeCount: result.situationGraph?.nodes?.length ?? 0,
|
||||||
situationGraphEdgeCount: result.situationGraph?.edges?.length ?? 0,
|
situationGraphEdgeCount: result.situationGraph?.edges?.length ?? 0,
|
||||||
selectedQuestion: result.selectedQuestion?.question ?? null,
|
selectedQuestion: result.selectedQuestion?.question ?? null,
|
||||||
@@ -148,6 +152,7 @@ async function runStartCaseExperiment(scenarioInput, experimentInstruction, opti
|
|||||||
assessmentProgress: result.assessment?.progress ?? null,
|
assessmentProgress: result.assessment?.progress ?? null,
|
||||||
endToEndElapsedMs: Date.now() - endToEndStartedAt,
|
endToEndElapsedMs: Date.now() - endToEndStartedAt,
|
||||||
diagnostics: result.diagnostics ?? null,
|
diagnostics: result.diagnostics ?? null,
|
||||||
|
providerApiPath: result.providerApiPath ?? null,
|
||||||
error: result.success ? null : (result.error ?? "unknown"),
|
error: result.success ? null : (result.error ?? "unknown"),
|
||||||
statusCode: result.statusCode ?? (result.success ? 200 : 500),
|
statusCode: result.statusCode ?? (result.success ? 200 : 500),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -374,6 +374,34 @@ describe("lib/graph/orchestrator startCase", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns initial reconstruction evidence before downstream question generation", async () => {
|
||||||
|
const analysis = makeAnalysisResult();
|
||||||
|
mockAnalyseScenario.mockResolvedValue(analysis);
|
||||||
|
const downstreamProvider = {
|
||||||
|
generateReconstruction: vi.fn(() => {
|
||||||
|
throw new Error("downstream question generation must not run");
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const { startCase } = await import("@/lib/graph/orchestrator.js");
|
||||||
|
|
||||||
|
const result = await startCase(
|
||||||
|
{ scenario: "Scenario text" },
|
||||||
|
{ reconstructionOnly: true, provider: downstreamProvider },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
reconstruction: analysis.reconstruction,
|
||||||
|
selectedQuestion: null,
|
||||||
|
});
|
||||||
|
expect(result.situationGraph.nodes.length).toBeGreaterThan(0);
|
||||||
|
expect(result.diagnostics.graphReferenceValidation).toEqual({
|
||||||
|
valid: true,
|
||||||
|
errors: [],
|
||||||
|
});
|
||||||
|
expect(downstreamProvider.generateReconstruction).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects invalid request input without throwing", async () => {
|
it("rejects invalid request input without throwing", async () => {
|
||||||
const { startCase } = await import("@/lib/graph/orchestrator.js");
|
const { startCase } = await import("@/lib/graph/orchestrator.js");
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ describe("start-case-experiment-helper.cjs apparatus", () => {
|
|||||||
startCase: async (_input, dependencies) => {
|
startCase: async (_input, dependencies) => {
|
||||||
expect(typeof dependencies.reconstructionProvider?.generateReconstruction).toBe("function");
|
expect(typeof dependencies.reconstructionProvider?.generateReconstruction).toBe("function");
|
||||||
expect(dependencies.reconstructionModelName).toBe("gpt-5.6-terra");
|
expect(dependencies.reconstructionModelName).toBe("gpt-5.6-terra");
|
||||||
|
expect(dependencies.reconstructionOnly).toBe(true);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
situationGraph: { nodes: [], edges: [] },
|
situationGraph: { nodes: [], edges: [] },
|
||||||
|
|||||||
Reference in New Issue
Block a user