import { cleanText } from "./textUtils.js"; const OLLAMA_API_URL = process.env.OLLAMA_API_URL; export const OLLAMA_MODEL = "qwen3.5-122b-a10b"; function effectiveModel(explicit) { if (explicit !== undefined && explicit !== null) return explicit; return process.env.OLLAMA_MODEL || OLLAMA_MODEL; } export async function initializeModel() { if (process.env.OLLAMA_MODEL) return process.env.OLLAMA_MODEL; try { const apiUrl = process.env.OLLAMA_API_URL; const isOpenWebUI = apiUrl?.includes("/api/chat/completions"); const baseUrl = apiUrl?.replace(/\/api\/.*$/, ""); const url = isOpenWebUI ? `${baseUrl}/api/v1/models` : `${baseUrl}/api/tags`; const headers = isOpenWebUI && process.env.OLLAMA_API_KEY ? { "Authorization": `Bearer ${process.env.OLLAMA_API_KEY}` } : {}; const res = await fetch(url, { headers }); if (res.ok) { const data = await res.json(); const model = isOpenWebUI ? data.data?.[0]?.id || data.data?.[0]?.name : data.models?.[0]?.name; if (model) { process.env.OLLAMA_MODEL = model; console.log(`Using default model: ${model}`); return model; } } } catch { // fall through to warn below } console.warn(`Could not fetch default model, using: ${OLLAMA_MODEL}`); return OLLAMA_MODEL; } export { cleanText }; export function inferApiType(url) { if (!url) return "ollama-generate"; if (url.includes("/api/chat/completions")) return "open-webui"; if (url.includes("/api/chat")) return "ollama-chat"; return "ollama-generate"; } async function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function callOllamaBase(prompt, model, retries, stepName, apiType) { const isUsingOpenWebUI = apiType === "open-webui"; const isUsingOllamaChat = apiType === "ollama-chat"; const resolvedModel = effectiveModel(model); const attempts = Array.from({ length: retries }, (_, index) => index + 1); for (const attempt of attempts) { try { const promptCharCount = prompt.length; const promptWordCount = prompt.split(/\s+/).length; console.log( `\n[${stepName}] Sending prompt (attempt ${attempt}/${retries})`, ); console.log( `Prompt: ${promptCharCount} chars, ~${promptWordCount} words`, ); const headers = isUsingOpenWebUI && process.env.OLLAMA_API_KEY ? { "Content-Type": "application/json", "Authorization": `Bearer ${process.env.OLLAMA_API_KEY}`, } : { "Content-Type": "application/json" }; const body = isUsingOpenWebUI || isUsingOllamaChat ? { model: resolvedModel, messages: [{ role: "user", content: prompt }] } : { model: resolvedModel, prompt, stream: false }; const response = await fetch(OLLAMA_API_URL, { method: "POST", headers, body: JSON.stringify(body), }); if (!response.ok) { const errorData = await response.text().catch(() => null); const errorDetails = errorData ? `: ${errorData}` : ""; throw new Error( `Ollama request failed: ${response.status} ${response.statusText}${errorDetails}`, ); } const data = await response.json(); const rawText = isUsingOpenWebUI ? data.choices?.[0]?.message?.content : isUsingOllamaChat ? data.message?.content : data.response; if (!rawText) throw new Error("No response from Ollama"); const cleaned = cleanText(rawText); console.log( `[${stepName}] Received: ${rawText.length} chars, ~${rawText.split(/\s+/).length} words`, ); return cleaned; } catch (err) { console.warn(`[${stepName}] Attempt ${attempt} failed: ${err.message}`); if (attempt === retries) throw err; const delay = 1000 * Math.pow(2, attempt) + Math.random() * 500; console.log(`Retrying in ${Math.round(delay / 1000)}s...`); await sleep(delay); } } } export async function callOllama( prompt, model, retries = 5, stepName = "unknown", ) { const apiType = inferApiType(OLLAMA_API_URL); return callOllamaBase(prompt, model, retries, stepName, apiType); } export async function callOllamaExplicit( prompt, model, retries = 5, stepName = "unknown", apiType = "ollama-generate", ) { return callOllamaBase(prompt, model, retries, stepName, apiType); }