69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const SYSTEM_PROMPT = `You are a film research assistant for an international audience.
|
|
You MUST respond in English with valid JSON only — no markdown fences, no commentary.
|
|
Use a clear, neutral tone. If uncertain about a scene or timestamp, state uncertainty and set confidence to "low".
|
|
Do not invent scenes, quotes, or timestamps. Base answers on the movie context provided.`;
|
|
|
|
export async function generateStructuredResponse<T extends z.ZodType>(
|
|
schema: T,
|
|
userPrompt: string,
|
|
): Promise<z.infer<T>> {
|
|
const apiKey = process.env.LLM_API_KEY;
|
|
if (!apiKey) {
|
|
throw new Error("LLM_API_KEY is not set");
|
|
}
|
|
|
|
const baseUrl = process.env.LLM_BASE_URL ?? "https://api.openai.com/v1";
|
|
const model = process.env.LLM_MODEL ?? "gpt-4o-mini";
|
|
|
|
const headers: Record<string, string> = {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
if (process.env.LLM_HTTP_REFERER) {
|
|
headers["HTTP-Referer"] = process.env.LLM_HTTP_REFERER;
|
|
}
|
|
|
|
if (process.env.LLM_APP_TITLE) {
|
|
headers["X-Title"] = process.env.LLM_APP_TITLE;
|
|
}
|
|
|
|
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({
|
|
model,
|
|
temperature: 0.2,
|
|
response_format: { type: "json_object" },
|
|
messages: [
|
|
{ role: "system", content: SYSTEM_PROMPT },
|
|
{ role: "user", content: userPrompt },
|
|
],
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(`LLM request failed: ${response.status} ${errorText}`);
|
|
}
|
|
|
|
const data = (await response.json()) as {
|
|
choices: Array<{ message: { content: string } }>;
|
|
};
|
|
|
|
const content = data.choices[0]?.message?.content;
|
|
if (!content) {
|
|
throw new Error("LLM returned empty content");
|
|
}
|
|
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(content);
|
|
} catch {
|
|
throw new Error("LLM returned invalid JSON");
|
|
}
|
|
|
|
return schema.parse(parsed);
|
|
} |