goblin-agents 2.4.0 → 2.4.2

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.
Files changed (3) hide show
  1. package/README.md +220 -206
  2. package/lib/llm/utils.js +8 -2
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # 📘 Documentation du module goblin-agents
1
+ # 📘 goblin-agents
2
2
 
3
3
  ## Aperçu
4
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.
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/OpenRouter. Ce module facilite l'utilisation d'agents IA dans diverses applications en offrant des fonctionnalités avancées : gestion de conversations multi-contextes, analyse de documents PDF par OCR, génération d'embeddings vectoriels pour la recherche sémantique, utilisation d'outils externes, journalisation HTML des échanges, et support du paradigme ReAct pour des agents autonomes.
6
6
 
7
7
  ## Sommaire
8
8
 
@@ -15,28 +15,48 @@ Le module `goblin-agents` fournit une infrastructure complète pour intégrer de
15
15
 
16
16
  ## Structure du module
17
17
 
18
- - **AiAgent** : Acteur principal qui encapsule un agent IA et ses fonctionnalités
19
- - **Providers** : Classes pour interagir avec différents fournisseurs de LLM (Ollama, OpenAI)
20
- - **Utilitaires** : Fonctions pour le traitement de texte, l'OCR, et la manipulation de documents
18
+ Le module est organisé autour du pattern Elf Archetype, avec une séparation claire entre la logique métier et l'interface de l'acteur :
21
19
 
