Files
scrollsmith/ollamaClient.js

162 lines
4.9 KiB
JavaScript

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:4b";
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;
}
}