diff options
| author | Agando <roduccomm@gmail.com> | 2026-05-29 06:01:03 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-05-29 06:01:03 +0200 |
| commit | 09f14c97ba87bf16348d025d8cbbbe21cafb8097 (patch) | |
| tree | 7fb42ef8c4e19cac0e60a716d82be826d1059d9c /modules/core/ollama-manager.js | |
| parent | f8a04e76ea9aa5156e6e80a7534f1aa5a336429b (diff) | |
auto: 2026-05-29_06:01 changes
Diffstat (limited to 'modules/core/ollama-manager.js')
| -rw-r--r-- | modules/core/ollama-manager.js | 83 |
1 files changed, 81 insertions, 2 deletions
diff --git a/modules/core/ollama-manager.js b/modules/core/ollama-manager.js index c790e6f..3f46b13 100644 --- a/modules/core/ollama-manager.js +++ b/modules/core/ollama-manager.js @@ -592,6 +592,12 @@ class OllamaManager { // ─── Chat With Tools ───────────────────────────────────────────────────── + _isModelType(modelName, type) { + if (!modelName) return false; + const lower = modelName.toLowerCase(); + return lower.includes(type); + } + async chatWithTools(messages, tools, model = null) { if (!this.connected) { this.terminal.print('Ollama not connected. Use ollamaconnect first.', 'error'); @@ -658,7 +664,8 @@ class OllamaManager { error: null }; } else { - // OpenAI-compatible provider + // OpenAI-compatible provider (OpenRouter, DeepSeek, etc.) + const isDeepSeek = this._isModelType(useModel, 'deepseek'); const headers = { 'Content-Type': 'application/json' }; if (provider.apiKey) { headers['Authorization'] = 'Bearer ' + provider.apiKey; @@ -668,6 +675,11 @@ class OllamaManager { messages: messages, stream: false }; + if (isDeepSeek) { + body.temperature = 0.3; + body.max_tokens = 4096; + body.stop = ['<|end|>']; + } if (tools && tools.length > 0) { body.tools = tools; } @@ -688,8 +700,75 @@ class OllamaManager { const choice = data.choices?.[0]; const message = choice?.message || null; const toolCalls = message?.tool_calls || null; + const content = message?.content || ''; + + // DeepSeek: handle null content with tool calls — send empty string + if (isDeepSeek && content === '' && toolCalls && toolCalls.length > 0) { + return { + message: { role: 'assistant', content: '' }, + tool_calls: toolCalls.map(tc => ({ + id: tc.id, + type: tc.type || 'function', + function: { + name: tc.function?.name || '', + arguments: tc.function?.arguments || '{}' + } + })), + error: null + }; + } + + // DeepSeek: empty response with no tools — retry once with stream forcing + if (isDeepSeek && content === '' && (!toolCalls || toolCalls.length === 0)) { + this.terminal.print('⚠️ DeepSeek returned empty — retrying with stream...', 'warning'); + body.stream = true; + delete body.stop; + const retryResponse = await fetch(`${provider.endpoint}/v1/chat/completions`, { + method: 'POST', + headers, + body: JSON.stringify(body), + signal: this._abortController ? undefined : undefined + }); + if (retryResponse.ok) { + const reader = retryResponse.body.getReader(); + const decoder = new TextDecoder(); + let fullText = ''; + let retryBuffer = ''; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + retryBuffer += decoder.decode(value, { stream: true }); + const lines = retryBuffer.split('\n'); + retryBuffer = lines.pop() || ''; + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed === 'data: [DONE]') continue; + if (trimmed.startsWith('data: ')) { + try { + const parsed = JSON.parse(trimmed.substring(6)); + const chunk = parsed.choices?.[0]?.delta?.content || ''; + if (chunk) fullText += chunk; + } catch (e) {} + } + } + } + if (fullText) { + return { + message: { role: 'assistant', content: fullText }, + tool_calls: null, + error: null + }; + } + } + return { + message: { role: 'assistant', content: '' }, + tool_calls: null, + error: null + }; + } + return { - message: message ? { role: message.role || 'assistant', content: message.content || '' } : null, + message: message ? { role: message.role || 'assistant', content: content } : null, tool_calls: toolCalls ? toolCalls.map(tc => ({ id: tc.id, type: tc.type || 'function', |