22
- 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`).
20
+ - **`AiAgent`** : Acteur principal persisté (Elf.Archetype) qui encapsule un agent IA et expose ses fonctionnalités sur le bus Xcraft
21
+ - **`AiAgentLogic`** : Logique de mutation d'état de l'acteur (pattern Redux)
22
+ - **`LLMProviders`** : Registre et classes concrètes pour interagir avec différents fournisseurs LLM (Ollama, OpenAI)
23
+ - **`Logger`** : Système de journalisation HTML des échanges IA par session
24
+ - **Utilitaires** : Fonctions pour le traitement de texte, HTML, OCR, découpage de chunks et embeddings
23
25
 
24
26
  ## Fonctionnement global
25
27
 
26
- 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.
28
+ Chaque agent est configuré avec un modèle spécifique, un fournisseur LLM, un prompt système et des paramètres qui définissent son comportement. Les agents peuvent maintenir plusieurs **contextes de conversation** simultanément via un `contextId`, chaque contexte conservant son propre historique de messages.
27
29
 
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.
30
+ Le flux général d'utilisation d'un agent :
29
31
 
30
- Le module prend en charge diverses fonctionnalités avancées :
32
+ ```
33
+ [Création/chargement de l'agent]
34
+ ↓
35
+ [Configuration : provider, model, prompt, tools, options]
36
+ ↓
37
+ [Appel de méthodes : chat, gen, embed, ask, react...]
38
+ ↓
39
+ [Le provider LLM (Ollama ou OpenAI) traite la requête]
40
+ ↓
41
+ [La réponse est stockée dans l'historique du contexte]
42
+ ↓
43
+ [Persistance optionnelle via save()]
44
+ ```
45
+
46
+ Un **mutex par agent** garantit la sérialisation des requêtes simultanées sur le même agent. Chaque échange peut être journalisé dans un fichier HTML de rapport via le `Logger`.
47
+
48
+ Le module supporte plusieurs modes d'interaction :
49
+
50
+ - **Génération simple** (`gen`) : prompt → réponse sans historique
51
+ - **Chat conversationnel** (`chat`) : avec historique de contexte, appels d'outils optionnels
52
+ - **Streaming** (`ask`) : réponse en flux temps réel via événements Xcraft (Ollama uniquement)
53
+ - **Vision/OCR** (`vision`, `readPDFpages`) : analyse d'images et de PDFs
54
+ - **Embeddings** (`embed`, `embedInBatch`) : vectorisation pour la recherche sémantique
55
+ - **ReAct** (`react`) : raisonnement et action autonomes avec orchestration multi-agents
31
56
 
32
- - Génération de texte à partir de prompts
33
- - Conversations interactives avec historique
34
- - Extraction de texte à partir d'images de documents PDF
35
- - Génération d'embeddings vectoriels pour la recherche sémantique
36
- - Utilisation d'outils externes via un système d'appels d'outils
37
- - Paradigme ReAct (Reasoning and Acting) pour des agents plus autonomes
57
+ ### Gestion de version des agents
38
58
 
39
- 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.
59
+ La méthode `beforePersistOnServer` protège contre les régressions de version : si un client tente de persister un agent avec une version différente de celle configurée dans `goblin-agents`, les données du serveur sont restaurées depuis le dernier état persisté (`cryo`).
40
60
 
41
61
  ## Exemples d'utilisation
42
62
 
@@ -49,33 +69,27 @@ const {AiAgent} = require('goblin-agents/lib/llm/aiAgent.js');
49
69
  async initAgents() {
50
70
  const feedId = await this.newQuestFeed();
51
71
 
52
- // Création d'un agent avec Ollama (local)
53
- const ollamaAgentId = 'aiAgent@ollama-mistral-assistant';
54
- const agent = await new AiAgent(this).create(ollamaAgentId, feedId, {
72
+ // Agent local via Ollama
73
+ const agent = await new AiAgent(this).create('aiAgent@mistral-assistant', feedId, {
55
74
  name: 'Assistant Personnel',
56
75
  role: 'assistant',
57
76
  prompt: 'Tu es un assistant personnel serviable et précis.',
58
77
  provider: 'ollama',
59
78
  model: 'mistral-small',
60
79
  host: 'http://127.0.0.1:11434',
61
- options: {
62
- temperature: 0.7
63
- },
64
- usability: 'stable'
80
+ options: {temperature: 0.7},
81
+ usability: 'stable',
65
82
  });
66
83
 
67
- // Création d'un agent avec OpenAI (cloud)
68
- const openAiAgentId = 'aiAgent@gpt-4o-assistant';
69
- const cloudAgent = await new AiAgent(this).create(openAiAgentId, feedId, {
70
- name: 'Assistant Personnel',
84
+ // Agent cloud via OpenRouter
85
+ const cloudAgent = await new AiAgent(this).create('aiAgent@gpt4o-assistant', feedId, {
86
+ name: 'Assistant GPT-4o',
71
87
  role: 'assistant',
72
88
  provider: 'open-ai',
73
89
  model: 'openai/gpt-4o',
74
90
  host: 'https://openrouter.ai/api/v1',
75
- headers: {
76
- Authorization: `Bearer <votre-clef-api>`,
77
- },
78
- usability: 'stable'
91
+ headers: {Authorization: `Bearer <votre-clef-api>`},
92
+ usability: 'stable',
79
93
  });
80
94
  }
81
95
  ```
@@ -83,19 +97,14 @@ async initAgents() {
83
97
  ### Conversation avec un agent
84
98
 
85
99
  ```javascript
86
- // Dans une méthode d'un acteur Elf
87
100
  async askAgent(desktopId, agentId, contextId, question, saveExchange = false) {
88
101
  const feedId = await this.newQuestFeed();
89
-
90
- // Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
91
102
  const agent = await new AiAgent(this).create(agentId, feedId);
92
103
 
93
- // Continue la conversation sur ce contexte par ex. 'story-xyz' et
94
- // obtient une réponse à la question de l'utilisateur
104
+ // Conversation avec historique sur le contexte 'story-xyz'
95
105
  const response = await agent.chat(contextId, question, desktopId);
96
106
 
97
- if(saveExchange) {
98
- //Persiste l'échange contextuel nouvelle paire (question->réponse) sur le disque
107
+ if (saveExchange) {
99
108
  await agent.save();
100
109
  }
101
110
  return response;
@@ -105,258 +114,263 @@ async askAgent(desktopId, agentId, contextId, question, saveExchange = false) {
105
114
  ### Utilisation d'outils externes
106
115
 
107
116
  ```javascript
108
- // Dans une méthode d'un acteur Elf
109
- async addWeatherToolSupportToAgent(agentId) {
117
+ async addWeatherToolToAgent(agentId, contextId, desktopId) {
110
118
  const feedId = await this.newQuestFeed();
111
-
112
- // Instancie l'agent approprié par ex. 'aiAgent@gpt-4o-assistant'
113
119
  const agent = await new AiAgent(this).create(agentId, feedId);
114
120
 
115
- // Configurer un agent avec des outils
116
- // Provoquera des envois de commandes sur le bus à l'acteur "weather"
117
- // en appelant la quête "getWeather(city)"
118
121
  await agent.patch({
119
- toolServiceId: 'weather', //acteur (ici le singleton weather) qui expose des quêtes "outils"
122
+ toolServiceId: 'weather', // acteur singleton qui expose les quêtes "outils"
120
123
  tools: [
121
124
  {
122
125
  type: 'function',
123
126
  function: {
124
- name: 'getWeather', //nom de la quête de l'acteur qui récupère la météo
127
+ name: 'getWeather',
125
128
  description: 'Obtenir la météo pour une ville',
126
129
  parameters: {
127
130
  type: 'object',
128
131
  properties: {
129
- city: {
130
- type: 'string',
131
- description: 'Nom de la ville'
132
- }
132
+ city: {type: 'string', description: 'Nom de la ville'},
133
133
  },
134
- required: ['city']
135
- }
136
- }
137
- }
138
- ]
134
+ required: ['city'],
135
+ },
136
+ },
137
+ },
138
+ ],
139
139
  });
140
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);
141
+ const response = await agent.chat(contextId, "Quel temps fait-il à Paris?", desktopId);
142
+ return response;
144
143
  }
145
144
  ```
146
145
 
147
- ### Génération d'embeddings pour la recherche sémantique
146
+ ### Génération d'embeddings par lots
148
147
 
149
148
  ```javascript
