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:
+15
-21
@@ -1,7 +1,18 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { Ollama } = require('ollama');
|
||||||
const conf = require('>/conf');
|
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).
|
// Pull the first JSON object out of a model response( tolerates ```json fences / prose).
|
||||||
function extractJSON(content){
|
function extractJSON(content){
|
||||||
let text = String(content).replace(/```json/gi, '').replace(/```/g, '').trim();
|
let text = String(content).replace(/```json/gi, '').replace(/```/g, '').trim();
|
||||||
@@ -19,16 +30,9 @@ 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){
|
async function chatJSON(system, user){
|
||||||
let res = await fetch(`${conf.ollama.url}/api/chat`, {
|
let res = await client.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,
|
model: conf.ollama.model,
|
||||||
stream: false,
|
stream: false,
|
||||||
format: 'json',
|
format: 'json',
|
||||||
@@ -36,18 +40,8 @@ async function chatJSON(system, user){
|
|||||||
{ role: 'system', content: system },
|
{ role: 'system', content: system },
|
||||||
{ role: 'user', content: user },
|
{ role: 'user', content: user },
|
||||||
],
|
],
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
if(!res.ok){
|
return extractJSON(res.message && res.message.content);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { chatJSON, extractJSON };
|
module.exports = { client, chatJSON, extractJSON };
|
||||||
|
|||||||
+4
-32
@@ -22,17 +22,6 @@ function humanSize(bytes){
|
|||||||
return `${bytes.toFixed(bytes < 10 && i > 0 ? 1 : 0)} ${units[i]}`;
|
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.
|
// Turn a raw TPB category id into 'movie' | 'tv' | null.
|
||||||
function videoKind(category){
|
function videoKind(category){
|
||||||
category = parseInt(category, 10);
|
category = parseInt(category, 10);
|
||||||
@@ -179,27 +168,10 @@ async function rankReleases(candidates, meta){
|
|||||||
imdb: t.imdb || null,
|
imdb: t.imdb || null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let body = {
|
let parsed = await ollama.chatJSON(
|
||||||
model: conf.ollama.model,
|
buildSystemPrompt(meta.title, meta.year, meta.today),
|
||||||
stream: false,
|
JSON.stringify(compact)
|
||||||
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 options = Array.isArray(parsed.options) ? parsed.options : [];
|
let options = Array.isArray(parsed.options) ? parsed.options : [];
|
||||||
if(!options.length) throw new Error('Model returned no options');
|
if(!options.length) throw new Error('Model returned no options');
|
||||||
return options;
|
return options;
|
||||||
|
|||||||
Generated
+16
@@ -20,6 +20,7 @@
|
|||||||
"lru-native2": "^1.2.6",
|
"lru-native2": "^1.2.6",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
|
"ollama": "^0.6.3",
|
||||||
"p2psub": "^0.1.9",
|
"p2psub": "^0.1.9",
|
||||||
"sequelize": "^6.35.2",
|
"sequelize": "^6.35.2",
|
||||||
"sequelize-cli": "^6.6.2",
|
"sequelize-cli": "^6.6.2",
|
||||||
@@ -2933,6 +2934,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ollama": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-fetch": "^3.6.20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/on-finished": {
|
"node_modules/on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
@@ -4411,6 +4421,12 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-fetch": {
|
||||||
|
"version": "3.6.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
|
||||||
|
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/whatwg-url": {
|
"node_modules/whatwg-url": {
|
||||||
"version": "14.2.0",
|
"version": "14.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"lru-native2": "^1.2.6",
|
"lru-native2": "^1.2.6",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
|
"ollama": "^0.6.3",
|
||||||
"p2psub": "^0.1.9",
|
"p2psub": "^0.1.9",
|
||||||
"sequelize": "^6.35.2",
|
"sequelize": "^6.35.2",
|
||||||
"sequelize-cli": "^6.6.2",
|
"sequelize-cli": "^6.6.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user