UNPKG

ollama-code-qwen

Version:

Un assistant IA en ligne de commande utilisant Ollama et le modèle qwen2.5-coder pour aider au développement, avec des capacités MCP améliorées et détection d'intentions en français et anglais

351 lines (309 loc) 11.7 kB
import fetch from 'node-fetch'; import { qwenCoderConfig } from '../models/qwen-coder.js'; /** * Service pour interagir avec l'API Ollama */ export class OllamaService { /** * Initialise le service Ollama * @param {string} modelName - Nom du modèle Ollama à utiliser * @param {string} host - URL de l'hôte Ollama */ constructor(modelName = 'qwen2.5-coder:14b', host = 'http://192.168.1.16:11434') { this.host = host; this.modelName = modelName; this.apiGenerateUrl = `${host}/api/generate`; this.apiChatUrl = `${host}/api/chat`; // Charger la configuration spécifique si c'est qwen2.5-coder this.isQwenCoder = modelName === 'qwen2.5-coder:14b'; this.modelConfig = this.isQwenCoder ? qwenCoderConfig : null; } /** * Vérifie si le serveur Ollama est en cours d'exécution * @returns {Promise<boolean>} - True si le serveur est en cours d'exécution */ async checkServer() { try { const response = await fetch(this.host, { method: 'GET' }); return response.status === 200; } catch (error) { return false; } } /** * Génère du texte à l'aide de l'API Ollama * @param {string} prompt - Prompt utilisateur * @param {string} systemPrompt - Prompt système optionnel * @param {number} temperature - Température de génération (plus élevée = plus créative) * @param {number} maxTokens - Nombre maximal de tokens à générer * @returns {Promise<string>} - Texte généré */ async generate(prompt, systemPrompt = null, temperature = 0.7, maxTokens = 2048, timeout = 60000) { // Utiliser la config spécifique pour qwen2.5-coder si disponible if (this.isQwenCoder) { temperature = this.modelConfig.generationParams.temperature; maxTokens = this.modelConfig.generationParams.max_tokens; // Utiliser le prompt système spécifique s'il n'y en a pas déjà un if (!systemPrompt) { systemPrompt = this.modelConfig.systemPrompt; } } const payload = { model: this.modelName, prompt, temperature, max_tokens: maxTokens, stream: false }; if (systemPrompt) { payload.system = systemPrompt; } try { // Configuration du timeout pour la requête const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(this.apiGenerateUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { if (response.status === 404) { throw new Error(`Modèle '${this.modelName}' introuvable. Assurez-vous qu'il est téléchargé avec 'ollama pull ${this.modelName}'`); } else { throw new Error(`Erreur HTTP ${response.status}: ${await response.text()}`); } } const data = await response.json(); return data.response; } catch (timeoutError) { throw new Error(`La requête a expiré après ${timeout/1000} secondes : ${timeoutError.message}`); } } catch (error) { if (error.code === 'ECONNREFUSED') { throw new Error(`Impossible de se connecter au serveur Ollama à ${this.host}. Assurez-vous que Ollama est en cours d'exécution.`); } throw error; } } /** * Génère du texte en mode streaming * @param {string} prompt - Prompt utilisateur * @param {string} systemPrompt - Prompt système optionnel * @param {number} temperature - Température de génération * @param {number} maxTokens - Nombre maximal de tokens à générer * @param {Function} onToken - Callback appelé pour chaque token * @returns {Promise<string>} - Texte généré complet */ async streamGenerate(prompt, systemPrompt = null, temperature = 0.7, maxTokens = 2048, onToken = null) { // Utiliser la config spécifique pour qwen2.5-coder si disponible if (this.isQwenCoder) { temperature = this.modelConfig.generationParams.temperature; maxTokens = this.modelConfig.generationParams.max_tokens; // Utiliser le prompt système spécifique s'il n'y en a pas déjà un if (!systemPrompt) { systemPrompt = this.modelConfig.systemPrompt; } } const payload = { model: this.modelName, prompt, temperature, max_tokens: maxTokens, stream: true }; if (systemPrompt) { payload.system = systemPrompt; } try { const response = await fetch(this.apiGenerateUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { if (response.status === 404) { throw new Error(`Modèle '${this.modelName}' introuvable.`); } else { throw new Error(`Erreur HTTP ${response.status}: ${await response.text()}`); } } const reader = response.body.getReader(); const decoder = new TextDecoder(); let fullText = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); try { // Ollama renvoie des objets JSON par ligne const lines = chunk.split('\n').filter(line => line.trim()); for (const line of lines) { const data = JSON.parse(line); if (onToken && data.response) { onToken(data.response); fullText += data.response; } } } catch (e) { console.error('Error parsing streaming response:', e); } } return fullText; } catch (error) { if (error.code === 'ECONNREFUSED') { throw new Error(`Impossible de se connecter au serveur Ollama.`); } throw error; } } /** * Génère une réponse de chat à l'aide de l'API Ollama * @param {Array} messages - Liste des messages avec role et content * @param {number} temperature - Température de génération * @param {number} maxTokens - Nombre maximal de tokens à générer * @returns {Promise<string>} - Texte généré */ async chat(messages, temperature = 0.7, maxTokens = 2048, timeout = 60000) { // Utiliser la config spécifique pour qwen2.5-coder si disponible if (this.isQwenCoder) { temperature = this.modelConfig.generationParams.temperature; maxTokens = this.modelConfig.generationParams.max_tokens; // Si le premier message est un système, utiliser le prompt système spécifique if (messages.length > 0 && messages[0].role === 'system' && !messages[0].content.includes('qwen2.5-coder')) { messages[0].content = `${this.modelConfig.systemPrompt}\n\n${messages[0].content}`; } } const payload = { model: this.modelName, messages, temperature, max_tokens: maxTokens, stream: false }; try { // Configuration du timeout pour la requête const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(this.apiChatUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { if (response.status === 404) { throw new Error(`Modèle '${this.modelName}' introuvable. Assurez-vous qu'il est téléchargé avec 'ollama pull ${this.modelName}'`); } else { throw new Error(`Erreur HTTP ${response.status}: ${await response.text()}`); } } const data = await response.json(); return data.message.content; } catch (timeoutError) { throw new Error(`La requête a expiré après ${timeout/1000} secondes : ${timeoutError.message}`); } } catch (error) { if (error.code === 'ECONNREFUSED') { throw new Error(`Impossible de se connecter au serveur Ollama à ${this.host}. Assurez-vous que Ollama est en cours d'exécution.`); } throw error; } } /** * Génère une réponse de chat en mode streaming * @param {Array} messages - Liste des messages avec role et content * @param {number} temperature - Température de génération * @param {number} maxTokens - Nombre maximal de tokens à générer * @param {Function} onToken - Callback appelé pour chaque token * @returns {Promise<string>} - Texte généré complet */ async streamChat(messages, temperature = 0.7, maxTokens = 2048, onToken = null) { // Utiliser la config spécifique pour qwen2.5-coder si disponible if (this.isQwenCoder) { temperature = this.modelConfig.generationParams.temperature; maxTokens = this.modelConfig.generationParams.max_tokens; // Si le premier message est un système, utiliser le prompt système spécifique if (messages.length > 0 && messages[0].role === 'system' && !messages[0].content.includes('qwen2.5-coder')) { messages[0].content = `${this.modelConfig.systemPrompt}\n\n${messages[0].content}`; } } const payload = { model: this.modelName, messages, temperature, max_tokens: maxTokens, stream: true }; try { const response = await fetch(this.apiChatUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { if (response.status === 404) { throw new Error(`Modèle '${this.modelName}' introuvable.`); } else { throw new Error(`Erreur HTTP ${response.status}: ${await response.text()}`); } } const reader = response.body.getReader(); const decoder = new TextDecoder(); let fullText = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); try { // Ollama renvoie des objets JSON par ligne const lines = chunk.split('\n').filter(line => line.trim()); for (const line of lines) { const data = JSON.parse(line); if (onToken && data.message && data.message.content) { onToken(data.message.content); fullText += data.message.content; } } } catch (e) { console.error('Error parsing streaming response:', e); } } return fullText; } catch (error) { if (error.code === 'ECONNREFUSED') { throw new Error(`Impossible de se connecter au serveur Ollama.`); } throw error; } } /** * Liste les modèles disponibles dans Ollama * @returns {Promise<Array>} - Liste des modèles disponibles */ async listModels() { try { const response = await fetch(`${this.host}/api/tags`); if (!response.ok) { throw new Error(`Erreur HTTP ${response.status}: ${await response.text()}`); } const data = await response.json(); return data.models || []; } catch (error) { throw new Error(`Impossible de lister les modèles: ${error.message}`); } } }