mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-04 06:15:32 +02:00
I've enhanced logging, transformed thinking tags, and cleaned comments.
- I implemented universal logging for all API providers in src/models/, ensuring calls to logger.js for text and vision logs. - I added transformation of <thinking>...</thinking> tags to <think>...</think> in all provider responses before logging, for correct categorization by logger.js. - I standardized the input to logger.js's log() function to be a JSON string of the message history (system prompt + turns). - I removed unnecessary comments from most API provider files, settings.js, and prompter.js to improve readability. Note: I encountered some issues that prevented final comment cleanup for qwen.js, vllm.js, and logger.js. Their core logging functionality and tag transformations (for qwen.js and vllm.js) are in place from previous steps.
This commit is contained in:
parent
62bcb1950c
commit
857d14e64c
15 changed files with 144 additions and 327 deletions
|
@ -7,13 +7,10 @@ export class Claude {
|
||||||
constructor(model_name, url, params) {
|
constructor(model_name, url, params) {
|
||||||
this.model_name = model_name;
|
this.model_name = model_name;
|
||||||
this.params = params || {};
|
this.params = params || {};
|
||||||
|
|
||||||
let config = {};
|
let config = {};
|
||||||
if (url)
|
if (url)
|
||||||
config.baseURL = url;
|
config.baseURL = url;
|
||||||
|
|
||||||
config.apiKey = getKey('ANTHROPIC_API_KEY');
|
config.apiKey = getKey('ANTHROPIC_API_KEY');
|
||||||
|
|
||||||
this.anthropic = new Anthropic(config);
|
this.anthropic = new Anthropic(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +21,7 @@ export class Claude {
|
||||||
console.log('Awaiting anthropic api response...')
|
console.log('Awaiting anthropic api response...')
|
||||||
if (!this.params.max_tokens) {
|
if (!this.params.max_tokens) {
|
||||||
if (this.params.thinking?.budget_tokens) {
|
if (this.params.thinking?.budget_tokens) {
|
||||||
this.params.max_tokens = this.params.thinking.budget_tokens + 1000;
|
this.params.max_tokens = this.params.thinking.budget_tokens + 1000; // max_tokens must be greater
|
||||||
// max_tokens must be greater than thinking.budget_tokens
|
|
||||||
} else {
|
} else {
|
||||||
this.params.max_tokens = 4096;
|
this.params.max_tokens = 4096;
|
||||||
}
|
}
|
||||||
|
@ -36,9 +32,7 @@ export class Claude {
|
||||||
messages: messages,
|
messages: messages,
|
||||||
...(this.params || {})
|
...(this.params || {})
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Received.')
|
console.log('Received.')
|
||||||
// get first content of type text
|
|
||||||
const textContent = resp.content.find(content => content.type === 'text');
|
const textContent = resp.content.find(content => content.type === 'text');
|
||||||
if (textContent) {
|
if (textContent) {
|
||||||
res = textContent.text;
|
res = textContent.text;
|
||||||
|
@ -46,8 +40,7 @@ export class Claude {
|
||||||
console.warn('No text content found in the response.');
|
console.warn('No text content found in the response.');
|
||||||
res = 'No response from Claude.';
|
res = 'No response from Claude.';
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
if (err.message.includes("does not support image input")) {
|
if (err.message.includes("does not support image input")) {
|
||||||
res = "Vision is only supported by certain models.";
|
res = "Vision is only supported by certain models.";
|
||||||
} else {
|
} else {
|
||||||
|
@ -56,15 +49,16 @@ export class Claude {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
const logMessagesForClaude = [{ role: "system", content: systemMessage }].concat(turns);
|
const logMessagesForClaude = [{ role: "system", content: systemMessage }].concat(turns);
|
||||||
// The actual 'turns' passed to anthropic.messages.create are already strictFormatted
|
if (typeof res === 'string') {
|
||||||
// For logging, we want to capture the input as it was conceptually given.
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(logMessagesForClaude), res);
|
log(JSON.stringify(logMessagesForClaude), res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendVisionRequest(turns, systemMessage, imageBuffer) {
|
async sendVisionRequest(turns, systemMessage, imageBuffer) {
|
||||||
const visionUserMessageContent = [
|
const visionUserMessageContent = [
|
||||||
{ type: "text", text: systemMessage }, // Text part of the vision message
|
{ type: "text", text: systemMessage },
|
||||||
{
|
{
|
||||||
type: "image",
|
type: "image",
|
||||||
source: {
|
source: {
|
||||||
|
@ -74,23 +68,11 @@ export class Claude {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
// Create the turns structure that will actually be sent to the API
|
|
||||||
const turnsForAPIRequest = [...turns, { role: "user", content: visionUserMessageContent }];
|
const turnsForAPIRequest = [...turns, { role: "user", content: visionUserMessageContent }];
|
||||||
|
|
||||||
// Call sendRequest. Note: Claude's sendRequest takes systemMessage separately.
|
const res = await this.sendRequest(turnsForAPIRequest, systemMessage);
|
||||||
// The systemMessage parameter for sendRequest here should be the overall system instruction,
|
|
||||||
// not the text part of the vision message if that's already included in turnsForAPIRequest.
|
|
||||||
// Assuming the passed 'systemMessage' to sendVisionRequest is the vision prompt.
|
|
||||||
// And the actual system prompt for the Claude API call is handled by sendRequest's own 'systemMessage' param.
|
|
||||||
// Let's assume the 'systemMessage' passed to sendVisionRequest is the primary text prompt for the vision task.
|
|
||||||
// The 'sendRequest' function will handle its own logging using log().
|
|
||||||
|
|
||||||
const res = await this.sendRequest(turnsForAPIRequest, systemMessage); // This will call log() internally for the text part.
|
|
||||||
|
|
||||||
// After getting the response, specifically log the vision interaction.
|
|
||||||
if (imageBuffer && res) {
|
if (imageBuffer && res) {
|
||||||
// 'turns' are the original conversation turns *before* adding the vision-specific user message.
|
|
||||||
// 'systemMessage' here is used as the 'visionMessage' (the text prompt accompanying the image).
|
|
||||||
logVision(turns, imageBuffer, res, systemMessage);
|
logVision(turns, imageBuffer, res, systemMessage);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -7,38 +7,30 @@ export class DeepSeek {
|
||||||
constructor(model_name, url, params) {
|
constructor(model_name, url, params) {
|
||||||
this.model_name = model_name;
|
this.model_name = model_name;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
let config = {};
|
let config = {};
|
||||||
|
|
||||||
config.baseURL = url || 'https://api.deepseek.com';
|
config.baseURL = url || 'https://api.deepseek.com';
|
||||||
config.apiKey = getKey('DEEPSEEK_API_KEY');
|
config.apiKey = getKey('DEEPSEEK_API_KEY');
|
||||||
|
|
||||||
this.openai = new OpenAIApi(config);
|
this.openai = new OpenAIApi(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage, stop_seq='***') {
|
async sendRequest(turns, systemMessage, stop_seq='***') {
|
||||||
let messages = [{'role': 'system', 'content': systemMessage}].concat(turns);
|
let messages = [{'role': 'system', 'content': systemMessage}].concat(turns);
|
||||||
|
|
||||||
messages = strictFormat(messages);
|
messages = strictFormat(messages);
|
||||||
|
|
||||||
const pack = {
|
const pack = {
|
||||||
model: this.model_name || "deepseek-chat",
|
model: this.model_name || "deepseek-chat",
|
||||||
messages,
|
messages,
|
||||||
stop: stop_seq,
|
stop: stop_seq,
|
||||||
...(this.params || {})
|
...(this.params || {})
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = null;
|
let res = null;
|
||||||
try {
|
try {
|
||||||
console.log('Awaiting deepseek api response...')
|
console.log('Awaiting deepseek api response...')
|
||||||
// console.log('Messages:', messages);
|
|
||||||
let completion = await this.openai.chat.completions.create(pack);
|
let completion = await this.openai.chat.completions.create(pack);
|
||||||
if (completion.choices[0].finish_reason == 'length')
|
if (completion.choices[0].finish_reason == 'length')
|
||||||
throw new Error('Context length exceeded');
|
throw new Error('Context length exceeded');
|
||||||
console.log('Received.')
|
console.log('Received.')
|
||||||
res = completion.choices[0].message.content;
|
res = completion.choices[0].message.content;
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) {
|
if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) {
|
||||||
console.log('Context length exceeded, trying again with shorter context.');
|
console.log('Context length exceeded, trying again with shorter context.');
|
||||||
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
||||||
|
@ -47,6 +39,9 @@ export class DeepSeek {
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), res);
|
log(JSON.stringify(messages), res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +50,3 @@ export class DeepSeek {
|
||||||
throw new Error('Embeddings are not supported by Deepseek.');
|
throw new Error('Embeddings are not supported by Deepseek.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,28 +9,12 @@ export class Gemini {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.safetySettings = [
|
this.safetySettings = [
|
||||||
{
|
{ "category": "HARM_CATEGORY_DANGEROUS", "threshold": "BLOCK_NONE" },
|
||||||
"category": "HARM_CATEGORY_DANGEROUS",
|
{ "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE" },
|
||||||
"threshold": "BLOCK_NONE",
|
{ "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE" },
|
||||||
},
|
{ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE" },
|
||||||
{
|
{ "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE" },
|
||||||
"category": "HARM_CATEGORY_HARASSMENT",
|
|
||||||
"threshold": "BLOCK_NONE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
|
||||||
"threshold": "BLOCK_NONE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
|
||||||
"threshold": "BLOCK_NONE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
|
||||||
"threshold": "BLOCK_NONE",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
this.genAI = new GoogleGenerativeAI(getKey('GEMINI_API_KEY'));
|
this.genAI = new GoogleGenerativeAI(getKey('GEMINI_API_KEY'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,20 +25,11 @@ export class Gemini {
|
||||||
// systemInstruction does not work bc google is trash
|
// systemInstruction does not work bc google is trash
|
||||||
};
|
};
|
||||||
if (this.url) {
|
if (this.url) {
|
||||||
model = this.genAI.getGenerativeModel(
|
model = this.genAI.getGenerativeModel(modelConfig, { baseUrl: this.url }, { safetySettings: this.safetySettings });
|
||||||
modelConfig,
|
|
||||||
{ baseUrl: this.url },
|
|
||||||
{ safetySettings: this.safetySettings }
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
model = this.genAI.getGenerativeModel(
|
model = this.genAI.getGenerativeModel(modelConfig, { safetySettings: this.safetySettings });
|
||||||
modelConfig,
|
|
||||||
{ safetySettings: this.safetySettings }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Awaiting Google API response...');
|
console.log('Awaiting Google API response...');
|
||||||
|
|
||||||
const originalTurnsForLog = [{role: 'system', content: systemMessage}, ...turns];
|
const originalTurnsForLog = [{role: 'system', content: systemMessage}, ...turns];
|
||||||
turns.unshift({ role: 'system', content: systemMessage });
|
turns.unshift({ role: 'system', content: systemMessage });
|
||||||
turns = strictFormat(turns);
|
turns = strictFormat(turns);
|
||||||
|
@ -65,25 +40,14 @@ export class Gemini {
|
||||||
parts: [{ text: turn.content }]
|
parts: [{ text: turn.content }]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await model.generateContent({
|
const result = await model.generateContent({
|
||||||
contents,
|
contents,
|
||||||
generationConfig: {
|
generationConfig: { ...(this.params || {}) }
|
||||||
...(this.params || {})
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const response = await result.response;
|
const response = await result.response;
|
||||||
let text;
|
let text;
|
||||||
|
|
||||||
// Handle "thinking" models since they smart
|
|
||||||
if (this.model_name && this.model_name.includes("thinking")) {
|
if (this.model_name && this.model_name.includes("thinking")) {
|
||||||
if (
|
if (response.candidates?.length > 0 && response.candidates[0].content?.parts?.length > 1) {
|
||||||
response.candidates &&
|
|
||||||
response.candidates.length > 0 &&
|
|
||||||
response.candidates[0].content &&
|
|
||||||
response.candidates[0].content.parts &&
|
|
||||||
response.candidates[0].content.parts.length > 1
|
|
||||||
) {
|
|
||||||
text = response.candidates[0].content.parts[1].text;
|
text = response.candidates[0].content.parts[1].text;
|
||||||
} else {
|
} else {
|
||||||
console.warn("Unexpected response structure for thinking model:", response);
|
console.warn("Unexpected response structure for thinking model:", response);
|
||||||
|
@ -92,9 +56,10 @@ export class Gemini {
|
||||||
} else {
|
} else {
|
||||||
text = response.text();
|
text = response.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Received.');
|
console.log('Received.');
|
||||||
|
if (typeof text === 'string') {
|
||||||
|
text = text.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(originalTurnsForLog), text);
|
log(JSON.stringify(originalTurnsForLog), text);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
@ -102,25 +67,11 @@ export class Gemini {
|
||||||
async sendVisionRequest(turns, systemMessage, imageBuffer) {
|
async sendVisionRequest(turns, systemMessage, imageBuffer) {
|
||||||
let model;
|
let model;
|
||||||
if (this.url) {
|
if (this.url) {
|
||||||
model = this.genAI.getGenerativeModel(
|
model = this.genAI.getGenerativeModel({ model: this.model_name || "gemini-1.5-flash" }, { baseUrl: this.url }, { safetySettings: this.safetySettings });
|
||||||
{ model: this.model_name || "gemini-1.5-flash" },
|
|
||||||
{ baseUrl: this.url },
|
|
||||||
{ safetySettings: this.safetySettings }
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
model = this.genAI.getGenerativeModel(
|
model = this.genAI.getGenerativeModel({ model: this.model_name || "gemini-1.5-flash" }, { safetySettings: this.safetySettings });
|
||||||
{ model: this.model_name || "gemini-1.5-flash" },
|
|
||||||
{ safetySettings: this.safetySettings }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
const imagePart = { inlineData: { data: imageBuffer.toString('base64'), mimeType: 'image/jpeg' } };
|
||||||
const imagePart = {
|
|
||||||
inlineData: {
|
|
||||||
data: imageBuffer.toString('base64'),
|
|
||||||
mimeType: 'image/jpeg'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stop_seq = '***';
|
const stop_seq = '***';
|
||||||
const prompt = toSinglePrompt(turns, systemMessage, stop_seq, 'model');
|
const prompt = toSinglePrompt(turns, systemMessage, stop_seq, 'model');
|
||||||
let res = null;
|
let res = null;
|
||||||
|
@ -131,11 +82,9 @@ export class Gemini {
|
||||||
const text = response.text();
|
const text = response.text();
|
||||||
console.log('Received.');
|
console.log('Received.');
|
||||||
if (imageBuffer && text) {
|
if (imageBuffer && text) {
|
||||||
// 'turns' is the original conversation history.
|
|
||||||
// 'prompt' is the vision message text.
|
|
||||||
logVision(turns, imageBuffer, text, prompt);
|
logVision(turns, imageBuffer, text, prompt);
|
||||||
}
|
}
|
||||||
if (!text.includes(stop_seq)) return text; // No logging for this early return? Or log text then return text? Assuming logVision is the primary goal.
|
if (!text.includes(stop_seq)) return text;
|
||||||
const idx = text.indexOf(stop_seq);
|
const idx = text.indexOf(stop_seq);
|
||||||
res = text.slice(0, idx);
|
res = text.slice(0, idx);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -146,6 +95,9 @@ export class Gemini {
|
||||||
res = "An unexpected error occurred, please try again.";
|
res = "An unexpected error occurred, please try again.";
|
||||||
}
|
}
|
||||||
const loggedTurnsForError = [{role: 'system', content: systemMessage}, ...turns];
|
const loggedTurnsForError = [{role: 'system', content: systemMessage}, ...turns];
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(loggedTurnsForError), res);
|
log(JSON.stringify(loggedTurnsForError), res);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@ -154,16 +106,10 @@ export class Gemini {
|
||||||
async embed(text) {
|
async embed(text) {
|
||||||
let model;
|
let model;
|
||||||
if (this.url) {
|
if (this.url) {
|
||||||
model = this.genAI.getGenerativeModel(
|
model = this.genAI.getGenerativeModel({ model: "text-embedding-004" }, { baseUrl: this.url });
|
||||||
{ model: "text-embedding-004" },
|
|
||||||
{ baseUrl: this.url }
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
model = this.genAI.getGenerativeModel(
|
model = this.genAI.getGenerativeModel({ model: "text-embedding-004" });
|
||||||
{ model: "text-embedding-004" }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await model.embedContent(text);
|
const result = await model.embedContent(text);
|
||||||
return result.embedding.values;
|
return result.embedding.values;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import OpenAIApi from 'openai';
|
import OpenAIApi from 'openai';
|
||||||
import { getKey } from '../utils/keys.js';
|
import { getKey } from '../utils/keys.js';
|
||||||
import { log, logVision } from '../../logger.js'; // Added import
|
import { log, logVision } from '../../logger.js';
|
||||||
|
|
||||||
export class GLHF {
|
export class GLHF {
|
||||||
constructor(model_name, url) {
|
constructor(model_name, url) {
|
||||||
|
@ -16,8 +16,7 @@ export class GLHF {
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage, stop_seq = '***') {
|
async sendRequest(turns, systemMessage, stop_seq = '***') {
|
||||||
// Construct the message array for the API request.
|
let messages = [{ role: 'system', content: systemMessage }].concat(turns);
|
||||||
let messages = [{ role: 'system', content: systemMessage }].concat(turns); // messages for API and logging
|
|
||||||
const pack = {
|
const pack = {
|
||||||
model: this.model_name || "hf:meta-llama/Llama-3.1-405B-Instruct",
|
model: this.model_name || "hf:meta-llama/Llama-3.1-405B-Instruct",
|
||||||
messages,
|
messages,
|
||||||
|
@ -37,21 +36,18 @@ export class GLHF {
|
||||||
throw new Error('Context length exceeded');
|
throw new Error('Context length exceeded');
|
||||||
}
|
}
|
||||||
let res = completion.choices[0].message.content;
|
let res = completion.choices[0].message.content;
|
||||||
// If there's an open <think> tag without a corresponding </think>, retry.
|
|
||||||
if (res.includes("<think>") && !res.includes("</think>")) {
|
if (res.includes("<think>") && !res.includes("</think>")) {
|
||||||
console.warn("Partial <think> block detected. Re-generating...");
|
console.warn("Partial <think> block detected. Re-generating...");
|
||||||
if (attempt < maxAttempts) continue; // Continue if not the last attempt
|
if (attempt < maxAttempts) continue;
|
||||||
}
|
}
|
||||||
// If there's a closing </think> tag but no opening <think>, prepend one.
|
|
||||||
if (res.includes("</think>") && !res.includes("<think>")) {
|
if (res.includes("</think>") && !res.includes("<think>")) {
|
||||||
res = "<think>" + res;
|
res = "<think>" + res;
|
||||||
}
|
}
|
||||||
finalRes = res.replace(/<\|separator\|>/g, '*no response*');
|
finalRes = res.replace(/<\|separator\|>/g, '*no response*');
|
||||||
break; // Valid response obtained.
|
break;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if ((err.message === 'Context length exceeded' || err.code === 'context_length_exceeded') && turns.length > 1) {
|
if ((err.message === 'Context length exceeded' || err.code === 'context_length_exceeded') && turns.length > 1) {
|
||||||
console.log('Context length exceeded, trying again with shorter context.');
|
console.log('Context length exceeded, trying again with shorter context.');
|
||||||
// Recursive call will handle its own logging
|
|
||||||
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
||||||
} else {
|
} else {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -60,10 +56,14 @@ export class GLHF {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (finalRes === null) { // Should only be reached if loop completed due to continue on last attempt
|
if (finalRes === null) {
|
||||||
finalRes = "I thought too hard, sorry, try again";
|
finalRes = "I thought too hard, sorry, try again";
|
||||||
}
|
}
|
||||||
log(JSON.stringify(messages), finalRes); // Added log call
|
|
||||||
|
if (typeof finalRes === 'string') {
|
||||||
|
finalRes = finalRes.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
|
log(JSON.stringify(messages), finalRes);
|
||||||
return finalRes;
|
return finalRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,12 @@ export class GPT {
|
||||||
constructor(model_name, url, params) {
|
constructor(model_name, url, params) {
|
||||||
this.model_name = model_name;
|
this.model_name = model_name;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
let config = {};
|
let config = {};
|
||||||
if (url)
|
if (url)
|
||||||
config.baseURL = url;
|
config.baseURL = url;
|
||||||
|
|
||||||
if (hasKey('OPENAI_ORG_ID'))
|
if (hasKey('OPENAI_ORG_ID'))
|
||||||
config.organization = getKey('OPENAI_ORG_ID');
|
config.organization = getKey('OPENAI_ORG_ID');
|
||||||
|
|
||||||
config.apiKey = getKey('OPENAI_API_KEY');
|
config.apiKey = getKey('OPENAI_API_KEY');
|
||||||
|
|
||||||
this.openai = new OpenAIApi(config);
|
this.openai = new OpenAIApi(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,19 +28,15 @@ export class GPT {
|
||||||
if (this.model_name.includes('o1')) {
|
if (this.model_name.includes('o1')) {
|
||||||
delete pack.stop;
|
delete pack.stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = null;
|
let res = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Awaiting openai api response from model', this.model_name)
|
console.log('Awaiting openai api response from model', this.model_name)
|
||||||
// console.log('Messages:', messages);
|
|
||||||
let completion = await this.openai.chat.completions.create(pack);
|
let completion = await this.openai.chat.completions.create(pack);
|
||||||
if (completion.choices[0].finish_reason == 'length')
|
if (completion.choices[0].finish_reason == 'length')
|
||||||
throw new Error('Context length exceeded');
|
throw new Error('Context length exceeded');
|
||||||
console.log('Received.')
|
console.log('Received.')
|
||||||
res = completion.choices[0].message.content;
|
res = completion.choices[0].message.content;
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) {
|
if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) {
|
||||||
console.log('Context length exceeded, trying again with shorter context.');
|
console.log('Context length exceeded, trying again with shorter context.');
|
||||||
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
||||||
|
@ -56,39 +48,29 @@ export class GPT {
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assuming res is assigned in both try and catch.
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), res);
|
log(JSON.stringify(messages), res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendVisionRequest(original_turns, systemMessage, imageBuffer) { // Renamed 'messages' to 'original_turns'
|
async sendVisionRequest(original_turns, systemMessage, imageBuffer) {
|
||||||
const imageFormattedTurns = [...original_turns];
|
const imageFormattedTurns = [...original_turns];
|
||||||
imageFormattedTurns.push({
|
imageFormattedTurns.push({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [
|
content: [
|
||||||
{ type: "text", text: systemMessage }, // This is the vision prompt text
|
{ type: "text", text: systemMessage },
|
||||||
{
|
{
|
||||||
type: "image_url",
|
type: "image_url",
|
||||||
image_url: {
|
image_url: { url: `data:image/jpeg;base64,${imageBuffer.toString('base64')}` }
|
||||||
url: `data:image/jpeg;base64,${imageBuffer.toString('base64')}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pass a system message to sendRequest. If systemMessage is purely for vision prompt,
|
const res = await this.sendRequest(imageFormattedTurns, systemMessage);
|
||||||
// then the main system message for the API call itself might be different or empty.
|
|
||||||
// For GPT, system messages are part of the 'messages' array.
|
|
||||||
// The sendRequest will create its 'messages' array including a system role.
|
|
||||||
// Let's assume the 'systemMessage' param here is the specific prompt for the vision task.
|
|
||||||
// The 'sendRequest' will use its own 'systemMessage' parameter from its signature for the API system message.
|
|
||||||
// For consistency, the 'systemMessage' for the API call in sendRequest should be the overarching one.
|
|
||||||
|
|
||||||
const res = await this.sendRequest(imageFormattedTurns, systemMessage); // This will call log() for the text part.
|
|
||||||
|
|
||||||
if (imageBuffer && res) {
|
if (imageBuffer && res) {
|
||||||
// 'original_turns' is the conversation history before adding the image-specific content.
|
|
||||||
// 'systemMessage' is the vision prompt text.
|
|
||||||
logVision(original_turns, imageBuffer, res, systemMessage);
|
logVision(original_turns, imageBuffer, res, systemMessage);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@ -104,8 +86,4 @@ export class GPT {
|
||||||
});
|
});
|
||||||
return embedding.data[0].embedding;
|
return embedding.data[0].embedding;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,39 +8,32 @@ export class Grok {
|
||||||
this.model_name = model_name;
|
this.model_name = model_name;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
let config = {};
|
let config = {};
|
||||||
if (url)
|
if (url)
|
||||||
config.baseURL = url;
|
config.baseURL = url;
|
||||||
else
|
else
|
||||||
config.baseURL = "https://api.x.ai/v1"
|
config.baseURL = "https://api.x.ai/v1"
|
||||||
|
|
||||||
config.apiKey = getKey('XAI_API_KEY');
|
config.apiKey = getKey('XAI_API_KEY');
|
||||||
|
|
||||||
this.openai = new OpenAIApi(config);
|
this.openai = new OpenAIApi(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage, stop_seq='***') {
|
async sendRequest(turns, systemMessage, stop_seq='***') {
|
||||||
let messages = [{'role': 'system', 'content': systemMessage}].concat(turns);
|
let messages = [{'role': 'system', 'content': systemMessage}].concat(turns);
|
||||||
|
|
||||||
const pack = {
|
const pack = {
|
||||||
model: this.model_name || "grok-beta",
|
model: this.model_name || "grok-beta",
|
||||||
messages,
|
messages,
|
||||||
stop: [stop_seq],
|
stop: [stop_seq],
|
||||||
...(this.params || {})
|
...(this.params || {})
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = null;
|
let res = null;
|
||||||
try {
|
try {
|
||||||
console.log('Awaiting xai api response...')
|
console.log('Awaiting xai api response...')
|
||||||
///console.log('Messages:', messages);
|
|
||||||
let completion = await this.openai.chat.completions.create(pack);
|
let completion = await this.openai.chat.completions.create(pack);
|
||||||
if (completion.choices[0].finish_reason == 'length')
|
if (completion.choices[0].finish_reason == 'length')
|
||||||
throw new Error('Context length exceeded');
|
throw new Error('Context length exceeded');
|
||||||
console.log('Received.')
|
console.log('Received.')
|
||||||
res = completion.choices[0].message.content;
|
res = completion.choices[0].message.content;
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) {
|
if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) {
|
||||||
console.log('Context length exceeded, trying again with shorter context.');
|
console.log('Context length exceeded, trying again with shorter context.');
|
||||||
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
|
||||||
|
@ -53,7 +46,10 @@ export class Grok {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// sometimes outputs special token <|separator|>, just replace it
|
// sometimes outputs special token <|separator|>, just replace it
|
||||||
const finalResponseText = res ? res.replace(/<\|separator\|>/g, '*no response*') : (res === null ? "*no response*" : res);
|
let finalResponseText = res ? res.replace(/<\|separator\|>/g, '*no response*') : (res === null ? "*no response*" : res);
|
||||||
|
if (typeof finalResponseText === 'string') {
|
||||||
|
finalResponseText = finalResponseText.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), finalResponseText);
|
log(JSON.stringify(messages), finalResponseText);
|
||||||
return finalResponseText;
|
return finalResponseText;
|
||||||
}
|
}
|
||||||
|
@ -63,20 +59,17 @@ export class Grok {
|
||||||
imageFormattedTurns.push({
|
imageFormattedTurns.push({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [
|
content: [
|
||||||
{ type: "text", text: systemMessage }, // systemMessage is the vision prompt
|
{ type: "text", text: systemMessage },
|
||||||
{
|
{
|
||||||
type: "image_url",
|
type: "image_url",
|
||||||
image_url: {
|
image_url: { url: `data:image/jpeg;base64,${imageBuffer.toString('base64')}` }
|
||||||
url: `data:image/jpeg;base64,${imageBuffer.toString('base64')}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assuming 'systemMessage' (the vision prompt) should also act as the system message for this specific API call.
|
const res = await this.sendRequest(imageFormattedTurns, systemMessage);
|
||||||
const res = await this.sendRequest(imageFormattedTurns, systemMessage); // sendRequest will call log()
|
|
||||||
|
|
||||||
if (imageBuffer && res) { // Check res to ensure a response was received
|
if (imageBuffer && res) {
|
||||||
logVision(original_turns, imageBuffer, res, systemMessage);
|
logVision(original_turns, imageBuffer, res, systemMessage);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@ -86,6 +79,3 @@ export class Grok {
|
||||||
throw new Error('Embeddings are not supported by Grok.');
|
throw new Error('Embeddings are not supported by Grok.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,7 @@ import { log, logVision } from '../../logger.js';
|
||||||
|
|
||||||
// Umbrella class for everything under the sun... That GroqCloud provides, that is.
|
// Umbrella class for everything under the sun... That GroqCloud provides, that is.
|
||||||
export class GroqCloudAPI {
|
export class GroqCloudAPI {
|
||||||
|
|
||||||
constructor(model_name, url, params) {
|
constructor(model_name, url, params) {
|
||||||
|
|
||||||
this.model_name = model_name;
|
this.model_name = model_name;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.params = params || {};
|
this.params = params || {};
|
||||||
|
@ -19,21 +17,15 @@ export class GroqCloudAPI {
|
||||||
delete this.params.tools;
|
delete this.params.tools;
|
||||||
// This is just a bit of future-proofing in case we drag Mindcraft in that direction.
|
// This is just a bit of future-proofing in case we drag Mindcraft in that direction.
|
||||||
|
|
||||||
// I'm going to do a sneaky ReplicateAPI theft for a lot of this, aren't I?
|
|
||||||
if (this.url)
|
if (this.url)
|
||||||
console.warn("Groq Cloud has no implementation for custom URLs. Ignoring provided URL.");
|
console.warn("Groq Cloud has no implementation for custom URLs. Ignoring provided URL.");
|
||||||
|
|
||||||
this.groq = new Groq({ apiKey: getKey('GROQCLOUD_API_KEY') });
|
this.groq = new Groq({ apiKey: getKey('GROQCLOUD_API_KEY') });
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage, stop_seq = null) {
|
async sendRequest(turns, systemMessage, stop_seq = null) {
|
||||||
// Construct messages array
|
|
||||||
let messages = [{"role": "system", "content": systemMessage}].concat(turns);
|
let messages = [{"role": "system", "content": systemMessage}].concat(turns);
|
||||||
|
|
||||||
let res = null;
|
let res = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("Awaiting Groq response...");
|
console.log("Awaiting Groq response...");
|
||||||
|
|
||||||
|
@ -43,7 +35,6 @@ export class GroqCloudAPI {
|
||||||
this.params.max_completion_tokens = this.params.max_tokens;
|
this.params.max_completion_tokens = this.params.max_tokens;
|
||||||
delete this.params.max_tokens;
|
delete this.params.max_tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.params.max_completion_tokens) {
|
if (!this.params.max_completion_tokens) {
|
||||||
this.params.max_completion_tokens = 4000;
|
this.params.max_completion_tokens = 4000;
|
||||||
}
|
}
|
||||||
|
@ -56,16 +47,15 @@ export class GroqCloudAPI {
|
||||||
...(this.params || {})
|
...(this.params || {})
|
||||||
});
|
});
|
||||||
|
|
||||||
// res = completion.choices[0].message; // Original assignment
|
let responseText = completion.choices[0].message.content;
|
||||||
let responseText = completion.choices[0].message.content; // Get content
|
if (typeof responseText === 'string') {
|
||||||
|
responseText = responseText.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
log(JSON.stringify(messages), responseText); // Log here
|
}
|
||||||
|
log(JSON.stringify(messages), responseText);
|
||||||
// Original cleaning of <think> tags for the *returned* response (not affecting log)
|
// Original cleaning of <think> tags for the *returned* response (not affecting log)
|
||||||
responseText = responseText.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
responseText = responseText.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||||||
return responseText;
|
return responseText;
|
||||||
}
|
} catch(err) {
|
||||||
catch(err) {
|
|
||||||
if (err.message.includes("content must be a string")) {
|
if (err.message.includes("content must be a string")) {
|
||||||
res = "Vision is only supported by certain models.";
|
res = "Vision is only supported by certain models.";
|
||||||
} else {
|
} else {
|
||||||
|
@ -73,32 +63,28 @@ export class GroqCloudAPI {
|
||||||
res = "My brain disconnected, try again.";
|
res = "My brain disconnected, try again.";
|
||||||
}
|
}
|
||||||
console.log(err);
|
console.log(err);
|
||||||
// Log error response
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), res);
|
log(JSON.stringify(messages), res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
// This return is now unreachable due to returns in try/catch, but if logic changes, ensure logging covers it.
|
|
||||||
// log(JSON.stringify(messages), res);
|
|
||||||
// return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendVisionRequest(original_turns, systemMessage, imageBuffer) {
|
async sendVisionRequest(original_turns, systemMessage, imageBuffer) {
|
||||||
const imageMessages = [...original_turns]; // Use a copy
|
const imageMessages = [...original_turns];
|
||||||
imageMessages.push({
|
imageMessages.push({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [
|
content: [
|
||||||
{ type: "text", text: systemMessage }, // systemMessage is the vision prompt
|
{ type: "text", text: systemMessage },
|
||||||
{
|
{
|
||||||
type: "image_url",
|
type: "image_url",
|
||||||
image_url: {
|
image_url: { url: `data:image/jpeg;base64,${imageBuffer.toString('base64')}` }
|
||||||
url: `data:image/jpeg;base64,${imageBuffer.toString('base64')}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assuming 'systemMessage' (the vision prompt) should also act as the system message for this API call.
|
const res = await this.sendRequest(imageMessages, systemMessage);
|
||||||
const res = await this.sendRequest(imageMessages, systemMessage); // sendRequest will call log()
|
|
||||||
|
|
||||||
if (imageBuffer && res) {
|
if (imageBuffer && res) {
|
||||||
logVision(original_turns, imageBuffer, res, systemMessage);
|
logVision(original_turns, imageBuffer, res, systemMessage);
|
||||||
|
|
|
@ -5,29 +5,22 @@ import { log, logVision } from '../../logger.js';
|
||||||
|
|
||||||
export class HuggingFace {
|
export class HuggingFace {
|
||||||
constructor(model_name, url, params) {
|
constructor(model_name, url, params) {
|
||||||
// Remove 'huggingface/' prefix if present
|
|
||||||
this.model_name = model_name.replace('huggingface/', '');
|
this.model_name = model_name.replace('huggingface/', '');
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
if (this.url) {
|
if (this.url) {
|
||||||
console.warn("Hugging Face doesn't support custom urls!");
|
console.warn("Hugging Face doesn't support custom urls!");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.huggingface = new HfInference(getKey('HUGGINGFACE_API_KEY'));
|
this.huggingface = new HfInference(getKey('HUGGINGFACE_API_KEY'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage) {
|
async sendRequest(turns, systemMessage) {
|
||||||
const stop_seq = '***';
|
const stop_seq = '***';
|
||||||
// Build a single prompt from the conversation turns
|
|
||||||
const prompt = toSinglePrompt(turns, null, stop_seq);
|
const prompt = toSinglePrompt(turns, null, stop_seq);
|
||||||
// Fallback model if none was provided
|
|
||||||
const model_name = this.model_name || 'meta-llama/Meta-Llama-3-8B';
|
const model_name = this.model_name || 'meta-llama/Meta-Llama-3-8B';
|
||||||
// Combine system message with the prompt
|
|
||||||
const logInputMessages = [{role: 'system', content: systemMessage}, ...turns];
|
const logInputMessages = [{role: 'system', content: systemMessage}, ...turns];
|
||||||
const input = systemMessage + "\n" + prompt;
|
const input = systemMessage + "
|
||||||
|
" + prompt;
|
||||||
// We'll try up to 5 times in case of partial <think> blocks for DeepSeek-R1 models.
|
|
||||||
const maxAttempts = 5;
|
const maxAttempts = 5;
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
let finalRes = null;
|
let finalRes = null;
|
||||||
|
@ -37,7 +30,6 @@ export class HuggingFace {
|
||||||
console.log(`Awaiting Hugging Face API response... (model: ${model_name}, attempt: ${attempt})`);
|
console.log(`Awaiting Hugging Face API response... (model: ${model_name}, attempt: ${attempt})`);
|
||||||
let res = '';
|
let res = '';
|
||||||
try {
|
try {
|
||||||
// Consume the streaming response chunk by chunk
|
|
||||||
for await (const chunk of this.huggingface.chatCompletionStream({
|
for await (const chunk of this.huggingface.chatCompletionStream({
|
||||||
model: model_name,
|
model: model_name,
|
||||||
messages: [{ role: "user", content: input }],
|
messages: [{ role: "user", content: input }],
|
||||||
|
@ -48,36 +40,31 @@ export class HuggingFace {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
// Break out immediately; we only retry when handling partial <think> tags.
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the model is DeepSeek-R1, check for mismatched <think> blocks.
|
const hasOpenTag = res.includes("<think>");
|
||||||
const hasOpenTag = res.includes("<think>");
|
const hasCloseTag = res.includes("</think>");
|
||||||
const hasCloseTag = res.includes("</think>");
|
|
||||||
|
|
||||||
// If there's a partial mismatch, warn and retry the entire request.
|
|
||||||
if ((hasOpenTag && !hasCloseTag)) {
|
|
||||||
console.warn("Partial <think> block detected. Re-generating...");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both tags are present, remove the <think> block entirely.
|
|
||||||
if (hasOpenTag && hasCloseTag) {
|
|
||||||
res = res.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if ((hasOpenTag && !hasCloseTag)) {
|
||||||
|
console.warn("Partial <think> block detected. Re-generating...");
|
||||||
|
if (attempt < maxAttempts) continue;
|
||||||
|
}
|
||||||
|
if (hasOpenTag && hasCloseTag) {
|
||||||
|
res = res.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||||||
|
}
|
||||||
finalRes = res;
|
finalRes = res;
|
||||||
break; // Exit loop if we got a valid response.
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no valid response was obtained after max attempts, assign a fallback.
|
|
||||||
if (finalRes == null) {
|
if (finalRes == null) {
|
||||||
console.warn("Could not get a valid <think> block or normal response after max attempts.");
|
console.warn("Could not get a valid response after max attempts.");
|
||||||
finalRes = 'I thought too hard, sorry, try again.';
|
finalRes = 'I thought too hard, sorry, try again.';
|
||||||
}
|
}
|
||||||
console.log('Received.');
|
console.log('Received.');
|
||||||
console.log(finalRes);
|
if (typeof finalRes === 'string') {
|
||||||
|
finalRes = finalRes.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(logInputMessages), finalRes);
|
log(JSON.stringify(logInputMessages), finalRes);
|
||||||
return finalRes;
|
return finalRes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { getKey } from '../utils/keys.js';
|
import { getKey } from '../utils/keys.js';
|
||||||
import { log, logVision } from '../../logger.js'; // Added import
|
import { log, logVision } from '../../logger.js';
|
||||||
|
|
||||||
export class Hyperbolic {
|
export class Hyperbolic {
|
||||||
constructor(modelName, apiUrl) {
|
constructor(modelName, apiUrl) {
|
||||||
this.modelName = modelName || "deepseek-ai/DeepSeek-V3";
|
this.modelName = modelName || "deepseek-ai/DeepSeek-V3";
|
||||||
this.apiUrl = apiUrl || "https://api.hyperbolic.xyz/v1/chat/completions";
|
this.apiUrl = apiUrl || "https://api.hyperbolic.xyz/v1/chat/completions";
|
||||||
|
|
||||||
// Retrieve the Hyperbolic API key from keys.js
|
|
||||||
this.apiKey = getKey('HYPERBOLIC_API_KEY');
|
this.apiKey = getKey('HYPERBOLIC_API_KEY');
|
||||||
if (!this.apiKey) {
|
if (!this.apiKey) {
|
||||||
throw new Error('HYPERBOLIC_API_KEY not found. Check your keys.js file.');
|
throw new Error('HYPERBOLIC_API_KEY not found. Check your keys.js file.');
|
||||||
|
@ -15,7 +13,6 @@ export class Hyperbolic {
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage, stopSeq = '***') {
|
async sendRequest(turns, systemMessage, stopSeq = '***') {
|
||||||
const messages = [{ role: 'system', content: systemMessage }, ...turns];
|
const messages = [{ role: 'system', content: systemMessage }, ...turns];
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
model: this.modelName,
|
model: this.modelName,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
|
@ -27,14 +24,12 @@ export class Hyperbolic {
|
||||||
|
|
||||||
const maxAttempts = 5;
|
const maxAttempts = 5;
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
let finalRes = null; // Holds the content after <think> processing and <|separator|> replacement
|
let finalRes = null;
|
||||||
let rawCompletionContent = null; // Holds raw content from API for each attempt
|
let rawCompletionContent = null;
|
||||||
|
|
||||||
while (attempt < maxAttempts) {
|
while (attempt < maxAttempts) {
|
||||||
attempt++;
|
attempt++;
|
||||||
console.log(`Awaiting Hyperbolic API response... (attempt: ${attempt})`);
|
console.log(`Awaiting Hyperbolic API response... (attempt: ${attempt})`);
|
||||||
// console.log('Messages:', messages); // Original console log
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(this.apiUrl, {
|
const response = await fetch(this.apiUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -44,36 +39,27 @@ export class Hyperbolic {
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data?.choices?.[0]?.finish_reason === 'length') {
|
if (data?.choices?.[0]?.finish_reason === 'length') {
|
||||||
throw new Error('Context length exceeded');
|
throw new Error('Context length exceeded');
|
||||||
}
|
}
|
||||||
|
|
||||||
rawCompletionContent = data?.choices?.[0]?.message?.content || '';
|
rawCompletionContent = data?.choices?.[0]?.message?.content || '';
|
||||||
console.log('Received response from Hyperbolic.');
|
console.log('Received response from Hyperbolic.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (
|
if ((err.message === 'Context length exceeded' || err.code === 'context_length_exceeded') && turns.length > 1) {
|
||||||
(err.message === 'Context length exceeded' || err.code === 'context_length_exceeded') &&
|
|
||||||
turns.length > 1
|
|
||||||
) {
|
|
||||||
console.log('Context length exceeded, trying again with a shorter context...');
|
console.log('Context length exceeded, trying again with a shorter context...');
|
||||||
// Recursive call handles its own logging
|
|
||||||
return await this.sendRequest(turns.slice(1), systemMessage, stopSeq);
|
return await this.sendRequest(turns.slice(1), systemMessage, stopSeq);
|
||||||
} else {
|
} else {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
rawCompletionContent = 'My brain disconnected, try again.';
|
rawCompletionContent = 'My brain disconnected, try again.';
|
||||||
// Assign to finalRes here if we are to break and log this error immediately
|
|
||||||
finalRes = rawCompletionContent;
|
finalRes = rawCompletionContent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process <think> blocks
|
|
||||||
let processedContent = rawCompletionContent;
|
let processedContent = rawCompletionContent;
|
||||||
const hasOpenTag = processedContent.includes("<think>");
|
const hasOpenTag = processedContent.includes("<think>");
|
||||||
const hasCloseTag = processedContent.includes("</think>");
|
const hasCloseTag = processedContent.includes("</think>");
|
||||||
|
@ -81,31 +67,27 @@ export class Hyperbolic {
|
||||||
if ((hasOpenTag && !hasCloseTag)) {
|
if ((hasOpenTag && !hasCloseTag)) {
|
||||||
console.warn("Partial <think> block detected. Re-generating...");
|
console.warn("Partial <think> block detected. Re-generating...");
|
||||||
if (attempt < maxAttempts) continue;
|
if (attempt < maxAttempts) continue;
|
||||||
// If last attempt, use the content as is (or error if preferred)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCloseTag && !hasOpenTag) {
|
if (hasCloseTag && !hasOpenTag) {
|
||||||
processedContent = '<think>' + processedContent;
|
processedContent = '<think>' + processedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasOpenTag && hasCloseTag) {
|
if (hasOpenTag && hasCloseTag) {
|
||||||
processedContent = processedContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
processedContent = processedContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
finalRes = processedContent.replace(/<\|separator\|>/g, '*no response*');
|
finalRes = processedContent.replace(/<\|separator\|>/g, '*no response*');
|
||||||
|
|
||||||
// If not retrying due to partial tag, break
|
|
||||||
if (!(hasOpenTag && !hasCloseTag && attempt < maxAttempts)) {
|
if (!(hasOpenTag && !hasCloseTag && attempt < maxAttempts)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalRes == null) {
|
if (finalRes == null) {
|
||||||
console.warn("Could not get a valid response after max attempts, or an error occurred on the last attempt.");
|
finalRes = rawCompletionContent || 'I thought too hard, sorry, try again.';
|
||||||
finalRes = rawCompletionContent || 'I thought too hard, sorry, try again.'; // Use raw if finalRes never got set
|
finalRes = finalRes.replace(/<\|separator\|>/g, '*no response*');
|
||||||
finalRes = finalRes.replace(/<\|separator\|>/g, '*no response*'); // Clean one last time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof finalRes === 'string') {
|
||||||
|
finalRes = finalRes.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), finalRes);
|
log(JSON.stringify(messages), finalRes);
|
||||||
return finalRes;
|
return finalRes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,10 @@ export class Local {
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage) {
|
async sendRequest(turns, systemMessage) {
|
||||||
let model = this.model_name || 'llama3.1'; // Updated to llama3.1, as it is more performant than llama3
|
let model = this.model_name || 'llama3.1';
|
||||||
let messages = strictFormat(turns);
|
let messages = strictFormat(turns);
|
||||||
messages.unshift({ role: 'system', content: systemMessage });
|
messages.unshift({ role: 'system', content: systemMessage });
|
||||||
|
|
||||||
// We'll attempt up to 5 times for models with deepseek-r1-esk reasoning if the <think> tags are mismatched.
|
|
||||||
const maxAttempts = 5;
|
const maxAttempts = 5;
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
let finalRes = null;
|
let finalRes = null;
|
||||||
|
@ -25,14 +24,14 @@ export class Local {
|
||||||
console.log(`Awaiting local response... (model: ${model}, attempt: ${attempt})`);
|
console.log(`Awaiting local response... (model: ${model}, attempt: ${attempt})`);
|
||||||
let res = null;
|
let res = null;
|
||||||
try {
|
try {
|
||||||
res = await this.send(this.chat_endpoint, {
|
let apiResponse = await this.send(this.chat_endpoint, {
|
||||||
model: model,
|
model: model,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
stream: false,
|
stream: false,
|
||||||
...(this.params || {})
|
...(this.params || {})
|
||||||
});
|
});
|
||||||
if (res) {
|
if (apiResponse) {
|
||||||
res = res['message']['content'];
|
res = apiResponse['message']['content'];
|
||||||
} else {
|
} else {
|
||||||
res = 'No response data.';
|
res = 'No response data.';
|
||||||
}
|
}
|
||||||
|
@ -44,38 +43,32 @@ export class Local {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the model name includes "deepseek-r1" or "Andy-3.5-reasoning", then handle the <think> block.
|
const hasOpenTag = res.includes("<think>");
|
||||||
const hasOpenTag = res.includes("<think>");
|
const hasCloseTag = res.includes("</think>");
|
||||||
const hasCloseTag = res.includes("</think>");
|
|
||||||
|
|
||||||
// If there's a partial mismatch, retry to get a complete response.
|
|
||||||
if ((hasOpenTag && !hasCloseTag)) {
|
|
||||||
console.warn("Partial <think> block detected. Re-generating...");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If </think> is present but <think> is not, prepend <think>
|
|
||||||
if (hasCloseTag && !hasOpenTag) {
|
|
||||||
res = '<think>' + res;
|
|
||||||
}
|
|
||||||
// Changed this so if the model reasons, using <think> and </think> but doesn't start the message with <think>, <think> ges prepended to the message so no error occur.
|
|
||||||
|
|
||||||
// If both tags appear, remove them (and everything inside).
|
|
||||||
if (hasOpenTag && hasCloseTag) {
|
|
||||||
res = res.replace(/<think>[\s\S]*?<\/think>/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if ((hasOpenTag && !hasCloseTag)) {
|
||||||
|
console.warn("Partial <think> block detected. Re-generating...");
|
||||||
|
if (attempt < maxAttempts) continue;
|
||||||
|
}
|
||||||
|
if (hasCloseTag && !hasOpenTag) {
|
||||||
|
res = '<think>' + res;
|
||||||
|
}
|
||||||
|
if (hasOpenTag && hasCloseTag) {
|
||||||
|
res = res.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||||||
|
}
|
||||||
finalRes = res;
|
finalRes = res;
|
||||||
break; // Exit the loop if we got a valid response.
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalRes == null) {
|
if (finalRes == null) {
|
||||||
console.warn("Could not get a valid <think> block or normal response after max attempts.");
|
console.warn("Could not get a valid response after max attempts.");
|
||||||
finalRes = 'I thought too hard, sorry, try again.';
|
finalRes = 'I thought too hard, sorry, try again.';
|
||||||
}
|
}
|
||||||
|
if (typeof finalRes === 'string') {
|
||||||
|
finalRes = finalRes.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), finalRes);
|
log(JSON.stringify(messages), finalRes);
|
||||||
return finalRes;
|
return finalRes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,56 +5,35 @@ import { log, logVision } from '../../logger.js';
|
||||||
|
|
||||||
export class Mistral {
|
export class Mistral {
|
||||||
#client;
|
#client;
|
||||||
|
|
||||||
constructor(model_name, url, params) {
|
constructor(model_name, url, params) {
|
||||||
this.model_name = model_name;
|
this.model_name = model_name;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
if (typeof url === "string") {
|
if (typeof url === "string") {
|
||||||
console.warn("Mistral does not support custom URL's, ignoring!");
|
console.warn("Mistral does not support custom URL's, ignoring!");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getKey("MISTRAL_API_KEY")) {
|
if (!getKey("MISTRAL_API_KEY")) {
|
||||||
throw new Error("Mistral API Key missing, make sure to set MISTRAL_API_KEY in settings.json")
|
throw new Error("Mistral API Key missing, make sure to set MISTRAL_API_KEY in settings.json")
|
||||||
}
|
}
|
||||||
|
this.#client = new MistralClient({ apiKey: getKey("MISTRAL_API_KEY") });
|
||||||
this.#client = new MistralClient(
|
|
||||||
{
|
|
||||||
apiKey: getKey("MISTRAL_API_KEY")
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// Prevents the following code from running when model not specified
|
if (typeof this.model_name === "string" && typeof this.model_name.split("/")[1] !== "undefined") {
|
||||||
if (typeof this.model_name === "undefined") return;
|
this.model_name = this.model_name.split("/")[1];
|
||||||
|
|
||||||
// get the model name without the "mistral" or "mistralai" prefix
|
|
||||||
// e.g "mistral/mistral-large-latest" -> "mistral-large-latest"
|
|
||||||
if (typeof model_name.split("/")[1] !== "undefined") {
|
|
||||||
this.model_name = model_name.split("/")[1];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(turns, systemMessage) {
|
async sendRequest(turns, systemMessage) {
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
|
const model = this.model_name || "mistral-large-latest";
|
||||||
|
const messages = [{ role: "system", content: systemMessage }];
|
||||||
|
messages.push(...strictFormat(turns));
|
||||||
try {
|
try {
|
||||||
const model = this.model_name || "mistral-large-latest";
|
|
||||||
|
|
||||||
const messages = [
|
|
||||||
{ role: "system", content: systemMessage }
|
|
||||||
];
|
|
||||||
messages.push(...strictFormat(turns));
|
|
||||||
|
|
||||||
console.log('Awaiting mistral api response...')
|
console.log('Awaiting mistral api response...')
|
||||||
const response = await this.#client.chat.complete({
|
const response = await this.#client.chat.complete({
|
||||||
model,
|
model,
|
||||||
messages,
|
messages,
|
||||||
...(this.params || {})
|
...(this.params || {})
|
||||||
});
|
});
|
||||||
|
|
||||||
result = response.choices[0].message.content;
|
result = response.choices[0].message.content;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message.includes("A request containing images has been given to a model which does not have the 'vision' capability.")) {
|
if (err.message.includes("A request containing images has been given to a model which does not have the 'vision' capability.")) {
|
||||||
|
@ -64,36 +43,26 @@ export class Mistral {
|
||||||
}
|
}
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
result = result.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), result);
|
log(JSON.stringify(messages), result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendVisionRequest(original_turns, systemMessage, imageBuffer) {
|
async sendVisionRequest(original_turns, systemMessage, imageBuffer) {
|
||||||
const imageFormattedTurns = [...original_turns];
|
const imageFormattedTurns = [...original_turns];
|
||||||
// The user message content should be an array for Mistral when including images
|
|
||||||
const userMessageContent = [{ type: "text", text: systemMessage }];
|
const userMessageContent = [{ type: "text", text: systemMessage }];
|
||||||
userMessageContent.push({
|
userMessageContent.push({
|
||||||
type: "image_url", // This structure is based on current code; Mistral SDK might prefer different if it auto-detects from base64 content.
|
type: "image_url",
|
||||||
// The provided code uses 'imageUrl'. Mistral SDK docs show 'image_url' for some contexts or direct base64.
|
|
||||||
// For `chat.complete`, it's usually within the 'content' array of a user message.
|
|
||||||
imageUrl: `data:image/jpeg;base64,${imageBuffer.toString('base64')}`
|
imageUrl: `data:image/jpeg;base64,${imageBuffer.toString('base64')}`
|
||||||
});
|
});
|
||||||
imageFormattedTurns.push({
|
imageFormattedTurns.push({ role: "user", content: userMessageContent });
|
||||||
role: "user",
|
|
||||||
content: userMessageContent // Content is an array
|
|
||||||
});
|
|
||||||
|
|
||||||
// 'systemMessage' passed to sendRequest should be the overarching system prompt.
|
const res = await this.sendRequest(imageFormattedTurns, systemMessage);
|
||||||
// If the 'systemMessage' parameter of sendVisionRequest is the vision text prompt,
|
|
||||||
// and it's already incorporated into imageFormattedTurns, then the systemMessage for sendRequest
|
|
||||||
// might be a different, more general one, or empty if not applicable.
|
|
||||||
// For now, let's assume the 'systemMessage' param of sendVisionRequest is the main prompt for this turn
|
|
||||||
// and should also serve as the system-level instruction for the API call via sendRequest.
|
|
||||||
const res = await this.sendRequest(imageFormattedTurns, systemMessage); // sendRequest will call log()
|
|
||||||
|
|
||||||
if (imageBuffer && res) {
|
if (imageBuffer && res) {
|
||||||
logVision(original_turns, imageBuffer, res, systemMessage); // systemMessage here is the vision prompt
|
logVision(original_turns, imageBuffer, res, systemMessage);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,10 @@ export class Novita {
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log(JSON.stringify(messages), res); // Log before stripping <think> tags
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
|
log(JSON.stringify(messages), res); // Log transformed res
|
||||||
|
|
||||||
// Existing stripping logic for <think> tags
|
// Existing stripping logic for <think> tags
|
||||||
if (res && typeof res === 'string' && res.includes('<think>')) {
|
if (res && typeof res === 'string' && res.includes('<think>')) {
|
||||||
|
|
|
@ -46,6 +46,9 @@ export class Qwen {
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), res);
|
log(JSON.stringify(messages), res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ export class ReplicateAPI {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(logInputMessages), res);
|
log(JSON.stringify(logInputMessages), res);
|
||||||
console.log('Received.');
|
console.log('Received.');
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -57,6 +57,9 @@ export class VLLM {
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
res = res.replace(/<thinking>/g, '<think>').replace(/<\/thinking>/g, '</think>');
|
||||||
|
}
|
||||||
log(JSON.stringify(messages), res);
|
log(JSON.stringify(messages), res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue