agentic-api 2.0.31 → 2.0.491
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/agents/agents.example.js +21 -22
- package/dist/src/agents/authentication.js +1 -2
- package/dist/src/agents/prompts.d.ts +5 -4
- package/dist/src/agents/prompts.js +44 -87
- package/dist/src/agents/reducer.core.d.ts +24 -2
- package/dist/src/agents/reducer.core.js +125 -35
- package/dist/src/agents/reducer.loaders.d.ts +55 -1
- package/dist/src/agents/reducer.loaders.js +114 -1
- package/dist/src/agents/reducer.types.d.ts +45 -2
- package/dist/src/agents/semantic.js +1 -2
- package/dist/src/agents/simulator.d.ts +11 -3
- package/dist/src/agents/simulator.executor.d.ts +14 -4
- package/dist/src/agents/simulator.executor.js +81 -23
- package/dist/src/agents/simulator.js +128 -42
- package/dist/src/agents/simulator.prompts.d.ts +9 -7
- package/dist/src/agents/simulator.prompts.js +66 -86
- package/dist/src/agents/simulator.types.d.ts +23 -5
- package/dist/src/agents/simulator.utils.d.ts +7 -2
- package/dist/src/agents/simulator.utils.js +31 -11
- package/dist/src/agents/system.js +1 -2
- package/dist/src/execute/helpers.d.ts +75 -0
- package/dist/src/execute/helpers.js +139 -0
- package/dist/src/execute/index.d.ts +11 -0
- package/dist/src/execute/index.js +44 -0
- package/dist/src/execute/legacy.d.ts +46 -0
- package/dist/src/execute/legacy.js +460 -0
- package/dist/src/execute/modelconfig.d.ts +19 -0
- package/dist/src/execute/modelconfig.js +56 -0
- package/dist/src/execute/responses.d.ts +55 -0
- package/dist/src/execute/responses.js +594 -0
- package/dist/src/execute/shared.d.ts +83 -0
- package/dist/src/execute/shared.js +188 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/{princing.openai.d.ts → pricing.llm.d.ts} +6 -0
- package/dist/src/pricing.llm.js +255 -0
- package/dist/src/prompts.d.ts +13 -4
- package/dist/src/prompts.js +221 -114
- package/dist/src/rag/embeddings.d.ts +36 -18
- package/dist/src/rag/embeddings.js +131 -128
- package/dist/src/rag/index.d.ts +5 -5
- package/dist/src/rag/index.js +14 -17
- package/dist/src/rag/parser.d.ts +2 -1
- package/dist/src/rag/parser.js +11 -14
- package/dist/src/rag/rag.examples.d.ts +27 -0
- package/dist/src/rag/rag.examples.js +151 -0
- package/dist/src/rag/rag.manager.d.ts +383 -0
- package/dist/src/rag/rag.manager.js +1390 -0
- package/dist/src/rag/types.d.ts +128 -12
- package/dist/src/rag/types.js +100 -1
- package/dist/src/rag/usecase.d.ts +37 -0
- package/dist/src/rag/usecase.js +96 -7
- package/dist/src/rules/git/git.e2e.helper.js +22 -2
- package/dist/src/rules/git/git.health.d.ts +61 -2
- package/dist/src/rules/git/git.health.js +333 -11
- package/dist/src/rules/git/index.d.ts +2 -2
- package/dist/src/rules/git/index.js +13 -1
- package/dist/src/rules/git/repo.d.ts +160 -0
- package/dist/src/rules/git/repo.js +777 -0
- package/dist/src/rules/git/repo.pr.js +117 -13
- package/dist/src/rules/git/repo.tools.d.ts +22 -1
- package/dist/src/rules/git/repo.tools.js +50 -1
- package/dist/src/rules/types.d.ts +27 -14
- package/dist/src/rules/utils.matter.d.ts +0 -4
- package/dist/src/rules/utils.matter.js +35 -7
- package/dist/src/scrapper.d.ts +15 -22
- package/dist/src/scrapper.js +58 -110
- package/dist/src/stategraph/index.d.ts +1 -1
- package/dist/src/stategraph/stategraph.d.ts +56 -2
- package/dist/src/stategraph/stategraph.js +134 -6
- package/dist/src/stategraph/stategraph.storage.js +8 -0
- package/dist/src/stategraph/types.d.ts +27 -0
- package/dist/src/types.d.ts +46 -9
- package/dist/src/types.js +8 -7
- package/dist/src/usecase.d.ts +11 -2
- package/dist/src/usecase.js +27 -35
- package/dist/src/utils.d.ts +32 -18
- package/dist/src/utils.js +87 -129
- package/package.json +10 -3
- package/dist/src/agents/digestor.test.d.ts +0 -1
- package/dist/src/agents/digestor.test.js +0 -45
- package/dist/src/agents/reducer.example.d.ts +0 -28
- package/dist/src/agents/reducer.example.js +0 -118
- package/dist/src/agents/reducer.process.d.ts +0 -16
- package/dist/src/agents/reducer.process.js +0 -143
- package/dist/src/agents/reducer.tools.d.ts +0 -29
- package/dist/src/agents/reducer.tools.js +0 -157
- package/dist/src/agents/simpleExample.d.ts +0 -3
- package/dist/src/agents/simpleExample.js +0 -38
- package/dist/src/agents/system-review.d.ts +0 -5
- package/dist/src/agents/system-review.js +0 -181
- package/dist/src/agents/systemReview.d.ts +0 -4
- package/dist/src/agents/systemReview.js +0 -22
- package/dist/src/execute.d.ts +0 -49
- package/dist/src/execute.js +0 -564
- package/dist/src/princing.openai.js +0 -54
- package/dist/src/rag/tools.d.ts +0 -76
- package/dist/src/rag/tools.js +0 -196
- package/dist/src/rules/user.mapper.d.ts +0 -61
- package/dist/src/rules/user.mapper.js +0 -160
- package/dist/src/rules/utils/slug.d.ts +0 -22
- package/dist/src/rules/utils/slug.js +0 -35
|
@@ -0,0 +1,1390 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RAGManager = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const embeddings_1 = require("./embeddings");
|
|
11
|
+
const parser_1 = require("./parser");
|
|
12
|
+
const usecase_1 = require("./usecase");
|
|
13
|
+
/** Configuration RAG par défaut */
|
|
14
|
+
const DEFAULT_RAG_CONFIG = {
|
|
15
|
+
dimensions: 1536,
|
|
16
|
+
distance: 'cosine',
|
|
17
|
+
version: '1.0',
|
|
18
|
+
startId: 1000,
|
|
19
|
+
hnswConfig: { ef: 200, m: 32 }
|
|
20
|
+
};
|
|
21
|
+
const DEFAULT_RAG_INSTANCE = {};
|
|
22
|
+
/**
|
|
23
|
+
* Gestionnaire multi-RAG stateless pour gérer plusieurs index RAG
|
|
24
|
+
*/
|
|
25
|
+
class RAGManager {
|
|
26
|
+
constructor(config) {
|
|
27
|
+
this.cachedRegistry = null;
|
|
28
|
+
this.lastLoadTime = 0;
|
|
29
|
+
/**
|
|
30
|
+
* Map des embeddings chargés pour permettre les updates réactifs
|
|
31
|
+
* Note: Pour une meilleure gestion mémoire, appeler cleanDeadReferences() périodiquement
|
|
32
|
+
*/
|
|
33
|
+
this.loadedEmbeddings = new Map();
|
|
34
|
+
if (!config.baseDir || !(0, fs_1.existsSync)(config.baseDir)) {
|
|
35
|
+
throw new Error(`Le dossier du registre RAG n'existe pas: ${config.baseDir}`);
|
|
36
|
+
}
|
|
37
|
+
this.config = {
|
|
38
|
+
tempDir: path_1.default.join(config.baseDir, 'temp'),
|
|
39
|
+
archiveDir: path_1.default.join(config.baseDir, 'archives'),
|
|
40
|
+
maxArchives: 5,
|
|
41
|
+
...config
|
|
42
|
+
};
|
|
43
|
+
this.registryFile = path_1.default.join(this.config.baseDir, 'registry.json');
|
|
44
|
+
// Créer les dossiers nécessaires
|
|
45
|
+
this.ensureDirectories();
|
|
46
|
+
// Charger le registre initial
|
|
47
|
+
this.loadRegistry();
|
|
48
|
+
//
|
|
49
|
+
// store instance in memory
|
|
50
|
+
DEFAULT_RAG_INSTANCE[this.config.baseDir] = this;
|
|
51
|
+
const registry = this.getRegistry();
|
|
52
|
+
const defaultName = registry.defaultName || 'RAGRegistry';
|
|
53
|
+
DEFAULT_RAG_INSTANCE[defaultName] = this;
|
|
54
|
+
}
|
|
55
|
+
static get(config) {
|
|
56
|
+
// Vérifier si une instance existe déjà pour ce baseDir
|
|
57
|
+
if (DEFAULT_RAG_INSTANCE[config.baseDir]) {
|
|
58
|
+
return DEFAULT_RAG_INSTANCE[config.baseDir];
|
|
59
|
+
}
|
|
60
|
+
// Créer une nouvelle instance
|
|
61
|
+
return new RAGManager(config);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Nettoie le cache des instances (utile pour les tests)
|
|
65
|
+
*/
|
|
66
|
+
static clearCache() {
|
|
67
|
+
Object.keys(DEFAULT_RAG_INSTANCE).forEach(key => {
|
|
68
|
+
delete DEFAULT_RAG_INSTANCE[key];
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Crée les dossiers nécessaires s'ils n'existent pas
|
|
73
|
+
*/
|
|
74
|
+
ensureDirectories() {
|
|
75
|
+
[this.config.baseDir, this.config.tempDir, this.config.archiveDir].forEach(dir => {
|
|
76
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
77
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Charge le registre depuis le fichier JSON et scan les dossiers
|
|
83
|
+
*/
|
|
84
|
+
loadRegistry() {
|
|
85
|
+
this.lastLoadTime = Date.now();
|
|
86
|
+
// Charger le registre existant ou créer un nouveau
|
|
87
|
+
let registry;
|
|
88
|
+
if ((0, fs_1.existsSync)(this.registryFile)) {
|
|
89
|
+
try {
|
|
90
|
+
registry = JSON.parse((0, fs_1.readFileSync)(this.registryFile, 'utf8'));
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.warn('Erreur lors du chargement du registre, création d\'un nouveau:', error);
|
|
94
|
+
registry = {
|
|
95
|
+
version: '1.0',
|
|
96
|
+
registries: {},
|
|
97
|
+
defaultName: 'RAGRegistry'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
registry = {
|
|
103
|
+
version: '1.0',
|
|
104
|
+
registries: {},
|
|
105
|
+
defaultName: 'RAGRegistry'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Définir defaultName par défaut s'il n'est pas défini
|
|
109
|
+
if (!registry.defaultName) {
|
|
110
|
+
registry.defaultName = 'RAGRegistry';
|
|
111
|
+
}
|
|
112
|
+
// Scanner les dossiers pour découvrir les RAG existants
|
|
113
|
+
this.scanDirectoriesForRAGs(registry);
|
|
114
|
+
// Sauvegarder le registre mis à jour (seulement si des changements détectés)
|
|
115
|
+
this.saveRegistry(registry);
|
|
116
|
+
// Mettre en cache
|
|
117
|
+
this.cachedRegistry = registry;
|
|
118
|
+
return registry;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Scanne les dossiers pour découvrir les RAG existants
|
|
122
|
+
*/
|
|
123
|
+
scanDirectoriesForRAGs(registry) {
|
|
124
|
+
if (!(0, fs_1.existsSync)(this.config.baseDir)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const entries = (0, fs_1.readdirSync)(this.config.baseDir, { withFileTypes: true });
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (entry.isDirectory() &&
|
|
130
|
+
entry.name !== 'temp' &&
|
|
131
|
+
entry.name !== 'archives' &&
|
|
132
|
+
entry.name !== 'registry.json') {
|
|
133
|
+
const ragPath = path_1.default.join(this.config.baseDir, entry.name);
|
|
134
|
+
const metadataFile = path_1.default.join(ragPath, 'rag-metadata.json');
|
|
135
|
+
// Vérifier si c'est un dossier RAG valide
|
|
136
|
+
if ((0, fs_1.existsSync)(metadataFile)) {
|
|
137
|
+
// Lire les métadonnées pour obtenir le nom d'alias potentiel
|
|
138
|
+
let aliasName;
|
|
139
|
+
try {
|
|
140
|
+
const metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
|
|
141
|
+
aliasName = metadata.name || metadata.displayName;
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
// Ignorer les erreurs de parsing
|
|
145
|
+
}
|
|
146
|
+
// Ajouter ou mettre à jour l'entrée dans le registre
|
|
147
|
+
if (!registry.registries[entry.name]) {
|
|
148
|
+
const ragEntry = {
|
|
149
|
+
name: aliasName || entry.name, // Utiliser l'alias si disponible
|
|
150
|
+
configPath: ragPath,
|
|
151
|
+
status: 'active'
|
|
152
|
+
};
|
|
153
|
+
// Ajouter sous la clé du dossier
|
|
154
|
+
registry.registries[entry.name] = ragEntry;
|
|
155
|
+
// Ajouter aussi sous l'alias si différent
|
|
156
|
+
if (aliasName && aliasName !== entry.name) {
|
|
157
|
+
registry.registries[aliasName] = ragEntry;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Mettre à jour le chemin si nécessaire
|
|
162
|
+
registry.registries[entry.name].configPath = ragPath;
|
|
163
|
+
// Mettre à jour aussi l'alias si présent
|
|
164
|
+
const existingName = registry.registries[entry.name].name;
|
|
165
|
+
if (existingName && existingName !== entry.name && registry.registries[existingName]) {
|
|
166
|
+
registry.registries[existingName].configPath = ragPath;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Vérifie si un RAG existe dans le registre (peu importe son statut)
|
|
175
|
+
* Recherche par clé du dictionnaire OU par entry.name
|
|
176
|
+
*/
|
|
177
|
+
exists(name) {
|
|
178
|
+
const registry = this.getRegistry();
|
|
179
|
+
// Chercher d'abord par clé (comportement normal)
|
|
180
|
+
const entry = name in registry.registries || Object.values(registry.registries).some(entry => entry.name === name);
|
|
181
|
+
return entry;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Obtient le registre en cache ou le recharge si nécessaire
|
|
185
|
+
*/
|
|
186
|
+
getRegistry() {
|
|
187
|
+
const timeout = 3600 * 4; // 4 hours
|
|
188
|
+
if (!this.cachedRegistry || Date.now() - this.lastLoadTime > timeout) {
|
|
189
|
+
return this.loadRegistry();
|
|
190
|
+
}
|
|
191
|
+
return this.cachedRegistry;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Sauvegarde le registre dans le fichier JSON
|
|
195
|
+
*/
|
|
196
|
+
saveRegistry(registry) {
|
|
197
|
+
try {
|
|
198
|
+
this.cachedRegistry = registry;
|
|
199
|
+
(0, fs_1.writeFileSync)(this.registryFile, JSON.stringify(registry, null, 2), 'utf8');
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.error('Erreur lors de la sauvegarde du registre RAG:', error);
|
|
203
|
+
throw new Error(`Impossible de sauvegarder le registre RAG: ${error}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Notifie les embeddings chargés de se mettre à jour
|
|
208
|
+
*
|
|
209
|
+
* @param name Nom du RAG à mettre à jour
|
|
210
|
+
* @param opts Options de mise à jour (action: 'rename' pour renommer un document)
|
|
211
|
+
*
|
|
212
|
+
* FIXME: Gérer le cas où l'embedding est en cours d'exécution (recherche/indexation)
|
|
213
|
+
* Actuellement, l'update est appelé immédiatement sans vérifier si l'embedding est busy.
|
|
214
|
+
* Solutions possibles:
|
|
215
|
+
* - Ajouter un flag `isBusy` dans Embeddings
|
|
216
|
+
* - Implémenter une queue d'updates
|
|
217
|
+
* - Utiliser un système de locks
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* // Après un build
|
|
222
|
+
* this.notifyUpdate('procedures-stable');
|
|
223
|
+
*
|
|
224
|
+
* // Après un rename de document
|
|
225
|
+
* this.notifyUpdate('procedures-stable', {
|
|
226
|
+
* action: 'rename',
|
|
227
|
+
* oldFile: 'old.md',
|
|
228
|
+
* newFile: 'new.md'
|
|
229
|
+
* });
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
notifyUpdate(name, opts) {
|
|
233
|
+
const embeddingData = this.loadedEmbeddings.get(name);
|
|
234
|
+
console.log(`\n🔔 notifyUpdate('${name}') appelé`);
|
|
235
|
+
console.log(` Embedding en cache: ${embeddingData ? 'OUI' : 'NON'}`);
|
|
236
|
+
// Si l'embedding n'est pas encore chargé, rien à faire
|
|
237
|
+
// Il sera chargé avec la nouvelle config au prochain load()
|
|
238
|
+
if (!embeddingData) {
|
|
239
|
+
console.log(` ⏭️ Skip: sera chargé avec la nouvelle config au prochain load()`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Charger la nouvelle configuration depuis le registre
|
|
243
|
+
const entry = this.getEntry(name);
|
|
244
|
+
if (!entry) {
|
|
245
|
+
console.log(` ⚠️ Entry '${name}' introuvable dans le registre`);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
console.log(` 📁 entry.configPath: ${entry.configPath}`);
|
|
249
|
+
// L'embedding est chargé, forcer son rechargement via update()
|
|
250
|
+
// update() modifie l'instance EN PLACE, donc tous ceux qui ont une référence voient la nouvelle version
|
|
251
|
+
const { embedding } = embeddingData;
|
|
252
|
+
try {
|
|
253
|
+
const newConfig = this.loadConfig(entry.configPath);
|
|
254
|
+
console.log(` 🔄 Appel de embedding.update() avec baseDir: ${newConfig.baseDir}`);
|
|
255
|
+
// FIXME: Vérifier si l'embedding est busy avant de faire l'update
|
|
256
|
+
// update() va recharger l'index, les métadonnées et le mapping depuis newConfig.baseDir
|
|
257
|
+
// update() modifie l'instance en place, donc tous les utilisateurs voient la nouvelle version
|
|
258
|
+
embedding.update(newConfig);
|
|
259
|
+
// Mettre à jour le timestamp du cache
|
|
260
|
+
embeddingData.loadTime = Date.now();
|
|
261
|
+
console.log(` ✅ Embedding rechargé avec succès`);
|
|
262
|
+
console.log(` ✅ embedding.isReady(): ${embedding.isReady()}`);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
console.error(` ❌ Erreur lors de la mise à jour de l'embedding '${name}':`, error);
|
|
266
|
+
// En cas d'erreur, update() a déjà fait un rollback de sa propre config
|
|
267
|
+
// L'instance continue de fonctionner avec l'ancienne config
|
|
268
|
+
// Ne pas throw pour éviter de bloquer le flow
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Génère un nom d'archive avec timestamp YYYMMDD
|
|
273
|
+
*/
|
|
274
|
+
generateArchiveName(name) {
|
|
275
|
+
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
276
|
+
return `${name}-${timestamp}`;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Archive un RAG existant
|
|
280
|
+
*/
|
|
281
|
+
async archive(name) {
|
|
282
|
+
const entry = this.getEntry(name);
|
|
283
|
+
if (!entry || !(0, fs_1.existsSync)(entry.configPath)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const archiveName = this.generateArchiveName(name);
|
|
287
|
+
const archivePath = path_1.default.join(this.config.archiveDir, archiveName);
|
|
288
|
+
try {
|
|
289
|
+
// Copier le dossier vers les archives
|
|
290
|
+
(0, fs_1.renameSync)(entry.configPath, archivePath);
|
|
291
|
+
if (this.config.verbose)
|
|
292
|
+
console.log(`✅ RAG archivé: ${name} -> ${archiveName}`);
|
|
293
|
+
// Nettoyer les anciennes archives si nécessaire
|
|
294
|
+
await this.cleanOldArchives(name);
|
|
295
|
+
return archiveName;
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.error(`Erreur lors de l'archivage de ${name}:`, error);
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Charge la configuration depuis le fichier metadata
|
|
304
|
+
*/
|
|
305
|
+
loadConfig(configPath) {
|
|
306
|
+
const metadataFile = path_1.default.join(configPath, 'rag-metadata.json');
|
|
307
|
+
if (!(0, fs_1.existsSync)(metadataFile)) {
|
|
308
|
+
throw new Error(`Fichier metadata manquant: ${metadataFile}`);
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
|
|
312
|
+
// Extraire la configuration depuis les métadonnées
|
|
313
|
+
const ragConfig = {
|
|
314
|
+
baseDir: configPath,
|
|
315
|
+
dimensions: metadata.config?.dimensions || 1536,
|
|
316
|
+
distance: metadata.config?.distance || 'cosine',
|
|
317
|
+
version: metadata.config?.version || metadata.version || '1.0',
|
|
318
|
+
startId: metadata.config?.startId || 1000,
|
|
319
|
+
hnswConfig: metadata.config?.hnswConfig || { ef: 200, m: 32 }
|
|
320
|
+
};
|
|
321
|
+
return ragConfig;
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
throw new Error(`Erreur lors du chargement de la configuration RAG: ${error}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Sauvegarde les métadonnées RAG dans un dossier
|
|
329
|
+
*
|
|
330
|
+
* @param dirPath Chemin du dossier où sauvegarder les métadonnées
|
|
331
|
+
* @param metadata Métadonnées complètes du RAG
|
|
332
|
+
* @private
|
|
333
|
+
*/
|
|
334
|
+
saveMetadata(dirPath, metadata) {
|
|
335
|
+
const metadataFile = path_1.default.join(dirPath, types_1.RAG_FILES.METADATA);
|
|
336
|
+
try {
|
|
337
|
+
(0, fs_1.writeFileSync)(metadataFile, JSON.stringify(metadata, null, 2), 'utf8');
|
|
338
|
+
if (this.config.verbose) {
|
|
339
|
+
console.log(`✅ Métadonnées sauvegardées: ${metadataFile}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
throw new Error(`Échec de la sauvegarde des métadonnées: ${error}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Sauvegarde le mapping RAG dans un dossier
|
|
348
|
+
*
|
|
349
|
+
* @param dirPath Chemin du dossier où sauvegarder le mapping
|
|
350
|
+
* @param mapping Mapping ID → ref des sections
|
|
351
|
+
* @param buildResult Résultat de la construction pour les métadonnées du mapping
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
saveMapping(dirPath, mapping, buildResult) {
|
|
355
|
+
const mappingFile = path_1.default.join(dirPath, types_1.RAG_FILES.MAPPING);
|
|
356
|
+
const mappingData = {
|
|
357
|
+
type: 'mapping',
|
|
358
|
+
version: buildResult.version,
|
|
359
|
+
tag: buildResult.tag,
|
|
360
|
+
mapping,
|
|
361
|
+
createdAt: new Date().toISOString()
|
|
362
|
+
};
|
|
363
|
+
try {
|
|
364
|
+
(0, fs_1.writeFileSync)(mappingFile, JSON.stringify(mappingData, null, 2), 'utf8');
|
|
365
|
+
if (this.config.verbose) {
|
|
366
|
+
console.log(`✅ Mapping sauvegardé: ${mappingFile}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
throw new Error(`Échec de la sauvegarde du mapping: ${error}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Nettoie les anciennes archives d'un RAG
|
|
375
|
+
*/
|
|
376
|
+
async cleanOldArchives(name) {
|
|
377
|
+
if (!this.config.maxArchives || this.config.maxArchives <= 0)
|
|
378
|
+
return;
|
|
379
|
+
try {
|
|
380
|
+
const archives = (0, fs_1.readdirSync)(this.config.archiveDir)
|
|
381
|
+
.filter(dir => dir.startsWith(`${name}-`))
|
|
382
|
+
.sort()
|
|
383
|
+
.reverse(); // Plus récent en premier
|
|
384
|
+
if (archives.length > this.config.maxArchives) {
|
|
385
|
+
const toDelete = archives.slice(this.config.maxArchives);
|
|
386
|
+
for (const archive of toDelete) {
|
|
387
|
+
const archivePath = path_1.default.join(this.config.archiveDir, archive);
|
|
388
|
+
(0, fs_1.rmSync)(archivePath, { recursive: true, force: true });
|
|
389
|
+
console.log(`🗑️ Archive supprimée: ${archive}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
console.warn(`Erreur lors du nettoyage des archives pour ${name}:`, error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Récupère l'entrée d'un RAG dans le registre
|
|
399
|
+
*
|
|
400
|
+
* @param name Nom du RAG
|
|
401
|
+
* @returns L'entrée du registre ou undefined si non trouvé
|
|
402
|
+
*/
|
|
403
|
+
getEntry(name) {
|
|
404
|
+
const registry = this.getRegistry();
|
|
405
|
+
// Chercher d'abord par clé (comportement normal)
|
|
406
|
+
const entry = registry.registries[name] || Object.values(registry.registries).find(e => e.name === name);
|
|
407
|
+
return entry;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Crée un nouveau RAG dans le registre et retourne le chemin temporaire
|
|
411
|
+
*
|
|
412
|
+
* Le registre stocke:
|
|
413
|
+
* - configPath: le tempPath (source de construction)
|
|
414
|
+
* - destDir: le chemin de destination finale
|
|
415
|
+
* - status: 'building'
|
|
416
|
+
* - description: label descriptif court (optionnel)
|
|
417
|
+
*
|
|
418
|
+
* Après build(), configPath sera remplacé par destDir et status = 'active'
|
|
419
|
+
*
|
|
420
|
+
* @param name Nom du RAG
|
|
421
|
+
* @param options Options de création
|
|
422
|
+
* @returns Chemin du dossier temporaire créé
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```typescript
|
|
426
|
+
* // Création simple
|
|
427
|
+
* await ragManager.create('my-rag', { description: 'Mon RAG' });
|
|
428
|
+
*
|
|
429
|
+
* // Création avec fork
|
|
430
|
+
* await ragManager.create('child-rag', {
|
|
431
|
+
* description: 'PR validation',
|
|
432
|
+
* fork: 'parent-rag',
|
|
433
|
+
* exclude: ['modif-1.md', 'modif-2.md']
|
|
434
|
+
* });
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
async create(name, options) {
|
|
438
|
+
if (this.exists(name)) {
|
|
439
|
+
throw new Error(`RAG '${name}' existe déjà`);
|
|
440
|
+
}
|
|
441
|
+
// Support legacy: string → description
|
|
442
|
+
const opts = typeof options === 'string' ? { description: options } : (options || {});
|
|
443
|
+
// Créer dossier temporaire avec timestamp
|
|
444
|
+
const tempPath = path_1.default.join(this.config.tempDir, `${name}-${Date.now()}`);
|
|
445
|
+
(0, fs_1.mkdirSync)(tempPath, { recursive: true });
|
|
446
|
+
// Chemin de destination finale
|
|
447
|
+
const destDir = path_1.default.join(this.config.baseDir, name);
|
|
448
|
+
// Créer l'entrée dans le registre
|
|
449
|
+
const registry = this.getRegistry();
|
|
450
|
+
registry.registries[name] = {
|
|
451
|
+
name,
|
|
452
|
+
configPath: tempPath, // Source temporaire (tant que building)
|
|
453
|
+
destDir: destDir, // Destination finale (pour après build)
|
|
454
|
+
status: 'building',
|
|
455
|
+
description: opts.description, // Label descriptif court
|
|
456
|
+
checkpoint: opts.checkpoint // Hash du dernier commit
|
|
457
|
+
};
|
|
458
|
+
// Définir comme défaut si c'est le premier
|
|
459
|
+
if (Object.keys(registry.registries).length === 1) {
|
|
460
|
+
registry.defaultName = name;
|
|
461
|
+
}
|
|
462
|
+
this.saveRegistry(registry);
|
|
463
|
+
if (this.config.verbose)
|
|
464
|
+
console.log(`✅ RAG créé: ${name} (temp: ${tempPath}, dest: ${destDir})`);
|
|
465
|
+
// Si option fork, copier les documents du parent
|
|
466
|
+
if (opts.fork) {
|
|
467
|
+
if (this.config.verbose) {
|
|
468
|
+
console.log(`🔄 Fork depuis '${opts.fork}'...`);
|
|
469
|
+
}
|
|
470
|
+
const forkResult = this.fork(opts.fork, name, { exclude: opts.exclude });
|
|
471
|
+
if (this.config.verbose) {
|
|
472
|
+
console.log(`✅ Fork terminé: ${forkResult.documents} documents copiés (${forkResult.files} fichiers)`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return tempPath; // Retourner le chemin temp
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Sauvegarde un document dans le dossier de construction d'un RAG
|
|
479
|
+
*
|
|
480
|
+
* Le registre est consulté pour trouver le dossier (configPath en mode building)
|
|
481
|
+
*
|
|
482
|
+
* @param name Nom du RAG
|
|
483
|
+
* @param filename Nom du fichier (ex: 'my-document.md')
|
|
484
|
+
* @param content Contenu markdown avec front-matter
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* ```typescript
|
|
488
|
+
* await ragManager.create('my-rag');
|
|
489
|
+
* ragManager.addDocument('my-rag', 'procedure.md', markdownContent);
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
addDocument(name, filename, content) {
|
|
493
|
+
// Récupérer le chemin depuis le registre
|
|
494
|
+
const entry = this.getEntry(name);
|
|
495
|
+
if (!entry) {
|
|
496
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
497
|
+
}
|
|
498
|
+
// if (status.status !== 'building') {
|
|
499
|
+
// throw new Error(`RAG '${name}' n'est pas en construction (status: ${status.status})`);
|
|
500
|
+
// }
|
|
501
|
+
const buildPath = entry.configPath;
|
|
502
|
+
if (!(0, fs_1.existsSync)(buildPath)) {
|
|
503
|
+
throw new Error(`Le dossier de construction n'existe pas: ${buildPath}`);
|
|
504
|
+
}
|
|
505
|
+
const filePath = path_1.default.join(buildPath, filename);
|
|
506
|
+
const shaPath = path_1.default.join(buildPath, filename + '.sha');
|
|
507
|
+
// ✅ Sauvegarder le fichier
|
|
508
|
+
(0, fs_1.writeFileSync)(filePath, content, 'utf8');
|
|
509
|
+
// ✅ Sauvegarder le fingerprint
|
|
510
|
+
const sha = (0, usecase_1.calculateFingerprint)(content);
|
|
511
|
+
(0, fs_1.writeFileSync)(shaPath, sha, 'utf8');
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Prépare un RAG enfant en copiant les documents d'un RAG parent
|
|
515
|
+
*
|
|
516
|
+
* Cette méthode fork un RAG existant en copiant tous ses documents:
|
|
517
|
+
* - `filename.md` - Fichier markdown original
|
|
518
|
+
* - `filename.md.sha` - Empreinte SHA du fichier original
|
|
519
|
+
* - `filename.enhanced.md` - Version enrichie (si disponible)
|
|
520
|
+
* - `filename.query.json` - Métadonnées et use cases (si disponible)
|
|
521
|
+
*
|
|
522
|
+
* Les fichiers d'index (vectors.dat, mapping.json, metadata.json) ne sont PAS copiés
|
|
523
|
+
* car ils seront reconstruits par build()
|
|
524
|
+
*
|
|
525
|
+
* @param fromRagName Nom du RAG parent
|
|
526
|
+
* @param toRagName Nom du RAG enfant (doit déjà exister via create())
|
|
527
|
+
* @param options Options de fork
|
|
528
|
+
* @returns Objet avec le nombre de documents et fichiers copiés
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```typescript
|
|
532
|
+
* // Créer le RAG enfant
|
|
533
|
+
* await ragManager.create('rule-validation-1', 'PR validation');
|
|
534
|
+
*
|
|
535
|
+
* // Fork depuis le parent en excluant les fichiers modifiés
|
|
536
|
+
* const result = await ragManager.fork('rule-editor', 'rule-validation-1', {
|
|
537
|
+
* exclude: ['modif-1.md', 'modif-2.md']
|
|
538
|
+
* });
|
|
539
|
+
* console.log(`✅ ${result.documents} documents copiés (${result.files} fichiers)`);
|
|
540
|
+
*
|
|
541
|
+
* // Ajouter les documents modifiés
|
|
542
|
+
* ragManager.addDocument('rule-validation-1', 'modif-1.md', newContent1);
|
|
543
|
+
* ragManager.addDocument('rule-validation-1', 'modif-2.md', newContent2);
|
|
544
|
+
*
|
|
545
|
+
* // Builder l'index
|
|
546
|
+
* await ragManager.build('rule-validation-1', allDocs);
|
|
547
|
+
* ```
|
|
548
|
+
*/
|
|
549
|
+
fork(fromRagName, toRagName, options = {}) {
|
|
550
|
+
const excludes = options.exclude || [];
|
|
551
|
+
if (this.config.verbose) {
|
|
552
|
+
console.log(`🔄 Fork de '${fromRagName}' vers '${toRagName}' (${excludes.length} exclusions)`);
|
|
553
|
+
}
|
|
554
|
+
const result = this.addDocumentFrom(fromRagName, toRagName, excludes);
|
|
555
|
+
if (this.config.verbose) {
|
|
556
|
+
console.log(`✅ Fork terminé: ${result.documents} documents copiés (${result.files} fichiers)`);
|
|
557
|
+
}
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* [USAGE INTERNE] Copie tous les documents d'un RAG parent vers un RAG destination
|
|
562
|
+
*
|
|
563
|
+
* Cette méthode copie tous les fichiers associés aux documents indexés:
|
|
564
|
+
* - `filename.md` - Fichier markdown original
|
|
565
|
+
* - `filename.md.sha` - Empreinte SHA du fichier original
|
|
566
|
+
* - `filename.enhanced.md` - Version enrichie (si disponible)
|
|
567
|
+
* - `filename.query.json` - Métadonnées et use cases (si disponible)
|
|
568
|
+
*
|
|
569
|
+
* @private
|
|
570
|
+
* @param fromRagName Nom du RAG source
|
|
571
|
+
* @param toRagName Nom du RAG destination
|
|
572
|
+
* @param excludes Liste des fichiers à ne PAS copier (ex: ['procedure-1.md', 'procedure-2.md'])
|
|
573
|
+
* @returns Objet avec le nombre de documents copiés et le nombre de fichiers copiés
|
|
574
|
+
*/
|
|
575
|
+
addDocumentFrom(fromRagName, toRagName, excludes = []) {
|
|
576
|
+
// Récupérer le RAG source
|
|
577
|
+
const source = this.getEntry(fromRagName);
|
|
578
|
+
if (!source) {
|
|
579
|
+
throw new Error(`RAG source '${fromRagName}' n'existe pas`);
|
|
580
|
+
}
|
|
581
|
+
// Vérifier que le RAG source est actif
|
|
582
|
+
if (source.status !== 'active') {
|
|
583
|
+
throw new Error(`RAG source '${fromRagName}' n'est pas actif (status: ${source.status})`);
|
|
584
|
+
}
|
|
585
|
+
// Récupérer le RAG destination
|
|
586
|
+
const dest = this.getEntry(toRagName);
|
|
587
|
+
if (!dest) {
|
|
588
|
+
throw new Error(`RAG destination '${toRagName}' n'existe pas`);
|
|
589
|
+
}
|
|
590
|
+
// Charger les métadonnées du parent pour obtenir la liste des documents
|
|
591
|
+
let parentMetadata;
|
|
592
|
+
try {
|
|
593
|
+
const parentEmbeddings = this.load(fromRagName);
|
|
594
|
+
parentMetadata = parentEmbeddings.getMetadata();
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
throw new Error(`Impossible de charger les métadonnées du RAG '${fromRagName}': ${error}`);
|
|
598
|
+
}
|
|
599
|
+
// Créer un Set des fichiers à exclure (normaliser les noms)
|
|
600
|
+
const excludeSet = new Set(excludes.map(f => f.replace(/\.md$/, '')));
|
|
601
|
+
// Liste des extensions de fichiers à copier
|
|
602
|
+
const extensions = [
|
|
603
|
+
'.md', // Fichier original
|
|
604
|
+
'.md.sha', // Empreinte
|
|
605
|
+
'.enhanced.md', // Version enrichie
|
|
606
|
+
'.query.json' // Métadonnées et use cases
|
|
607
|
+
];
|
|
608
|
+
let copiedDocuments = 0;
|
|
609
|
+
let copiedFiles = 0;
|
|
610
|
+
// Parcourir tous les documents dans les métadonnées
|
|
611
|
+
for (const docRef of Object.values(parentMetadata.documents)) {
|
|
612
|
+
const baseName = docRef.filename.replace(/\.md$/, '');
|
|
613
|
+
// Vérifier si ce document est dans la liste d'exclusion
|
|
614
|
+
if (excludeSet.has(baseName)) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
let documentCopied = false;
|
|
618
|
+
// Copier tous les fichiers existants pour ce document
|
|
619
|
+
for (const ext of extensions) {
|
|
620
|
+
const sourceFile = path_1.default.join(source.configPath, baseName + ext);
|
|
621
|
+
const destFile = path_1.default.join(dest.configPath, baseName + ext);
|
|
622
|
+
if ((0, fs_1.existsSync)(sourceFile)) {
|
|
623
|
+
(0, fs_1.copyFileSync)(sourceFile, destFile);
|
|
624
|
+
copiedFiles++;
|
|
625
|
+
documentCopied = true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (documentCopied) {
|
|
629
|
+
copiedDocuments++;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Note: Les fichiers d'index (vectors.dat, mapping.json, metadata.json)
|
|
633
|
+
// ne sont PAS copiés car ils seront reconstruits par build()
|
|
634
|
+
return { documents: copiedDocuments, files: copiedFiles };
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Compare le contenu d'un document avec celui stocké dans le RAG
|
|
638
|
+
*
|
|
639
|
+
* @param name Nom du RAG
|
|
640
|
+
* @param file Nom du fichier (ex: 'ppe-budget.md')
|
|
641
|
+
* @param content Contenu à comparer
|
|
642
|
+
* @returns true si identique, false sinon
|
|
643
|
+
*/
|
|
644
|
+
documentEqual(name, file, content) {
|
|
645
|
+
const entry = this.getEntry(name);
|
|
646
|
+
if (!entry) {
|
|
647
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
648
|
+
}
|
|
649
|
+
const configPath = entry.configPath;
|
|
650
|
+
const mdPath = path_1.default.join(configPath, file);
|
|
651
|
+
const shaPath = path_1.default.join(configPath, file + '.sha');
|
|
652
|
+
// Vérifier si le fichier et son sha existent
|
|
653
|
+
if (!(0, fs_1.existsSync)(mdPath) || !(0, fs_1.existsSync)(shaPath)) {
|
|
654
|
+
return false; // Fichier n'existe pas encore
|
|
655
|
+
}
|
|
656
|
+
// Comparer les fingerprints
|
|
657
|
+
try {
|
|
658
|
+
const storedSha = (0, fs_1.readFileSync)(shaPath, 'utf8');
|
|
659
|
+
const newSha = (0, usecase_1.calculateFingerprint)(content);
|
|
660
|
+
return storedSha === newSha;
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
return false; // Erreur de lecture = considérer comme différent
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Charge les use cases d'un document depuis un RAG
|
|
668
|
+
*
|
|
669
|
+
* @param name Nom du RAG
|
|
670
|
+
* @param filename Nom du fichier de base (ex: 'procedure.md')
|
|
671
|
+
* @returns Les use cases du document
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* ```typescript
|
|
675
|
+
* const queries = ragManager.loadUseCases('my-rag', 'procedure.md');
|
|
676
|
+
* console.log(`${queries.queries.length} use cases trouvés`);
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
679
|
+
loadUseCases(name, filename) {
|
|
680
|
+
const entry = this.getEntry(name);
|
|
681
|
+
if (!entry) {
|
|
682
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
683
|
+
}
|
|
684
|
+
const configPath = entry.configPath;
|
|
685
|
+
const baseName = filename.replace(/\.md$/, '');
|
|
686
|
+
const queryFile = baseName + '.query.json';
|
|
687
|
+
return (0, usecase_1.loadUseCases)(queryFile, { baseDir: configPath });
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Construit un RAG de manière atomique
|
|
691
|
+
*
|
|
692
|
+
* Utilise le registre pour trouver le dossier de construction (configPath)
|
|
693
|
+
* et la destination finale (destDir)
|
|
694
|
+
*
|
|
695
|
+
* @param name Nom du RAG
|
|
696
|
+
* @param documents Documents parsés (optionnel, sinon charge depuis configPath)
|
|
697
|
+
* @param ragConfig Configuration optionnelle du RAG
|
|
698
|
+
*/
|
|
699
|
+
async build(name, documents, ragConfig) {
|
|
700
|
+
let docs;
|
|
701
|
+
let isLegacyMode = false;
|
|
702
|
+
let entry;
|
|
703
|
+
let buildPath;
|
|
704
|
+
let destDir;
|
|
705
|
+
// Vérifier que le RAG existe
|
|
706
|
+
if (!this.exists(name)) {
|
|
707
|
+
throw new Error(`RAG '${name}' n'existe pas. Appelez d'abord create() ou create() avec fork.`);
|
|
708
|
+
}
|
|
709
|
+
entry = this.getEntry(name);
|
|
710
|
+
buildPath = entry.configPath;
|
|
711
|
+
destDir = entry.destDir;
|
|
712
|
+
// Déterminer le mode
|
|
713
|
+
if (documents) {
|
|
714
|
+
// ✅ Mode legacy : documents fournis
|
|
715
|
+
isLegacyMode = true;
|
|
716
|
+
docs = documents;
|
|
717
|
+
// Valider les documents
|
|
718
|
+
for (const document of docs) {
|
|
719
|
+
(0, types_1.isValidRAGMatter)(document.matter);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
// ✅ Mode normal : charger depuis le dossier
|
|
724
|
+
if (!(0, fs_1.existsSync)(buildPath)) {
|
|
725
|
+
throw new Error(`Le dossier de construction n'existe pas: ${buildPath}`);
|
|
726
|
+
}
|
|
727
|
+
// ✅ Charger les fichiers : .enhanced.md en priorité, sinon .md
|
|
728
|
+
docs = await this._loadDocumentsFromPath(buildPath);
|
|
729
|
+
}
|
|
730
|
+
console.log(`🔄 Construction du RAG: ${name}`);
|
|
731
|
+
// Marquer comme en construction
|
|
732
|
+
const registry = this.getRegistry();
|
|
733
|
+
entry.status = 'building';
|
|
734
|
+
this.cachedRegistry = { ...registry };
|
|
735
|
+
try {
|
|
736
|
+
// Configuration pour la construction
|
|
737
|
+
const buildConfig = {
|
|
738
|
+
baseDir: buildPath,
|
|
739
|
+
...DEFAULT_RAG_CONFIG,
|
|
740
|
+
...ragConfig
|
|
741
|
+
};
|
|
742
|
+
// Construire dans le dossier de construction
|
|
743
|
+
const embeddings = new embeddings_1.Embeddings(buildConfig);
|
|
744
|
+
// ✅ NOUVEAU : Récupérer le résultat de la construction
|
|
745
|
+
const buildResult = await embeddings.indexFromDocuments(docs);
|
|
746
|
+
console.log(`✅ Construction terminée dans: ${buildPath}`);
|
|
747
|
+
// ✅ NOUVEAU : Créer et sauvegarder les métadonnées
|
|
748
|
+
const metadata = {
|
|
749
|
+
version: buildResult.version,
|
|
750
|
+
tag: buildResult.tag,
|
|
751
|
+
createdAt: new Date().toISOString(),
|
|
752
|
+
config: buildConfig,
|
|
753
|
+
documents: buildResult.documents,
|
|
754
|
+
stats: {
|
|
755
|
+
totalDocuments: docs.length,
|
|
756
|
+
totalSections: buildResult.totalVectors,
|
|
757
|
+
vectorDimensions: buildResult.vectorDimensions,
|
|
758
|
+
indexSize: `${Math.round(buildResult.totalVectors * buildResult.vectorDimensions * 4 / 1024 / 1024)}MB`
|
|
759
|
+
},
|
|
760
|
+
files: {
|
|
761
|
+
vectorsFile: types_1.RAG_FILES.VECTORS,
|
|
762
|
+
mappingFile: types_1.RAG_FILES.MAPPING,
|
|
763
|
+
queriesFile: types_1.RAG_FILES.QUERIES
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
//
|
|
767
|
+
// active the RAG after the build is successful
|
|
768
|
+
entry.status = 'active';
|
|
769
|
+
// Sauvegarder les métadonnées et le mapping
|
|
770
|
+
this.saveMetadata(buildPath, metadata);
|
|
771
|
+
this.saveMapping(buildPath, buildResult.mapping, buildResult);
|
|
772
|
+
// ✅ En mode legacy, on construit directement dans configPath (pas de déplacement)
|
|
773
|
+
if (!isLegacyMode && destDir) {
|
|
774
|
+
// Archiver l'ancienne version si elle existe
|
|
775
|
+
if ((0, fs_1.existsSync)(destDir)) {
|
|
776
|
+
await this.archive(name);
|
|
777
|
+
}
|
|
778
|
+
// Basculement atomique: buildPath -> destDir
|
|
779
|
+
(0, fs_1.renameSync)(buildPath, destDir);
|
|
780
|
+
// Mettre à jour configPath avec destDir
|
|
781
|
+
entry.configPath = destDir;
|
|
782
|
+
delete entry.destDir;
|
|
783
|
+
}
|
|
784
|
+
// Marquer comme actif
|
|
785
|
+
entry.status = 'active';
|
|
786
|
+
this.cachedRegistry = { ...registry };
|
|
787
|
+
this.saveRegistry(registry);
|
|
788
|
+
console.log(`✅ RAG '${name}' construit avec succès`);
|
|
789
|
+
console.log(` 📁 configPath final: ${entry.configPath}`);
|
|
790
|
+
console.log(` 📦 Embeddings chargés en cache: ${this.loadedEmbeddings.size}`);
|
|
791
|
+
console.log(` 🔍 Embedding '${name}' dans le cache: ${this.loadedEmbeddings.has(name) ? 'OUI' : 'NON'}`);
|
|
792
|
+
// Notifier les embeddings chargés de se mettre à jour
|
|
793
|
+
this.notifyUpdate(name);
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
// Nettoyer en cas d'erreur (seulement en mode legacy)
|
|
797
|
+
if (isLegacyMode && (0, fs_1.existsSync)(buildPath)) {
|
|
798
|
+
(0, fs_1.rmSync)(buildPath, { recursive: true, force: true });
|
|
799
|
+
}
|
|
800
|
+
entry.status = 'error';
|
|
801
|
+
this.cachedRegistry = { ...registry };
|
|
802
|
+
console.error(`❌ Erreur lors de la construction de '${name}':`, error);
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Charge tous les documents depuis un dossier
|
|
808
|
+
* Version interne qui prend un chemin direct
|
|
809
|
+
*
|
|
810
|
+
* @param baseDir Chemin du dossier
|
|
811
|
+
* @param endsWith Extension des fichiers à charger
|
|
812
|
+
* @returns Liste des documents parsés et validés
|
|
813
|
+
*/
|
|
814
|
+
async _loadDocumentsFromPath(baseDir, endsWith) {
|
|
815
|
+
const docs = [];
|
|
816
|
+
if (endsWith) {
|
|
817
|
+
// ✅ Mode legacy : charger les fichiers avec une extension spécifique
|
|
818
|
+
const files = (0, fs_1.readdirSync)(baseDir).filter(f => f.endsWith(endsWith));
|
|
819
|
+
for (const file of files) {
|
|
820
|
+
try {
|
|
821
|
+
const filePath = path_1.default.join(baseDir, file);
|
|
822
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
823
|
+
// ✅ Utiliser le nom original (sans .enhanced) pour la référence
|
|
824
|
+
const originalName = endsWith === '.enhanced.md'
|
|
825
|
+
? file.replace('.enhanced.md', '.md')
|
|
826
|
+
: file;
|
|
827
|
+
const parsedDoc = (0, parser_1.getSections)(content, originalName);
|
|
828
|
+
// Valider le document
|
|
829
|
+
(0, types_1.isValidRAGMatter)(parsedDoc.matter);
|
|
830
|
+
docs.push(parsedDoc);
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
console.warn(`⚠️ Document invalide ignoré: ${file}`, error);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
// ✅ Mode intelligent : pour chaque .md, utiliser .enhanced.md s'il existe, sinon .md
|
|
839
|
+
const mdFiles = (0, fs_1.readdirSync)(baseDir)
|
|
840
|
+
.filter(f => f.endsWith('.md') && !f.endsWith('.enhanced.md'));
|
|
841
|
+
for (const mdFile of mdFiles) {
|
|
842
|
+
try {
|
|
843
|
+
const baseName = mdFile.replace('.md', '');
|
|
844
|
+
const enhancedPath = path_1.default.join(baseDir, baseName + '.enhanced.md');
|
|
845
|
+
const mdPath = path_1.default.join(baseDir, mdFile);
|
|
846
|
+
// Utiliser .enhanced.md en priorité, sinon .md
|
|
847
|
+
const filePath = (0, fs_1.existsSync)(enhancedPath) ? enhancedPath : mdPath;
|
|
848
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
849
|
+
const parsedDoc = (0, parser_1.getSections)(content, mdFile);
|
|
850
|
+
// Valider le document
|
|
851
|
+
(0, types_1.isValidRAGMatter)(parsedDoc.matter);
|
|
852
|
+
docs.push(parsedDoc);
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
console.warn(`⚠️ Document invalide ignoré: ${mdFile}`, error);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (docs.length === 0) {
|
|
860
|
+
throw new Error(`Aucun document valide trouvé dans ${baseDir}`);
|
|
861
|
+
}
|
|
862
|
+
return docs;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Charge tous les documents depuis un dossier du RAG
|
|
866
|
+
* Version publique qui utilise le nom du RAG
|
|
867
|
+
*
|
|
868
|
+
* @param name Nom du RAG
|
|
869
|
+
* @param endsWith Extension des fichiers à charger (par défaut '.enhanced.md')
|
|
870
|
+
* @returns Liste des documents parsés et validés
|
|
871
|
+
*/
|
|
872
|
+
async loadDocumentsFromDir(name, endsWith = '.enhanced.md') {
|
|
873
|
+
const entry = this.getEntry(name);
|
|
874
|
+
if (!entry) {
|
|
875
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
876
|
+
}
|
|
877
|
+
return this._loadDocumentsFromPath(entry.configPath, endsWith);
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Supprime un RAG du registre
|
|
881
|
+
*/
|
|
882
|
+
async delete(name, archiveFlag = true) {
|
|
883
|
+
if (!this.exists(name)) {
|
|
884
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
885
|
+
}
|
|
886
|
+
const entry = this.getEntry(name);
|
|
887
|
+
const registry = this.getRegistry();
|
|
888
|
+
// Archiver si demandé
|
|
889
|
+
if (archiveFlag) {
|
|
890
|
+
await this.archive(name);
|
|
891
|
+
}
|
|
892
|
+
else if ((0, fs_1.existsSync)(entry.configPath)) {
|
|
893
|
+
(0, fs_1.rmSync)(entry.configPath, { recursive: true, force: true });
|
|
894
|
+
}
|
|
895
|
+
// Supprimer du registre
|
|
896
|
+
delete registry.registries[name];
|
|
897
|
+
// Supprimer de la map des embeddings chargés
|
|
898
|
+
this.loadedEmbeddings.delete(name);
|
|
899
|
+
// Changer le défaut si nécessaire
|
|
900
|
+
if (registry.defaultName === name) {
|
|
901
|
+
const remainingRAGs = Object.keys(registry.registries);
|
|
902
|
+
registry.defaultName = remainingRAGs.length > 0 ? remainingRAGs[0] : 'RAGRegistry';
|
|
903
|
+
}
|
|
904
|
+
this.saveRegistry(registry);
|
|
905
|
+
console.log(`✅ RAG supprimé: ${name}`);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Renomme un document dans un RAG existant
|
|
909
|
+
*
|
|
910
|
+
* Cette méthode renomme tous les fichiers associés à un document:
|
|
911
|
+
* - `oldFile.md` → `newFile.md`
|
|
912
|
+
* - `oldFile.md.sha` → `newFile.md.sha`
|
|
913
|
+
* - `oldFile.enhanced.md` → `newFile.enhanced.md`
|
|
914
|
+
* - `oldFile.query.json` → `newFile.query.json`
|
|
915
|
+
*
|
|
916
|
+
* Et met à jour les références dans les fichiers JSON (mapping, metadata, queries)
|
|
917
|
+
*
|
|
918
|
+
* @param ragName Nom du RAG concerné
|
|
919
|
+
* @param oldFile Ancien nom du fichier (ex: 'old-name.md')
|
|
920
|
+
* @param newFile Nouveau nom du fichier (ex: 'new-name.md')
|
|
921
|
+
*
|
|
922
|
+
* @example
|
|
923
|
+
* ```typescript
|
|
924
|
+
* ragManager.renameDocument('rule-validation-1', 'old-procedure.md', 'new-procedure.md');
|
|
925
|
+
* ```
|
|
926
|
+
*/
|
|
927
|
+
renameDocument(ragName, oldFile, newFile) {
|
|
928
|
+
const entry = this.getEntry(ragName);
|
|
929
|
+
if (!entry) {
|
|
930
|
+
if (this.config.verbose) {
|
|
931
|
+
console.log(`⚠️ RAG '${ragName}' n'existe pas, skip rename document`);
|
|
932
|
+
}
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const configPath = entry.configPath;
|
|
936
|
+
//
|
|
937
|
+
// Liste des fichiers à renommer (sans extension pour le basename)
|
|
938
|
+
const oldBase = oldFile.replace(/\.md$/, '');
|
|
939
|
+
const newBase = newFile.replace(/\.md$/, '');
|
|
940
|
+
const filesToRename = [
|
|
941
|
+
{ old: `${oldBase}.md`, new: `${newBase}.md` },
|
|
942
|
+
{ old: `${oldBase}.md.sha`, new: `${newBase}.md.sha` },
|
|
943
|
+
{ old: `${oldBase}.enhanced.md`, new: `${newBase}.enhanced.md` },
|
|
944
|
+
{ old: `${oldBase}.query.json`, new: `${newBase}.query.json` }
|
|
945
|
+
];
|
|
946
|
+
//
|
|
947
|
+
// Renommer les fichiers physiques
|
|
948
|
+
let renamedCount = 0;
|
|
949
|
+
for (const { old, new: newName } of filesToRename) {
|
|
950
|
+
const oldPath = path_1.default.join(configPath, old);
|
|
951
|
+
const newPath = path_1.default.join(configPath, newName);
|
|
952
|
+
if ((0, fs_1.existsSync)(oldPath)) {
|
|
953
|
+
try {
|
|
954
|
+
(0, fs_1.renameSync)(oldPath, newPath);
|
|
955
|
+
renamedCount++;
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
console.warn(`⚠️ Erreur renommage ${old} → ${newName}:`, error);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (renamedCount === 0) {
|
|
963
|
+
if (this.config.verbose) {
|
|
964
|
+
console.log(`⚠️ Aucun fichier à renommer pour ${oldFile} dans RAG '${ragName}'`);
|
|
965
|
+
}
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
//
|
|
969
|
+
// Mettre à jour les références dans les fichiers JSON
|
|
970
|
+
this.updateJSONReferences(configPath, oldFile, newFile);
|
|
971
|
+
if (this.config.verbose) {
|
|
972
|
+
console.log(`✅ Document renommé dans RAG '${ragName}': ${oldFile} → ${newFile} (${renamedCount} fichiers)`);
|
|
973
|
+
}
|
|
974
|
+
//
|
|
975
|
+
// Notifier les embeddings chargés de se mettre à jour avec l'action rename
|
|
976
|
+
this.notifyUpdate(ragName, {
|
|
977
|
+
action: 'rename',
|
|
978
|
+
oldFile,
|
|
979
|
+
newFile
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Met à jour les références d'un fichier dans les JSON du RAG
|
|
984
|
+
*
|
|
985
|
+
* @param configPath Chemin du RAG
|
|
986
|
+
* @param oldFile Ancien nom du fichier
|
|
987
|
+
* @param newFile Nouveau nom du fichier
|
|
988
|
+
* @private
|
|
989
|
+
*/
|
|
990
|
+
updateJSONReferences(configPath, oldFile, newFile) {
|
|
991
|
+
const metadataFile = path_1.default.join(configPath, types_1.RAG_FILES.METADATA);
|
|
992
|
+
const mappingFile = path_1.default.join(configPath, types_1.RAG_FILES.MAPPING);
|
|
993
|
+
//
|
|
994
|
+
// Mettre à jour metadata.json
|
|
995
|
+
if ((0, fs_1.existsSync)(metadataFile)) {
|
|
996
|
+
try {
|
|
997
|
+
const metadata = JSON.parse((0, fs_1.readFileSync)(metadataFile, 'utf8'));
|
|
998
|
+
//
|
|
999
|
+
// Mettre à jour les références dans documents
|
|
1000
|
+
if (metadata.documents) {
|
|
1001
|
+
const docs = Object.entries(metadata.documents);
|
|
1002
|
+
for (const [key, doc] of docs) {
|
|
1003
|
+
if (doc.filename === oldFile) {
|
|
1004
|
+
doc.filename = newFile;
|
|
1005
|
+
}
|
|
1006
|
+
//
|
|
1007
|
+
// Mettre à jour aussi les références dans les sections
|
|
1008
|
+
if (key.startsWith(oldFile)) {
|
|
1009
|
+
const newKey = key.replace(oldFile, newFile);
|
|
1010
|
+
delete metadata.documents[key];
|
|
1011
|
+
metadata.documents[newKey] = doc;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
(0, fs_1.writeFileSync)(metadataFile, JSON.stringify(metadata, null, 2), 'utf8');
|
|
1016
|
+
}
|
|
1017
|
+
catch (error) {
|
|
1018
|
+
console.warn(`⚠️ Erreur mise à jour metadata.json:`, error);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
//
|
|
1022
|
+
// Mettre à jour mapping.json
|
|
1023
|
+
if ((0, fs_1.existsSync)(mappingFile)) {
|
|
1024
|
+
try {
|
|
1025
|
+
const mappingData = JSON.parse((0, fs_1.readFileSync)(mappingFile, 'utf8'));
|
|
1026
|
+
//
|
|
1027
|
+
// Mettre à jour les clés du mapping
|
|
1028
|
+
if (mappingData.mapping) {
|
|
1029
|
+
const entries = Object.entries(mappingData.mapping);
|
|
1030
|
+
for (const [id, ref] of entries) {
|
|
1031
|
+
if (ref.includes(oldFile)) {
|
|
1032
|
+
mappingData.mapping[id] = ref.replace(oldFile, newFile);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
(0, fs_1.writeFileSync)(mappingFile, JSON.stringify(mappingData, null, 2), 'utf8');
|
|
1037
|
+
}
|
|
1038
|
+
catch (error) {
|
|
1039
|
+
console.warn(`⚠️ Erreur mise à jour mapping.json:`, error);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Renomme un RAG existant
|
|
1045
|
+
*
|
|
1046
|
+
* @param from Nom actuel du RAG
|
|
1047
|
+
* @param to Nouveau nom du RAG
|
|
1048
|
+
* @param description Description optionnelle du RAG
|
|
1049
|
+
* @throws Error si le nom est trop court, si une collision existe, ou si le RAG source n'existe pas
|
|
1050
|
+
*
|
|
1051
|
+
* @example
|
|
1052
|
+
* ```typescript
|
|
1053
|
+
* await ragManager.rename('old-name', 'new-name', 'Description mise à jour');
|
|
1054
|
+
* ```
|
|
1055
|
+
*/
|
|
1056
|
+
async rename(from, to, description) {
|
|
1057
|
+
// Validation du nom de destination
|
|
1058
|
+
const MIN_NAME_LENGTH = 3;
|
|
1059
|
+
if (to.length < MIN_NAME_LENGTH) {
|
|
1060
|
+
throw new Error(`Le nom '${to}' est trop court (minimum ${MIN_NAME_LENGTH} caractères)`);
|
|
1061
|
+
}
|
|
1062
|
+
// Vérifier que le RAG source existe
|
|
1063
|
+
if (!this.exists(from)) {
|
|
1064
|
+
throw new Error(`RAG source '${from}' n'existe pas`);
|
|
1065
|
+
}
|
|
1066
|
+
// Éviter les collisions
|
|
1067
|
+
if (this.exists(to)) {
|
|
1068
|
+
throw new Error(`RAG de destination '${to}' existe déjà - collision détectée`);
|
|
1069
|
+
}
|
|
1070
|
+
// Éviter les noms réservés
|
|
1071
|
+
const reservedNames = ['temp', 'archives', 'registry', 'config'];
|
|
1072
|
+
if (reservedNames.includes(to.toLowerCase())) {
|
|
1073
|
+
throw new Error(`Le nom '${to}' est réservé et ne peut pas être utilisé`);
|
|
1074
|
+
}
|
|
1075
|
+
const sourceEntry = this.getEntry(from);
|
|
1076
|
+
const sourcePath = sourceEntry.configPath;
|
|
1077
|
+
const destPath = path_1.default.join(this.config.baseDir, to);
|
|
1078
|
+
try {
|
|
1079
|
+
// Renommer le dossier physique
|
|
1080
|
+
if ((0, fs_1.existsSync)(sourcePath)) {
|
|
1081
|
+
(0, fs_1.renameSync)(sourcePath, destPath);
|
|
1082
|
+
if (this.config.verbose)
|
|
1083
|
+
console.log(`📁 Dossier renommé: ${sourcePath} → ${destPath}`);
|
|
1084
|
+
}
|
|
1085
|
+
// Mettre à jour le registre
|
|
1086
|
+
const registry = this.getRegistry();
|
|
1087
|
+
registry.registries[to] = {
|
|
1088
|
+
name: to,
|
|
1089
|
+
configPath: destPath,
|
|
1090
|
+
status: sourceEntry.status,
|
|
1091
|
+
description: description || sourceEntry.description
|
|
1092
|
+
};
|
|
1093
|
+
delete registry.registries[from];
|
|
1094
|
+
// Mettre à jour le défaut si nécessaire
|
|
1095
|
+
if (registry.defaultName === from) {
|
|
1096
|
+
registry.defaultName = to;
|
|
1097
|
+
}
|
|
1098
|
+
// Mettre à jour la map des embeddings chargés
|
|
1099
|
+
const embeddingData = this.loadedEmbeddings.get(from);
|
|
1100
|
+
if (embeddingData) {
|
|
1101
|
+
this.loadedEmbeddings.set(to, embeddingData);
|
|
1102
|
+
this.loadedEmbeddings.delete(from);
|
|
1103
|
+
// Notifier l'embedding de se mettre à jour avec le nouveau chemin
|
|
1104
|
+
this.notifyUpdate(to);
|
|
1105
|
+
}
|
|
1106
|
+
// Mettre à jour le cache
|
|
1107
|
+
this.saveRegistry(registry);
|
|
1108
|
+
console.log(`✅ RAG renommé: ${from} → ${to}`);
|
|
1109
|
+
}
|
|
1110
|
+
catch (error) {
|
|
1111
|
+
console.error(`❌ Erreur lors du renommage de '${from}' en '${to}':`, error);
|
|
1112
|
+
throw new Error(`Impossible de renommer le RAG: ${error}`);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Met à jour le statut d'un RAG
|
|
1117
|
+
*
|
|
1118
|
+
* @param name Nom du RAG
|
|
1119
|
+
* @param status Nouveau statut ('active' ou 'paused')
|
|
1120
|
+
* @throws Error si le RAG n'existe pas ou si le statut est invalide
|
|
1121
|
+
*
|
|
1122
|
+
* @example
|
|
1123
|
+
* ```typescript
|
|
1124
|
+
* ragManager.updateStatus('knowledge', 'paused');
|
|
1125
|
+
* ```
|
|
1126
|
+
*/
|
|
1127
|
+
pause(name) {
|
|
1128
|
+
if (!this.exists(name)) {
|
|
1129
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
1130
|
+
}
|
|
1131
|
+
const entry = this.getEntry(name);
|
|
1132
|
+
if (!entry) {
|
|
1133
|
+
throw new Error(`Entrée RAG '${name}' non trouvée dans le registre`);
|
|
1134
|
+
}
|
|
1135
|
+
// FIXME missing test
|
|
1136
|
+
if (entry.status !== 'active') {
|
|
1137
|
+
throw new Error(`Impossible de mettre en pause '${name}' car il n'est pas actif`);
|
|
1138
|
+
}
|
|
1139
|
+
// Mettre à jour le statut
|
|
1140
|
+
entry.status = 'paused';
|
|
1141
|
+
// Notifier l'embedding de mettre à jour sont état
|
|
1142
|
+
this.notifyUpdate(name);
|
|
1143
|
+
// Sauvegarder le registre
|
|
1144
|
+
const registry = this.getRegistry();
|
|
1145
|
+
this.saveRegistry(registry);
|
|
1146
|
+
}
|
|
1147
|
+
unpause(name) {
|
|
1148
|
+
if (!this.exists(name)) {
|
|
1149
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
1150
|
+
}
|
|
1151
|
+
const entry = this.getEntry(name);
|
|
1152
|
+
if (!entry) {
|
|
1153
|
+
throw new Error(`Entrée RAG '${name}' non trouvée dans le registre`);
|
|
1154
|
+
}
|
|
1155
|
+
// Mettre à jour le statut
|
|
1156
|
+
entry.status = 'active';
|
|
1157
|
+
// Notifier l'embedding de mettre à jour sont état
|
|
1158
|
+
this.notifyUpdate(name);
|
|
1159
|
+
// Sauvegarder le registre
|
|
1160
|
+
const registry = this.getRegistry();
|
|
1161
|
+
this.saveRegistry(registry);
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Nettoie les embeddings chargés obsolètes (plus vieux que maxAge)
|
|
1165
|
+
* Utile pour libérer de la mémoire
|
|
1166
|
+
*
|
|
1167
|
+
* @param maxAge Âge maximum en millisecondes (défaut: 1 heure)
|
|
1168
|
+
*/
|
|
1169
|
+
cleanDeadReferences(maxAge = 3600000) {
|
|
1170
|
+
const now = Date.now();
|
|
1171
|
+
const deadKeys = [];
|
|
1172
|
+
for (const [name, data] of this.loadedEmbeddings.entries()) {
|
|
1173
|
+
// Supprimer les embeddings trop vieux ou non valides
|
|
1174
|
+
if (now - data.loadTime >= maxAge) {
|
|
1175
|
+
deadKeys.push(name);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
//
|
|
1179
|
+
// remove embeddings from cached registry
|
|
1180
|
+
for (const key of deadKeys) {
|
|
1181
|
+
this.loadedEmbeddings.delete(key);
|
|
1182
|
+
}
|
|
1183
|
+
if (deadKeys.length > 0 && this.config.verbose) {
|
|
1184
|
+
console.log(`🗑️ Nettoyage: ${deadKeys.length} embedding(s) obsolète(s) supprimé(s)`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Obtient le nombre d'embeddings actuellement chargés
|
|
1189
|
+
*/
|
|
1190
|
+
getLoadedCount() {
|
|
1191
|
+
return this.loadedEmbeddings.size;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Charge un RAG par son nom et l'enregistre pour les updates réactifs
|
|
1195
|
+
*
|
|
1196
|
+
* Permet le chargement si:
|
|
1197
|
+
* - Statut = 'active' : RAG complètement prêt
|
|
1198
|
+
* - Statut = 'building' : RAG en construction mais déjà utilisable (recherche partielle)
|
|
1199
|
+
*/
|
|
1200
|
+
load(name) {
|
|
1201
|
+
if (!this.exists(name)) {
|
|
1202
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
1203
|
+
}
|
|
1204
|
+
const entry = this.getEntry(name);
|
|
1205
|
+
if (!entry) {
|
|
1206
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
1207
|
+
}
|
|
1208
|
+
// Permettre le chargement si actif ou en cours de construction
|
|
1209
|
+
if (entry.status !== 'active' && entry.status !== 'building') {
|
|
1210
|
+
throw new Error(`RAG '${name}' n'est pas disponible (statut: ${entry.status})`);
|
|
1211
|
+
}
|
|
1212
|
+
//
|
|
1213
|
+
// use cached embedding if available
|
|
1214
|
+
const embeddingData = this.loadedEmbeddings.get(name);
|
|
1215
|
+
if (embeddingData) {
|
|
1216
|
+
return embeddingData.embedding;
|
|
1217
|
+
}
|
|
1218
|
+
// Vérifier que le dossier contient bien les fichiers nécessaires avant de charger
|
|
1219
|
+
const metadataFile = path_1.default.join(entry.configPath, types_1.RAG_FILES.METADATA);
|
|
1220
|
+
if (!(0, fs_1.existsSync)(metadataFile)) {
|
|
1221
|
+
const statusMsg = entry.status === 'building'
|
|
1222
|
+
? `Le RAG '${name}' est en cours de construction. Les fichiers ne sont pas encore disponibles.`
|
|
1223
|
+
: `Le RAG '${name}' est corrompu (fichier metadata manquant).`;
|
|
1224
|
+
throw new Error(statusMsg);
|
|
1225
|
+
}
|
|
1226
|
+
// Charger la configuration depuis le fichier metadata
|
|
1227
|
+
const ragConfig = this.loadConfig(entry.configPath);
|
|
1228
|
+
// Instancier et vérifier l'Embeddings
|
|
1229
|
+
const embeddings = new embeddings_1.Embeddings(ragConfig);
|
|
1230
|
+
if (!embeddings.isReady()) {
|
|
1231
|
+
throw new Error(`RAG '${name}' n'est pas prêt (fichiers manquants)`);
|
|
1232
|
+
}
|
|
1233
|
+
// Enregistrer l'embedding pour les updates réactifs
|
|
1234
|
+
this.loadedEmbeddings.set(name, {
|
|
1235
|
+
embedding: embeddings,
|
|
1236
|
+
loadTime: Date.now()
|
|
1237
|
+
});
|
|
1238
|
+
if (this.config.verbose) {
|
|
1239
|
+
const statusMsg = entry.status === 'building' ? '(en construction)' : '';
|
|
1240
|
+
console.log(`✅ RAG chargé: ${name} ${statusMsg}`);
|
|
1241
|
+
}
|
|
1242
|
+
return embeddings;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Vérifie l'état d'un RAG
|
|
1246
|
+
*/
|
|
1247
|
+
verify(name) {
|
|
1248
|
+
if (!this.exists(name)) {
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
const entry = this.getEntry(name);
|
|
1252
|
+
const registry = this.getRegistry();
|
|
1253
|
+
// Vérifier que le dossier existe
|
|
1254
|
+
if (!(0, fs_1.existsSync)(entry.configPath)) {
|
|
1255
|
+
// Mettre à jour le statut
|
|
1256
|
+
entry.status = 'error';
|
|
1257
|
+
this.saveRegistry(registry);
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
try {
|
|
1261
|
+
// Charger la configuration depuis le metadata
|
|
1262
|
+
const ragConfig = this.loadConfig(entry.configPath);
|
|
1263
|
+
const embeddingData = this.loadedEmbeddings.get(name);
|
|
1264
|
+
const embeddings = (embeddingData) ? embeddingData.embedding : new embeddings_1.Embeddings(ragConfig);
|
|
1265
|
+
const isReady = embeddings.isReady();
|
|
1266
|
+
// Mettre à jour le statut (stateless - pas de sauvegarde automatique)
|
|
1267
|
+
// Si le RAG est en "building", ne pas changer son statut
|
|
1268
|
+
if (entry.status !== 'building') {
|
|
1269
|
+
entry.status = isReady ? 'active' : 'error';
|
|
1270
|
+
}
|
|
1271
|
+
this.cachedRegistry = { ...registry };
|
|
1272
|
+
// Un RAG en "building" est considéré comme valide (il existe)
|
|
1273
|
+
return isReady || entry.status === 'building';
|
|
1274
|
+
}
|
|
1275
|
+
catch (error) {
|
|
1276
|
+
// Erreur lors du chargement de la configuration
|
|
1277
|
+
// Ne pas changer le statut si c'est "building"
|
|
1278
|
+
if (entry.status !== 'building') {
|
|
1279
|
+
entry.status = 'error';
|
|
1280
|
+
}
|
|
1281
|
+
this.cachedRegistry = { ...registry };
|
|
1282
|
+
return entry.status === 'building'; // Un RAG en "building" est valide
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Liste tous les RAG disponibles
|
|
1287
|
+
*/
|
|
1288
|
+
list() {
|
|
1289
|
+
const registry = this.getRegistry();
|
|
1290
|
+
return Object.values(registry.registries) || [];
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Obtient le statut d'un RAG
|
|
1294
|
+
*/
|
|
1295
|
+
getStatus(name) {
|
|
1296
|
+
if (!this.exists(name)) {
|
|
1297
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
1298
|
+
}
|
|
1299
|
+
return this.getEntry(name);
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Obtient la configuration d'un RAG depuis son metadata
|
|
1303
|
+
*/
|
|
1304
|
+
getConfig(name) {
|
|
1305
|
+
if (!this.exists(name)) {
|
|
1306
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
1307
|
+
}
|
|
1308
|
+
const entry = this.getEntry(name);
|
|
1309
|
+
try {
|
|
1310
|
+
return this.loadConfig(entry.configPath);
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
console.warn(`Erreur lors du chargement de la config pour '${name}':`, error);
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Définit le RAG par défaut
|
|
1319
|
+
*/
|
|
1320
|
+
async setDefault(name) {
|
|
1321
|
+
if (!this.exists(name)) {
|
|
1322
|
+
throw new Error(`RAG '${name}' n'existe pas`);
|
|
1323
|
+
}
|
|
1324
|
+
const registry = this.getRegistry();
|
|
1325
|
+
registry.defaultName = name;
|
|
1326
|
+
this.saveRegistry(registry);
|
|
1327
|
+
console.log(`✅ RAG par défaut défini: ${name}`);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Obtient le RAG par défaut
|
|
1331
|
+
*/
|
|
1332
|
+
getDefault() {
|
|
1333
|
+
const registry = this.getRegistry();
|
|
1334
|
+
return registry.defaultName || 'RAGRegistry';
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Obtient les statistiques de tous les RAG
|
|
1338
|
+
*/
|
|
1339
|
+
getStats() {
|
|
1340
|
+
const rags = this.list();
|
|
1341
|
+
const stats = [];
|
|
1342
|
+
for (const rag of rags) {
|
|
1343
|
+
try {
|
|
1344
|
+
const isValid = this.verify(rag.name);
|
|
1345
|
+
let metadata = null;
|
|
1346
|
+
if (isValid) {
|
|
1347
|
+
try {
|
|
1348
|
+
const embeddings = this.load(rag.name);
|
|
1349
|
+
metadata = embeddings.getMetadata();
|
|
1350
|
+
}
|
|
1351
|
+
catch (error) {
|
|
1352
|
+
// Si le chargement échoue, essayer d'obtenir au moins la config
|
|
1353
|
+
const config = this.getConfig(rag.name);
|
|
1354
|
+
if (config) {
|
|
1355
|
+
metadata = {
|
|
1356
|
+
stats: {
|
|
1357
|
+
totalSections: 0,
|
|
1358
|
+
vectorDimensions: config.dimensions || 1536,
|
|
1359
|
+
indexSize: 'inconnu'
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
stats.push({
|
|
1366
|
+
name: rag.name,
|
|
1367
|
+
status: rag.status,
|
|
1368
|
+
isValid,
|
|
1369
|
+
configPath: rag.configPath,
|
|
1370
|
+
metadata: metadata ? {
|
|
1371
|
+
totalSections: metadata.stats?.totalSections || 0,
|
|
1372
|
+
vectorDimensions: metadata.stats?.vectorDimensions || 0,
|
|
1373
|
+
indexSize: metadata.stats?.indexSize || 'inconnu'
|
|
1374
|
+
} : null
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
catch (error) {
|
|
1378
|
+
stats.push({
|
|
1379
|
+
name: rag.name,
|
|
1380
|
+
status: 'error',
|
|
1381
|
+
isValid: false,
|
|
1382
|
+
configPath: rag.configPath,
|
|
1383
|
+
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return stats;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
exports.RAGManager = RAGManager;
|