39 lines
1.5 KiB
TypeScript
39 lines
1.5 KiB
TypeScript
import type { Db } from "../db";
|
|
import { DEFAULTS, getSetting } from "../routes/settings";
|
|
|
|
export type LlmDeps = { db: Db; key: Buffer; fetchImpl?: typeof fetch };
|
|
|
|
export async function chatJSON(deps: LlmDeps, opts: { system: string; user: string }): Promise<unknown> {
|
|
const f = deps.fetchImpl ?? fetch;
|
|
const base = (await getSetting(deps.db, "llm_base_url", deps.key)) ?? DEFAULTS.llm_base_url;
|
|
const model = (await getSetting(deps.db, "llm_model", deps.key)) ?? DEFAULTS.llm_model;
|
|
const apiKey = await getSetting(deps.db, "llm_key", deps.key);
|
|
|
|
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
|
|
|
|
const res = await f(`${base.replace(/\/$/, "")}/chat/completions`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({
|
|
model,
|
|
response_format: { type: "json_object" },
|
|
messages: [
|
|
{ role: "system", content: opts.system },
|
|
{ role: "user", content: opts.user },
|
|
],
|
|
}),
|
|
});
|
|
if (!res.ok) throw new Error(`llm_error: provider returned ${res.status}`);
|
|
|
|
const data = (await res.json()) as { choices?: { message?: { content?: string } }[] };
|
|
const content = data.choices?.[0]?.message?.content;
|
|
if (!content) throw new Error("llm_error: empty completion");
|
|
const stripped = content.replace(/^\s*```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "");
|
|
try {
|
|
return JSON.parse(stripped);
|
|
} catch {
|
|
throw new Error("llm_error: completion was not valid JSON");
|
|
}
|
|
}
|