forked from wmantly/mc-bot-town
Mostly works
This commit is contained in:
@@ -6,6 +6,15 @@ class GeminiProvider {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.session = null;
|
||||
this.tools = [];
|
||||
}
|
||||
|
||||
supportsTools() {
|
||||
return true;
|
||||
}
|
||||
|
||||
setTools(tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
async start(history) {
|
||||
@@ -18,7 +27,7 @@ class GeminiProvider {
|
||||
}
|
||||
|
||||
__settings(history) {
|
||||
return {
|
||||
const settings = {
|
||||
generationConfig: {
|
||||
temperature: this.config.temperature || 1,
|
||||
topP: this.config.topP || 0.95,
|
||||
@@ -55,6 +64,19 @@ class GeminiProvider {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Add tools if configured
|
||||
if (this.tools && this.tools.length > 0) {
|
||||
settings.tools = this.tools.map(tool => ({
|
||||
functionDeclarations: [{
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
parameters: tool.parameters
|
||||
}]
|
||||
}));
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
async chat(message, retryCount = 0) {
|
||||
@@ -65,6 +87,9 @@ class GeminiProvider {
|
||||
if (retryCount > 3) {
|
||||
throw new Error(`Gemini API error after ${retryCount} retries: ${error.message}`);
|
||||
}
|
||||
const baseDelay = Math.min(1000 * Math.pow(2, retryCount), 30000);
|
||||
const jitter = Math.random() * 1000;
|
||||
await new Promise(resolve => setTimeout(resolve, baseDelay + jitter));
|
||||
// Recover by removing last history entry and restarting
|
||||
this.session.params.history.pop();
|
||||
await this.start(this.session.params.history);
|
||||
@@ -80,6 +105,14 @@ class GeminiProvider {
|
||||
return result.response.text();
|
||||
}
|
||||
|
||||
getToolCalls(result) {
|
||||
// Gemini returns function calls in result.response.functionCalls()
|
||||
if (result.response && typeof result.response.functionCalls === 'function') {
|
||||
return result.response.functionCalls();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async close() {
|
||||
this.session = null;
|
||||
}
|
||||
|
||||
@@ -22,4 +22,11 @@ module.exports = {
|
||||
ProviderFactory,
|
||||
GeminiProvider,
|
||||
OllamaProvider
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider interface for tool support
|
||||
* All providers must implement these methods:
|
||||
* - setTools(tools): Configure available tools for function calling
|
||||
* - supportsTools(): boolean - whether this provider supports tool calling
|
||||
*/
|
||||
@@ -10,12 +10,20 @@ class OllamaProvider {
|
||||
this.baseUrl = config.baseUrl || 'http://localhost:11434';
|
||||
this.model = config.model || 'llama3.2';
|
||||
this.messages = [];
|
||||
this.tools = [];
|
||||
}
|
||||
|
||||
supportsTools() {
|
||||
return true;
|
||||
}
|
||||
|
||||
setTools(tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
async start(history) {
|
||||
// Convert Gemini-style history to Ollama format if needed
|
||||
this.messages = history || [];
|
||||
|
||||
|
||||
if (this.config.prompt) {
|
||||
console.log('Ollama provider initialized with model:', this.model);
|
||||
}
|
||||
@@ -27,13 +35,12 @@ class OllamaProvider {
|
||||
top_p: this.config.topP || 0.95,
|
||||
top_k: this.config.topK || 64,
|
||||
num_predict: this.config.maxOutputTokens || 2048,
|
||||
|
||||
num_ctx: this.config.num_ctx,
|
||||
};
|
||||
}
|
||||
|
||||
__jsonFormat() {
|
||||
return 'json'
|
||||
/* return {
|
||||
return {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
@@ -43,12 +50,26 @@ class OllamaProvider {
|
||||
},
|
||||
required: ['text', 'delay']
|
||||
}
|
||||
};*/
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip markdown code fences (```json ... ```) from a response string.
|
||||
* Ollama models (especially smaller ones) sometimes wrap their output in fences
|
||||
* even when the system prompt says not to.
|
||||
*/
|
||||
static stripMarkdownFences(text) {
|
||||
if (!text || typeof text !== 'string') return text;
|
||||
let cleaned = text.trim();
|
||||
// Remove leading ```json or ``` fences
|
||||
cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, '');
|
||||
// Remove trailing ``` fences
|
||||
cleaned = cleaned.replace(/\n?```\s*$/, '');
|
||||
return cleaned.trim();
|
||||
}
|
||||
|
||||
async chat(message, retryCount = 0) {
|
||||
try {
|
||||
// Build conversation from prompt + history
|
||||
const messages = [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -64,33 +85,45 @@ class OllamaProvider {
|
||||
}
|
||||
];
|
||||
|
||||
// console.log('Ollama messages', messages)
|
||||
const requestBody = {
|
||||
model: this.model,
|
||||
messages: messages,
|
||||
stream: false,
|
||||
think: false,
|
||||
format: this.__jsonFormat(),
|
||||
options: this.__settings()
|
||||
};
|
||||
// console.log('Ollama request:', JSON.stringify(requestBody, null, 2));
|
||||
|
||||
// Only set format when NO tools are configured.
|
||||
// format + tools together confuses smaller models — they try to
|
||||
// satisfy both constraints and produce garbage (_/empty responses).
|
||||
const hasTools = this.tools && this.tools.length > 0;
|
||||
if (!hasTools) {
|
||||
requestBody.format = this.__jsonFormat();
|
||||
}
|
||||
|
||||
if (hasTools) {
|
||||
requestBody.tools = this.tools.map(tool => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
parameters: tool.parameters
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
`${this.baseUrl}/api/chat`,
|
||||
requestBody,
|
||||
{
|
||||
// timeout: this.config.timeout || 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Log raw response for debugging
|
||||
const rawContent = response.data.message.content;
|
||||
const messageData = response.data.message;
|
||||
console.log('Ollama response', rawContent)
|
||||
// console.log('Ollama raw response:', JSON.stringify(rawContent));
|
||||
// console.log('Ollama raw response length:', rawContent?.length);
|
||||
|
||||
// Update history
|
||||
this.messages.push({
|
||||
@@ -105,14 +138,25 @@ class OllamaProvider {
|
||||
content: rawContent
|
||||
});
|
||||
|
||||
// Return in a format compatible with the Ai class
|
||||
return {
|
||||
// The text() closure strips markdown code fences so consumers
|
||||
// (processResponse, getToolCalls) get clean content.
|
||||
const result = {
|
||||
response: {
|
||||
text: () => response.data.message.content
|
||||
text: () => {
|
||||
let content = messageData.content || rawContent;
|
||||
content = OllamaProvider.stripMarkdownFences(content);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Ollama may return tool calls in messageData.tool_calls
|
||||
if (messageData.tool_calls) {
|
||||
result.tool_calls = messageData.tool_calls;
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
// Log detailed error information
|
||||
const errorDetails = {
|
||||
message: error.message,
|
||||
status: error.response?.status,
|
||||
@@ -125,8 +169,9 @@ class OllamaProvider {
|
||||
if (retryCount > 3) {
|
||||
throw new Error(`Ollama API error after ${retryCount} retries: ${error.message}`);
|
||||
}
|
||||
// Retry after delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500 * (retryCount + 1)));
|
||||
const baseDelay = Math.min(1000 * Math.pow(2, retryCount), 30000);
|
||||
const jitter = Math.random() * 1000;
|
||||
await new Promise(resolve => setTimeout(resolve, baseDelay + jitter));
|
||||
return await this.chat(message, retryCount + 1);
|
||||
}
|
||||
}
|
||||
@@ -144,4 +189,4 @@ class OllamaProvider {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OllamaProvider;
|
||||
module.exports = OllamaProvider;
|
||||
|
||||
Reference in New Issue
Block a user