|
import { strictFormat } from '../utils/text.js'; |
|
|
|
export class Local { |
|
constructor(model_name, url) { |
|
this.model_name = model_name; |
|
this.url = url || 'http://127.0.0.1:11434'; |
|
this.chat_endpoint = '/api/chat'; |
|
this.embedding_endpoint = '/api/embeddings'; |
|
} |
|
|
|
|
|
|
|
|
|
async sendRequest(turns, systemMessage) { |
|
|
|
const model = this.model_name || 'llama3'; |
|
|
|
|
|
let messages = strictFormat(turns); |
|
messages.unshift({ role: 'system', content: systemMessage }); |
|
console.log('Messages:', messages); |
|
|
|
|
|
const maxAttempts = 5; |
|
let attempt = 0; |
|
let finalRes = null; |
|
|
|
while (attempt < maxAttempts) { |
|
attempt++; |
|
console.log(`Awaiting local response... (model: ${model}, attempt: ${attempt})`); |
|
|
|
|
|
let res; |
|
try { |
|
const responseData = await this.send(this.chat_endpoint, { |
|
model: model, |
|
messages: messages, |
|
stream: false |
|
}); |
|
|
|
res = responseData?.message?.content || 'No response data.'; |
|
} catch (err) { |
|
|
|
if (err.message.toLowerCase().includes('context length') && turns.length > 1) { |
|
console.log('Context length exceeded, trying again with shorter context.'); |
|
return await this.sendRequest(turns.slice(1), systemMessage); |
|
} else { |
|
console.log(err); |
|
res = 'My brain disconnected, try again.'; |
|
} |
|
} |
|
|
|
|
|
if (this.model_name && this.model_name.includes("deepseek-r1") || this.model_name.includes("Andy_3.5")) { |
|
const hasOpenTag = res.includes("<think>"); |
|
const hasCloseTag = res.includes("</think>"); |
|
|
|
|
|
if ((hasOpenTag && !hasCloseTag) || (!hasOpenTag && hasCloseTag)) { |
|
console.warn("Partial <think> block detected. Re-generating..."); |
|
|
|
continue; |
|
} |
|
|
|
if (hasOpenTag && hasCloseTag) { |
|
res = res.replace(/<think>[\s\S]*?<\/think>/g, ''); |
|
} |
|
} |
|
|
|
finalRes = res; |
|
break; |
|
} |
|
|
|
|
|
if (finalRes == null) { |
|
|
|
console.warn("Could not get a valid <think> block or normal response after max attempts."); |
|
finalRes = 'Response incomplete, please try again.'; |
|
} |
|
return finalRes; |
|
} |
|
|
|
|
|
|
|
|
|
async embed(text) { |
|
let model = this.model_name || 'nomic-embed-text'; |
|
let body = { model: model, prompt: text }; |
|
let res = await this.send(this.embedding_endpoint, body); |
|
return res['embedding']; |
|
} |
|
|
|
|
|
|
|
|
|
async send(endpoint, body) { |
|
const url = new URL(endpoint, this.url); |
|
const method = 'POST'; |
|
const headers = new Headers(); |
|
const request = new Request(url, { |
|
method, |
|
headers, |
|
body: JSON.stringify(body) |
|
}); |
|
|
|
let data = null; |
|
try { |
|
const res = await fetch(request); |
|
if (res.ok) { |
|
data = await res.json(); |
|
} else { |
|
throw new Error(`Ollama Status: ${res.status}`); |
|
} |
|
} catch (err) { |
|
console.error('Failed to send Ollama request.'); |
|
console.error(err); |
|
throw err; |
|
} |
|
return data; |
|
} |
|
} |