goblin-agents 0.1.4 → 0.1.10
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 +373 -0
- package/lib/llm/providers.js +5 -1
- package/lib/llm/utils.js +39 -51
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# 📘 Documentation du module goblin-agents
|
|
2
|
+
|
|
3
|
+
## Aperçu
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## Structure du module
|
|
8
|
+
|
|
9
|
+
- **AiAgent** : Acteur principal qui encapsule un agent IA et ses fonctionnalités
|
|
10
|
+
- **Providers** : Classes pour interagir avec différents fournisseurs de LLM (Ollama, OpenAI)
|
|
11
|
+
- **Utilitaires** : Fonctions pour le traitement de texte, l'OCR, et la manipulation de documents
|
|
12
|
+
|
|
13
|
+
Le module est organisé autour du pattern Elf, avec une séparation claire entre la logique métier (`AiAgentLogic`) et l'interface de l'acteur (`AiAgent`).
|
|
14
|
+
|
|
15
|
+
## Fonctionnement global
|
|
16
|
+
|
|
17
|
+
Le module `goblin-agents` permet de créer et gérer des agents IA qui peuvent interagir avec les utilisateurs via des conversations textuelles. Chaque agent est configuré avec un modèle spécifique, un fournisseur (Ollama ou OpenAI), et des paramètres qui définissent son comportement.
|
|
18
|
+
|
|
19
|
+
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
|
+
|
|
21
|
+
Le module prend en charge diverses fonctionnalités avancées :
|
|
22
|
+
|
|
23
|
+
- Génération de texte à partir de prompts
|
|
24
|
+
- Conversations interactives avec historique
|
|
25
|
+
- Extraction de texte à partir d'images de documents PDF
|
|
26
|
+
- Génération d'embeddings vectoriels pour la recherche sémantique
|
|
27
|
+
- Utilisation d'outils externes via un système d'appels d'outils
|
|
28
|
+
- Paradigme ReAct (Reasoning and Acting) pour des agents plus autonomes
|
|
29
|
+
|
|
30
|
+
Les agents peuvent être configurés avec une grande variété d'options qui influencent leur comportement, comme la température (créativité), la taille du contexte, et diverses pénalités pour éviter les répétitions.
|
|
31
|
+
|
|
32
|
+
## Exemples d'utilisation
|
|
33
|
+
|
|
34
|
+
### Création d'un agent IA
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
const {AiAgent} = require('goblin-agents/lib/llm/aiAgent.js');
|
|
38
|
+
|
|
39
|
+
// Dans une méthode d'un acteur Elf
|
|
40
|
+
async initAgents() {
|
|
41
|
+
const feedId = await this.newQuestFeed();
|
|
42
|
+
|
|
43
|
+
// Création d'un agent avec Ollama (local)
|
|
44
|
+
const ollamaAgentId = SmartId.from('aiAgent', 'ollama-mistral-assistant');
|
|
45
|
+
const agent = await new AiAgent(this).create(ollamaAgentId, feedId, {
|
|
46
|
+
name: 'Assistant Personnel',
|
|
47
|
+
role: 'assistant',
|
|
48
|
+
prompt: 'Tu es un assistant personnel serviable et précis.',
|
|
49
|
+
provider: 'ollama',
|
|
50
|
+
model: 'mistral-small',
|
|
51
|
+
host: 'http://127.0.0.1:11434',
|
|
52
|
+
options: {
|
|
53
|
+
temperature: 0.7
|
|
54
|
+
},
|
|
55
|
+
usability: 'stable'
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Création d'un agent avec OpenAI (cloud)
|
|
59
|
+
const openAiAgentId = SmartId.from('aiAgent', 'gpt-4o-assistant');
|
|
60
|
+
const agent = await new AiAgent(this).create(openAiAgentId, feedId, {
|
|
61
|
+
name: 'Assistant Personnel',
|
|
62
|
+
role: 'assistant',
|
|
63
|
+
provider: 'open-ai',
|
|
64
|
+
model: 'openai/gpt-4o',
|
|
65
|
+
host: 'https://openrouter.ai/api/v1',
|
|
66
|
+
headers: {
|
|
67
|
+
Authorization: `Bearer <votre-clef-api>`,
|
|
68
|
+
},
|
|
69
|
+
usability: 'stable'
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Conversation avec un agent
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
// Dans une méthode d'un acteur Elf
|
|
77
|
+
async askAgent(desktopId, agentId, contextId, question, saveExchange = false) {
|
|
78
|
+
const feedId = await this.newQuestFeed();
|
|
79
|
+
|
|
80
|
+
// Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
|
|
81
|
+
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
82
|
+
|
|
83
|
+
// Continue la conversation sur ce contexte par ex. 'story-xyz' et
|
|
84
|
+
// obtient une réponse à la question de l'utilisateur
|
|
85
|
+
const response = await agent.chat(contextId, question, desktopId);
|
|
86
|
+
|
|
87
|
+
if(saveExchange) {
|
|
88
|
+
//Persiste l'échange contextuel nouvelle paire (question->réponse) sur le disque
|
|
89
|
+
await agent.save();
|
|
90
|
+
}
|
|
91
|
+
return response;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Résumer et "squasher" un contexte
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
// Dans une méthode d'un acteur Elf
|
|
99
|
+
async resumeAndSquashContexte(agentId, contextId) {
|
|
100
|
+
const feedId = await this.newQuestFeed();
|
|
101
|
+
|
|
102
|
+
// Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
|
|
103
|
+
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
104
|
+
|
|
105
|
+
// Demande un résumé des échanges
|
|
106
|
+
const resumePrompt = "Résume l'essentiel de nos échanges en utilisant l'historique des messages";
|
|
107
|
+
const resumedContent = await agent.resumeExchange(resumePrompt, contextId);
|
|
108
|
+
|
|
109
|
+
// Reset la mémoire contextuel et persiste le changement
|
|
110
|
+
await agent.reset(contextId, true);
|
|
111
|
+
|
|
112
|
+
// Ajoute un nouvel historique d'échange a partir du résumé
|
|
113
|
+
await agent.set(contextId, [{role: 'assistant', content: resumedContent}]);
|
|
114
|
+
|
|
115
|
+
// Sauve ce nouvel état
|
|
116
|
+
await agent.save();
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Utilisation d'outils externes
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// Dans une méthode d'un acteur Elf
|
|
124
|
+
async addWeatherToolSupportToAgent(agentId) {
|
|
125
|
+
const feedId = await this.newQuestFeed();
|
|
126
|
+
|
|
127
|
+
// Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
|
|
128
|
+
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
129
|
+
|
|
130
|
+
// Configurer un agent avec des outils
|
|
131
|
+
// Provoquera des envois de commandes sur le bus à l'acteur "weather"
|
|
132
|
+
// en appelant la quête "getWeather(city)"
|
|
133
|
+
await agent.patch({
|
|
134
|
+
toolServiceId: 'weather', //acteur (ici le singleton weather) qui expose des quêtes "outils"
|
|
135
|
+
tools: [
|
|
136
|
+
{
|
|
137
|
+
type: 'function',
|
|
138
|
+
function: {
|
|
139
|
+
name: 'getWeather', //nom de la quête de l'acteur qui récupère la météo
|
|
140
|
+
description: 'Obtenir la météo pour une ville',
|
|
141
|
+
parameters: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
city: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
description: 'Nom de la ville'
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
required: ['city']
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
// L'agent pourra maintenant utiliser l'outil weather.getWeather
|
|
159
|
+
const question = "Quel temps fait-il à Paris aujourd'hui?";
|
|
160
|
+
const response = await agent.chat(contextId, question, desktopId);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Génération d'embeddings pour la recherche sémantique
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
// Dans une méthode d'un acteur Elf
|
|
167
|
+
// {
|
|
168
|
+
// 'indexContent@article-1', 'Contenu à indexer'
|
|
169
|
+
// 'indexContent@article-2', 'Un autre contenu à indexer'
|
|
170
|
+
// }
|
|
171
|
+
//
|
|
172
|
+
async updateKnowledgeBase(articles) {
|
|
173
|
+
const feedId = await this.newQuestFeed();
|
|
174
|
+
|
|
175
|
+
// Instancie l'agent approprié pour faire un embedding
|
|
176
|
+
// par ex. 'aiAgent@default-embedder'
|
|
177
|
+
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
178
|
+
|
|
179
|
+
// Récupère les textes à indexer
|
|
180
|
+
const texts = Object.values(articles);
|
|
181
|
+
|
|
182
|
+
// Prépare un boucle d'insertion par id d'article
|
|
183
|
+
const articlesIds = Object.keys(articles);
|
|
184
|
+
|
|
185
|
+
// Générer des embeddings pour plusieurs textes en une seule requête
|
|
186
|
+
const vectorsBatch = await agent.embedInBatch(texts);
|
|
187
|
+
|
|
188
|
+
// Upsert les articles dans un archetype IndexedContent
|
|
189
|
+
for(const articleId of articleIds) {
|
|
190
|
+
// Récupère les vecteurs correspondant au texte dans le lot
|
|
191
|
+
// et le texte original
|
|
192
|
+
const index = articleIds.indexOf(articleId);
|
|
193
|
+
const vectors = vectorsBatch[index];
|
|
194
|
+
const text = texts[index];
|
|
195
|
+
|
|
196
|
+
const indexedContent = new IndexedContentState({
|
|
197
|
+
id: articleId,
|
|
198
|
+
text, //texte original de l'article
|
|
199
|
+
meta: {
|
|
200
|
+
locale: 'fr', //définit la locale
|
|
201
|
+
scope: 'knowledge-base', //définit le scope
|
|
202
|
+
vectors, //vecteur correspondants au texte
|
|
203
|
+
status: 'published',
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Effectue l'upsert de l'archetype
|
|
208
|
+
await new IndexedContent(this).insertOrReplace(
|
|
209
|
+
articleId,
|
|
210
|
+
feedId,
|
|
211
|
+
indexedContent
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Dans une méthode d'un acteur Elf
|
|
217
|
+
async queryKnowledgeBase(question = "Comment fonctionne l'apprentissage par renforcement?") {
|
|
218
|
+
const feedId = await this.newQuestFeed();
|
|
219
|
+
|
|
220
|
+
// Instancie l'agent approprié pour faire un embedding
|
|
221
|
+
// par ex. 'aiAgent@default-embedder'
|
|
222
|
+
const agent = await new AiAgent(this).create(agentId, feedId);
|
|
223
|
+
|
|
224
|
+
// Générer un embedding pour un texte, par ex. pour une recherche
|
|
225
|
+
// Convertit la question texte en vecteurs
|
|
226
|
+
const vectors = await agent.embed(question);
|
|
227
|
+
|
|
228
|
+
// Effectue une recherche par similitude dans cryo
|
|
229
|
+
// Utilise un Archetype "IndexedContent"
|
|
230
|
+
// Limite la recherche au contenu indexé en français
|
|
231
|
+
// avec un scope 'knowledge-base'
|
|
232
|
+
// les 100 premier résultats les plus pertinents
|
|
233
|
+
const results = await this.cryo.searchDistance2(
|
|
234
|
+
IndexedContentLogic.db,
|
|
235
|
+
vectors,
|
|
236
|
+
['fr'],
|
|
237
|
+
['knowledge-base'],
|
|
238
|
+
100
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// Itère sur les données et retourne les résultats
|
|
242
|
+
// a l'appelant
|
|
243
|
+
return Array.from(results);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Interactions avec d'autres modules
|
|
248
|
+
|
|
249
|
+
Le module `goblin-agents` interagit avec plusieurs autres composants de l'écosystème Xcraft :
|
|
250
|
+
|
|
251
|
+
- **[xcraft-core-goblin]** : Fournit l'infrastructure Elf pour la création d'acteurs
|
|
252
|
+
- **[xcraft-core-stones]** : Utilisé pour la définition des types de données
|
|
253
|
+
- **[xcraft-core-utils]** : Utilisé pour les appels API REST et la gestion des verrous
|
|
254
|
+
|
|
255
|
+
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.
|
|
256
|
+
|
|
257
|
+
## Configuration avancée
|
|
258
|
+
|
|
259
|
+
| Option | Description | Type | Valeur par défaut |
|
|
260
|
+
|--------|-------------|------|------------------|
|
|
261
|
+
| version | Version des agents | Number | 2 |
|
|
262
|
+
| defaultProfile | Profil par défaut des agents | String | null |
|
|
263
|
+
| defaultSettings | Paramètres par défaut | Object | null |
|
|
264
|
+
| profiles | Profils pour remplacer les paramètres | Object | {} |
|
|
265
|
+
| settings | Paramètres par nom | Object | {} |
|
|
266
|
+
|
|
267
|
+
## Détails des sources
|
|
268
|
+
|
|
269
|
+
### `aiAgent.js`
|
|
270
|
+
|
|
271
|
+
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.
|
|
272
|
+
|
|
273
|
+
### `config.js`
|
|
274
|
+
|
|
275
|
+
Ce fichier définit la configuration du module, permettant de spécifier :
|
|
276
|
+
|
|
277
|
+
- La version des agents (`version`)
|
|
278
|
+
- Le profil par défaut des agents (`defaultProfile`)
|
|
279
|
+
- Les paramètres par défaut (`defaultSettings`)
|
|
280
|
+
- Les profils pour remplacer les paramètres (`profiles`)
|
|
281
|
+
- Les paramètres par nom (`settings`)
|
|
282
|
+
|
|
283
|
+
Cette configuration permet de personnaliser le comportement des agents au niveau de l'application.
|
|
284
|
+
|
|
285
|
+
### `lib/llm/aiAgent.js`
|
|
286
|
+
|
|
287
|
+
Ce fichier contient la définition complète de l'acteur AiAgent, avec :
|
|
288
|
+
|
|
289
|
+
- **MessageShape** : Définit la structure des messages échangés (rôle, contenu, images, appels d'outils)
|
|
290
|
+
- **OptionsShape** : Définit les nombreuses options de configuration des modèles LLM
|
|
291
|
+
- **AiAgentShape** : Définit la structure des données de l'agent
|
|
292
|
+
- **AiAgentState** : Classe d'état de l'agent basée sur le shape
|
|
293
|
+
- **AiAgentLogic** : Logique métier de l'agent (mutations d'état)
|
|
294
|
+
- **AiAgent** : Classe principale de l'acteur avec les méthodes d'interaction
|
|
295
|
+
|
|
296
|
+
#### État et modèle de données
|
|
297
|
+
|
|
298
|
+
L'état d'un agent IA est défini par la classe `AiAgentShape` qui contient les propriétés suivantes :
|
|
299
|
+
|
|
300
|
+
- `id` : Identifiant unique de l'agent
|
|
301
|
+
- `version` : Version de l'agent
|
|
302
|
+
- `name` : Nom de l'agent
|
|
303
|
+
- `role` : Rôle de l'agent (assistant, etc.)
|
|
304
|
+
- `prompt` : Prompt par défaut utilisé pour définir le comportement de l'agent
|
|
305
|
+
- `provider` : Fournisseur LLM ('ollama' ou 'open-ai')
|
|
306
|
+
- `model` : Modèle de langage utilisé
|
|
307
|
+
- `host` : URL du serveur LLM
|
|
308
|
+
- `headers` : En-têtes HTTP pour les requêtes API
|
|
309
|
+
- `messages` : Historique des messages par contexte
|
|
310
|
+
- `options` : Options de configuration du modèle
|
|
311
|
+
- `format` : Format de sortie attendu
|
|
312
|
+
- `tools` : Outils externes disponibles pour l'agent
|
|
313
|
+
- `toolServiceId` : Identifiant du service d'outils
|
|
314
|
+
- `usability` : État d'utilisabilité ('disabled', 'experimental', 'stable', 'deprecated')
|
|
315
|
+
- `meta` : Métadonnées de l'agent
|
|
316
|
+
|
|
317
|
+
#### Méthodes publiques
|
|
318
|
+
|
|
319
|
+
- **`create(id, desktopId, agentState)`** - Crée un nouvel agent avec l'ID et l'état spécifiés.
|
|
320
|
+
- **`patch(agentState)`** - Met à jour l'état de l'agent avec les propriétés fournies.
|
|
321
|
+
- **`upgrade(version, agentState)`** - Met à niveau l'agent vers une nouvelle version avec l'état spécifié.
|
|
322
|
+
- **`gen(prompt)`** - Génère du texte à partir d'un prompt.
|
|
323
|
+
- **`reset(contextId, save)`** - Réinitialise l'historique des messages pour un contexte spécifique.
|
|
324
|
+
- **`readPDFpages(pdfPath)`** - Extrait le texte des pages d'un document PDF.
|
|
325
|
+
- **`embed(rawText)`** - Génère un embedding vectoriel pour un texte donné.
|
|
326
|
+
- **`embedInBatch(rawTexts)`** - Génère des embeddings vectoriels pour plusieurs textes en une seule requête.
|
|
327
|
+
- **`set(contextId, messages)`** - Définit l'historique des messages pour un contexte spécifique.
|
|
328
|
+
- **`chat(contextId, question, userDesktopId, sessionId)`** - Engage une conversation avec l'agent dans un contexte spécifique.
|
|
329
|
+
- **`callTools(contextId, message, userDesktopId, sessionId)`** - Appelle des outils externes en fonction des demandes de l'agent.
|
|
330
|
+
- **`addToolMessage(contextId, toolId, toolName, answer)`** - Ajoute une réponse d'outil à l'historique des messages.
|
|
331
|
+
- **`ask(contextId, question, questionId)`** - Pose une question à l'agent avec streaming de la réponse.
|
|
332
|
+
- **`resumeExchange(resumePrompt, contextId)`** - Résume un échange de conversation.
|
|
333
|
+
- **`react(reactPrompt, contextId, question)`** - Utilise le paradigme ReAct pour raisonner et agir sur une question.
|
|
334
|
+
- **`callAgent(contextId, agentId, action, feedId)`** - Appelle un autre agent pour effectuer une action.
|
|
335
|
+
- **`addAssistantMessage(contextId, message)`** - Ajoute un message de l'assistant à l'historique.
|
|
336
|
+
- **`save()`** - Persiste l'état de l'agent.
|
|
337
|
+
- **`change(path, newValue)`** - Modifie une propriété spécifique de l'état de l'agent.
|
|
338
|
+
- **`getUserChatContextHistory(contextId, filterTool)`** - Récupère l'historique des messages pour un contexte spécifique.
|
|
339
|
+
- **`getBaseSettings()`** - Récupère les paramètres de base de l'agent.
|
|
340
|
+
- **`getUsability()`** - Récupère l'état d'utilisabilité de l'agent.
|
|
341
|
+
- **`trash()`** - Marque l'agent comme supprimé.
|
|
342
|
+
- **`delete()`** - Supprime l'agent.
|
|
343
|
+
|
|
344
|
+
### `lib/llm/providers.js`
|
|
345
|
+
|
|
346
|
+
Ce fichier définit les classes pour interagir avec différents fournisseurs de LLM :
|
|
347
|
+
|
|
348
|
+
- **LLMProvider** : Classe abstraite définissant l'interface commune
|
|
349
|
+
- **OllamaProvider** : Implémentation pour le fournisseur Ollama (local)
|
|
350
|
+
- **OpenAIProvider** : Implémentation pour OpenAI et services compatibles
|
|
351
|
+
|
|
352
|
+
Ces classes encapsulent les détails d'implémentation spécifiques à chaque fournisseur, offrant une interface unifiée pour le reste du module. Elles gèrent les différences dans les formats de requête et de réponse, permettant à l'acteur AiAgent de fonctionner de manière transparente avec différents fournisseurs.
|
|
353
|
+
|
|
354
|
+
### `lib/llm/utils.js`
|
|
355
|
+
|
|
356
|
+
Ce fichier contient diverses fonctions utilitaires pour :
|
|
357
|
+
|
|
358
|
+
- Extraction de texte à partir d'images PDF (`getPDFImages`)
|
|
359
|
+
- Nettoyage et simplification de contenu HTML (`cleanHtmlContent`, `simplifyHtml`)
|
|
360
|
+
- Découpage de texte en segments gérables (`splitLongText`, `html2chunks`)
|
|
361
|
+
- Estimation du nombre de tokens (`estimateTokenCount`)
|
|
362
|
+
- Construction de prompts à partir d'articles (`buildPromptFromArticles`)
|
|
363
|
+
- Traitement par lots des embeddings (`embedChunksInBatch`, `chunkArray`)
|
|
364
|
+
|
|
365
|
+
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.
|
|
366
|
+
|
|
367
|
+
Le module inclut également des fonctionnalités spécifiques pour l'OCR (reconnaissance optique de caractères) avec un prompt système dédié (`OCR_SYSTEM_PROMPT`) qui guide les modèles multimodaux dans l'extraction de texte à partir d'images.
|
|
368
|
+
|
|
369
|
+
_Cette documentation a été mise à jour automatiquement._
|
|
370
|
+
|
|
371
|
+
[xcraft-core-goblin]: https://github.com/Xcraft-Inc/xcraft-core-goblin
|
|
372
|
+
[xcraft-core-stones]: https://github.com/Xcraft-Inc/xcraft-core-stones
|
|
373
|
+
[xcraft-core-utils]: https://github.com/Xcraft-Inc/xcraft-core-utils
|
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
|
};
|