goblin-agents 0.1.5 → 0.1.11
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/README.md +183 -111
- package/lib/llm/providers.js +5 -1
- package/lib/llm/utils.js +39 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
Le module `goblin-agents` fournit une infrastructure complète pour intégrer des agents d'intelligence artificielle (IA) dans l'écosystème Xcraft. Il permet de créer, configurer et interagir avec différents modèles de langage (LLM) via des fournisseurs comme Ollama et OpenAI. Ce module est conçu pour faciliter l'utilisation d'agents IA dans diverses applications, en offrant des fonctionnalités avancées comme la gestion de conversations, l'analyse de documents PDF, et l'utilisation d'outils externes.
|
|
6
6
|
|
|
7
|
+
## Sommaire
|
|
8
|
+
|
|
9
|
+
- [Structure du module](#structure-du-module)
|
|
10
|
+
- [Fonctionnement global](#fonctionnement-global)
|
|
11
|
+
- [Exemples d'utilisation](#exemples-dutilisation)
|
|
12
|
+
- [Interactions avec d'autres modules](#interactions-avec-dautres-modules)
|
|
13
|
+
- [Configuration avancée](#configuration-avancée)
|
|
14
|
+
- [Détails des sources](#détails-des-sources)
|
|
15
|
+
|
|
7
16
|
## Structure du module
|
|
8
17
|
|
|
9
18
|
- **AiAgent** : Acteur principal qui encapsule un agent IA et ses fonctionnalités
|
|
@@ -19,6 +28,7 @@ Le module `goblin-agents` permet de créer et gérer des agents IA qui peuvent i
|
|
|
19
28
|
Les agents peuvent maintenir plusieurs contextes de conversation simultanément, ce qui permet d'avoir des conversations distinctes avec différents utilisateurs ou sur différents sujets. Chaque contexte conserve son propre historique de messages, ce qui permet à l'agent de maintenir la cohérence dans ses réponses.
|
|
20
29
|
|
|
21
30
|
Le module prend en charge diverses fonctionnalités avancées :
|
|
31
|
+
|
|
22
32
|
- Génération de texte à partir de prompts
|
|
23
33
|
- Conversations interactives avec historique
|
|
24
34
|
- Extraction de texte à partir d'images de documents PDF
|
|
@@ -36,11 +46,11 @@ Les agents peuvent être configurés avec une grande variété d'options qui inf
|
|
|
36
46
|
const {AiAgent} = require('goblin-agents/lib/llm/aiAgent.js');
|
|
37
47
|
|
|
38
48
|
// Dans une méthode d'un acteur Elf
|
|
39
|
-
async initAgents() {
|
|
49
|
+
async initAgents() {
|
|
40
50
|
const feedId = await this.newQuestFeed();
|
|
41
51
|
|
|
42
52
|
// Création d'un agent avec Ollama (local)
|
|
43
|
-
const ollamaAgentId =
|
|
53
|
+
const ollamaAgentId = 'aiAgent@ollama-mistral-assistant';
|
|
44
54
|
const agent = await new AiAgent(this).create(ollamaAgentId, feedId, {
|
|
45
55
|
name: 'Assistant Personnel',
|
|
46
56
|
role: 'assistant',
|
|
@@ -55,8 +65,8 @@ async initAgents() {
|
|
|
55
65
|
});
|
|
56
66
|
|
|
57
67
|
// Création d'un agent avec OpenAI (cloud)
|
|
58
|
-
const openAiAgentId =
|
|
59
|
-
const
|
|
68
|
+
const openAiAgentId = 'aiAgent@gpt-4o-assistant';
|
|
69
|
+
const cloudAgent = await new AiAgent(this).create(openAiAgentId, feedId, {
|
|
60
70
|
name: 'Assistant Personnel',
|
|
61
71
|
role: 'assistant',
|
|
62
72
|
provider: 'open-ai',
|
|
@@ -67,20 +77,20 @@ async initAgents() {
|
|
|
67
77
|
},
|
|
68
78
|
usability: 'stable'
|
|
69
79
|
});
|
|
80
|
+
}
|
|
70
81
|
```
|
|
71
82
|
|
|
72
83
|
### Conversation avec un agent
|
|
73
84
|
|
|
74
85
|
```javascript
|
|
75
|
-
|
|
76
86
|
// Dans une méthode d'un acteur Elf
|
|
77
87
|
async askAgent(desktopId, agentId, contextId, question, saveExchange = false) {
|
|
78
88
|
const feedId = await this.newQuestFeed();
|
|
79
|
-
|
|
89
|
+
|
|
80
90
|
// Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
|
|
81
91
|
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
82
|
-
|
|
83
|
-
// Continue la conversation sur ce contexte par ex. 'story-xyz' et
|
|
92
|
+
|
|
93
|
+
// Continue la conversation sur ce contexte par ex. 'story-xyz' et
|
|
84
94
|
// obtient une réponse à la question de l'utilisateur
|
|
85
95
|
const response = await agent.chat(contextId, question, desktopId);
|
|
86
96
|
|
|
@@ -92,40 +102,13 @@ async askAgent(desktopId, agentId, contextId, question, saveExchange = false) {
|
|
|
92
102
|
}
|
|
93
103
|
```
|
|
94
104
|
|
|
95
|
-
### Résumer et "squasher" un contexte
|
|
96
|
-
|
|
97
|
-
```javascript
|
|
98
|
-
|
|
99
|
-
// Dans une méthode d'un acteur Elf
|
|
100
|
-
async resumeAndSquashContexte(agentId, contextId) {
|
|
101
|
-
const feedId = await this.newQuestFeed();
|
|
102
|
-
|
|
103
|
-
// Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
|
|
104
|
-
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
105
|
-
|
|
106
|
-
// Demande un résumé des échanges
|
|
107
|
-
const resumePrompt = "Résume l'essentiel de nos échanges en utilisant l'historique des messages";
|
|
108
|
-
const resumedContent = await agent.resumeExchange(resumePrompt, contextId);
|
|
109
|
-
|
|
110
|
-
// Reset la mémoire contextuel et persiste le changement
|
|
111
|
-
await agent.reset(contextId, true);
|
|
112
|
-
|
|
113
|
-
// Ajoute un nouvel historique d'échange a partir du résumé
|
|
114
|
-
await agent.set(contextId, [{role: 'assistant', content: resumedContent}]);
|
|
115
|
-
|
|
116
|
-
// Sauve ce nouvel état
|
|
117
|
-
await agent.save();
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
105
|
### Utilisation d'outils externes
|
|
122
106
|
|
|
123
107
|
```javascript
|
|
124
|
-
|
|
125
108
|
// Dans une méthode d'un acteur Elf
|
|
126
109
|
async addWeatherToolSupportToAgent(agentId) {
|
|
127
110
|
const feedId = await this.newQuestFeed();
|
|
128
|
-
|
|
111
|
+
|
|
129
112
|
// Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
|
|
130
113
|
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
131
114
|
|
|
@@ -154,25 +137,17 @@ async addWeatherToolSupportToAgent(agentId) {
|
|
|
154
137
|
}
|
|
155
138
|
]
|
|
156
139
|
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// L'agent pourra maintenant utiliser l'outil weather.getWeather
|
|
161
|
-
const question = "Quel temps fait-il à Paris aujourd'hui?";
|
|
162
|
-
const response = await agent.chat(contextId, question, desktopId);
|
|
163
140
|
|
|
141
|
+
// L'agent pourra maintenant utiliser l'outil weather.getWeather
|
|
142
|
+
const question = "Quel temps fait-il à Paris aujourd'hui?";
|
|
143
|
+
const response = await agent.chat(contextId, question, desktopId);
|
|
144
|
+
}
|
|
164
145
|
```
|
|
165
146
|
|
|
166
147
|
### Génération d'embeddings pour la recherche sémantique
|
|
167
148
|
|
|
168
149
|
```javascript
|
|
169
|
-
|
|
170
150
|
// Dans une méthode d'un acteur Elf
|
|
171
|
-
// {
|
|
172
|
-
// 'indexContent@article-1', 'Contenu à indexer'
|
|
173
|
-
// 'indexContent@article-2', 'Un autre contenu à indexer'
|
|
174
|
-
// }
|
|
175
|
-
//
|
|
176
151
|
async updateKnowledgeBase(articles) {
|
|
177
152
|
const feedId = await this.newQuestFeed();
|
|
178
153
|
|
|
@@ -189,9 +164,9 @@ async updateKnowledgeBase(articles) {
|
|
|
189
164
|
// Générer des embeddings pour plusieurs textes en une seule requête
|
|
190
165
|
const vectorsBatch = await agent.embedInBatch(texts);
|
|
191
166
|
|
|
192
|
-
// Upsert les articles dans un archetype IndexedContent
|
|
167
|
+
// Upsert les articles dans un archetype IndexedContent
|
|
193
168
|
for(const articleId of articleIds) {
|
|
194
|
-
// Récupère les vecteurs correspondant au texte dans le lot
|
|
169
|
+
// Récupère les vecteurs correspondant au texte dans le lot
|
|
195
170
|
// et le texte original
|
|
196
171
|
const index = articleIds.indexOf(articleId);
|
|
197
172
|
const vectors = vectorsBatch[index];
|
|
@@ -216,66 +191,40 @@ async updateKnowledgeBase(articles) {
|
|
|
216
191
|
);
|
|
217
192
|
}
|
|
218
193
|
}
|
|
219
|
-
|
|
220
|
-
// Dans une méthode d'un acteur Elf
|
|
221
|
-
async queryKnowledgeBase(question = "Comment fonctionne l'apprentissage par renforcement?") {
|
|
222
|
-
const feedId = await this.newQuestFeed();
|
|
223
|
-
|
|
224
|
-
// Instancie l'agent approprié pour faire un embedding
|
|
225
|
-
// par ex. 'aiAgent@default-embedder'
|
|
226
|
-
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
227
|
-
|
|
228
|
-
// Générer un embedding pour un texte, par ex. pour une recherche
|
|
229
|
-
// Convertit la question texte en vecteurs
|
|
230
|
-
const vectors = await agent.embed(question);
|
|
231
|
-
|
|
232
|
-
// Effectue une recherche par similitude dans cryo
|
|
233
|
-
// Utilise un Archetype "IndexedContent"
|
|
234
|
-
// Limite la recherche au contenu indexé en français
|
|
235
|
-
// avec un scope 'knowledge-base'
|
|
236
|
-
// les 100 premier résultats les plus pertinents
|
|
237
|
-
const results = await this.cryo.searchDistance2(
|
|
238
|
-
IndexedContentLogic.db,
|
|
239
|
-
vectors,
|
|
240
|
-
['fr'],
|
|
241
|
-
['knowledge-base'],
|
|
242
|
-
100
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
// Itère sur les données et retourne les résultats
|
|
246
|
-
// a l'appelant
|
|
247
|
-
return Array.from(results);
|
|
248
|
-
}
|
|
249
194
|
```
|
|
250
195
|
|
|
251
196
|
## Interactions avec d'autres modules
|
|
252
197
|
|
|
253
198
|
Le module `goblin-agents` interagit avec plusieurs autres composants de l'écosystème Xcraft :
|
|
254
199
|
|
|
255
|
-
- **xcraft-core-goblin** : Fournit l'infrastructure Elf pour la création d'acteurs
|
|
256
|
-
- **xcraft-core-stones** : Utilisé pour la définition des types de données
|
|
257
|
-
- **xcraft-core-utils** : Utilisé pour les appels API REST et la gestion des verrous
|
|
200
|
+
- **[xcraft-core-goblin]** : Fournit l'infrastructure Elf pour la création d'acteurs
|
|
201
|
+
- **[xcraft-core-stones]** : Utilisé pour la définition des types de données
|
|
202
|
+
- **[xcraft-core-utils]** : Utilisé pour les appels API REST et la gestion des verrous
|
|
203
|
+
- **[xcraft-core-etc]** : Utilisé pour la gestion de la configuration du module
|
|
258
204
|
|
|
259
205
|
Le module peut également interagir avec d'autres services Goblin via le système d'appels d'outils, permettant aux agents d'effectuer des actions concrètes dans l'application.
|
|
260
206
|
|
|
261
207
|
## Configuration avancée
|
|
262
208
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
209
|
+
| Option | Description | Type | Valeur par défaut |
|
|
210
|
+
| --------------- | ------------------------------------- | ------ | ----------------- |
|
|
211
|
+
| version | Version des agents | Number | 2 |
|
|
212
|
+
| defaultProfile | Profil par défaut des agents | String | null |
|
|
213
|
+
| defaultSettings | Paramètres par défaut | Object | null |
|
|
214
|
+
| profiles | Profils pour remplacer les paramètres | Object | {} |
|
|
215
|
+
| settings | Paramètres par nom | Object | {} |
|
|
268
216
|
|
|
269
217
|
## Détails des sources
|
|
270
218
|
|
|
271
219
|
### `aiAgent.js`
|
|
272
220
|
|
|
273
|
-
Ce fichier est le point d'entrée du module, exportant les commandes Xcraft pour l'acteur AiAgent. Il importe l'acteur et sa logique depuis le fichier principal et les expose au système Xcraft.
|
|
221
|
+
Ce fichier est le point d'entrée du module, exportant les commandes Xcraft pour l'acteur AiAgent. Il importe l'acteur et sa logique depuis le fichier principal et les expose au système Xcraft via `Elf.birth()`.
|
|
274
222
|
|
|
275
223
|
### `config.js`
|
|
276
224
|
|
|
277
225
|
Ce fichier définit la configuration du module, permettant de spécifier :
|
|
278
226
|
|
|
227
|
+
- La version des agents (`version`)
|
|
279
228
|
- Le profil par défaut des agents (`defaultProfile`)
|
|
280
229
|
- Les paramètres par défaut (`defaultSettings`)
|
|
281
230
|
- Les profils pour remplacer les paramètres (`profiles`)
|
|
@@ -294,38 +243,161 @@ Ce fichier contient la définition complète de l'acteur AiAgent, avec :
|
|
|
294
243
|
- **AiAgentLogic** : Logique métier de l'agent (mutations d'état)
|
|
295
244
|
- **AiAgent** : Classe principale de l'acteur avec les méthodes d'interaction
|
|
296
245
|
|
|
297
|
-
|
|
246
|
+
#### État et modèle de données
|
|
247
|
+
|
|
248
|
+
L'état d'un agent IA est défini par la classe `AiAgentShape` qui contient les propriétés suivantes :
|
|
249
|
+
|
|
250
|
+
- `id` : Identifiant unique de l'agent
|
|
251
|
+
- `version` : Version de l'agent
|
|
252
|
+
- `name` : Nom de l'agent
|
|
253
|
+
- `role` : Rôle de l'agent (assistant, etc.)
|
|
254
|
+
- `prompt` : Prompt par défaut utilisé pour définir le comportement de l'agent
|
|
255
|
+
- `provider` : Fournisseur LLM ('ollama' ou 'open-ai')
|
|
256
|
+
- `model` : Modèle de langage utilisé
|
|
257
|
+
- `host` : URL du serveur LLM
|
|
258
|
+
- `headers` : En-têtes HTTP pour les requêtes API
|
|
259
|
+
- `messages` : Historique des messages par contexte
|
|
260
|
+
- `options` : Options de configuration du modèle (voir OptionsShape)
|
|
261
|
+
- `format` : Format de sortie attendu
|
|
262
|
+
- `tools` : Outils externes disponibles pour l'agent
|
|
263
|
+
- `toolServiceId` : Identifiant du service d'outils
|
|
264
|
+
- `usability` : État d'utilisabilité ('disabled', 'experimental', 'stable', 'deprecated')
|
|
265
|
+
- `meta` : Métadonnées de l'agent
|
|
266
|
+
|
|
267
|
+
La classe `OptionsShape` définit de nombreuses options de configuration pour les modèles LLM, incluant des paramètres comme la température, la taille du contexte, les pénalités de répétition, et les options d'optimisation mémoire.
|
|
268
|
+
|
|
269
|
+
#### Cycle de vie de l'acteur
|
|
270
|
+
|
|
271
|
+
L'acteur AiAgent suit le cycle de vie standard des acteurs Elf Archetype :
|
|
272
|
+
|
|
273
|
+
1. **Création** : Via la méthode `create()` qui initialise l'agent avec un ID et un état
|
|
274
|
+
2. **Configuration** : Via `patch()` pour modifier les paramètres ou `upgrade()` pour les mises à jour de version
|
|
275
|
+
3. **Utilisation** : Via les méthodes de conversation et de génération
|
|
276
|
+
4. **Persistance** : Via `save()` pour sauvegarder l'état
|
|
277
|
+
5. **Suppression** : Via `trash()` pour marquer comme supprimé ou `delete()` pour suppression définitive
|
|
278
|
+
|
|
279
|
+
#### Méthodes publiques
|
|
298
280
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
-
|
|
302
|
-
|
|
303
|
-
-
|
|
304
|
-
|
|
281
|
+
**`create(id, desktopId, agentState)`** - Crée un nouvel agent avec l'ID et l'état spécifiés. Persiste automatiquement l'agent après création.
|
|
282
|
+
|
|
283
|
+
**`patch(agentState)`** - Met à jour l'état de l'agent avec les propriétés fournies et persiste les changements.
|
|
284
|
+
|
|
285
|
+
**`upgrade(version, agentState)`** - Met à niveau l'agent vers une nouvelle version avec l'état spécifié, uniquement si la version est supérieure à la version actuelle.
|
|
286
|
+
|
|
287
|
+
**`gen(prompt)`** - Génère du texte à partir d'un prompt en utilisant le modèle configuré de l'agent.
|
|
288
|
+
|
|
289
|
+
**`reset(contextId, save)`** - Réinitialise l'historique des messages pour un contexte spécifique. Si aucun contextId n'est fourni, réinitialise tous les contextes.
|
|
290
|
+
|
|
291
|
+
**`readPDFpages(pdfPath)`** - Extrait le texte des pages d'un document PDF en utilisant l'OCR via un modèle multimodal.
|
|
292
|
+
|
|
293
|
+
**`embed(rawText)`** - Génère un embedding vectoriel pour un texte donné, retourné sous forme de littéral SQL hexadécimal.
|
|
294
|
+
|
|
295
|
+
**`embedInBatch(rawTexts)`** - Génère des embeddings vectoriels pour plusieurs textes en une seule requête, optimisé pour les traitements par lots.
|
|
296
|
+
|
|
297
|
+
**`set(contextId, messages)`** - Définit directement l'historique des messages pour un contexte spécifique.
|
|
298
|
+
|
|
299
|
+
**`chat(contextId, question, userDesktopId, sessionId)`** - Engage une conversation avec l'agent dans un contexte spécifique. Gère automatiquement l'historique et les appels d'outils.
|
|
300
|
+
|
|
301
|
+
**`ask(contextId, question, questionId)`** - Pose une question à l'agent avec streaming de la réponse (uniquement pour Ollama). Émet des événements pour chaque partie de la réponse.
|
|
302
|
+
|
|
303
|
+
**`resumeExchange(resumePrompt, contextId)`** - Reprend un échange existant avec un nouveau prompt système, en utilisant l'historique du contexte comme base.
|
|
304
|
+
|
|
305
|
+
**`react(reactPrompt, contextId, question)`** - Utilise le paradigme ReAct (Reasoning and Acting) pour permettre à l'agent de raisonner et d'agir de manière autonome.
|
|
306
|
+
|
|
307
|
+
**`callAgent(contextId, agentId, action, feedId)`** - Appelle un autre agent pour effectuer une action spécifique dans le cadre d'une orchestration multi-agents.
|
|
308
|
+
|
|
309
|
+
**`addAssistantMessage(contextId, message)`** - Ajoute un message de l'assistant à l'historique d'un contexte spécifique.
|
|
310
|
+
|
|
311
|
+
**`change(path, newValue)`** - Modifie une propriété spécifique de l'état de l'agent avec gestion automatique des types pour certains paramètres.
|
|
312
|
+
|
|
313
|
+
**`getUserChatContextHistory(contextId, filterTool)`** - Récupère l'historique des messages pour un contexte spécifique avec option de filtrage des messages d'outils.
|
|
314
|
+
|
|
315
|
+
**`getBaseSettings()`** - Retourne les paramètres de base de l'agent (provider, host, headers, model).
|
|
316
|
+
|
|
317
|
+
**`getUsability()`** - Retourne l'état d'utilisabilité de l'agent.
|
|
318
|
+
|
|
319
|
+
**`trash()`** - Marque l'agent comme supprimé en changeant son statut meta à 'trashed'.
|
|
320
|
+
|
|
321
|
+
**`save()`** - Persiste l'état actuel de l'agent sur le disque.
|
|
305
322
|
|
|
306
323
|
### `lib/llm/providers.js`
|
|
307
324
|
|
|
308
|
-
Ce fichier définit
|
|
325
|
+
Ce fichier définit l'architecture des fournisseurs de LLM avec une classe abstraite `LLMProvider` et ses implémentations concrètes :
|
|
326
|
+
|
|
327
|
+
#### LLMProvider (classe abstraite)
|
|
328
|
+
|
|
329
|
+
Définit l'interface commune que tous les fournisseurs doivent implémenter :
|
|
330
|
+
|
|
331
|
+
- `chat()` : Conversation avec le modèle
|
|
332
|
+
- `gen()` : Génération de texte
|
|
333
|
+
- `embed()` : Génération d'embeddings
|
|
334
|
+
- `embedInBatch()` : Génération d'embeddings par lots
|
|
309
335
|
|
|
310
|
-
|
|
311
|
-
- **OllamaProvider** : Implémentation pour le fournisseur Ollama (local)
|
|
312
|
-
- **OpenAIProvider** : Implémentation pour OpenAI et services compatibles
|
|
336
|
+
#### OllamaProvider
|
|
313
337
|
|
|
314
|
-
|
|
338
|
+
Implémentation pour le fournisseur Ollama (serveur local) :
|
|
339
|
+
|
|
340
|
+
- Utilise la bibliothèque `ollama` pour communiquer avec le serveur
|
|
341
|
+
- Gère le streaming pour les conversations en temps réel
|
|
342
|
+
- Implémente un sémaphore pour limiter les requêtes d'embedding simultanées (max 4)
|
|
343
|
+
- Supporte tous les formats de sortie JSON structurés
|
|
344
|
+
|
|
345
|
+
#### OpenAIProvider
|
|
346
|
+
|
|
347
|
+
Implémentation pour OpenAI et services compatibles (OpenRouter, etc.) :
|
|
348
|
+
|
|
349
|
+
- Utilise `RestAPI` de xcraft-core-utils pour les appels HTTP
|
|
350
|
+
- Adapte les formats de requête entre Ollama et OpenAI
|
|
351
|
+
- Gère les schémas JSON pour les réponses structurées
|
|
352
|
+
- Supporte les en-têtes d'authentification personnalisés
|
|
315
353
|
|
|
316
354
|
### `lib/llm/utils.js`
|
|
317
355
|
|
|
318
|
-
Ce fichier contient
|
|
356
|
+
Ce fichier contient un ensemble complet d'utilitaires pour le traitement de texte et de documents :
|
|
357
|
+
|
|
358
|
+
#### Traitement de documents PDF
|
|
359
|
+
|
|
360
|
+
**`getPDFImages(pdfPath)`** - Convertit les pages d'un PDF en images base64 pour l'OCR avec des options optimisées pour la qualité du texte.
|
|
361
|
+
|
|
362
|
+
**`OCR_SYSTEM_PROMPT`** - Prompt système spécialisé pour guider les modèles multimodaux dans l'extraction de texte à partir d'images.
|
|
363
|
+
|
|
364
|
+
#### Traitement de texte et HTML
|
|
365
|
+
|
|
366
|
+
**`cleanHtmlContent(html)`** - Nettoie le contenu HTML en supprimant les balises et en normalisant les espaces.
|
|
367
|
+
|
|
368
|
+
**`simplifyHtml(html)`** - Simplifie le HTML en gardant uniquement les éléments sûrs via sanitize-html.
|
|
369
|
+
|
|
370
|
+
**`extractTitlesAndContent(html)`** - Extrait intelligemment les titres et le contenu d'un document HTML en préservant la structure.
|
|
371
|
+
|
|
372
|
+
**`html2chunks(html, chunkSize, chunkOverlap)`** - Découpe un document HTML en chunks en utilisant RecursiveCharacterTextSplitter de LangChain.
|
|
373
|
+
|
|
374
|
+
#### Gestion des tokens et découpage
|
|
375
|
+
|
|
376
|
+
**`estimateTokenCount(text)`** - Estime le nombre de tokens d'un texte (approximation basée sur les mots × 1.33).
|
|
377
|
+
|
|
378
|
+
**`splitLongText(text, maxLength, baseId)`** - Découpe un texte long en segments de taille maximale en préservant les mots entiers.
|
|
379
|
+
|
|
380
|
+
**`sliceText(text, chunkSize)`** - Découpe un texte en chunks en respectant les limites de phrases.
|
|
381
|
+
|
|
382
|
+
**`groupSentences(text, maxSize)`** - Groupe les phrases en chunks de taille optimale pour les modèles de langage.
|
|
383
|
+
|
|
384
|
+
#### Traitement par lots et optimisation
|
|
385
|
+
|
|
386
|
+
**`chunkArray(array, size)`** - Divise un tableau en sous-tableaux de taille spécifiée pour le traitement par lots.
|
|
387
|
+
|
|
388
|
+
**`embedChunksInBatch(agent, allChunks, batchSize)`** - Traite les embeddings par lots en associant chaque embedding à son contenu d'origine.
|
|
389
|
+
|
|
390
|
+
**`buildPromptFromArticlesMulti(initialQuestion, questionsWithArticles, maxTotalTokens)`** - Construit des prompts optimisés à partir de multiples articles en respectant les limites de tokens.
|
|
391
|
+
|
|
392
|
+
#### Utilitaires de conversion
|
|
319
393
|
|
|
320
|
-
-
|
|
321
|
-
- Nettoyage et simplification de contenu HTML (`cleanHtmlContent`, `simplifyHtml`)
|
|
322
|
-
- Découpage de texte en segments gérables (`splitLongText`, `html2chunks`)
|
|
323
|
-
- Estimation du nombre de tokens (`estimateTokenCount`)
|
|
324
|
-
- Construction de prompts à partir d'articles (`buildPromptFromArticles`)
|
|
325
|
-
- Traitement par lots des embeddings (`embedChunksInBatch`, `chunkArray`)
|
|
394
|
+
**`vectorToSqlLiteral(vec)`** - Convertit un vecteur Float32Array en littéral SQL hexadécimal pour le stockage en base de données.
|
|
326
395
|
|
|
327
|
-
Ces utilitaires sont essentiels pour préparer les données avant de les envoyer aux modèles de langage et pour traiter les résultats. Ils permettent notamment de gérer les contraintes de taille de contexte des modèles en découpant intelligemment les textes longs.
|
|
396
|
+
Ces utilitaires sont essentiels pour préparer les données avant de les envoyer aux modèles de langage et pour traiter les résultats. Ils permettent notamment de gérer les contraintes de taille de contexte des modèles en découpant intelligemment les textes longs tout en préservant la cohérence sémantique.
|
|
328
397
|
|
|
329
|
-
|
|
398
|
+
_Cette documentation a été mise à jour automatiquement._
|
|
330
399
|
|
|
331
|
-
|
|
400
|
+
[xcraft-core-goblin]: https://github.com/Xcraft-Inc/xcraft-core-goblin
|
|
401
|
+
[xcraft-core-stones]: https://github.com/Xcraft-Inc/xcraft-core-stones
|
|
402
|
+
[xcraft-core-utils]: https://github.com/Xcraft-Inc/xcraft-core-utils
|
|
403
|
+
[xcraft-core-etc]: https://github.com/Xcraft-Inc/xcraft-core-etc
|
package/lib/llm/providers.js
CHANGED
|
@@ -151,7 +151,11 @@ class OpenAIProvider extends LLMProvider {
|
|
|
151
151
|
const chat = await this.#openAI._post(query, chatRequest);
|
|
152
152
|
const {choices} = chat;
|
|
153
153
|
if (chat.error) {
|
|
154
|
-
const err = new Error(
|
|
154
|
+
const err = new Error(
|
|
155
|
+
chat.error?.metadata
|
|
156
|
+
? JSON.stringify(chat.error?.metadata)
|
|
157
|
+
: chat.error.message
|
|
158
|
+
);
|
|
155
159
|
err.code = chat.error.code;
|
|
156
160
|
throw err;
|
|
157
161
|
}
|
package/lib/llm/utils.js
CHANGED
|
@@ -77,7 +77,7 @@ const splitLongText = (text, maxLength, baseId) => {
|
|
|
77
77
|
return segments;
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
const html2chunks = async (html, chunkSize =
|
|
80
|
+
const html2chunks = async (html, chunkSize = 300, chunkOverlap = 0) => {
|
|
81
81
|
const textSplitter = RecursiveCharacterTextSplitter.fromLanguage('html', {
|
|
82
82
|
chunkSize,
|
|
83
83
|
chunkOverlap,
|
|
@@ -223,58 +223,46 @@ function groupSentences(text, maxSize) {
|
|
|
223
223
|
return chunks;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
context += segmentContext;
|
|
251
|
-
|
|
252
|
-
// Ajouter du contexte avant et après le segment
|
|
253
|
-
const beforeContext = article.content.substring(
|
|
254
|
-
Math.max(0, s.start - 50),
|
|
255
|
-
s.start
|
|
256
|
-
);
|
|
257
|
-
const afterContext = article.content.substring(
|
|
258
|
-
s.end,
|
|
259
|
-
Math.min(article.content.length, s.end + 50)
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
const additionalContext = `### Contexte avant: ###\n${beforeContext}\n### Contexte après: ###\n${afterContext}\n`;
|
|
263
|
-
currentTokenCount += estimateTokenCount(additionalContext);
|
|
226
|
+
function buildPromptFromArticlesMulti(
|
|
227
|
+
initialQuestion,
|
|
228
|
+
questionsWithArticles,
|
|
229
|
+
maxTotalTokens
|
|
230
|
+
) {
|
|
231
|
+
let prompt = '[CONTEXT]\n\n';
|
|
232
|
+
let currentTokenCount = estimateTokenCount(prompt);
|
|
233
|
+
|
|
234
|
+
const buffer = 16000; // Réserve de tokens pour l'instruction + réponse
|
|
235
|
+
const maxTokensPerQuestion = Math.floor(
|
|
236
|
+
(maxTotalTokens - buffer) / questionsWithArticles.length
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
for (const {articles} of questionsWithArticles) {
|
|
240
|
+
let section = '';
|
|
241
|
+
let sectionTokenCount = estimateTokenCount(section);
|
|
242
|
+
for (const article of articles) {
|
|
243
|
+
const {title, sourceId, description} = article;
|
|
244
|
+
const articleBlock = `\n# ${title} \n## Lien: ${sourceId}\n\n${description}\n`;
|
|
245
|
+
const tokensForArticle = estimateTokenCount(articleBlock);
|
|
246
|
+
|
|
247
|
+
if (sectionTokenCount + tokensForArticle > maxTokensPerQuestion) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
264
250
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
251
|
+
section += articleBlock;
|
|
252
|
+
sectionTokenCount += tokensForArticle;
|
|
253
|
+
}
|
|
268
254
|
|
|
269
|
-
|
|
270
|
-
|
|
255
|
+
if (currentTokenCount + sectionTokenCount > maxTotalTokens - buffer) {
|
|
256
|
+
break;
|
|
271
257
|
}
|
|
272
|
-
});
|
|
273
258
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
259
|
+
prompt += section;
|
|
260
|
+
currentTokenCount += sectionTokenCount;
|
|
261
|
+
}
|
|
262
|
+
const questionContext = '[QUESTION]\n\n' + initialQuestion;
|
|
263
|
+
prompt += questionContext;
|
|
264
|
+
return prompt.trim();
|
|
265
|
+
}
|
|
278
266
|
|
|
279
267
|
/**
|
|
280
268
|
* Split an array into smaller chunks of a given size.
|
|
@@ -301,7 +289,7 @@ function chunkArray(array, size) {
|
|
|
301
289
|
* @returns {Promise<object>} A map of embeddings structured as:
|
|
302
290
|
* { [contentId]: { [key]: { chunk, embedding } } }
|
|
303
291
|
*/
|
|
304
|
-
async function embedChunksInBatch(agent, allChunks, batchSize =
|
|
292
|
+
async function embedChunksInBatch(agent, allChunks, batchSize = 50) {
|
|
305
293
|
const embeddingsByContent = {};
|
|
306
294
|
|
|
307
295
|
for (let start = 0; start < allChunks.length; start += batchSize) {
|
|
@@ -340,5 +328,5 @@ module.exports = {
|
|
|
340
328
|
getPDFImages,
|
|
341
329
|
sliceText,
|
|
342
330
|
groupSentences,
|
|
343
|
-
|
|
331
|
+
buildPromptFromArticlesMulti,
|
|
344
332
|
};
|