Shaheer Sarfaraz 82b261c7bc
Refactor LLM service into modular adapters and policies (#83)
* llm migration

* orchestrator runer

* Decompose runPipeline steps

* dedupe

* refactor(settings): unify settings conversion metadata and round-trip tests

* refactor(llm): extract shared provider strategy factory

* refactor(settings-ui): add reusable numeric setting section

* test(orchestrator): stabilize usePipelineSources localStorage setup

* comments
2026-02-04 21:48:28 +00:00

77 lines
2.2 KiB
TypeScript

import type { LlmRequestOptions, ResponseMode } from "../types";
import { buildHeaders, joinUrl } from "../utils/http";
import { getNestedValue } from "../utils/object";
import { createProviderStrategy } from "./factory";
export const openAiStrategy = createProviderStrategy({
provider: "openai",
defaultBaseUrl: "https://api.openai.com",
requiresApiKey: true,
modes: ["json_schema", "json_object", "none"],
validationPaths: ["/v1/models"],
buildRequest: ({ mode, baseUrl, apiKey, model, messages, jsonSchema }) => {
const input = ensureJsonInstructionIfNeeded(messages, mode);
const body: Record<string, unknown> = {
model,
input,
};
if (mode === "json_schema") {
body.text = {
format: {
type: "json_schema",
name: jsonSchema.name,
strict: true,
schema: jsonSchema.schema,
},
};
} else if (mode === "json_object") {
body.text = { format: { type: "json_object" } };
}
return {
url: joinUrl(baseUrl, "/v1/responses"),
headers: buildHeaders({ apiKey, provider: "openai" }),
body,
};
},
extractText: (response) => {
const direct = getNestedValue(response, ["output_text"]);
if (typeof direct === "string" && direct.trim()) return direct;
const output = getNestedValue(response, ["output"]);
if (!Array.isArray(output)) return null;
for (const item of output) {
const content = getNestedValue(item, ["content"]);
if (!Array.isArray(content)) continue;
for (const part of content) {
const type = getNestedValue(part, ["type"]);
const text = getNestedValue(part, ["text"]);
if (type === "output_text" && typeof text === "string") {
return text;
}
}
}
return null;
},
});
function ensureJsonInstructionIfNeeded(
messages: LlmRequestOptions<unknown>["messages"],
mode: ResponseMode,
) {
if (mode !== "json_object") return messages;
const hasJson = messages.some((message) =>
message.content.toLowerCase().includes("json"),
);
if (hasJson) return messages;
return [
{
role: "system" as const,
content: "Respond with valid JSON.",
},
...messages,
];
}