150
- // Dans une méthode d'un acteur Elf
151
- async updateKnowledgeBase(articles) {
149
+ async indexArticles(agentId, articles) {
152
150
  const feedId = await this.newQuestFeed();
153
-
154
- // Instancie l'agent approprié pour faire un embedding
155
- // par ex. 'aiAgent@default-embedder'
156
151
  const agent = await new AiAgent(this).create(agentId, feedId);
157
152
 
158
- // Récupère les textes à indexer
153
+ const articleIds = Object.keys(articles);
159
154
  const texts = Object.values(articles);
160
155
 
161
- // Prépare un boucle d'insertion par id d'article
162
- const articlesIds = Object.keys(articles);
163
-
164
- // Générer des embeddings pour plusieurs textes en une seule requête
165
156
  const vectorsBatch = await agent.embedInBatch(texts);
166
157
 
167
- // Upsert les articles dans un archetype IndexedContent
168
- for(const articleId of articleIds) {
169
- // Récupère les vecteurs correspondant au texte dans le lot
170
- // et le texte original
171
- const index = articleIds.indexOf(articleId);
172
- const vectors = vectorsBatch[index];
173
- const text = texts[index];
174
-
175
- const indexedContent = new IndexedContentState({
176
- id: articleId,
177
- text, //texte original de l'article
178
- meta: {
179
- locale: 'fr', //définit la locale
180
- scope: 'knowledge-base', //définit le scope
181
- vectors, //vecteur correspondants au texte
182
- status: 'published',
183
- }
184
- });
185
-
186
- // Effectue l'upsert de l'archetype
187
- await new IndexedContent(this).insertOrReplace(
188
- articleId,
189
- feedId,
190
- indexedContent
191
- );
158
+ for (let i = 0; i < articleIds.length; i++) {
159
+ const articleId = articleIds[i];
160
+ const vectors = vectorsBatch[i];
161
+ const text = texts[i];
162
+ // Insérer dans une base vectorielle...
192
163
  }
193
164
  }
194
165
  ```
195
166
 
196
- ## Interactions avec d'autres modules
167
+ ### Agent ReAct autonome
197
168
 
198
- Le module `goblin-agents` interagit avec plusieurs autres composants de l'écosystème Xcraft :
169
+ ```javascript
170
+ async runReactAgent(agentId, contextId, question) {
171
+ const feedId = await this.newQuestFeed();
172
+ const agent = await new AiAgent(this).create(agentId, feedId);
173
+
174
+ const reactPrompt = `Tu es un agent autonome. Raisonne étape par étape et agis.`;
175
+ const result = await agent.react(reactPrompt, contextId, question);
176
+ // result : { agentId, action, result, observation }
177
+ return result;
178
+ }
179
+ ```
180
+
181
+ ## Interactions avec d'autres modules
199
182
 
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
183
+ - **[xcraft-core-goblin]** : Fournit l'infrastructure Elf/Archetype pour la création et la persistance des acteurs
184
+ - **[xcraft-core-stones]** : Utilisé pour la définition et la validation des types de données (shapes)
185
+ - **[xcraft-core-utils]** : Utilisé pour les appels API REST (`RestAPI`), la gestion des verrous (`getMutex`, `Semaphore`)
186
+ - **[xcraft-core-etc]** : Utilisé pour charger la configuration du module (version des agents)
204
187
 
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.
188
+ Le module peut interagir avec n'importe quel autre acteur Goblin/Elf via le système d'appels d'outils : l'agent envoie des commandes sur le bus Xcraft vers le `toolServiceId` configuré, permettant aux agents IA d'effectuer des actions concrètes dans l'application.
206
189
 
207
190
  ## Configuration avancée
208
191
 
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 | {} |
192
+ | Option | Description | Type | Valeur par défaut |
193
+ | --------- | ------------------ | ------ | ----------------- |
194
+ | `version` | Version des agents | Number | `2` |
216
195
 
217
196
  ## Détails des sources
218
197
 
219
198
  ### `aiAgent.js`
220
199
 
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()`.
200
+ Point d'entrée du module. Exporte les commandes Xcraft pour l'acteur `AiAgent` via `Elf.birth()`, ce qui enregistre l'acteur sur le bus Xcraft au démarrage du serveur.
222
201
 
223
202
  ### `lib/llm/aiAgent.js`
224
203
 
225
- Ce fichier contient la définition complète de l'acteur AiAgent, avec :
226
-
227
- - **MessageShape** : Définit la structure des messages échangés (rôle, contenu, images, appels d'outils)
228
- - **OptionsShape** : Définit les nombreuses options de configuration des modèles LLM
229
- - **AiAgentShape** : Définit la structure des données de l'agent
230
- - **AiAgentState** : Classe d'état de l'agent basée sur le shape
231
- - **AiAgentLogic** : Logique métier de l'agent (mutations d'état)
232
- - **AiAgent** : Classe principale de l'acteur avec les méthodes d'interaction
204
+ Cœur du module. Contient la définition complète de l'acteur `AiAgent`.
233
205
 
234
206
  #### État et modèle de données
235
207
 
236
- L'état d'un agent IA est défini par la classe `AiAgentShape` qui contient les propriétés suivantes :
237
-
238
- - `id` : Identifiant unique de l'agent
239
- - `version` : Version de l'agent
240
- - `name` : Nom de l'agent
241
- - `role` : Rôle de l'agent (assistant, etc.)
242
- - `prompt` : Prompt par défaut utilisé pour définir le comportement de l'agent
243
- - `provider` : Fournisseur LLM ('ollama' ou 'open-ai')
244
- - `model` : Modèle de langage utilisé
245
- - `host` : URL du serveur LLM
246
- - `headers` : En-têtes HTTP pour les requêtes API
247
- - `messages` : Historique des messages par contexte
248
- - `options` : Options de configuration du modèle (voir OptionsShape)
249
- - `format` : Format de sortie attendu
250
- - `tools` : Outils externes disponibles pour l'agent
251
- - `toolServiceId` : Identifiant du service d'outils
252
- - `usability` : État d'utilisabilité ('disabled', 'experimental', 'stable', 'deprecated')
253
- - `meta` : Métadonnées de l'agent
254
-
255
- 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.
208
+ L'état de l'agent est défini par `AiAgentShape` :
209
+
210
+ | Propriété | Type | Description |
211
+ | --------------- | ---------------------------------------------------------- | ------------------------------------------------------- |
212
+ | `id` | `id('aiAgent')` | Identifiant unique de l'agent |
213
+ | `version` | `number` | Version de l'agent (protection contre les régressions) |
214
+ | `name` | `string` | Nom de l'agent |
215
+ | `role` | `string` | Rôle de l'agent (ex : `'assistant'`) |
216
+ | `prompt` | `string` | Prompt système par défaut |
217
+ | `provider` | `'ollama' \| 'open-ai'` | Fournisseur LLM |
218
+ | `model` | `string` | Modèle LLM principal |
219
+ | `otherModels` | `array(string)` | Modèles alternatifs (rotation possible) |
220
+ | `host` | `string` | URL du serveur LLM (défaut : `http://127.0.0.1:11434`) |
221
+ | `headers` | `record(string, string)` | En-têtes HTTP pour les requêtes API (authentification…) |
222
+ | `messages` | `record(string, array(MessageShape))` | Historique des messages indexé par `contextId` |
223
+ | `options` | `option(OptionsShape)` | Options de configuration du modèle LLM |
224
+ | `format` | `option(object)` | Format de sortie JSON attendu |
225
+ | `tools` | `option(array(object))` | Définitions des outils externes disponibles |
226
+ | `toolServiceId` | `option(string)` | ID de l'acteur exposant les quêtes outils |
227
+ | `usability` | `'disabled' \| 'experimental' \| 'stable' \| 'deprecated'` | État d'utilisabilité de l'agent |
228
+ | `reasoning` | `option(boolean)` | Active le mode raisonnement (think tags) |
229
+ | `vectorQuality` | `option('f32' \| 'int8')` | Qualité des vecteurs pour les embeddings |
230
+ | `meta` | `MetaShape` | Métadonnées (statut : `'published'` ou `'trashed'`) |
231
+
232
+ **`MessageShape`** décrit chaque message de l'historique :
233
+
234
+ - `role` : `'assistant' | 'user' | 'tool'`
235
+ - `content` : contenu textuel du message
236
+ - `images` : liste optionnelle d'images base64
237
+ - `tool_calls` : appels d'outils optionnels
238
+ - `tool_call_id` / `tool_name` : référence à un appel d'outil (messages de type `tool`)
239
+
240
+ **`OptionsShape`** expose tous les paramètres de fine-tuning des modèles Ollama : `temperature`, `num_ctx`, `num_gpu`, `top_k`, `top_p`, `repeat_penalty`, `seed`, `stop`, `mirostat`, etc. Chaque option est facultative (`option(...)`).
256
241
 
257
242
  #### Cycle de vie de l'acteur
258
243
 
259
- L'acteur AiAgent suit le cycle de vie standard des acteurs Elf Archetype :
244
+ `AiAgent` est un **Elf.Archetype** persisté dans la base `agents` avec un index sur `role`.
260
245
 
261
- 1. **Création** : Via la méthode `create()` qui initialise l'agent avec un ID et un état
262
- 2. **Configuration** : Via `patch()` pour modifier les paramètres ou `upgrade()` pour les mises à jour de version
263
- 3. **Utilisation** : Via les méthodes de conversation et de génération
264
- 4. **Persistance** : Via `save()` pour sauvegarder l'état
265
- 5. **Suppression** : Via `trash()` pour marquer comme supprimé ou `delete()` pour suppression définitive
246
+ 1. **Création** : `create(id, desktopId, agentState?)` — initialise l'agent, applique les valeurs par défaut, persiste.
247
+ 2. **Mise à jour** : `patch(agentState)` — met à jour les paramètres et persiste.
248
+ 3. **Migration de version** : `upgrade(version, agentState)` — applique une mise à niveau uniquement si la version est supérieure à la version actuelle.
249
+ 4. **Utilisation** : méthodes de conversation, génération, embeddings.
250
+ 5. **Persistance manuelle** : `save()` — persiste l'état courant.
251
+ 6. **Suppression logique** : `trash()` — passe le statut meta à `'trashed'`.
252
+ 7. **Suppression physique** : `delete()` — méthode présente (vide, gérée par le framework).
266
253
 
267
254
  #### Méthodes publiques
268
255
 
269
- - **`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.
270
- - **`patch(agentState)`** — Met à jour l'état de l'agent avec les propriétés fournies et persiste les changements.
271
- - **`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.
272
- - **`gen(prompt)`** — Génère du texte à partir d'un prompt en utilisant le modèle configuré de l'agent.
273
- - **`reset(contextId, save=false)`** — Réinitialise l'historique des messages pour un contexte spécifique. Si aucun contextId n'est fourni, réinitialise tous les contextes.
274
- - **`readPDFpages(pdfPath)`** — Extrait le texte des pages d'un document PDF en utilisant l'OCR via un modèle multimodal.
275
- - **`embed(rawText)`** — Génère un embedding vectoriel pour un texte donné, retourné sous forme de littéral SQL hexadécimal.
276
- - **`embedInBatch(rawTexts=[])`** — Génère des embeddings vectoriels pour plusieurs textes en une seule requête, optimisé pour les traitements par lots avec gestion des verrous.
277
- - **`set(contextId, messages)`** — Définit directement l'historique des messages pour un contexte spécifique.
278
- - **`chat(contextId, question, userDesktopId, sessionId=null)`** — Engage une conversation avec l'agent dans un contexte spécifique. Gère automatiquement l'historique et les appels d'outils.
279
- - **`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.
280
- - **`resumeExchange(resumePrompt, contextId)`** — Reprend un échange existant avec un nouveau prompt système, en utilisant l'historique du contexte comme base.
281
- - **`react(reactPrompt, contextId, question)`** — Utilise le paradigme ReAct (Reasoning and Acting) pour permettre à l'agent de raisonner et d'agir de manière autonome.
282
- - **`callAgent(contextId, agentId, action, feedId)`** — Appelle un autre agent pour effectuer une action spécifique dans le cadre d'une orchestration multi-agents.
283
- - **`addAssistantMessage(contextId, message)`** — Ajoute un message de l'assistant à l'historique d'un contexte spécifique.
284
- - **`change(path, newValue)`** — Modifie une propriété spécifique de l'état de l'agent avec gestion automatique des types pour certains paramètres.
285
- - **`getUserChatContextHistory(contextId, filterTool=true)`** — Récupère l'historique des messages pour un contexte spécifique avec option de filtrage des messages d'outils.
286
- - **`getBaseSettings()`** — Retourne les paramètres de base de l'agent (provider, host, headers, model).
287
- - **`getUsability()`** — Retourne l'état d'utilisabilité de l'agent.
288
- - **`trash()`** — Marque l'agent comme supprimé en changeant son statut meta à 'trashed'.
289
- - **`save()`** — Persiste l'état actuel de l'agent sur le disque.
256
+ - **`create(id, desktopId, agentState?)`** — Crée et persiste un nouvel agent. `agentState` permet d'initialiser les propriétés dès la création.
257
+ - **`patch(agentState)`** — Met à jour les paramètres de l'agent (provider, model, tools, options, etc.) et persiste.
258
+ - **`upgrade(version, agentState)`** — Migre l'agent vers une nouvelle version si `version > state.version`.
259
+ - **`useOtherModel(model)`** — Permute le modèle actif avec un modèle de `otherModels`. Retourne `false` si le modèle n'est pas dans la liste.
260
+ - **`getModelList()`** — Retourne la liste complète des modèles disponibles (`[model, ...otherModels]`).
261
+ - **`gen(prompt, contextId='default')`** — Génère une réponse à partir d'un prompt sans historique conversationnel. Journalise l'échange.
262
+ - **`reset(contextId, save=false)`** — Réinitialise l'historique d'un contexte. Sans `contextId`, réinitialise tous les contextes.
263
+ - **`readPDFpages(pdfPath, pages)`** — Extrait le texte des pages spécifiées d'un PDF via OCR multimodal. Retourne un tableau de descriptions par page.
264
+ - **`vision(context, image)`** — Analyse une image base64 avec un contexte textuel. Retourne la description générée par le modèle.
265
+ - **`embed(rawText)`** — Génère un embedding vectoriel pour un texte. Retourne une chaîne hex SQL (float32 ou int8 selon `vectorQuality`).
266
+ - **`embedInBatch(rawTexts=[])`** — Génère des embeddings pour plusieurs textes en une requête. Retourne un tableau de chaînes hex SQL.
267
+ - **`set(contextId, messages)`** — Remplace directement l'historique d'un contexte par une liste de messages.
268
+ - **`chat(contextId, question, userDesktopId)`** — Conversation avec historique. Gère automatiquement les appels d'outils si configurés. Retourne la réponse texte ou `true` si un second tour d'outils est nécessaire.
269
+ - **`callTools(contextId, message, userDesktopId)`** — Exécute les appels d'outils demandés par le modèle, envoie les commandes sur le bus Xcraft et enregistre les résultats dans l'historique.
270
+ - **`addToolMessage(contextId, toolId, toolName, answer)`** — Ajoute un message de résultat d'outil dans l'historique d'un contexte.
271
+ - **`ask(contextId, question, questionId)`** — Pose une question avec streaming (Ollama uniquement). Émet des événements `<questionId>` pour chaque fragment de réponse.
272
+ - **`resumeExchange(resumePrompt, contextId)`** — Reprend un échange existant avec un nouveau prompt système pour générer un résumé ou une synthèse de la conversation.
273
+ - **`react(reactPrompt, contextId, question)`** — Paradigme ReAct : le modèle produit une observation, une pensée, une action et un résultat structurés (JSON). Peut déléguer à un autre agent via `callAgent`.
274
+ - **`callAgent(contextId, agentId, action, feedId)`** — Instancie et interroge un autre agent `AiAgent` pour une action dans un workflow multi-agents.
275
+ - **`addAssistantMessage(contextId, message)`** — Ajoute directement un message assistant dans l'historique d'un contexte.
276
+ - **`save()`** — Persiste l'état courant sur le disque.
277
+ - **`change(path, newValue)`** — Modifie une propriété de l'état par chemin (ex : `'options.temperature'`). Convertit automatiquement les types pour `temperature` (float) et `num_ctx` (int).
278
+ - **`getUserChatContextHistory(contextId, filterTool=true)`** — Retourne l'historique utilisateur d'un contexte, en filtrant optionnellement les messages d'outils.
279
+ - **`getToolChatContextHistory(contextId)`** — Retourne uniquement les messages de type `tool` de l'historique d'un contexte.
280
+ - **`getBaseSettings()`** — Retourne les paramètres de base : `{provider, host, headers, model, otherModels}`.
281
+ - **`trash()`** — Marque l'agent comme supprimé (`meta.status = 'trashed'`) et persiste.
282
+ - **`publish()`** — Marque l'agent comme publié (`meta.status = 'published'`) et persiste.
290
283
 
291
284
  ### `lib/llm/providers.js`
292
285
 
293
- Ce fichier définit l'architecture des fournisseurs de LLM avec une classe abstraite `LLMProvider` et ses implémentations concrètes :
286
+ Registre des fournisseurs LLM. La classe `LLMProviders` expose une méthode statique `get(name)` qui retourne la classe du provider correspondant (`OllamaProvider` par défaut, `OpenAIProvider` pour `'open-ai'`).
294
287
 
295
- #### LLMProvider (classe abstraite)
288
+ ### `lib/llm/providers/provider.js`
296
289
 
297
- Définit l'interface commune que tous les fournisseurs doivent implémenter :
290
+ Classe abstraite `LLMProvider` définissant l'interface commune :
298
291
 
299
- - `chat()` : Conversation avec le modèle
300
- - `gen()` : Génération de texte
301
- - `embed()` : Génération d'embeddings
302
- - `embedInBatch()` : Génération d'embeddings par lots
292
+ - `chat(chatRequest)` : conversation avec le modèle
293
+ - `gen(genRequest)` : génération de texte
294
+ - `embed(embedRequest)` : embedding d'un texte
295
+ - `embedInBatch(embedRequest)` : embedding par lots
303
296
 
304
- #### OllamaProvider
297
+ Ne peut pas être instanciée directement.
305
298
 
306
- Implémentation pour le fournisseur Ollama (serveur local) :
299
+ ### `lib/llm/providers/ollama.js`
300
+
301
+ Implémentation concrète pour **Ollama** (serveur local) :
307
302
 
308
303
  - Utilise la bibliothèque `ollama` pour communiquer avec le serveur
309
- - Gère le streaming pour les conversations en temps réel
310
- - Implémente un sémaphore pour limiter les requêtes d'embedding simultanées (max 4)
311
- - Supporte tous les formats de sortie JSON structurés
304
+ - Implémente un **sémaphore** (`Semaphore(4)`) pour limiter à 4 requêtes d'embedding simultanées
305
+ - Supporte le mode `stream` et le mode raisonnement (`think: reasoning`)
306
+ - Parse automatiquement les réponses JSON structurées (`format`)
307
+
308
+ ### `lib/llm/providers/opanAi.js`
309
+
310
+ Implémentation concrète pour **OpenAI** et services compatibles (OpenRouter, etc.) :
311
+
312
+ - Utilise `RestAPI` de [xcraft-core-utils] pour les appels HTTP avec retry sur POST
313
+ - Adapte le format des requêtes : les `options` Ollama sont mergées à la racine de la requête
314
+ - Gère les schémas JSON structurés via `response_format` avec `json_schema`
315
+ - La méthode `gen` est implémentée via `chat` (conversion prompt → message user)
316
+ - Le mode `reasoning` n'est pas encore supporté (supprimé de la requête)
312
317
 
313
- #### OpenAIProvider
318
+ ### `lib/logger.js`
314
319
 
315
- Implémentation pour OpenAI et services compatibles (OpenRouter, etc.) :
320
+ Système de journalisation HTML des échanges IA. Chaque session de logging est identifiée par un `id` (typiquement le `contextId`).
316
321
 
317
- - Utilise `RestAPI` de xcraft-core-utils pour les appels HTTP
318
- - Adapte les formats de requête entre Ollama et OpenAI
319
- - Gère les schémas JSON pour les réponses structurées
320
- - Supporte les en-têtes d'authentification personnalisés
322
+ **Fonctionnement :**
323
+
324
+ - `Logger.get(id, quest)` : récupère ou crée un logger pour l'id donné. Enregistre automatiquement un `defer` sur la quête pour appeler `dispose()` à la fin.
325
+ - `push(step, payload)` : enregistre un message horodaté avec une étape nommée et un payload clé/valeur.
326
+ - `dispose()` : génère un rapport HTML dans `<xcraftRoot>/var/log/ai/` (fichier nommé `<id>_<timestamp>Z.html`) et nettoie le logger du registre. Le logger `'default'` ne génère pas de rapport.
327
+
328
+ Les rapports HTML sont construits à partir de templates dans `lib/report/` et peuvent servir à l'audit ou au débogage des conversations IA.
321
329
 
322
330
  ### `lib/llm/utils.js`
323
331
 
324
- Ce fichier contient un ensemble complet d'utilitaires pour le traitement de texte et de documents :
332
+ Ensemble complet d'utilitaires pour le traitement de texte, HTML et documents.
333
+
334
+ **Traitement de documents PDF :**
335
+
336
+ - **`getPDFImages(pdfPath, pages)`** — Convertit les pages spécifiées d'un PDF en images base64 (PNG). Nécessite `pdf-to-png-converter` comme dépendance explicite du projet consommateur.
337
+ - **`OCR_SYSTEM_PROMPT`** — Prompt système spécialisé pour guider les modèles multimodaux dans l'extraction fidèle de texte depuis des images.
325
338
 
326
- #### Traitement de documents PDF
339
+ **Traitement HTML :**
327
340
 
328
- - **`getPDFImages(pdfPath)`** — Convertit les pages d'un PDF en images base64 pour l'OCR avec des options optimisées pour la qualité du texte.
329
- - **`OCR_SYSTEM_PROMPT`** — Prompt système spécialisé pour guider les modèles multimodaux dans l'extraction de texte à partir d'images.
341
+ - **`cleanHtmlContent(html)`** — Supprime toutes les balises HTML et normalise les espaces blancs.
342
+ - **`simplifyHtml(html)`** — Nettoie le HTML en ne gardant que les éléments sûrs via `sanitize-html`.
343
+ - **`extractTitlesAndContent(html)`** — Extrait titres (`h1`–`h6`) et paragraphes en préservant la structure, avec découpage des segments longs.
344
+ - **`html2chunks(html, chunkSize=300, chunkOverlap=0, minChunkLength=10)`** — Découpe un document HTML en chunks via `RecursiveCharacterTextSplitter` de LangChain.
330
345
 
331
- #### Traitement de texte et HTML
346
+ **Gestion des tokens et découpage :**
332
347
 
333
- - **`cleanHtmlContent(html)`** — Nettoie le contenu HTML en supprimant les balises et en normalisant les espaces.
334
- - **`simplifyHtml(html)`** — Simplifie le HTML en gardant uniquement les éléments sûrs via sanitize-html.
335
- - **`extractTitlesAndContent(html)`** — Extrait intelligemment les titres et le contenu d'un document HTML en préservant la structure.
336
- - **`html2chunks(html, chunkSize=300, chunkOverlap=0)`** — Découpe un document HTML en chunks en utilisant RecursiveCharacterTextSplitter de LangChain.
348
+ - **`estimateTokenCount(text)`** — Estime le nombre de tokens (mots × 1.33).
349
+ - **`splitLongText(text, maxLength, baseId)`** — Découpe un texte en segments mot-à-mot avec IDs incrémentaux.
350
+ - **`sliceText(text, chunkSize)`** — Découpe par phrases en respectant `chunkSize`.
351
+ - **`groupSentences(text, maxSize)`** — Groupe des phrases en blocs de taille optimale, avec fallback mot-à-mot pour les phrases très longues.
337
352
 
338
- #### Gestion des tokens et découpage
353
+ **Traitement par lots et embeddings :**
339
354
 
340
- - **`estimateTokenCount(text)`** — Estime le nombre de tokens d'un texte (approximation basée sur les mots × 1.33).
341
- - **`splitLongText(text, maxLength, baseId)`** — Découpe un texte long en segments de taille maximale en préservant les mots entiers.
342
- - **`sliceText(text, chunkSize)`** — Découpe un texte en chunks en respectant les limites de phrases.
343
- - **`groupSentences(text, maxSize)`** — Groupe les phrases en chunks de taille optimale pour les modèles de langage.
355
+ - **`chunkArray(array, size)`** — Divise un tableau en sous-tableaux de taille fixe.
356
+ - **`embedChunksInBatch(agent, allChunks, batchSize=50, prefix='passage: ')`** — Traite les embeddings par lots et retourne une map `{ [contentId]: { [key]: { chunk, embedding } } }`.
357
+ - **`buildPromptFromArticlesMulti(articles, format='text', maxTotalTokens, dbg)`** — Construit un prompt multi-articles optimisé en respectant une limite de tokens (avec buffer de 16 000 tokens pour l'instruction et la réponse).
344
358
 
345
- #### Traitement par lots et optimisation
359
+ **Utilitaires divers :**
346
360
 
347
- - **`chunkArray(array, size)`** — Divise un tableau en sous-tableaux de taille spécifiée pour le traitement par lots.
348
- - **`embedChunksInBatch(agent, allChunks, batchSize=50)`** — Traite les embeddings par lots en associant chaque embedding à son contenu d'origine.
349
- - **`buildPromptFromArticlesMulti(initialQuestion, questionsWithArticles, maxTotalTokens)`** — Construit des prompts optimisés à partir de multiples articles en respectant les limites de tokens.
361
+ - **`splitThinkTags(input)`** — Extrait et sépare le contenu des balises `<think>...</think>` (raisonnement interne) du texte propre. Retourne `{ thinks, cleanText }`.
362
+ - **`vectorToSqlLiteral(vec)`** — Convertit un vecteur Float32Array en chaîne hex SQL (float32).
363
+ - **`vectorToSqlLiteralInt8(vec)`** — Convertit un vecteur normalisé [-1,1] en int8 quantifié, puis en chaîne hex SQL (plus compact, moins précis).
350
364
 
351
- #### Utilitaires de conversion
365
+ ## Licence
352
366
 
353
- - **`vectorToSqlLiteral(vec)`** — Convertit un vecteur Float32Array en littéral SQL hexadécimal pour le stockage en base de données.
367
+ Ce module est distribué sous [licence MIT](./LICENSE).
354
368
 
355
- 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.
369
+ ---
356
370
 
357
- _Cette documentation a été mise à jour automatiquement._
371
+ _Ce contenu a été généré par IA_
358
372
 
359
373
  [xcraft-core-goblin]: https://github.com/Xcraft-Inc/xcraft-core-goblin
360
374
  [xcraft-core-stones]: https://github.com/Xcraft-Inc/xcraft-core-stones
361
375
  [xcraft-core-utils]: https://github.com/Xcraft-Inc/xcraft-core-utils
362
- [xcraft-core-etc]: https://github.com/Xcraft-Inc/xcraft-core-etc
376
+ [xcraft-core-etc]: https://github.com/Xcraft-Inc/xcraft-core-etc
package/lib/llm/utils.js CHANGED
@@ -90,13 +90,19 @@ const splitLongText = (text, maxLength, baseId) => {
90
90
  return segments;
91
91
  };
92
92
 
93
- const html2chunks = async (html, chunkSize = 300, chunkOverlap = 50) => {
93
+ const html2chunks = async (
94
+ html,
95
+ chunkSize = 300,
96
+ chunkOverlap = 0,
97
+ minChunkLength = 10
98
+ ) => {
94
99
  const textSplitter = RecursiveCharacterTextSplitter.fromLanguage('html', {
95
100
  chunkSize,
96
101
  chunkOverlap,
97
102
  separators: RecursiveCharacterTextSplitter.getSeparatorsForLanguage('html'),
98
103
  });
99
- return await textSplitter.splitText(html);
104
+ const chunks = await textSplitter.splitText(html);
105
+ return chunks.filter((chunk) => chunk.length >= minChunkLength);
100
106
  };
101
107
 
102
108
  const extractTitlesAndContent = (html) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goblin-agents",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "AI Agents framework",
5
5
  "author": "Epsitec SA",
6
6
  "contributors": [
@@ -18,8 +18,8 @@
18
18
  "test": "echo \"Error: no test specified\" && exit 1"
19
19
  },
20
20
  "dependencies": {
21
- "@langchain/core": "^0.3.42",
22
- "@langchain/textsplitters": "^0.1.0",
21
+ "@langchain/core": "^1.1.28",
22
+ "@langchain/textsplitters": "^1.0.1",
23
23
  "fs-extra": "^11.3.0",
24
24
  "html-entities": "^2.4.0",
25
25
  "ollama": "^0.5.10",