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
JavaScript
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}`);
}
}
}