UNPKG

@measey/mycoder-agent

Version:

Agent module for mycoder - an AI-powered software development assistant

202 lines 7.09 kB
/** * Ollama provider implementation using the official Ollama npm package */ import { Ollama, } from 'ollama'; import { TokenUsage } from '../../tokens.js'; // Define model context window sizes for Ollama models // These are approximate and may vary based on specific model configurations const OLLAMA_CONTEXT_WINDOWS = { llama2: 4096, 'llama2-uncensored': 4096, 'llama2:13b': 4096, 'llama2:70b': 4096, mistral: 8192, 'mistral:7b': 8192, mixtral: 32768, codellama: 16384, phi: 2048, phi2: 2048, openchat: 8192, }; /** * Ollama provider implementation using the official Ollama npm package */ export class OllamaProvider { name = 'ollama'; provider = 'ollama.chat'; model; options; client; constructor(model, options = {}) { this.model = model; this.options = options; const baseUrl = options.baseUrl || process.env.OLLAMA_BASE_URL || 'http://localhost:11434'; this.client = new Ollama({ host: baseUrl }); } /** * Generate text using Ollama API via the official npm package */ async generateText(options) { const { messages, functions, temperature = 0.7, maxTokens: requestMaxTokens, topP, frequencyPenalty, presencePenalty, stopSequences, } = options; // Format messages for Ollama API const formattedMessages = this.formatMessages(messages); // Prepare request options const requestOptions = { model: this.model, messages: formattedMessages, stream: false, options: { temperature: temperature, ...(topP !== undefined && { top_p: topP }), ...(frequencyPenalty !== undefined && { frequency_penalty: frequencyPenalty, }), ...(presencePenalty !== undefined && { presence_penalty: presencePenalty, }), ...(stopSequences && stopSequences.length > 0 && { stop: stopSequences }), }, }; // Add max_tokens if provided if (requestMaxTokens !== undefined) { requestOptions.options = { ...requestOptions.options, num_predict: requestMaxTokens, }; } // Add functions/tools if provided if (functions && functions.length > 0) { requestOptions.tools = this.convertFunctionsToTools(functions); } // Make the API request using the Ollama client const response = await this.client.chat({ ...requestOptions, stream: false, }); // Extract content and tool calls const content = response.message?.content || ''; // Handle tool calls if present const toolCalls = this.extractToolCalls(response); // Create token usage from response data const tokenUsage = new TokenUsage(); tokenUsage.output = response.eval_count || 0; tokenUsage.input = response.prompt_eval_count || 0; // Calculate total tokens and get max tokens for the model const totalTokens = tokenUsage.input + tokenUsage.output; // Extract the base model name without specific parameters // Check if model exists in limits, otherwise use base model or default let contextWindow = OLLAMA_CONTEXT_WINDOWS[this.model]; if (!contextWindow) { const baseModelName = this.model.split(':')[0]; if (baseModelName) { contextWindow = OLLAMA_CONTEXT_WINDOWS[baseModelName]; } // If still no context window, use the one from configuration if available if (!contextWindow && this.options.contextWindow) { contextWindow = this.options.contextWindow; } } return { text: content, toolCalls: toolCalls, tokenUsage: tokenUsage, totalTokens, contextWindow, }; } /* interface Tool { type: string; function: { name: string; description: string; parameters: { type: string; required: string[]; properties: { [key: string]: { type: string; description: string; enum?: string[]; }; }; }; }; }*/ /** * Convert our function definitions to Ollama tool format */ convertFunctionsToTools(functions) { return functions.map((fn) => ({ type: 'function', function: { name: fn.name, description: fn.description, parameters: fn.parameters, }, })); } /** * Extract tool calls from Ollama response */ extractToolCalls(response) { if (!response.message?.tool_calls) { return []; } return response.message.tool_calls.map((toolCall) => { return { id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, name: toolCall.function?.name, content: typeof toolCall.function?.arguments === 'string' ? toolCall.function.arguments : JSON.stringify(toolCall.function?.arguments || {}), }; }); } /** * Format messages for Ollama API */ formatMessages(messages) { const output = []; messages.forEach((msg) => { switch (msg.role) { case 'user': case 'assistant': case 'system': output.push({ role: msg.role, content: msg.content, }); break; case 'tool_result': // Ollama expects tool results as a 'tool' role output.push({ role: 'tool', content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content), }); break; case 'tool_use': { // So there is an issue here is that ollama expects tool calls to be part of the assistant message // get last message and add tool call to it const lastMessage = output[output.length - 1]; lastMessage.tool_calls = [ { function: { name: msg.name, arguments: JSON.parse(msg.content), }, }, ]; break; } } }); return output; } } //# sourceMappingURL=ollama.js.map