smart-thinking-mcp
Version:
Un serveur MCP avancé pour le raisonnement multi-dimensionnel, adaptatif et collaboratif
188 lines • 6.92 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmbeddingService = void 0;
const feature_flags_1 = require("./feature-flags");
const config_1 = require("./config");
/**
* Service d'embeddings local sans dépendance externe.
*
* Phase 1 retire les appels Cohere et remplace la génération de vecteurs
* par une représentation déterministe construite à partir du texte.
*/
class EmbeddingService {
cache = new Map();
cacheSize = 0;
maxCacheSize = config_1.VerificationConfig.MEMORY.MAX_CACHE_SIZE;
cacheExpiration = config_1.VerificationConfig.MEMORY.CACHE_EXPIRATION;
static warnedExternalUsage = false;
constructor() {
if (feature_flags_1.FeatureFlags.externalEmbeddingEnabled && !EmbeddingService.warnedExternalUsage) {
console.warn('FeatureFlags.externalEmbeddingEnabled est à true mais aucun fournisseur externe n\'est disponible. ' +
'Réglez le flag à false ou implémentez un fournisseur local.');
EmbeddingService.warnedExternalUsage = true;
}
}
/**
* Génère un embedding déterministe à partir du texte (analyse de fréquence).
*/
buildLocalEmbedding(text) {
const normalised = text.toLowerCase();
const vector = new Array(32).fill(0);
for (let i = 0; i < normalised.length; i++) {
const code = normalised.charCodeAt(i);
// Regrouper les caractères par classe (a-z, chiffres, autre)
if (code >= 97 && code <= 122) {
vector[code - 97] += 1;
}
else if (code >= 48 && code <= 57) {
vector[26 + (code - 48)] += 1;
}
else if (!Number.isNaN(code)) {
vector[31] += 1;
}
}
// Normaliser par la longueur pour rester comparable
const length = normalised.length || 1;
return vector.map(value => value / length);
}
warnFeatureDisabled() {
if (!feature_flags_1.FeatureFlags.externalEmbeddingEnabled && !EmbeddingService.warnedExternalUsage) {
console.warn('EmbeddingService fonctionne en mode local; aucun appel API externe ne sera effectué.');
EmbeddingService.warnedExternalUsage = true;
}
}
addToCache(text, embedding) {
if (this.cache.has(text)) {
this.cache.delete(text);
this.cache.set(text, {
embedding,
timestamp: Date.now()
});
return;
}
if (this.cacheSize >= this.maxCacheSize) {
const oldest = this.cache.keys().next().value;
if (oldest !== undefined) {
this.cache.delete(oldest);
this.cacheSize--;
}
}
this.cache.set(text, {
embedding,
timestamp: Date.now()
});
this.cacheSize++;
}
/**
* Génère un embedding pour un texte donné.
*/
async getEmbedding(text) {
this.warnFeatureDisabled();
const truncatedText = text.length > 1000 ? text.substring(0, 1000) : text;
const cached = this.cache.get(truncatedText);
if (cached && (Date.now() - cached.timestamp) < this.cacheExpiration) {
cached.timestamp = Date.now();
this.cache.delete(truncatedText);
this.cache.set(truncatedText, cached);
return cached.embedding;
}
const embedding = this.buildLocalEmbedding(truncatedText);
this.addToCache(truncatedText, embedding);
return embedding;
}
/**
* Génère des embeddings pour plusieurs textes.
*/
async getEmbeddings(texts) {
return Promise.all(texts.map(text => this.getEmbedding(text)));
}
/**
* Calcule la similarité cosinus entre deux vecteurs.
*/
calculateCosineSimilarity(vecA, vecB) {
if (vecA.length === 0 || vecB.length === 0 || vecA.length !== vecB.length) {
return 0;
}
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);
if (normA === 0 || normB === 0) {
return 0;
}
const similarity = dotProduct / (normA * normB);
return Math.max(0, Math.min(1, similarity));
}
/**
* Trouve les textes les plus similaires.
*/
async findSimilarTexts(referenceText, candidateTexts, limit = 5, threshold = config_1.VerificationConfig.SIMILARITY.LOW_SIMILARITY) {
if (candidateTexts.length === 0) {
return [];
}
const referenceEmbedding = await this.getEmbedding(referenceText);
if (referenceEmbedding.length === 0) {
return [];
}
const candidateEmbeddings = await this.getEmbeddings(candidateTexts);
const similarities = candidateEmbeddings.map((embedding, index) => ({
text: candidateTexts[index],
score: this.calculateCosineSimilarity(referenceEmbedding, embedding)
}));
return similarities
.filter(item => item.score >= threshold)
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
/**
* Efface le cache ou supprime les entrées expirées.
*/
clearCache(all = false) {
if (all) {
this.cache.clear();
this.cacheSize = 0;
return;
}
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.
*/
configureCacheParams(maxSize, expiration) {
if (maxSize !== undefined) {
this.maxCacheSize = maxSize;
}
if (expiration !== undefined) {
this.cacheExpiration = expiration;
}
if (this.cacheSize > this.maxCacheSize) {
while (this.cacheSize > this.maxCacheSize) {
const oldest = this.cache.keys().next().value;
if (oldest === undefined)
break;
this.cache.delete(oldest);
this.cacheSize--;
}
}
}
/**
* Paramètres de batch conservés pour compatibilité (aucun effet en mode local).
*/
configureBatchParams(_batchSize, _batchDelay) {
console.warn('configureBatchParams n\'a pas d\'effet lorsque les embeddings externes sont désactivés.');
}
}
exports.EmbeddingService = EmbeddingService;
//# sourceMappingURL=embedding-service.js.map