goblin-agents 0.1.5 → 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 CHANGED
@@ -19,6 +19,7 @@ Le module `goblin-agents` permet de créer et gérer des agents IA qui peuvent i
19
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
20
 
21
21
  Le module prend en charge diverses fonctionnalités avancées :
22
+
22
23
  - Génération de texte à partir de prompts
23
24
  - Conversations interactives avec historique
24
25
  - Extraction de texte à partir d'images de documents PDF
@@ -36,7 +37,7 @@ Les agents peuvent être configurés avec une grande variété d'options qui inf
36
37
  const {AiAgent} = require('goblin-agents/lib/llm/aiAgent.js');
37
38
 
38
39
  // Dans une méthode d'un acteur Elf
39
- async initAgents() {
40
+ async initAgents() {
40
41
  const feedId = await this.newQuestFeed();
41
42
 
42
43
  // Création d'un agent avec Ollama (local)
@@ -72,15 +73,14 @@ async initAgents() {
72
73
  ### Conversation avec un agent
73
74
 
74
75
  ```javascript
75
-
76
76
  // Dans une méthode d'un acteur Elf
77
77
  async askAgent(desktopId, agentId, contextId, question, saveExchange = false) {
78
78
  const feedId = await this.newQuestFeed();
79
-
79
+
80
80
  // Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
81
81
  const agent = await new AiAgent(this).create(agentId, feedId);
82
-
83
- // Continue la conversation sur ce contexte par ex. 'story-xyz' et
82
+
83
+ // Continue la conversation sur ce contexte par ex. 'story-xyz' et
84
84
  // obtient une réponse à la question de l'utilisateur
85
85
  const response = await agent.chat(contextId, question, desktopId);
86
86
 
@@ -95,11 +95,10 @@ async askAgent(desktopId, agentId, contextId, question, saveExchange = false) {
95
95
  ### Résumer et "squasher" un contexte
96
96
 
97
97
  ```javascript
98
-
99
98
  // Dans une méthode d'un acteur Elf
100
99
  async resumeAndSquashContexte(agentId, contextId) {
101
100
  const feedId = await this.newQuestFeed();
102
-
101
+
103
102
  // Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
104
103
  const agent = await new AiAgent(this).create(agentId, feedId);
105
104
 
@@ -110,7 +109,7 @@ async resumeAndSquashContexte(agentId, contextId) {
110
109
  // Reset la mémoire contextuel et persiste le changement
111
110
  await agent.reset(contextId, true);
112
111
 
113
- // Ajoute un nouvel historique d'échange a partir du résumé
112
+ // Ajoute un nouvel historique d'échange a partir du résumé
114
113
  await agent.set(contextId, [{role: 'assistant', content: resumedContent}]);
115
114
 
116
115
  // Sauve ce nouvel état
@@ -121,11 +120,10 @@ async resumeAndSquashContexte(agentId, contextId) {
121
120
  ### Utilisation d'outils externes
122
121
 
123
122
  ```javascript
124
-
125
123
  // Dans une méthode d'un acteur Elf
126
124
  async addWeatherToolSupportToAgent(agentId) {
127
125
  const feedId = await this.newQuestFeed();
128
-
126
+
129
127
  // Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
130
128
  const agent = await new AiAgent(this).create(agentId, feedId);
131
129
 
@@ -160,13 +158,11 @@ async addWeatherToolSupportToAgent(agentId) {
160
158
  // L'agent pourra maintenant utiliser l'outil weather.getWeather
161
159
  const question = "Quel temps fait-il à Paris aujourd'hui?";
162
160
  const response = await agent.chat(contextId, question, desktopId);
163
-
164
161
  ```
165
162
 
166
163
  ### Génération d'embeddings pour la recherche sémantique
167
164
 
168
165
  ```javascript
169
-
170
166
  // Dans une méthode d'un acteur Elf
171
167
  // {
172
168
  // 'indexContent@article-1', 'Contenu à indexer'
@@ -189,9 +185,9 @@ async updateKnowledgeBase(articles) {
189
185
  // Générer des embeddings pour plusieurs textes en une seule requête
190
186
  const vectorsBatch = await agent.embedInBatch(texts);
191
187
 
192
- // Upsert les articles dans un archetype IndexedContent
188
+ // Upsert les articles dans un archetype IndexedContent
193
189
  for(const articleId of articleIds) {
194
- // Récupère les vecteurs correspondant au texte dans le lot
190
+ // Récupère les vecteurs correspondant au texte dans le lot
195
191
  // et le texte original
196
192
  const index = articleIds.indexOf(articleId);
197
193
  const vectors = vectorsBatch[index];
@@ -252,19 +248,21 @@ async queryKnowledgeBase(question = "Comment fonctionne l'apprentissage par renf
252
248
 
253
249
  Le module `goblin-agents` interagit avec plusieurs autres composants de l'écosystème Xcraft :
254
250
 
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
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
258
254
 
259
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.
260
256
 
261
257
  ## Configuration avancée
262
258
 
263
- - **version** : Version des agents (par défaut: 2)
264
- - **defaultProfile** : Profil par défaut des agents (par défaut: null)
265
- - **defaultSettings** : Paramètres par défaut (par défaut: null)
266
- - **profiles** : Profils pour remplacer les paramètres (par défaut: {})
267
- - **settings** : Paramètres par nom (par défaut: {})
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 | {} |
268
266
 
269
267
  ## Détails des sources
270
268
 
@@ -276,6 +274,7 @@ Ce fichier est le point d'entrée du module, exportant les commandes Xcraft pour
276
274
 
277
275
  Ce fichier définit la configuration du module, permettant de spécifier :
278
276
 
277
+ - La version des agents (`version`)
279
278
  - Le profil par défaut des agents (`defaultProfile`)
280
279
  - Les paramètres par défaut (`defaultSettings`)
281
280
  - Les profils pour remplacer les paramètres (`profiles`)
@@ -294,14 +293,53 @@ Ce fichier contient la définition complète de l'acteur AiAgent, avec :
294
293
  - **AiAgentLogic** : Logique métier de l'agent (mutations d'état)
295
294
  - **AiAgent** : Classe principale de l'acteur avec les méthodes d'interaction
296
295
 
297
- L'acteur AiAgent offre de nombreuses méthodes pour interagir avec les modèles de langage, gérer les conversations, et utiliser des outils externes. Il prend en charge différentes configurations et options pour personnaliser le comportement des modèles.
298
-
299
- Les options de configuration sont particulièrement riches et permettent d'ajuster finement le comportement des modèles, notamment :
300
- - Gestion de la mémoire (NUMA, VRAM)
301
- - Taille du contexte et traitement par lots
302
- - Paramètres de génération (température, top_k, top_p)
303
- - Pénalités pour éviter les répétitions
304
- - Algorithmes adaptatifs comme Mirostat
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.
305
343
 
306
344
  ### `lib/llm/providers.js`
307
345
 
@@ -328,4 +366,8 @@ Ces utilitaires sont essentiels pour préparer les données avant de les envoyer
328
366
 
329
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.
330
368
 
331
- *Ce document est une mise à jour de la documentation précédente.*
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
@@ -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(chat.error.message);
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 = 1000, chunkOverlap = 0) => {
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
- const buildPromptFromArticles = (articles, question, maxTokens) => {
227
- let context = '[CONTEXT]\n\n';
228
- let currentTokenCount = estimateTokenCount(context);
229
-
230
- articles.forEach((article) => {
231
- const {kind, title, link, locale} = article;
232
- const articleContext = `# ${kind}: ${title} #\nLien: ${link}\n Langue: ${locale}\n`;
233
-
234
- context += articleContext;
235
-
236
- if (article.segments && article.segments.length > 0) {
237
- context += '## Segments pertinents: ##\n';
238
- article.segments
239
- .sort((a, b) => b.score - a.score) // Trier par pertinence
240
- .forEach((s, i) => {
241
- let segmentContext = ` ${i + 1}. ${s.segment} (Pertinence: ${
242
- s.score
243
- })\n`;
244
- currentTokenCount += estimateTokenCount(segmentContext);
245
-
246
- if (currentTokenCount > maxTokens) {
247
- return;
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
- if (currentTokenCount > maxTokens) {
266
- return;
267
- }
251
+ section += articleBlock;
252
+ sectionTokenCount += tokensForArticle;
253
+ }
268
254
 
269
- context += additionalContext;
270
- });
255
+ if (currentTokenCount + sectionTokenCount > maxTotalTokens - buffer) {
256
+ break;
271
257
  }
272
- });
273
258
 
274
- const questionContext = '[QUESTION]\n\n' + question;
275
- context += questionContext;
276
- return context.trim();
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 = 500) {
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
- buildPromptFromArticles,
331
+ buildPromptFromArticlesMulti,
344
332
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goblin-agents",
3
- "version": "0.1.5",
3
+ "version": "0.1.10",
4
4
  "description": "AI Agents framework",
5
5
  "author": "Samuel Loup",
6
6
  "license": "MIT",