goblin-agents 2.3.1 → 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.
- package/README.md +220 -206
- package/lib/llm/aiAgent.js +23 -2
- package/lib/llm/utils.js +8 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# 📘
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
Le flux général d'utilisation d'un agent :
|
|
29
31
|
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
53
|
-
const
|
|
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
|
-
|
|
63
|
-
},
|
|
64
|
-
usability: 'stable'
|
|
80
|
+
options: {temperature: 0.7},
|
|
81
|
+
usability: 'stable',
|
|
65
82
|
});
|
|
66
83
|
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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',
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
146
|
+
### Génération d'embeddings par lots
|
|
148
147
|
|
|
149
148
|
```javascript
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
167
|
+
### Agent ReAct autonome
|
|
197
168
|
|
|
198
|
-
|
|
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
|
|
201
|
-
- **[xcraft-core-stones]** : Utilisé pour la définition des types de données
|
|
202
|
-
- **[xcraft-core-utils]** : Utilisé pour les appels API REST
|
|
203
|
-
- **[xcraft-core-etc]** : Utilisé pour
|
|
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
|
|
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
|
|
210
|
-
|
|
|
211
|
-
| version
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
244
|
+
`AiAgent` est un **Elf.Archetype** persisté dans la base `agents` avec un index sur `role`.
|
|
260
245
|
|
|
261
|
-
1. **Création** :
|
|
262
|
-
2. **
|
|
263
|
-
3. **
|
|
264
|
-
4. **
|
|
265
|
-
5. **
|
|
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
|
|
270
|
-
- **`patch(agentState)`** — Met à jour
|
|
271
|
-
- **`upgrade(version, agentState)`** —
|
|
272
|
-
- **`
|
|
273
|
-
- **`
|
|
274
|
-
- **`
|
|
275
|
-
- **`
|
|
276
|
-
- **`
|
|
277
|
-
- **`
|
|
278
|
-
- **`
|
|
279
|
-
- **`
|
|
280
|
-
- **`
|
|
281
|
-
- **`
|
|
282
|
-
- **`
|
|
283
|
-
- **`
|
|
284
|
-
- **`
|
|
285
|
-
- **`
|
|
286
|
-
- **`
|
|
287
|
-
- **`
|
|
288
|
-
- **`
|
|
289
|
-
- **`save()`** — Persiste l'état
|
|
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
|
-
|
|
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
|
-
|
|
288
|
+
### `lib/llm/providers/provider.js`
|
|
296
289
|
|
|
297
|
-
|
|
290
|
+
Classe abstraite `LLMProvider` définissant l'interface commune :
|
|
298
291
|
|
|
299
|
-
- `chat()` :
|
|
300
|
-
- `gen()` :
|
|
301
|
-
- `embed()` :
|
|
302
|
-
- `embedInBatch()` :
|
|
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
|
-
|
|
297
|
+
Ne peut pas être instanciée directement.
|
|
305
298
|
|
|
306
|
-
|
|
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
|
-
-
|
|
310
|
-
-
|
|
311
|
-
-
|
|
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
|
-
|
|
318
|
+
### `lib/logger.js`
|
|
314
319
|
|
|
315
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
-
|
|
320
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
339
|
+
**Traitement HTML :**
|
|
327
340
|
|
|
328
|
-
- **`
|
|
329
|
-
- **`
|
|
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
|
-
|
|
346
|
+
**Gestion des tokens et découpage :**
|
|
332
347
|
|
|
333
|
-
- **`
|
|
334
|
-
- **`
|
|
335
|
-
- **`
|
|
336
|
-
- **`
|
|
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
|
-
|
|
353
|
+
**Traitement par lots et embeddings :**
|
|
339
354
|
|
|
340
|
-
- **`
|
|
341
|
-
- **`
|
|
342
|
-
- **`
|
|
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
|
-
|
|
359
|
+
**Utilitaires divers :**
|
|
346
360
|
|
|
347
|
-
- **`
|
|
348
|
-
- **`
|
|
349
|
-
- **`
|
|
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
|
-
|
|
365
|
+
## Licence
|
|
352
366
|
|
|
353
|
-
|
|
367
|
+
Ce module est distribué sous [licence MIT](./LICENSE).
|
|
354
368
|
|
|
355
|
-
|
|
369
|
+
---
|
|
356
370
|
|
|
357
|
-
|
|
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/aiAgent.js
CHANGED
|
@@ -146,6 +146,7 @@ class AiAgentShape {
|
|
|
146
146
|
prompt = string; //default prompt
|
|
147
147
|
provider = enumeration('ollama', 'open-ai');
|
|
148
148
|
model = string;
|
|
149
|
+
otherModels = array(string);
|
|
149
150
|
host = string;
|
|
150
151
|
headers = record(string, string);
|
|
151
152
|
messages = record(string, array(MessageShape)); //for keeping chat history by context
|
|
@@ -167,6 +168,7 @@ const defaultValues = {
|
|
|
167
168
|
role: 'assistant',
|
|
168
169
|
provider: 'open-ai',
|
|
169
170
|
model: '',
|
|
171
|
+
otherModels: [],
|
|
170
172
|
options: {},
|
|
171
173
|
host: 'http://127.0.0.1:11434',
|
|
172
174
|
headers: {},
|
|
@@ -190,6 +192,7 @@ class AiAgentLogic extends Elf.Archetype {
|
|
|
190
192
|
prompt: defaultValues.prompt,
|
|
191
193
|
provider: defaultValues.provider,
|
|
192
194
|
model: defaultValues.model,
|
|
195
|
+
otherModels: defaultValues.otherModels,
|
|
193
196
|
messages: {},
|
|
194
197
|
options: defaultValues.options,
|
|
195
198
|
host: defaultValues.host,
|
|
@@ -288,6 +291,12 @@ class AiAgentLogic extends Elf.Archetype {
|
|
|
288
291
|
state._state.set(path, newValue);
|
|
289
292
|
}
|
|
290
293
|
|
|
294
|
+
useOtherModel(model) {
|
|
295
|
+
this.state.otherModels.deleteByValue(model);
|
|
296
|
+
this.state.otherModels.push(this.state.model);
|
|
297
|
+
this.state.model = model;
|
|
298
|
+
}
|
|
299
|
+
|
|
291
300
|
publish() {
|
|
292
301
|
const {state} = this;
|
|
293
302
|
state.meta.status = 'published';
|
|
@@ -378,6 +387,18 @@ class AiAgent extends Elf {
|
|
|
378
387
|
await this.persist();
|
|
379
388
|
}
|
|
380
389
|
|
|
390
|
+
async useOtherModel(model) {
|
|
391
|
+
if (!this.state.otherModels.includes(model)) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
this.logic.useOtherModel(model);
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async getModelList() {
|
|
399
|
+
return [this.state.model, ...this.state.otherModels];
|
|
400
|
+
}
|
|
401
|
+
|
|
381
402
|
async gen(prompt, contextId = 'default') {
|
|
382
403
|
const logger = Logger.get(contextId, this);
|
|
383
404
|
|
|
@@ -869,8 +890,8 @@ class AiAgent extends Elf {
|
|
|
869
890
|
}
|
|
870
891
|
|
|
871
892
|
async getBaseSettings() {
|
|
872
|
-
const {provider, host, headers, model} = this.state.toJS();
|
|
873
|
-
return {provider, host, headers, model};
|
|
893
|
+
const {provider, host, headers, model, otherModels} = this.state.toJS();
|
|
894
|
+
return {provider, host, headers, model, otherModels};
|
|
874
895
|
}
|
|
875
896
|
|
|
876
897
|
async trash() {
|
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 (
|
|
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
|
-
|
|
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.
|
|
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": "^
|
|
22
|
-
"@langchain/textsplitters": "^0.1
|
|
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",
|