const OLLAMA_API_URL = process.env.OLLAMA_API_URL; const OLLAMA_API_KEY = process.env.OLLAMA_API_KEY; export const OLLAMA_MODEL = process.env.OLLAMA_MODEL || "gemma3:latest"; function cleanText(str) { return str .replace(/^#+\s*/gm, "") .replace(/\*\*(.*?)\*\*/g, "$1") .replace(/[*_`]/g, "") .replace(/\s+/g, " ") .trim(); } 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"; for (let attempt = 1; attempt <= retries; attempt++) { 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 = { "Content-Type": "application/json" }; if (isUsingOpenWebUI && OLLAMA_API_KEY) { headers["Authorization"] = `Bearer ${OLLAMA_API_KEY}`; } const body = isUsingOpenWebUI || isUsingOllamaChat ? { model, messages: [{ role: "user", content: prompt }] } : { model, prompt, stream: false }; // Debug logging for Open WebUI if (isUsingOpenWebUI) { console.log(`[${stepName}] Using Open WebUI API`); console.log(`[${stepName}] Model name: "${model}"`); console.log(`[${stepName}] API URL: ${OLLAMA_API_URL}`); } const response = await fetch(OLLAMA_API_URL, { method: "POST", headers, body: JSON.stringify(body), }); if (!response.ok) { let errorDetails = ""; try { const errorData = await response.text(); errorDetails = errorData ? `: ${errorData}` : ""; } catch { // Ignore errors reading error response } const errorMsg = `Ollama request failed: ${response.status} ${response.statusText}${errorDetails}`; if (isUsingOpenWebUI) { console.error(`[${stepName}] Request details:`); console.error(` URL: ${OLLAMA_API_URL}`); console.error(` Model: "${model}"`); console.error(` Body: ${JSON.stringify(body, null, 2)}`); } throw new Error(errorMsg); } 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 = OLLAMA_MODEL, retries = 5, stepName = "unknown", ) { const apiType = inferApiType(OLLAMA_API_URL); return callOllamaBase(prompt, model, retries, stepName, apiType); } export async function callOllamaExplicit( prompt, model = OLLAMA_MODEL, retries = 5, stepName = "unknown", apiType = "ollama-generate", ) { return callOllamaBase(prompt, model, retries, stepName, apiType); } // Helper function to list available models from Open WebUI export async function listOpenWebUIModels() { if (!OLLAMA_API_URL || !OLLAMA_API_URL.includes("/api/chat/completions")) { console.log("Not using Open WebUI API, skipping model list"); return null; } try { const headers = { "Content-Type": "application/json" }; if (OLLAMA_API_KEY) { headers["Authorization"] = `Bearer ${OLLAMA_API_KEY}`; } // Try to get models from Open WebUI's models endpoint // Open WebUI might have a /api/v1/models endpoint const baseUrl = OLLAMA_API_URL.replace("/api/chat/completions", ""); const modelsUrl = `${baseUrl}/api/v1/models`; const response = await fetch(modelsUrl, { method: "GET", headers, }); if (response.ok) { const data = await response.json(); console.log("Available models from Open WebUI:", JSON.stringify(data, null, 2)); return data; } else { console.log(`Could not list models: ${response.status} ${response.statusText}`); return null; } } catch (err) { console.log(`Error listing models: ${err.message}`); return null; } }