aboutsummaryrefslogtreecommitdiff
path: root/modules/core/ollama-manager.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/ollama-manager.js')
-rw-r--r--modules/core/ollama-manager.js36
1 files changed, 27 insertions, 9 deletions
diff --git a/modules/core/ollama-manager.js b/modules/core/ollama-manager.js
index 4512662..7dcecc3 100644
--- a/modules/core/ollama-manager.js
+++ b/modules/core/ollama-manager.js
@@ -55,6 +55,19 @@ class OllamaManager {
// ─── Provider management ─────────────────────────────────────────────────
+ // Ollama's server-side default context is small (2k-4k depending on the build). The agent
+ // sends a system prompt, up to 20 messages and up to 24k chars of tool results, so without an
+ // explicit num_ctx the server silently truncates the prompt — the model "forgets" what it was
+ // just told. Send it on every Ollama request; the OpenAI-compatible branch has its own params.
+ _ctxTokens() {
+ const n = Number(this.numCtx || (typeof localStorage !== 'undefined' ? localStorage.getItem('webx11_ollama_numctx') : 0));
+ return Number.isFinite(n) && n >= 512 ? Math.floor(n) : 16384;
+ }
+
+ _ollamaOptions(extra) {
+ return Object.assign({ num_ctx: this._ctxTokens() }, extra || {});
+ }
+
_loadProviders() {
const stored = this._getSecret('webx11_llm_providers');
if (stored) {
@@ -271,7 +284,7 @@ class OllamaManager {
break;
} catch (error) {
this.terminal.log('WARN', 'Ollama', provider.name + ' endpoint ' + ep + ' failed: ' + error.message);
- this.terminal.print(`${provider.name} endpoint ${ep} failed: ${error.message}`, 'warning');
+ if (!this._quietConnect) this.terminal.print(`${provider.name} endpoint ${ep} failed: ${error.message}`, 'warning');
}
}
} else {
@@ -309,7 +322,7 @@ class OllamaManager {
success = true;
} catch (error) {
this.terminal.log('WARN', 'Ollama', provider.name + ' failed: ' + error.message);
- this.terminal.print(`${provider.name} failed: ${error.message}`, 'warning');
+ if (!this._quietConnect) this.terminal.print(`${provider.name} failed: ${error.message}`, 'warning');
}
}
@@ -319,7 +332,7 @@ class OllamaManager {
// If provider-based connect failed, try bare endpoint fallback (legacy)
for (const ep of allEndpoints) {
try {
- this.terminal.print(`Connecting (legacy fallback) to ${ep}...`, 'info');
+ if (!this._quietConnect) this.terminal.print(`Connecting (legacy fallback) to ${ep}...`, 'info');
const response = await fetch(`${ep}/api/tags`, {
method: 'GET', headers: { 'Content-Type': 'application/json' }
});
@@ -345,7 +358,7 @@ class OllamaManager {
return true;
} catch (error) {
this.terminal.log('WARN', 'Ollama', 'Endpoint ' + ep + ' failed: ' + error.message);
- this.terminal.print(`Endpoint ${ep} failed: ${error.message}`, 'warning');
+ if (!this._quietConnect) this.terminal.print(`Endpoint ${ep} failed: ${error.message}`, 'warning');
}
}
@@ -387,6 +400,10 @@ class OllamaManager {
this.terminal.print('Ollama: connecting on first use…', 'info');
let timedOut = false;
try {
+ // One failure should read as one failure: connect() prints a line per endpoint it
+ // tries, which made an auto-connect failure seven lines of noise. Keep the detail in
+ // the log, print only the outcome for an automatic attempt.
+ this._quietConnect = true;
this._connecting = this.connect();
// connect() uses a bare fetch(), so a blackholed endpoint would hang this promise
// forever and — because the promise is shared — every later message with it.
@@ -400,6 +417,7 @@ class OllamaManager {
// A throwing terminal.log() must not leave _connecting poisoned: log defensively.
try { this.terminal.log('WARN', 'Ollama', 'Auto-connect failed: ' + (e && e.message ? e.message : e)); } catch (_) {}
} finally {
+ this._quietConnect = false;
this._connecting = null;
}
if (this.connected && savedModel && !(this.models || []).some(m => m.name === savedModel)) {
@@ -471,7 +489,7 @@ class OllamaManager {
const response = await fetch(`${provider.endpoint}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled }),
+ body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled, options: this._ollamaOptions() }),
signal
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -641,7 +659,7 @@ class OllamaManager {
const response = await fetch(`${provider.endpoint}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled }),
+ body: JSON.stringify({ model: useModel, prompt: prompt, stream: this.streamEnabled, options: this._ollamaOptions() }),
signal
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -818,7 +836,7 @@ class OllamaManager {
try {
if (provider.type === 'ollama') {
- const body = { model: useModel, messages, stream: false };
+ const body = { model: useModel, messages, stream: false, options: this._ollamaOptions() };
if (tools && tools.length > 0) body.tools = tools;
const t0 = Date.now();
const response = await fetch(`${provider.endpoint}/api/chat`, {
@@ -1051,7 +1069,7 @@ ${toolDesc}
Only output XML function calls for tool tasks. If no tool is needed, answer normally.` };
const msgsWithTools = [toolContext, ...messages];
- const body = { model: useModel, messages: msgsWithTools, stream: false, options: { temperature: 0.1 } };
+ const body = { model: useModel, messages: msgsWithTools, stream: false, options: this._ollamaOptions({ temperature: 0.1 }) };
const response = await fetch(`${this.endpoint}/api/chat`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -1102,7 +1120,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm
// Original Ollama fallback
this.terminal.print(`Model doesn't support tool calling. Retrying /api/chat without tools...`, 'warning');
try {
- const body = { model: useModel, messages, stream: false };
+ const body = { model: useModel, messages, stream: false, options: this._ollamaOptions() };
const response = await fetch(`${this.endpoint}/api/chat`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),