NODEDC_1C/llm_normalizer/backend/tests/openaiResponsesClientLocalF...

126 lines
4.4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { OpenAIResponsesClient } from "../src/services/openaiResponsesClient";
describe("openai local responses fallback", () => {
const originalFetch = global.fetch;
afterEach(() => {
vi.restoreAllMocks();
global.fetch = originalFetch;
});
it("falls back to /chat/completions when /responses payload is parseable JSON but has no output_text", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(
new Response(JSON.stringify({ id: "resp-1", output: [], usage: { prompt_tokens: 4, completion_tokens: 3 } }), {
status: 200,
headers: { "content-type": "application/json" }
})
)
.mockResolvedValueOnce(
new Response(
JSON.stringify({
id: "chat-1",
choices: [{ message: { content: "{\"schema_version\":\"normalized_query_v2_0_2\"}" } }],
usage: { prompt_tokens: 7, completion_tokens: 5, total_tokens: 12 }
}),
{
status: 200,
headers: { "content-type": "application/json" }
}
)
);
global.fetch = fetchMock as unknown as typeof fetch;
const client = new OpenAIResponsesClient();
const response = await client.normalize(
{
llmProvider: "local",
apiKey: "",
model: "qwen2.5-14b-instruct-1m",
baseUrl: "http://127.0.0.1:1234",
temperature: 0
},
{
systemPrompt: "system",
developerPrompt: "developer",
domainPrompt: "domain",
userQuestion: "question",
schemaVersion: "v2_0_2"
}
);
expect(response.outputText).toContain("\"schema_version\":\"normalized_query_v2_0_2\"");
expect(response.usage.total_tokens).toBe(12);
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(String(fetchMock.mock.calls[0]?.[0] ?? "")).toContain("/responses");
expect(String(fetchMock.mock.calls[1]?.[0] ?? "")).toContain("/chat/completions");
});
it("retries alternative local base when endpoint mismatch payload is returned for chat completions", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(
new Response(JSON.stringify({ error: { message: "Unexpected endpoint or method. (POST /responses)" } }), {
status: 404,
headers: { "content-type": "application/json" }
})
)
.mockResolvedValueOnce(
new Response(JSON.stringify({ error: { message: "Unexpected endpoint or method. (POST /responses)" } }), {
status: 404,
headers: { "content-type": "application/json" }
})
)
.mockResolvedValueOnce(
new Response(JSON.stringify({ error: "Unexpected endpoint or method. (POST /chat/completions)" }), {
status: 200,
headers: { "content-type": "application/json" }
})
)
.mockResolvedValueOnce(
new Response(
JSON.stringify({
id: "chat-v1",
choices: [{ message: { content: "{\"schema_version\":\"normalized_query_v2_0_2\"}" } }],
usage: { prompt_tokens: 10, completion_tokens: 7, total_tokens: 17 }
}),
{
status: 200,
headers: { "content-type": "application/json" }
}
)
);
global.fetch = fetchMock as unknown as typeof fetch;
const client = new OpenAIResponsesClient();
const response = await client.normalize(
{
llmProvider: "local",
apiKey: "",
model: "qwen2.5-14b-instruct-1m",
baseUrl: "http://127.0.0.1:1234",
temperature: 0
},
{
systemPrompt: "system",
developerPrompt: "developer",
domainPrompt: "domain",
userQuestion: "question",
schemaVersion: "v2_0_2"
}
);
expect(response.outputText).toContain("\"schema_version\":\"normalized_query_v2_0_2\"");
expect(response.usage.total_tokens).toBe(17);
expect(fetchMock).toHaveBeenCalledTimes(4);
expect(String(fetchMock.mock.calls[0]?.[0] ?? "")).toBe("http://127.0.0.1:1234/responses");
expect(String(fetchMock.mock.calls[1]?.[0] ?? "")).toBe("http://127.0.0.1:1234/v1/responses");
expect(String(fetchMock.mock.calls[2]?.[0] ?? "")).toBe("http://127.0.0.1:1234/chat/completions");
expect(String(fetchMock.mock.calls[3]?.[0] ?? "")).toBe("http://127.0.0.1:1234/v1/chat/completions");
});
});