Use official ollama npm client for all LLM calls

Replaces hand-rolled fetch to /api/chat with the ollama@0.6.3 package.

The package supplies proper Accept/Content-Type/User-Agent headers and

auto-handles the Ollama protocol, fixing the CDN 403s Node's bare fetch

gets against ollama.com during filing.

Both organize and search LLM ranking now route through controller/ollama.js.

Signed-off-by: William Mantly <wmantly@gmail.com>
This commit is contained in:
2026-07-20 12:43:25 -04:00
parent 47a930eddb
commit bd9f058f26
4 changed files with 43 additions and 60 deletions
+22 -28
View File
@@ -1,7 +1,18 @@
'use strict';
const { Ollama } = require('ollama');
const conf = require('>/conf');
// Shared Ollama client. The npm package adds its own Accept / Content-Type / User-Agent
// headers and handles the Ollama chat protocol, which avoids the CDN-level 403s Node's
// bare fetch gets against ollama.com.
const client = new Ollama({
host: conf.ollama.url,
headers: {
'Authorization': `Bearer ${conf.ollama.apiKey}`,
},
});
// Pull the first JSON object out of a model response( tolerates ```json fences / prose).
function extractJSON(content){
let text = String(content).replace(/```json/gi, '').replace(/```/g, '').trim();
@@ -19,35 +30,18 @@ function extractJSON(content){
}
}
// One-shot JSON chat against the Ollama cloud model.
// One-shot JSON chat against the configured Ollama model.
async function chatJSON(system, user){
let res = await fetch(`${conf.ollama.url}/api/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${conf.ollama.apiKey}`,
'User-Agent': 'tpbproxy/1.0 (organize; +https://github.com/wmantly/tpbproxy)',
},
body: JSON.stringify({
model: conf.ollama.model,
stream: false,
format: 'json',
messages: [
{ role: 'system', content: system },
{ role: 'user', content: user },
],
}),
let res = await client.chat({
model: conf.ollama.model,
stream: false,
format: 'json',
messages: [
{ role: 'system', content: system },
{ role: 'user', content: user },
],
});
if(!res.ok){
let body = await res.text().catch(() => '');
console.error(`Ollama chat failed (${res.status}):`, body.slice(0, 500));
let error = new Error('OllamaError');
error.message = `Ollama chat failed( ${res.status})`;
error.status = 502;
throw error;
}
let data = await res.json();
return extractJSON(data.message && data.message.content);
return extractJSON(res.message && res.message.content);
}
module.exports = { chatJSON, extractJSON };
module.exports = { client, chatJSON, extractJSON };
+4 -32
View File
@@ -22,17 +22,6 @@ function humanSize(bytes){
return `${bytes.toFixed(bytes < 10 && i > 0 ? 1 : 0)} ${units[i]}`;
}
async function fetchJSON(url, options){
let res = await fetch(url, options);
if(!res.ok){
let error = new Error(`UpstreamError`);
error.message = `Request to ${url.split('?')[0]} failed( ${res.status})`;
error.status = 502;
throw error;
}
return await res.json();
}
// Turn a raw TPB category id into 'movie' | 'tv' | null.
function videoKind(category){
category = parseInt(category, 10);
@@ -179,27 +168,10 @@ async function rankReleases(candidates, meta){
imdb: t.imdb || null,
}));
let body = {
model: conf.ollama.model,
stream: false,
format: 'json',
messages: [
{ role: 'system', content: buildSystemPrompt(meta.title, meta.year, meta.today) },
{ role: 'user', content: JSON.stringify(compact) },
],
};
let data = await fetchJSON(`${conf.ollama.url}/api/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${conf.ollama.apiKey}`,
'User-Agent': 'tpbproxy/1.0 (search; +https://github.com/wmantly/tpbproxy)',
},
body: JSON.stringify(body),
});
let parsed = extractJSON(data.message && data.message.content);
let parsed = await ollama.chatJSON(
buildSystemPrompt(meta.title, meta.year, meta.today),
JSON.stringify(compact)
);
let options = Array.isArray(parsed.options) ? parsed.options : [];
if(!options.length) throw new Error('Model returned no options');
return options;