experiment(confidence-engine): route UI journey provider centrally
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockSynthesize = vi.fn();
|
||||
|
||||
vi.mock("@/lib/graph/investigation-overview-synthesis.js", () => ({
|
||||
synthesizeInvestigationOverview: (...args) => mockSynthesize(...args),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/llm/provider.js", () => ({
|
||||
getProvider: () => ({ generateReconstruction() {} }),
|
||||
getProviderModelName: () => "gpt-5.6-terra",
|
||||
}));
|
||||
|
||||
describe("POST /api/cases/overview provider routing", () => {
|
||||
beforeEach(() => mockSynthesize.mockClear());
|
||||
|
||||
it("passes the central provider and model resolution to overview synthesis", async () => {
|
||||
mockSynthesize.mockResolvedValue({ understanding: "Understanding", plausibleInterpretations: "None" });
|
||||
const { POST } = await import("@/app/api/cases/overview/route.js");
|
||||
const response = await POST(new Request("http://localhost/api/cases/overview", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ situationGraph: { nodes: [], edges: [] }, findings: [], plausibleInterpretations: [] }),
|
||||
}));
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockSynthesize.mock.calls[0][1]).toMatchObject({ modelName: "gpt-5.6-terra" });
|
||||
});
|
||||
});
|
||||
@@ -39,6 +39,8 @@ describe("app/api/cases/start route", () => {
|
||||
await POST(request);
|
||||
|
||||
expect(mockStartCase).toHaveBeenCalledWith({ scenario: "Scenario text" });
|
||||
expect(mockStartCase.mock.calls[0][0]).not.toHaveProperty("provider");
|
||||
expect(mockStartCase.mock.calls[0][0]).not.toHaveProperty("modelName");
|
||||
});
|
||||
|
||||
it("returns 200 on success", async () => {
|
||||
|
||||
@@ -72,6 +72,8 @@ describe("app/api/cases/update route", () => {
|
||||
);
|
||||
|
||||
expect(mockUpdateCase).toHaveBeenCalledWith(body, { applyProposal: true });
|
||||
expect(mockUpdateCase.mock.calls[0][0]).not.toHaveProperty("provider");
|
||||
expect(mockUpdateCase.mock.calls[0][0]).not.toHaveProperty("modelName");
|
||||
});
|
||||
|
||||
it("invalid JSON returns 400", async () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ vi.mock("@/lib/graph/current-understanding-synthesis.js", () => ({
|
||||
|
||||
vi.mock("@/lib/llm/provider.js", () => ({
|
||||
getProvider: () => ({}),
|
||||
getProviderModelName: () => "gpt-5.6-terra",
|
||||
}));
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────
|
||||
@@ -45,6 +46,7 @@ describe("POST /api/cases/synthesis — valid request", () => {
|
||||
expect(data.success).toBe(true);
|
||||
expect(data.currentUnderstanding).toBe("Synthesized result");
|
||||
expect(mockSynthesize).toHaveBeenCalledTimes(1);
|
||||
expect(mockSynthesize.mock.calls[0][1]).toMatchObject({ modelName: "gpt-5.6-terra" });
|
||||
});
|
||||
|
||||
it("returns narrative result on success", async () => {
|
||||
|
||||
@@ -51,6 +51,7 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
|
||||
vi.doMock("@/lib/llm/provider", () => ({
|
||||
getProvider: () => makeMockProvider(inventedModelId),
|
||||
getProviderModelName: () => "configured-model",
|
||||
}));
|
||||
|
||||
const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js");
|
||||
@@ -97,6 +98,7 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
});
|
||||
vi.doMock("@/lib/llm/provider", () => ({
|
||||
getProvider: () => ({ generateReconstruction }),
|
||||
getProviderModelName: () => "gpt-5.6-terra",
|
||||
}));
|
||||
const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js");
|
||||
|
||||
@@ -116,7 +118,7 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
expect(response.status).toBe(200);
|
||||
expect(generateReconstruction).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
process.env.OLLAMA_MODEL,
|
||||
"gpt-5.6-terra",
|
||||
focusedDeconstructJsonSchema,
|
||||
);
|
||||
});
|
||||
@@ -149,6 +151,7 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
providerExecution: { chatRequestAttempted: true },
|
||||
}),
|
||||
}),
|
||||
getProviderModelName: () => "configured-model",
|
||||
}));
|
||||
|
||||
const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js");
|
||||
@@ -228,6 +231,7 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
providerExecution: { chatRequestAttempted: true },
|
||||
}),
|
||||
}),
|
||||
getProviderModelName: () => "configured-model",
|
||||
}));
|
||||
|
||||
const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js");
|
||||
@@ -291,6 +295,7 @@ describe("focused-deconstruct targetNodeId identity boundary", () => {
|
||||
providerExecution: { chatRequestAttempted: true, chatRequestSucceeded: true },
|
||||
}),
|
||||
}),
|
||||
getProviderModelName: () => "configured-model",
|
||||
}));
|
||||
|
||||
const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js");
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/llm/provider.js", () => ({
|
||||
getProvider: () => ({ generateReconstruction() {} }),
|
||||
getProviderModelName: () => "gpt-5.6-terra",
|
||||
}));
|
||||
|
||||
// ── Fixtures ────────────────────────────────────────────────
|
||||
|
||||
function makePreparedEpisode(overrides = {}) {
|
||||
@@ -193,4 +198,22 @@ describe("episode reasoning seam (reconsiderCompletedEpisode)", () => {
|
||||
else process.env.OLLAMA_MODEL = prevModel;
|
||||
});
|
||||
|
||||
it("uses the central provider model resolution when episode mode supplies no model override", async () => {
|
||||
const provider = {
|
||||
generateReconstruction: vi.fn().mockResolvedValue(makeProposalResponse()),
|
||||
};
|
||||
const { reconsiderCompletedEpisode } = await import("@/lib/graph/orchestrator.js");
|
||||
|
||||
const result = await reconsiderCompletedEpisode(
|
||||
{ ...TEST_EPISODE, provider },
|
||||
{ buildEpisodePrompt: () => "EPISODE_PROMPT" },
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(provider.generateReconstruction).toHaveBeenCalledWith(
|
||||
"EPISODE_PROMPT",
|
||||
"gpt-5.6-terra",
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
createOpenAIStrictSchema,
|
||||
createOpenAIReconstructionProvider,
|
||||
getProvider,
|
||||
getProviderModelName,
|
||||
normaliseOpenAITransportResponse,
|
||||
reconstructionJsonSchema,
|
||||
} from "@/lib/llm/provider.js";
|
||||
@@ -11,6 +12,48 @@ import { reconstructionV2Schema } from "@/lib/reconstruction/schema.js";
|
||||
import { z } from "zod";
|
||||
|
||||
describe("OllamaLlmProvider chat capability detection", () => {
|
||||
it("keeps Ollama and its configured model when no experiment provider is selected", () => {
|
||||
const previousExperimentProvider = process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER;
|
||||
const previousModel = process.env.OLLAMA_MODEL;
|
||||
const previousOpenAIKey = process.env.OPENAI_API_KEY;
|
||||
delete process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER;
|
||||
process.env.OLLAMA_MODEL = "qwen-default";
|
||||
process.env.OPENAI_API_KEY = "present-but-not-selected";
|
||||
|
||||
try {
|
||||
expect(getProvider().constructor.name).toBe("OllamaLlmProvider");
|
||||
expect(getProviderModelName()).toBe("qwen-default");
|
||||
} finally {
|
||||
if (previousExperimentProvider === undefined) delete process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER;
|
||||
else process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER = previousExperimentProvider;
|
||||
if (previousModel === undefined) delete process.env.OLLAMA_MODEL;
|
||||
else process.env.OLLAMA_MODEL = previousModel;
|
||||
if (previousOpenAIKey === undefined) delete process.env.OPENAI_API_KEY;
|
||||
else process.env.OPENAI_API_KEY = previousOpenAIKey;
|
||||
}
|
||||
});
|
||||
|
||||
it("selects the existing OpenAI provider and Terra only for the explicit experiment configuration", () => {
|
||||
const previousExperimentProvider = process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER;
|
||||
const previousModel = process.env.OLLAMA_MODEL;
|
||||
const previousOpenAIKey = process.env.OPENAI_API_KEY;
|
||||
process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER = "openai";
|
||||
process.env.OPENAI_API_KEY = "test-key";
|
||||
process.env.OLLAMA_MODEL = "qwen-default";
|
||||
|
||||
try {
|
||||
expect(getProvider().constructor.name).toBe("OpenAIReconstructionProvider");
|
||||
expect(getProviderModelName()).toBe("gpt-5.6-terra");
|
||||
} finally {
|
||||
if (previousExperimentProvider === undefined) delete process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER;
|
||||
else process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER = previousExperimentProvider;
|
||||
if (previousModel === undefined) delete process.env.OLLAMA_MODEL;
|
||||
else process.env.OLLAMA_MODEL = previousModel;
|
||||
if (previousOpenAIKey === undefined) delete process.env.OPENAI_API_KEY;
|
||||
else process.env.OPENAI_API_KEY = previousOpenAIKey;
|
||||
}
|
||||
});
|
||||
|
||||
it("uses the configured model for the chat probe and keeps the chat path", async () => {
|
||||
const originalBaseUrl = process.env.OLLAMA_BASE_URL;
|
||||
const fetchSpy = vi.fn()
|
||||
|
||||
Reference in New Issue
Block a user