Wire mic through Whisper-compatible transcriptions on LLM_BASE_URL. Photos upload to MinIO and reach the vision model as base64 image_url parts.
75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import type { ChatCompletionRequest, ChatCompletionResult, ChatMessage, LlmClient } from "./types";
|
|
|
|
type OpenAiMessage = {
|
|
role: string;
|
|
content: ChatMessage["content"];
|
|
tool_calls?: Array<{
|
|
id: string;
|
|
type: "function";
|
|
function: { name: string; arguments: string };
|
|
}>;
|
|
tool_call_id?: string;
|
|
name?: string;
|
|
};
|
|
|
|
export function createOpenAiCompatibleClient(options: {
|
|
baseUrl: string;
|
|
apiKey: string | null;
|
|
model: string;
|
|
}): LlmClient {
|
|
const completionsUrl = `${options.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
|
|
return {
|
|
async chatCompletion(request: ChatCompletionRequest): Promise<ChatCompletionResult> {
|
|
const headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
};
|
|
if (options.apiKey) {
|
|
headers.Authorization = `Bearer ${options.apiKey}`;
|
|
}
|
|
|
|
const body = {
|
|
model: options.model,
|
|
messages: request.messages as OpenAiMessage[],
|
|
tools: request.tools,
|
|
tool_choice: request.tools?.length ? "auto" : undefined,
|
|
};
|
|
|
|
const response = await fetch(completionsUrl, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const detail = await response.text();
|
|
throw new Error(`LLM request failed (${response.status}): ${detail.slice(0, 400)}`);
|
|
}
|
|
|
|
const payload = (await response.json()) as {
|
|
choices?: Array<{
|
|
finish_reason?: string | null;
|
|
message?: OpenAiMessage;
|
|
}>;
|
|
};
|
|
|
|
const choice = payload.choices?.[0];
|
|
const message = choice?.message;
|
|
if (!message) {
|
|
throw new Error("LLM response missing message");
|
|
}
|
|
|
|
return {
|
|
message: {
|
|
role: message.role as ChatCompletionResult["message"]["role"],
|
|
content: message.content,
|
|
tool_calls: message.tool_calls,
|
|
tool_call_id: message.tool_call_id,
|
|
name: message.name,
|
|
},
|
|
finishReason: choice.finish_reason ?? null,
|
|
};
|
|
},
|
|
};
|
|
}
|