diff --git a/.gitignore b/.gitignore
index c55f6a9..22d104f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,4 @@ services/viaproxy/saves.json
services/viaproxy/viaproxy.yml
tmp/
wandb/
-experiments/
\ No newline at end of file
+experiments/
diff --git a/README.md b/README.md
index 2879c90..d1199b3 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,11 @@ You can configure the agent's name, model, and prompts in their profile like `an
| API | Config Variable | Example Model name | Docs |
|------|------|------|------|
| `openai` | `OPENAI_API_KEY` | `gpt-4o-mini` | [docs](https://platform.openai.com/docs/models) |
-| `google` | `GEMINI_API_KEY` | `gemini-pro` | [docs](https://ai.google.dev/gemini-api/docs/models/gemini) |
+| `google` | `GEMINI_API_KEY` | `gemini-2.0-flash` | [docs](https://ai.google.dev/gemini-api/docs/models/gemini) |
| `anthropic` | `ANTHROPIC_API_KEY` | `claude-3-haiku-20240307` | [docs](https://docs.anthropic.com/claude/docs/models-overview) |
| `xai` | `XAI_API_KEY` | `grok-2-1212` | [docs](https://docs.x.ai/docs) |
| `deepseek` | `DEEPSEEK_API_KEY` | `deepseek-chat` | [docs](https://api-docs.deepseek.com/) |
-| `ollama` (local) | n/a | `llama3` | [docs](https://ollama.com/library) |
+| `ollama` (local) | n/a | `llama3.1` | [docs](https://ollama.com/library) |
| `qwen` | `QWEN_API_KEY` | `qwen-max` | [Intl.](https://www.alibabacloud.com/help/en/model-studio/developer-reference/use-qwen-by-calling-api)/[cn](https://help.aliyun.com/zh/model-studio/getting-started/models) |
| `mistral` | `MISTRAL_API_KEY` | `mistral-large-latest` | [docs](https://docs.mistral.ai/getting-started/models/models_overview/) |
| `replicate` | `REPLICATE_API_KEY` | `replicate/meta/meta-llama-3-70b-instruct` | [docs](https://replicate.com/collections/language-models) |
@@ -51,9 +51,11 @@ You can configure the agent's name, model, and prompts in their profile like `an
| `huggingface` | `HUGGINGFACE_API_KEY` | `huggingface/mistralai/Mistral-Nemo-Instruct-2407` | [docs](https://huggingface.co/models) |
| `novita` | `NOVITA_API_KEY` | `novita/deepseek/deepseek-r1` | [docs](https://novita.ai/model-api/product/llm-api?utm_source=github_mindcraft&utm_medium=github_readme&utm_campaign=link) |
| `openrouter` | `OPENROUTER_API_KEY` | `openrouter/anthropic/claude-3.5-sonnet` | [docs](https://openrouter.ai/models) |
+| `glhf.chat` | `GHLF_API_KEY` | `glhf/hf:meta-llama/Llama-3.1-405B-Instruct` | [docs](https://glhf.chat/user-settings/api) |
+| `hyperbolic` | `HYPERBOLIC_API_KEY` | `hyperbolic/deepseek-ai/DeepSeek-V3` | [docs](https://docs.hyperbolic.xyz/docs/getting-started) |
If you use Ollama, to install the models used by default (generation and embedding), execute the following terminal command:
-`ollama pull llama3 && ollama pull nomic-embed-text`
+`ollama pull llama3.1 && ollama pull nomic-embed-text`
### Online Servers
To connect to online servers your bot will need an official Microsoft/Minecraft account. You can use your own personal one, but will need another account if you want to connect too and play with it. To connect, change these lines in `settings.js`:
diff --git a/evaluation_script.py b/evaluation_script.py
index a0b9959..beb170b 100644
--- a/evaluation_script.py
+++ b/evaluation_script.py
@@ -347,4 +347,4 @@ def main():
# run_experiment(args.task_path, args.task_id, args.num_exp)
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/example_tasks.json b/example_tasks.json
index 7210d55..f5717c3 100644
--- a/example_tasks.json
+++ b/example_tasks.json
@@ -109,4 +109,3 @@
"type": "techtree",
"timeout": 300
}
-}
\ No newline at end of file
diff --git a/keys.example.json b/keys.example.json
index 9aa3144..99286c5 100644
--- a/keys.example.json
+++ b/keys.example.json
@@ -10,6 +10,8 @@
"XAI_API_KEY": "",
"MISTRAL_API_KEY": "",
"DEEPSEEK_API_KEY": "",
+ "GHLF_API_KEY": "",
+ "HYPERBOLIC_API_KEY": "",
"NOVITA_API_KEY": "",
"OPENROUTER_API_KEY": ""
}
diff --git a/profiles/gemini.json b/profiles/gemini.json
index 4f3cf43..b8f1eb0 100644
--- a/profiles/gemini.json
+++ b/profiles/gemini.json
@@ -1,7 +1,7 @@
{
"name": "gemini",
-
- "model": "gemini-1.5-flash",
-
+
+ "model": "gemini-2.0-flash",
+
"cooldown": 10000
-}
\ No newline at end of file
+}
diff --git a/src/agent/action_manager.js b/src/agent/action_manager.js
index f5c6cae..227c031 100644
--- a/src/agent/action_manager.js
+++ b/src/agent/action_manager.js
@@ -151,5 +151,4 @@ export class ActionManager {
await this.stop(); // last attempt to stop
}, TIMEOUT_MINS * 60 * 1000);
}
-
-}
\ No newline at end of file
+}
diff --git a/src/agent/agent.js b/src/agent/agent.js
index 853df3f..4a955e1 100644
--- a/src/agent/agent.js
+++ b/src/agent/agent.js
@@ -86,7 +86,7 @@ export class Agent {
console.log(`${this.name} spawned.`);
this.clearBotLogs();
-
+
this._setupEventHandlers(save_data, init_message);
this.startEvents();
diff --git a/src/agent/history.js b/src/agent/history.js
index 4ef0c37..c76e0d3 100644
--- a/src/agent/history.js
+++ b/src/agent/history.js
@@ -117,4 +117,4 @@ export class History {
this.turns = [];
this.memory = '';
}
-}
\ No newline at end of file
+}
diff --git a/src/agent/self_prompter.js b/src/agent/self_prompter.js
index 3251f0e..19ead75 100644
--- a/src/agent/self_prompter.js
+++ b/src/agent/self_prompter.js
@@ -143,4 +143,4 @@ export class SelfPrompter {
// this stops it from responding from the handlemessage loop and the self-prompt loop at the same time
}
}
-}
\ No newline at end of file
+}
diff --git a/src/models/gemini.js b/src/models/gemini.js
index b816036..98ddd2e 100644
--- a/src/models/gemini.js
+++ b/src/models/gemini.js
@@ -39,7 +39,6 @@ export class Gemini {
model: this.model_name || "gemini-1.5-flash",
// systemInstruction does not work bc google is trash
};
-
if (this.url) {
model = this.genAI.getGenerativeModel(
modelConfig,
@@ -72,7 +71,26 @@ export class Gemini {
}
});
const response = await result.response;
- const text = response.text();
+ let text;
+
+ // Handle "thinking" models since they smart
+ if (this.model_name && this.model_name.includes("thinking")) {
+ if (
+ 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;
+ } else {
+ console.warn("Unexpected response structure for thinking model:", response);
+ text = response.text();
+ }
+ } else {
+ text = response.text();
+ }
+
console.log('Received.');
return text;
@@ -94,4 +112,4 @@ export class Gemini {
const result = await model.embedContent(text);
return result.embedding.values;
}
-}
\ No newline at end of file
+}
diff --git a/src/models/glhf.js b/src/models/glhf.js
new file mode 100644
index 0000000..d41b843
--- /dev/null
+++ b/src/models/glhf.js
@@ -0,0 +1,70 @@
+import OpenAIApi from 'openai';
+import { getKey } from '../utils/keys.js';
+
+export class GLHF {
+ constructor(model_name, url) {
+ this.model_name = model_name;
+ const apiKey = getKey('GHLF_API_KEY');
+ if (!apiKey) {
+ throw new Error('API key not found. Please check keys.json and ensure GHLF_API_KEY is defined.');
+ }
+ this.openai = new OpenAIApi({
+ apiKey,
+ baseURL: url || "https://glhf.chat/api/openai/v1"
+ });
+ }
+
+ async sendRequest(turns, systemMessage, stop_seq = '***') {
+ // Construct the message array for the API request.
+ let messages = [{ role: 'system', content: systemMessage }].concat(turns);
+ const pack = {
+ model: this.model_name || "hf:meta-llama/Llama-3.1-405B-Instruct",
+ messages,
+ stop: [stop_seq]
+ };
+
+ const maxAttempts = 5;
+ let attempt = 0;
+ let finalRes = null;
+
+ while (attempt < maxAttempts) {
+ attempt++;
+ console.log(`Awaiting glhf.chat API response... (attempt: ${attempt})`);
+ try {
+ let completion = await this.openai.chat.completions.create(pack);
+ if (completion.choices[0].finish_reason === 'length') {
+ throw new Error('Context length exceeded');
+ }
+ let res = completion.choices[0].message.content;
+ // If there's an open tag without a corresponding , retry.
+ if (res.includes("") && !res.includes("")) {
+ console.warn("Partial block detected. Re-generating...");
+ continue;
+ }
+ // If there's a closing tag but no opening , prepend one.
+ if (res.includes("") && !res.includes("")) {
+ res = "" + res;
+ }
+ finalRes = res.replace(/<\|separator\|>/g, '*no response*');
+ break; // Valid response obtained.
+ } catch (err) {
+ if ((err.message === 'Context length exceeded' || err.code === 'context_length_exceeded') && turns.length > 1) {
+ console.log('Context length exceeded, trying again with shorter context.');
+ return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
+ } else {
+ console.error(err);
+ finalRes = 'My brain disconnected, try again.';
+ break;
+ }
+ }
+ }
+ if (finalRes === null) {
+ finalRes = "I thought too hard, sorry, try again";
+ }
+ return finalRes;
+ }
+
+ async embed(text) {
+ throw new Error('Embeddings are not supported by glhf.');
+ }
+}
diff --git a/src/models/groq.js b/src/models/groq.js
index f369f1e..b3956ba 100644
--- a/src/models/groq.js
+++ b/src/models/groq.js
@@ -24,61 +24,83 @@ export class GroqCloudAPI {
this.groq = new Groq({ apiKey: getKey('GROQCLOUD_API_KEY') });
+
}
- async sendRequest(turns, systemMessage, stop_seq=null) {
+ async sendRequest(turns, systemMessage, stop_seq = null) {
+ // Variables for DeepSeek-R1 models
+ const maxAttempts = 5;
+ let attempt = 0;
+ let finalRes = null;
+ let res = null;
- let messages = [{"role": "system", "content": systemMessage}].concat(turns); // The standard for GroqCloud is just appending to a messages array starting with the system prompt, but
- // this is perfectly acceptable too, and I recommend it.
- // I still feel as though I should note it for any future revisions of MindCraft, though.
+ // Construct messages array
+ let messages = [{"role": "system", "content": systemMessage}].concat(turns);
- // These variables look odd, but they're for the future. Please keep them intact.
- let raw_res = null;
- let res = null;
- let tool_calls = null;
+ while (attempt < maxAttempts) {
+ attempt++;
- try {
+ // These variables look odd, but they're for the future.
+ let raw_res = null;
+ let tool_calls = null;
- console.log("Awaiting Groq response...");
+ try {
+ console.log("Awaiting Groq response...");
- if (this.params.max_tokens) {
+ // Handle deprecated max_tokens parameter
+ if (this.params.max_tokens) {
+ console.warn("GROQCLOUD WARNING: A profile is using `max_tokens`. This is deprecated. Please move to `max_completion_tokens`.");
+ this.params.max_completion_tokens = this.params.max_tokens;
+ delete this.params.max_tokens;
+ }
- console.warn("GROQCLOUD WARNING: A profile is using `max_tokens`. This is deprecated. Please move to `max_completion_tokens`.");
- this.params.max_completion_tokens = this.params.max_tokens;
- delete this.params.max_tokens;
+ if (!this.params.max_completion_tokens) {
+ this.params.max_completion_tokens = 8000; // Set it lower.
+ }
- }
+ let completion = await this.groq.chat.completions.create({
+ "messages": messages,
+ "model": this.model_name || "llama-3.3-70b-versatile",
+ "stream": false,
+ "stop": stop_seq,
+ ...(this.params || {})
+ });
- if (!this.params.max_completion_tokens) {
-
- this.params.max_completion_tokens = 8000; // Set it lower. This is a common theme.
-
- }
-
- let completion = await this.groq.chat.completions.create({
- "messages": messages,
- "model": this.model_name || "llama-3.3-70b-versatile",
- "stream": false,
- "stop": stop_seq,
- ...(this.params || {})
- });
-
- raw_res = completion.choices[0].message;
- res = raw_res.content;
-
- }
-
- catch(err) {
-
- console.log(err);
- res = "My brain just kinda stopped working. Try again.";
-
- }
-
- return res;
+ raw_res = completion.choices[0].message;
+ res = raw_res.content;
+ } catch (err) {
+ console.log(err);
+ res = "My brain just kinda stopped working. Try again.";
}
- async embed(_) {
- throw new Error('Embeddings are not supported by Groq.');
+ // Check for tag issues
+ const hasOpenTag = res.includes("");
+ const hasCloseTag = res.includes("");
+
+ // If a partial block is detected, log a warning and retry
+ if (hasOpenTag && !hasCloseTag) {
+ console.warn("Partial block detected. Re-generating Groq request...");
+ continue; // This will skip the rest of the loop and try again
}
+
+ // If only the closing tag is present, prepend an opening tag
+ if (hasCloseTag && !hasOpenTag) {
+ res = '' + res;
+ }
+
+ // Remove the complete block (and any content inside) from the response
+ res = res.replace(/[\s\S]*?<\/think>/g, '').trim();
+
+ finalRes = res;
+ break; // Exit the loop once a valid response is obtained
+ }
+
+ if (finalRes == null) {
+ console.warn("Could not obtain a valid block or normal response after max attempts.");
+ finalRes = "I thought too hard, sorry, try again.";
+ }
+
+ finalRes = finalRes.replace(/<\|separator\|>/g, '*no response*');
+ return finalRes;
+ }
}
diff --git a/src/models/huggingface.js b/src/models/huggingface.js
index dd5c89d..80c36e8 100644
--- a/src/models/huggingface.js
+++ b/src/models/huggingface.js
@@ -1,46 +1,85 @@
-import {toSinglePrompt} from '../utils/text.js';
-import {getKey} from '../utils/keys.js';
-import {HfInference} from "@huggingface/inference";
+import { toSinglePrompt } from '../utils/text.js';
+import { getKey } from '../utils/keys.js';
+import { HfInference } from "@huggingface/inference";
export class HuggingFace {
- constructor(model_name, url, params) {
- this.model_name = model_name.replace('huggingface/','');
- this.url = url;
- this.params = params;
+ constructor(model_name, url, params) {
+ // Remove 'huggingface/' prefix if present
+ this.model_name = model_name.replace('huggingface/', '');
+ this.url = url;
+ this.params = params;
- if (this.url) {
- console.warn("Hugging Face doesn't support custom urls!");
+ if (this.url) {
+ console.warn("Hugging Face doesn't support custom urls!");
+ }
+
+ this.huggingface = new HfInference(getKey('HUGGINGFACE_API_KEY'));
+ }
+
+ async sendRequest(turns, systemMessage) {
+ const stop_seq = '***';
+ // Build a single prompt from the conversation turns
+ 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';
+ // Combine system message with the prompt
+ const input = systemMessage + "\n" + prompt;
+
+ // We'll try up to 5 times in case of partial blocks for DeepSeek-R1 models.
+ const maxAttempts = 5;
+ let attempt = 0;
+ let finalRes = null;
+
+ while (attempt < maxAttempts) {
+ attempt++;
+ console.log(`Awaiting Hugging Face API response... (model: ${model_name}, attempt: ${attempt})`);
+ let res = '';
+ try {
+ // Consume the streaming response chunk by chunk
+ for await (const chunk of this.huggingface.chatCompletionStream({
+ model: model_name,
+ messages: [{ role: "user", content: input }],
+ ...(this.params || {})
+ })) {
+ res += (chunk.choices[0]?.delta?.content || "");
+ }
+ } catch (err) {
+ console.log(err);
+ res = 'My brain disconnected, try again.';
+ // Break out immediately; we only retry when handling partial tags.
+ break;
+ }
+
+ // If the model is DeepSeek-R1, check for mismatched blocks.
+ const hasOpenTag = res.includes("");
+ const hasCloseTag = res.includes("");
+
+ // If there's a partial mismatch, warn and retry the entire request.
+ if ((hasOpenTag && !hasCloseTag)) {
+ console.warn("Partial block detected. Re-generating...");
+ continue;
}
- this.huggingface = new HfInference(getKey('HUGGINGFACE_API_KEY'));
- }
-
- async sendRequest(turns, systemMessage) {
- const stop_seq = '***';
- const prompt = toSinglePrompt(turns, null, stop_seq);
- let model_name = this.model_name || 'meta-llama/Meta-Llama-3-8B';
-
- const input = systemMessage + "\n" + prompt;
- let res = '';
- try {
- console.log('Awaiting Hugging Face API response...');
- for await (const chunk of this.huggingface.chatCompletionStream({
- model: model_name,
- messages: [{ role: "user", content: input }],
- ...(this.params || {})
- })) {
- res += (chunk.choices[0]?.delta?.content || "");
- }
- } catch (err) {
- console.log(err);
- res = 'My brain disconnected, try again.';
+ // If both tags are present, remove the block entirely.
+ if (hasOpenTag && hasCloseTag) {
+ res = res.replace(/[\s\S]*?<\/think>/g, '').trim();
}
- console.log('Received.');
- console.log(res);
- return res;
+
+ finalRes = res;
+ break; // Exit loop if we got a valid response.
}
- async embed(text) {
- throw new Error('Embeddings are not supported by HuggingFace.');
+ // If no valid response was obtained after max attempts, assign a fallback.
+ if (finalRes == null) {
+ console.warn("Could not get a valid block or normal response after max attempts.");
+ finalRes = 'I thought too hard, sorry, try again.';
}
-}
\ No newline at end of file
+ console.log('Received.');
+ console.log(finalRes);
+ return finalRes;
+ }
+
+ async embed(text) {
+ throw new Error('Embeddings are not supported by HuggingFace.');
+ }
+}
diff --git a/src/models/hyperbolic.js b/src/models/hyperbolic.js
new file mode 100644
index 0000000..a2ccc48
--- /dev/null
+++ b/src/models/hyperbolic.js
@@ -0,0 +1,113 @@
+import { getKey } from '../utils/keys.js';
+
+export class Hyperbolic {
+ constructor(modelName, apiUrl) {
+ this.modelName = modelName || "deepseek-ai/DeepSeek-V3";
+ this.apiUrl = apiUrl || "https://api.hyperbolic.xyz/v1/chat/completions";
+
+ // Retrieve the Hyperbolic API key from keys.js
+ this.apiKey = getKey('HYPERBOLIC_API_KEY');
+ if (!this.apiKey) {
+ throw new Error('HYPERBOLIC_API_KEY not found. Check your keys.js file.');
+ }
+ }
+
+ /**
+ * Sends a chat completion request to the Hyperbolic endpoint.
+ *
+ * @param {Array} turns - An array of message objects, e.g. [{role: 'user', content: 'Hi'}].
+ * @param {string} systemMessage - The system prompt or instruction.
+ * @param {string} stopSeq - A stopping sequence, default '***'.
+ * @returns {Promise} - The model's reply.
+ */
+ async sendRequest(turns, systemMessage, stopSeq = '***') {
+ // Prepare the messages with a system prompt at the beginning
+ const messages = [{ role: 'system', content: systemMessage }, ...turns];
+
+ // Build the request payload
+ const payload = {
+ model: this.modelName,
+ messages: messages,
+ max_tokens: 8192,
+ temperature: 0.7,
+ top_p: 0.9,
+ stream: false
+ };
+
+ const maxAttempts = 5;
+ let attempt = 0;
+ let finalRes = null;
+
+ while (attempt < maxAttempts) {
+ attempt++;
+ console.log(`Awaiting Hyperbolic API response... (attempt: ${attempt})`);
+ console.log('Messages:', messages);
+
+ let completionContent = null;
+
+ try {
+ const response = await fetch(this.apiUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.apiKey}`
+ },
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ if (data?.choices?.[0]?.finish_reason === 'length') {
+ throw new Error('Context length exceeded');
+ }
+
+ completionContent = data?.choices?.[0]?.message?.content || '';
+ console.log('Received response from Hyperbolic.');
+ } catch (err) {
+ if (
+ (err.message === 'Context length exceeded' || err.code === 'context_length_exceeded') &&
+ turns.length > 1
+ ) {
+ console.log('Context length exceeded, trying again with a shorter context...');
+ return await this.sendRequest(turns.slice(1), systemMessage, stopSeq);
+ } else {
+ console.error(err);
+ completionContent = 'My brain disconnected, try again.';
+ }
+ }
+
+ // Check for blocks
+ const hasOpenTag = completionContent.includes("");
+ const hasCloseTag = completionContent.includes("");
+
+ if ((hasOpenTag && !hasCloseTag)) {
+ console.warn("Partial block detected. Re-generating...");
+ continue; // Retry the request
+ }
+
+ if (hasCloseTag && !hasOpenTag) {
+ completionContent = '' + completionContent;
+ }
+
+ if (hasOpenTag && hasCloseTag) {
+ completionContent = completionContent.replace(/[\s\S]*?<\/think>/g, '').trim();
+ }
+
+ finalRes = completionContent.replace(/<\|separator\|>/g, '*no response*');
+ break; // Valid response obtained—exit loop
+ }
+
+ if (finalRes == null) {
+ console.warn("Could not get a valid block or normal response after max attempts.");
+ finalRes = 'I thought too hard, sorry, try again.';
+ }
+ return finalRes;
+ }
+
+ async embed(text) {
+ throw new Error('Embeddings are not supported by Hyperbolic.');
+ }
+}
diff --git a/src/models/local.js b/src/models/local.js
index 23d7e0e..e51bcf8 100644
--- a/src/models/local.js
+++ b/src/models/local.js
@@ -10,45 +10,86 @@ export class Local {
}
async sendRequest(turns, systemMessage) {
- let model = this.model_name || 'llama3';
+ let model = this.model_name || 'llama3.1'; // Updated to llama3.1, as it is more performant than llama3
let messages = strictFormat(turns);
- messages.unshift({role: 'system', content: systemMessage});
- let res = null;
- try {
- console.log(`Awaiting local response... (model: ${model})`)
- res = await this.send(this.chat_endpoint, {
- model: model,
- messages: messages,
- stream: false,
- ...(this.params || {})
- });
- if (res)
- res = res['message']['content'];
- }
- catch (err) {
- if (err.message.toLowerCase().includes('context length') && turns.length > 1) {
- console.log('Context length exceeded, trying again with shorter context.');
- return await sendRequest(turns.slice(1), systemMessage, stop_seq);
- } else {
- console.log(err);
- res = 'My brain disconnected, try again.';
+ messages.unshift({ role: 'system', content: systemMessage });
+
+ // We'll attempt up to 5 times for models with deepseek-r1-esk reasoning if the tags are mismatched.
+ const maxAttempts = 5;
+ let attempt = 0;
+ let finalRes = null;
+
+ while (attempt < maxAttempts) {
+ attempt++;
+ console.log(`Awaiting local response... (model: ${model}, attempt: ${attempt})`);
+ let res = null;
+ try {
+ res = await this.send(this.chat_endpoint, {
+ model: model,
+ messages: messages,
+ stream: false,
+ ...(this.params || {})
+ });
+ if (res) {
+ res = res['message']['content'];
+ } else {
+ res = '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 the model name includes "deepseek-r1" or "Andy-3.5-reasoning", then handle the block.
+ const hasOpenTag = res.includes("");
+ const hasCloseTag = res.includes("");
+
+ // If there's a partial mismatch, retry to get a complete response.
+ if ((hasOpenTag && !hasCloseTag)) {
+ console.warn("Partial block detected. Re-generating...");
+ continue;
+ }
+
+ // If is present but is not, prepend
+ if (hasCloseTag && !hasOpenTag) {
+ res = '' + res;
+ }
+ // Changed this so if the model reasons, using and but doesn't start the message with , ges prepended to the message so no error occur.
+
+ // If both tags appear, remove them (and everything inside).
+ if (hasOpenTag && hasCloseTag) {
+ res = res.replace(/[\s\S]*?<\/think>/g, '');
+ }
+
+ finalRes = res;
+ break; // Exit the loop if we got a valid response.
}
- return res;
+
+ if (finalRes == null) {
+ console.warn("Could not get a valid block or normal response after max attempts.");
+ finalRes = 'I thought too hard, sorry, try again.';
+ }
+ return finalRes;
}
async embed(text) {
let model = this.model_name || 'nomic-embed-text';
- let body = {model: model, prompt: text};
+ let body = { model: model, input: text };
let res = await this.send(this.embedding_endpoint, body);
- return res['embedding']
+ return res['embedding'];
}
async send(endpoint, body) {
const url = new URL(endpoint, this.url);
let method = 'POST';
let headers = new Headers();
- const request = new Request(url, {method, headers, body: JSON.stringify(body)});
+ const request = new Request(url, { method, headers, body: JSON.stringify(body) });
let data = null;
try {
const res = await fetch(request);
@@ -63,4 +104,4 @@ export class Local {
}
return data;
}
-}
\ No newline at end of file
+}
diff --git a/src/models/prompter.js b/src/models/prompter.js
index 66ec80b..dd7231b 100644
--- a/src/models/prompter.js
+++ b/src/models/prompter.js
@@ -18,6 +18,8 @@ import { HuggingFace } from './huggingface.js';
import { Qwen } from "./qwen.js";
import { Grok } from "./grok.js";
import { DeepSeek } from './deepseek.js';
+import { Hyperbolic } from './hyperbolic.js';
+import { GLHF } from './glhf.js';
import { OpenRouter } from './openrouter.js';
export class Prompter {
@@ -40,7 +42,6 @@ export class Prompter {
}
// base overrides default, individual overrides base
-
this.convo_examples = null;
this.coding_examples = null;
@@ -120,10 +121,12 @@ export class Prompter {
profile = {model: profile};
}
if (!profile.api) {
- if (profile.model.includes('gemini'))
+ if (profile.model.includes('openrouter/'))
+ profile.api = 'openrouter'; // must do first because shares names with other models
+ else if (profile.model.includes('ollama/'))
+ profile.api = 'ollama'; // also must do early because shares names with other models
+ else if (profile.model.includes('gemini'))
profile.api = 'google';
- else if (profile.model.includes('openrouter/'))
- profile.api = 'openrouter'; // must do before others bc shares model names
else if (profile.model.includes('gpt') || profile.model.includes('o1')|| profile.model.includes('o3'))
profile.api = 'openai';
else if (profile.model.includes('claude'))
@@ -136,6 +139,10 @@ export class Prompter {
model_profile.api = 'mistral';
else if (profile.model.includes("groq/") || profile.model.includes("groqcloud/"))
profile.api = 'groq';
+ else if (profile.model.includes("glhf/"))
+ profile.api = 'glhf';
+ else if (profile.model.includes("hyperbolic/"))
+ profile.api = 'hyperbolic';
else if (profile.model.includes('novita/'))
profile.api = 'novita';
else if (profile.model.includes('qwen'))
@@ -144,16 +151,13 @@ export class Prompter {
profile.api = 'xai';
else if (profile.model.includes('deepseek'))
profile.api = 'deepseek';
- else if (profile.model.includes('mistral'))
+ else if (profile.model.includes('mistral'))
profile.api = 'mistral';
- else if (profile.model.includes('llama3'))
- profile.api = 'ollama';
else
- throw new Error('Unknown model:', profile.model);
+ throw new Error('Unknown model:', profile.model, 'Did you check the name is correct?'); // Asks the user if the name is correct
}
return profile;
}
-
_createModel(profile) {
let model = null;
if (profile.api === 'google')
@@ -165,13 +169,17 @@ export class Prompter {
else if (profile.api === 'replicate')
model = new ReplicateAPI(profile.model.replace('replicate/', ''), profile.url, profile.params);
else if (profile.api === 'ollama')
- model = new Local(profile.model, profile.url, profile.params);
+ model = new Local(profile.model.replace('ollama/', ''), profile.url, profile.params);
else if (profile.api === 'mistral')
model = new Mistral(profile.model, profile.url, profile.params);
else if (profile.api === 'groq')
model = new GroqCloudAPI(profile.model.replace('groq/', '').replace('groqcloud/', ''), profile.url, profile.params);
else if (profile.api === 'huggingface')
model = new HuggingFace(profile.model, profile.url, profile.params);
+ else if (profile.api === 'glhf')
+ model = new GLHF(profile.model.replace('glhf/', ''), profile.url, profile.params);
+ else if (profile.api === 'hyperbolic')
+ model = new Hyperbolic(profile.model.replace('hyperbolic/', ''), profile.url, profile.params);
else if (profile.api === 'novita')
model = new Novita(profile.model.replace('novita/', ''), profile.url, profile.params);
else if (profile.api === 'qwen')
@@ -186,7 +194,6 @@ export class Prompter {
throw new Error('Unknown API:', profile.api);
return model;
}
-
getName() {
return this.profile.name;
}