UNPKG

@entro314labs/ai-changelog-generator

Version:

AI-powered changelog generator with MCP server support - works with most providers, online and local models

214 lines (213 loc) 8.31 kB
/** * Ollama Provider for AI Changelog Generator * Supports local models via Ollama (January 2026) * * Recommended models: * - llama4:scout / llama4:maverick - Meta's latest multimodal models * - qwen3:235b / qwen3:30b - Alibaba's flagship MoE with thinking mode * - deepseek-r1:32b - Advanced reasoning model * - qwen2.5-coder:32b - Code-optimized model * - llama3.3:70b - Previous gen reliable option * * Install with: ollama pull <model-name> */ import process from 'node:process'; import { Ollama } from 'ollama'; import { ProviderError } from '../../../shared/utils/error-classes.js'; import { BaseProvider } from '../core/base-provider.js'; import { applyMixins } from '../utils/base-provider-helpers.js'; class OllamaProvider extends BaseProvider { constructor(config) { super(config); this.client = null; if (this.isAvailable()) { this.initializeClient(); } } initializeClient() { const clientOptions = this.buildClientOptions({ host: 'http://localhost:11434', }); this.client = new Ollama({ host: clientOptions.OLLAMA_HOST || clientOptions.host, }); } getName() { return 'ollama'; } isAvailable() { return !!this.config.OLLAMA_HOST; } getRequiredEnvVars() { return ['OLLAMA_HOST']; } async generateCompletion(messages, options = {}) { if (!this.isAvailable()) { return this.handleProviderError(new Error('Ollama provider is not configured'), 'generate_completion'); } try { // Test connection first time if not already done if (!this._connectionTested) { try { await this.client.list(); this._connectionTested = true; } catch (connectionError) { return this.handleProviderError(new Error(`Ollama server unreachable: ${connectionError.message}. Please run 'ollama serve' first.`), 'generate_completion'); } } const modelConfig = this.getProviderModelConfig(); const modelName = options.model || modelConfig.standardModel; const params = { model: modelName, messages, stream: !!options.stream, options: { temperature: options.temperature || 0.7, top_p: options.top_p || 0.9, num_predict: options.max_tokens || 1024, stop: options.stop || [], }, }; if (options.tools && this.getCapabilities(modelName).tool_use) { params.tools = options.tools; } if (options.response_format?.type === 'json_object' && this.getCapabilities(modelName).json_mode) { params.format = 'json'; } if (params.stream) { const stream = await this.client.chat(params); return { stream, model: modelName }; } const response = await this.client.chat(params); return { content: response.message.content, model: response.model, tokens: response.eval_count, finish_reason: response.done ? 'stop' : 'incomplete', tool_calls: response.message.tool_calls, }; } catch (error) { return this.handleProviderError(error, 'generate_completion', { model: options.model }); } } async generateEmbedding(text, options = {}) { if (!this.isAvailable()) { throw new ProviderError('Ollama provider is not configured', 'ollama', 'isAvailable'); } const modelName = options.model || this.config.OLLAMA_EMBEDDING_MODEL || this.config.AI_MODEL_EMBEDDING || 'nomic-embed-text'; const response = await this.client.embeddings({ model: modelName, prompt: text, options: { temperature: options.temperature || 0.0, }, }); return { embedding: response.embedding, model: modelName, tokens: response.token_count || 0, }; } // Ollama-specific helper methods async getAvailableModels() { if (!this.isAvailable()) { return []; } try { const response = await this.client.list(); return response.models.map((m) => m.name); } catch (error) { // Only log connection errors in development mode or when explicitly used if (!this._connectionErrorLogged && (process.env.NODE_ENV === 'development' || process.env.DEBUG)) { console.warn(`⚠️ Ollama connection failed: ${error.message}`); console.warn('💡 Make sure Ollama is running: ollama serve'); this._connectionErrorLogged = true; } return []; } } async pullModel(modelName) { if (!this.isAvailable()) { throw new ProviderError('Ollama provider is not configured', 'ollama', 'isAvailable'); } try { const pullStream = await this.client.pull({ model: modelName, stream: true }); return { stream: pullStream, model: modelName }; } catch (error) { throw new ProviderError(`Failed to pull model ${modelName}: ${error.message}`, 'ollama', 'pullModel', error, { modelName }); } } getCapabilities(modelName) { // Determine capabilities based on model name const isQwen3 = modelName && modelName.includes('qwen3'); const isDeepSeek = modelName && modelName.includes('deepseek'); const isLlama4 = modelName && modelName.includes('llama4'); const isCoder = modelName && modelName.includes('coder'); return { completion: true, streaming: true, // Tool use supported by newer models tool_use: isQwen3 || isLlama4 || (modelName && modelName.includes('llama3.3')), json_mode: true, // Thinking mode for reasoning models thinking_mode: isQwen3 || isDeepSeek, reasoning: isQwen3 || isDeepSeek || isLlama4, // Vision for multimodal models vision: isLlama4 || (modelName && modelName.includes('qwen3-vl')), coding_optimized: isCoder || isQwen3, offline: true, privacy_focused: true, }; } // Get recommended models for common use cases getRecommendedModels() { return [ { id: 'llama4:scout', name: 'Llama 4 Scout', description: "Meta's latest multimodal model - balanced performance", useCase: 'general', install: 'ollama pull llama4:scout', }, { id: 'qwen3:235b', name: 'Qwen 3 235B', description: "Alibaba's flagship MoE with thinking mode", useCase: 'complex_reasoning', install: 'ollama pull qwen3:235b', }, { id: 'deepseek-r1:32b', name: 'DeepSeek R1 32B', description: 'Advanced reasoning model - best for reasoning tasks', useCase: 'reasoning', install: 'ollama pull deepseek-r1:32b', }, { id: 'qwen2.5-coder:32b', name: 'Qwen 2.5 Coder 32B', description: 'Code-optimized model - best for coding tasks', useCase: 'coding', install: 'ollama pull qwen2.5-coder:32b', }, { id: 'llama3.3:70b', name: 'Llama 3.3 70B', description: 'Previous gen reliable option for general use', useCase: 'general', install: 'ollama pull llama3.3:70b', }, ]; } } // Apply mixins to add standard provider functionality export default applyMixins(OllamaProvider, 'ollama');