smart-thinking-mcp
Version:
Un serveur MCP avancé pour le raisonnement multi-dimensionnel, adaptatif et collaboratif
301 lines • 11.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmbeddingService = void 0;
const cohere_ai_1 = require("cohere-ai");
const config_1 = require("./config");
/**
* Service qui gère les embeddings vectoriels avec l'API Cohere
* Version optimisée avec batching et LRU cache avec expiration
*/
class EmbeddingService {
client;
// Cache LRU avec expiration
cache = new Map();
cacheSize = 0;
maxCacheSize = config_1.VerificationConfig.MEMORY.MAX_CACHE_SIZE;
cacheExpiration = config_1.VerificationConfig.MEMORY.CACHE_EXPIRATION;
// File d'attente pour le batching
batchQueue = [];
batchTimeout = null;
batchSize = config_1.EmbeddingConfig.BATCH_SIZE;
batchDelay = 50; // Délai en ms pour regrouper les requêtes
constructor(apiKey) {
this.client = new cohere_ai_1.CohereClient({
token: apiKey,
});
}
/**
* Génère un embedding pour un texte donné
* Utilise une file d'attente pour regrouper les requêtes en batch
*
* @param text Le texte pour lequel générer un embedding
* @returns Un vecteur d'embedding
*/
async getEmbedding(text) {
// Tronquer le texte s'il est trop long
const truncatedText = text.length > 1000 ? text.substring(0, 1000) : text;
// Vérifier si l'embedding est déjà en cache et n'a pas expiré
const cachedEntry = this.cache.get(truncatedText);
if (cachedEntry && (Date.now() - cachedEntry.timestamp) < this.cacheExpiration) {
// Rafraîchir le timestamp pour l'algorithme LRU
cachedEntry.timestamp = Date.now();
this.cache.delete(truncatedText);
this.cache.set(truncatedText, cachedEntry);
return cachedEntry.embedding;
}
// Sinon, ajouter à la file d'attente pour traitement en batch
return new Promise((resolve, reject) => {
this.batchQueue.push({
text: truncatedText,
resolve,
reject
});
// Planifier le traitement du batch s'il n'est pas déjà prévu
if (!this.batchTimeout) {
this.batchTimeout = setTimeout(() => this.processBatch(), this.batchDelay);
}
});
}
/**
* Traite la file d'attente des embeddings en lot
*/
async processBatch() {
// Réinitialiser le timeout
this.batchTimeout = null;
// Prendre les éléments de la file jusqu'à la taille maximale du batch
const batch = this.batchQueue.splice(0, this.batchSize);
if (batch.length === 0)
return;
const texts = batch.map(item => item.text);
try {
// Définir une variable pour les tentatives
let retryAttempts = config_1.EmbeddingConfig.RETRY_ATTEMPTS;
let embeddings = [];
// Boucle de tentatives avec délai exponentiel
while (retryAttempts > 0) {
try {
const response = await this.client.embed({
texts,
model: config_1.EmbeddingConfig.MODEL,
inputType: config_1.EmbeddingConfig.INPUT_TYPE,
});
// Type guard pour gérer les différents formats de réponse possibles
embeddings = Array.isArray(response.embeddings) &&
Array.isArray(response.embeddings[0]) ?
response.embeddings :
response.embeddings;
break; // Sortir de la boucle si réussi
}
catch (error) {
retryAttempts--;
if (retryAttempts === 0) {
throw error; // Relancer l'erreur si toutes les tentatives ont échoué
}
// Attendre avec un délai exponentiel avant de réessayer
await new Promise(resolve => setTimeout(resolve, config_1.EmbeddingConfig.RETRY_DELAY * (config_1.EmbeddingConfig.RETRY_ATTEMPTS - retryAttempts)));
}
}
// Mettre en cache et résoudre les promesses pour chaque texte
batch.forEach((item, index) => {
const embedding = embeddings[index] || [];
// Ajouter au cache avec horodatage pour l'expiration
this.addToCache(item.text, embedding);
// Résoudre la promesse
item.resolve(embedding);
});
}
catch (error) {
console.error('Erreur lors de la génération des embeddings en batch:', error);
// Rejeter toutes les promesses avec l'erreur
batch.forEach(item => {
item.reject(error instanceof Error ? error : new Error(String(error)));
});
}
// Traiter le batch suivant s'il reste des éléments dans la file
if (this.batchQueue.length > 0) {
this.batchTimeout = setTimeout(() => this.processBatch(), this.batchDelay);
}
}
/**
* Ajoute un embedding au cache LRU avec expiration
*
* @param text Le texte clé
* @param embedding Le vecteur à mettre en cache
*/
addToCache(text, embedding) {
// Si le cache est plein, supprimer l'entrée la plus ancienne (LRU)
if (this.cacheSize >= this.maxCacheSize && !this.cache.has(text)) {
// Trouver l'entrée la plus ancienne
let oldestKey = '';
let oldestTime = Infinity;
for (const [key, entry] of this.cache.entries()) {
if (entry.timestamp < oldestTime) {
oldestTime = entry.timestamp;
oldestKey = key;
}
}
// Supprimer l'entrée la plus ancienne
if (oldestKey) {
this.cache.delete(oldestKey);
this.cacheSize--;
}
}
// Ajouter ou mettre à jour dans le cache
this.cache.set(text, {
embedding,
timestamp: Date.now()
});
// Mettre à jour la taille du cache uniquement si c'est une nouvelle entrée
if (!this.cache.has(text)) {
this.cacheSize++;
}
}
/**
* Génère des embeddings pour plusieurs textes
* avec gestion optimisée des batchs et du cache
*
* @param texts Les textes pour lesquels générer des embeddings
* @returns Un tableau de vecteurs d'embedding
*/
async getEmbeddings(texts) {
// Effectuer les requêtes d'embedding en parallèle
const promises = texts.map(text => this.getEmbedding(text));
return Promise.all(promises);
}
/**
* Calcule la similarité cosinus entre deux vecteurs
*
* @param vecA Premier vecteur
* @param vecB Second vecteur
* @returns Score de similarité entre 0 et 1
*/
calculateCosineSimilarity(vecA, vecB) {
if (vecA.length === 0 || vecB.length === 0 || vecA.length !== vecB.length) {
return 0;
}
// Calcul optimisé du produit scalaire et des normes
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vecA.length; i++) {
dotProduct += vecA[i] * vecB[i];
normA += vecA[i] * vecA[i];
normB += vecB[i] * vecB[i];
}
normA = Math.sqrt(normA);
normB = Math.sqrt(normB);
// Éviter la division par zéro
if (normA === 0 || normB === 0) {
return 0;
}
// Similarité cosinus
const similarity = dotProduct / (normA * normB);
// Assurer que la similarité est dans l'intervalle [0, 1]
return Math.max(0, Math.min(1, similarity));
}
/**
* Trouve les textes les plus similaires à un texte de référence
* Utilise KNN pour la recherche de similarité
*
* @param referenceText Texte de référence
* @param candidateTexts Textes candidats
* @param limit Nombre maximum de résultats
* @param threshold Seuil de similarité minimum (optionnel)
* @returns Les textes les plus similaires avec leurs scores
*/
async findSimilarTexts(referenceText, candidateTexts, limit = 5, threshold = config_1.VerificationConfig.SIMILARITY.LOW_SIMILARITY) {
if (candidateTexts.length === 0) {
return [];
}
// Générer l'embedding pour le texte de référence
const referenceEmbedding = await this.getEmbedding(referenceText);
if (referenceEmbedding.length === 0) {
return [];
}
// Générer les embeddings pour tous les textes candidats
const candidateEmbeddings = await this.getEmbeddings(candidateTexts);
// Calculer les scores de similarité
const similarities = candidateEmbeddings.map((embedding, index) => ({
text: candidateTexts[index],
score: this.calculateCosineSimilarity(referenceEmbedding, embedding)
}));
// Trier par score de similarité décroissant et limiter les résultats
return similarities
.filter(item => item.score >= threshold) // Filtrer par seuil
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
/**
* Efface le cache d'embeddings ou supprime les entrées expirées
*
* @param all Si true, efface tout le cache, sinon seulement les entrées expirées
*/
clearCache(all = false) {
if (all) {
this.cache.clear();
this.cacheSize = 0;
return;
}
// Supprimer uniquement les entrées expirées
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > this.cacheExpiration) {
this.cache.delete(key);
this.cacheSize--;
}
}
}
/**
* Ajuste les paramètres du cache
*
* @param maxSize Taille maximale du cache
* @param expiration Durée de validité en millisecondes
*/
configureCacheParams(maxSize, expiration) {
if (maxSize !== undefined) {
this.maxCacheSize = maxSize;
}
if (expiration !== undefined) {
this.cacheExpiration = expiration;
}
// Si la nouvelle taille maximale est inférieure à la taille actuelle,
// supprimer les entrées les plus anciennes
if (this.cacheSize > this.maxCacheSize) {
this.reduceCache();
}
}
/**
* Réduit la taille du cache en supprimant les entrées les plus anciennes
*/
reduceCache() {
const entriesToRemove = this.cacheSize - this.maxCacheSize;
if (entriesToRemove <= 0)
return;
// Trier les entrées par timestamp
const entries = Array.from(this.cache.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp);
// Supprimer les plus anciennes
for (let i = 0; i < entriesToRemove; i++) {
if (i < entries.length) {
this.cache.delete(entries[i][0]);
}
}
this.cacheSize = Math.min(this.cacheSize, this.maxCacheSize);
}
/**
* Configure les paramètres de batch
*
* @param batchSize Taille maximale des lots
* @param batchDelay Délai en ms pour regrouper les requêtes
*/
configureBatchParams(batchSize, batchDelay) {
if (batchSize !== undefined) {
this.batchSize = batchSize;
}
if (batchDelay !== undefined) {
this.batchDelay = batchDelay;
}
}
}
exports.EmbeddingService = EmbeddingService;
//# sourceMappingURL=embedding-service.js.map