import type { LLMProvider } from "@maia/core"; import { buildChatBody, parseSse } from "./openai"; import { authHeaders, joinUrl, type MaiaSettings } from "./settings"; export * from "./settings"; export { buildChatBody, parseSse } from "./openai"; async function errorText(response: Response): Promise { try { return (await response.text()).slice(0, 500); } catch { return ""; } } /** * Provider unique : toutes les requêtes des agents partent au format OpenAI vers le endpoint OpenAI de LiteLLM * (LiteLLM_URL + OpenAI_EndPoint), quel que soit le modèle choisi. */ export function createLiteLLMProvider(model: string, settings: MaiaSettings): LLMProvider { const url = joinUrl(settings.LiteLLM_URL, settings.OpenAI_EndPoint); return { id: "litellm", model, capabilities: { tools: true }, async *chat(req, signal) { let response: Response; try { response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "text/event-stream", ...authHeaders(settings) }, body: JSON.stringify(buildChatBody(model, req)), signal, }); } catch (err) { if (signal?.aborted) throw err; throw new Error(`LiteLLM injoignable (${url}) : ${(err as Error).message}`); } if (!response.ok) { throw new Error(`Échec de l'appel au modèle ${model} (HTTP ${response.status} ${response.statusText}). ${await errorText(response)}`); } if (!response.body) throw new Error("Réponse LiteLLM sans contenu."); yield* parseSse(response.body as unknown as AsyncIterable); }, }; } export interface ModelChoice { value: string; // "litellm:" label: string; hint?: string; } /** Modèles disponibles sur le serveur LiteLLM (LiteLLM_URL + LiteLLM_Models). */ export async function listLiteLLMModels(settings: MaiaSettings, signal?: AbortSignal): Promise { const url = joinUrl(settings.LiteLLM_URL, settings.LiteLLM_Models); let response: Response; try { response = await fetch(url, { headers: { Accept: "application/json", ...authHeaders(settings) }, signal }); } catch (err) { throw new Error(`LiteLLM injoignable (${url}) : ${(err as Error).message}`); } if (!response.ok) { throw new Error(`Liste des modèles refusée (HTTP ${response.status} ${response.statusText}) : ${url}. ${await errorText(response)}`); } const json = (await response.json()) as { data?: { id?: string; model_name?: string }[] }; const names = new Set(); for (const m of json.data ?? []) { const name = m.id ?? m.model_name; if (name) names.add(name); } return [...names].sort().map((name) => ({ value: `litellm:${name}`, label: name, hint: "LiteLLM" })); }