data-primals-engine 1.0.4 → 1.0.6
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 +13 -9
- package/package.json +12 -13
- package/server.js +2 -28
- package/src/data.js +9 -2
- package/src/email.js +1 -1
- package/src/engine.js +7 -7
- package/src/i18n.js +603 -16631
- package/src/index.js +1 -1
- package/src/modules/assistant.js +52 -19
- package/src/modules/bucket.js +3 -3
- package/src/modules/data.js +44 -213
- package/src/modules/file.js +1 -1
- package/src/modules/user.js +2 -2
- package/src/modules/workflow.js +1 -1
- package/src/tutorials.js +0 -112
package/src/index.js
CHANGED
package/src/modules/assistant.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
|
7
7
|
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
8
8
|
import {searchData, patchData, deleteData, insertData} from "./data.js";
|
|
9
9
|
import { getDataAsString } from "../data.js";
|
|
10
|
-
import i18n from "
|
|
10
|
+
import i18n from "data-primals-engine/i18n";
|
|
11
11
|
import {generateLimiter} from "./user.js";
|
|
12
12
|
import rateLimit from "express-rate-limit";
|
|
13
13
|
|
|
@@ -20,11 +20,17 @@ export const assistantGlobalLimiter = rateLimit({
|
|
|
20
20
|
legacyHeaders: false, // Désactive les anciens en-têtes `X-RateLimit-*`
|
|
21
21
|
message: { success: false, message: "Trop de requêtes globales envoyées à l'assistant. Veuillez réessayer plus tard." },
|
|
22
22
|
skip: (req) => {
|
|
23
|
-
return !!req.
|
|
23
|
+
return !!req.fields?.confirmedAction;
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
const createSystemPrompt = (modelDefs
|
|
27
|
+
const createSystemPrompt = (modelDefs) => {
|
|
28
|
+
const cond1 = JSON.stringify({ "model": "event", "sort": "startDate:ASC", "limit": 0, "filter": { "$and": [{ "$nin": ["$tags", "festival"] }, { "$lt": ["$endDate", "$$NOW"] }] }}, null, 2);
|
|
29
|
+
const cond2 = JSON.stringify({ "model": "contact", "sort": "legalName:ASC", "limit": 5,"filter": {"$ne": [{ "$type": "$legalName"}, "missing"] }}, null, 2);
|
|
30
|
+
const cond3 = JSON.stringify({ "model": "order", "sort": "_id:DESC", "limit": 10,"filter": {"lang": { "$find": [{ "$eq": ["$$this.code", "fr"] }] } }}, null, 2);
|
|
31
|
+
const cond4 = JSON.stringify({ "model": "order", "sort": "updatedAt:DESC", "limit": 0,"filter": {"user": { "$find": { "roles": { "$find": [{ "$in": ["$$this.name", ["admin", "moderator"]] }] } }}}}, null, 2);
|
|
32
|
+
|
|
33
|
+
return `
|
|
28
34
|
Tu es "Prior", un assistant expert en analyse de données pour la plateforme data.primals.net.
|
|
29
35
|
Ta mission est d'aider l'utilisateur en répondant à ses questions sur ses données.
|
|
30
36
|
|
|
@@ -55,8 +61,11 @@ Tu as accès aux outils suivants. Tu ne dois utiliser QUE ces outils.
|
|
|
55
61
|
5. **displayMessage**: Pour répondre directement à l'utilisateur. N'utilise cette action QUE lorsque tu as toutes les informations nécessaires pour formuler une réponse finale.
|
|
56
62
|
- Utilisation: \`{ "action": "displayMessage", "params": { "message": "Ta réponse textuelle." } }\`
|
|
57
63
|
|
|
58
|
-
La spécification
|
|
59
|
-
utilise une chaine de
|
|
64
|
+
La spécification, pour t'aider à construire les filtres, est la suivante :
|
|
65
|
+
utilise une chaine de caractère convertible en ObjectId (mongodb) lorsque le nom du champ est _id
|
|
66
|
+
utilise une chaine de caracteres lorsque le type de champ est : string, string_t , password, url, phone, email, richtext
|
|
67
|
+
utilise un $FILTER en retour si le type de champ est code et language='json' et conditionBuilder=true
|
|
68
|
+
utilise une chaine si c'est un type de champ code par défaut.
|
|
60
69
|
utilise une structure { "iso2langcode":"content..." } pour le champ multi-traductions richtext_t
|
|
61
70
|
utilise un booléen pour : boolean
|
|
62
71
|
utilise un nombre pour number
|
|
@@ -73,12 +82,26 @@ PROCESSUS DE RAISONNEMENT:
|
|
|
73
82
|
2. Tu analyses la question et choisis l'outil approprié.
|
|
74
83
|
3. Tu réponds IMMÉDIATEMENT avec l'objet JSON correspondant SANS COMMENTAIRE.
|
|
75
84
|
|
|
85
|
+
Chacune de tes réponses doit être une étape en soi, ou au plus près d'une étape.
|
|
86
|
+
|
|
76
87
|
CONTEXTE ACTUEL:
|
|
77
88
|
- L'utilisateur a accès aux modèles de données suivants et ne peut utiliser les filtres que sur les champs associés:
|
|
78
89
|
${modelDefs.map(m => ` - Modèle "${m.name}":\n Champs: ${JSON.stringify(m.fields.map(f => ({ name: f.name, type: f.type, hint: f.hint })), null, 2)}`).join('\n')}
|
|
79
|
-
- Le format pour les filtres ($FILTER) est : ${conditionBuilderExample}
|
|
80
90
|
- Le format de $DATA est { modelFieldName: "value", otherModelFieldName: { subObj : true } }
|
|
81
|
-
|
|
91
|
+
- Exemples de filtres $FILTER utilisables (ils sont compatibles MongoDB) :
|
|
92
|
+
Filtre utilisable : { $eq: ["$status", 500 ] }
|
|
93
|
+
Filtre incorrect : { "status": 500 }
|
|
94
|
+
==
|
|
95
|
+
|
|
96
|
+
Exemples de "params" :
|
|
97
|
+
Je voudrais les événements non terminés, qui ne sont pas des festivals ou des salons :
|
|
98
|
+
\`${cond1}\`
|
|
99
|
+
Donne moi les 5 nouvelles entreprises
|
|
100
|
+
\`${cond2}\`
|
|
101
|
+
Je veux les 10 dernières traductions ajoutées dans la langue française.
|
|
102
|
+
\`${cond3}\`
|
|
103
|
+
Je veux les commandes qui ont été faites par un admin ou un modérateur
|
|
104
|
+
\`${cond4}\`
|
|
82
105
|
|
|
83
106
|
Règles ABSOLUES:
|
|
84
107
|
- UNE SEULE COMMANDE JSON PAR RÉPONSE
|
|
@@ -97,13 +120,19 @@ json
|
|
|
97
120
|
\`\`\`
|
|
98
121
|
|
|
99
122
|
Exemple de réponse INCORRECTE (INTERDIT) :
|
|
100
|
-
"Je vais d'abord rechercher ces requêtes. Voici la commande que je vais exécuter..."
|
|
123
|
+
"Je vais d'abord rechercher ces requêtes. Voici la commande que je vais exécuter..."
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
101
126
|
|
|
102
127
|
|
|
103
128
|
/**
|
|
104
129
|
* Gère la requête de chat, soit en exécutant une action confirmée,
|
|
105
130
|
* soit en lançant la boucle de raisonnement de l'IA.
|
|
106
131
|
*/
|
|
132
|
+
/**
|
|
133
|
+
* Gère la requête de chat, soit en exécutant une action confirmée,
|
|
134
|
+
* soit en lanant la boucle de raisonnement de l'IA.
|
|
135
|
+
*/
|
|
107
136
|
async function handleChatRequest(message, history, provider, context, user, confirmedAction) {
|
|
108
137
|
|
|
109
138
|
// --- GESTION D'UNE ACTION CONFIRMÉE ---
|
|
@@ -152,8 +181,7 @@ async function handleChatRequest(message, history, provider, context, user, conf
|
|
|
152
181
|
const relevantModelNames = new Set([modelName, 'alert', 'kpi', "dashboard", "request", "translation"]);
|
|
153
182
|
const modelDefs = allModels.filter(m => relevantModelNames.has(m.name));
|
|
154
183
|
|
|
155
|
-
const
|
|
156
|
-
const systemPrompt = createSystemPrompt(modelDefs, conditionBuilderExample);
|
|
184
|
+
const systemPrompt = createSystemPrompt(modelDefs);
|
|
157
185
|
|
|
158
186
|
const conversationHistory = history
|
|
159
187
|
.filter(msg => !(msg.from === 'bot' && msg.text.startsWith(i18n.t('assistant.welcome'))))
|
|
@@ -207,21 +235,27 @@ async function handleChatRequest(message, history, provider, context, user, conf
|
|
|
207
235
|
|
|
208
236
|
return { success: true, displayMessage: resultString };
|
|
209
237
|
}
|
|
210
|
-
|
|
211
238
|
case 'displayMessage':
|
|
212
239
|
return { success: true, displayMessage: parsedResponse.params.message };
|
|
213
|
-
|
|
214
240
|
case 'code':
|
|
215
|
-
return { success: true, displayMessage: "
|
|
216
|
-
|
|
241
|
+
return { success: true, displayMessage: "Voici le code : " + parsedResponse.params.code };
|
|
217
242
|
case 'post':
|
|
218
243
|
case 'update':
|
|
219
|
-
case 'delete':
|
|
244
|
+
case 'delete': {
|
|
245
|
+
const { model, filter, data } = parsedResponse.params;
|
|
246
|
+
|
|
247
|
+
// Un message générique. Les détails seront affichés par le front-end.
|
|
248
|
+
const confirmationMessage = i18n.t('assistant.confirmActionPrompt', "Veuillez confirmer l'action suivante :");
|
|
249
|
+
|
|
220
250
|
return {
|
|
221
251
|
success: true,
|
|
222
|
-
|
|
223
|
-
|
|
252
|
+
model,
|
|
253
|
+
filter,
|
|
254
|
+
data,
|
|
255
|
+
displayMessage: confirmationMessage, // Message détaillé pour l'utilisateur
|
|
256
|
+
confirmationRequest: parsedResponse // Action complète pour l'exécution
|
|
224
257
|
};
|
|
258
|
+
}
|
|
225
259
|
|
|
226
260
|
default:
|
|
227
261
|
return {
|
|
@@ -234,7 +268,6 @@ async function handleChatRequest(message, history, provider, context, user, conf
|
|
|
234
268
|
logger.warn("[Assistant] La boucle a atteint le nombre maximum de tours sans réponse.");
|
|
235
269
|
return { success: true, displayMessage: i18n.t('assistant.loopTimeout', "Désolé, je n'ai pas réussi à terminer ma pensée. Pouvez-vous reformuler votre demande ?"), actions: [] };
|
|
236
270
|
}
|
|
237
|
-
|
|
238
271
|
/**
|
|
239
272
|
* Exécute une action de modification (post, update, delete) après confirmation de l'utilisateur.
|
|
240
273
|
* @param {string} action - L'action à exécuter ('post', 'update', 'delete').
|
|
@@ -266,7 +299,7 @@ export async function onInit(engine) {
|
|
|
266
299
|
|
|
267
300
|
engine.post('/api/assistant/chat', [middlewareAuthenticator, userInitiator, assistantGlobalLimiter, generateLimiter], async (req, res) => {
|
|
268
301
|
// On récupère TOUTES les propriétés du body, y compris l'action confirmée
|
|
269
|
-
const {message, history, provider, context, confirmedAction} = req.
|
|
302
|
+
const {message, history, provider, context, confirmedAction} = req.fields;
|
|
270
303
|
|
|
271
304
|
// La validation ne s'applique que s'il n'y a pas d'action confirmée
|
|
272
305
|
if (!confirmedAction) {
|
package/src/modules/bucket.js
CHANGED
|
@@ -9,7 +9,7 @@ import {Logger} from "../gameObject.js";
|
|
|
9
9
|
import {middlewareAuthenticator, myFreePremiumAnonymousLimiter, userInitiator} from "./user.js";
|
|
10
10
|
import {awsDefaultConfig, maxBytesPerSecondThrottleData} from "../constants.js";
|
|
11
11
|
import crypto from "node:crypto";
|
|
12
|
-
import i18n from "
|
|
12
|
+
import i18n from "data-primals-engine/i18n";
|
|
13
13
|
import {sendEmail} from "../email.js";
|
|
14
14
|
import {throttleMiddleware} from "../middlewares/throttle.js";
|
|
15
15
|
|
|
@@ -179,7 +179,7 @@ export async function onInit(defaultEngine) {
|
|
|
179
179
|
|
|
180
180
|
engine.post('/api/user/s3-config', [middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
|
|
181
181
|
const user = req.me;
|
|
182
|
-
const { bucketName, accessKeyId, secretAccessKey, region, pathPrefix } = req.
|
|
182
|
+
const { bucketName, accessKeyId, secretAccessKey, region, pathPrefix } = req.fields;
|
|
183
183
|
|
|
184
184
|
// Validation basique des entrées
|
|
185
185
|
if (!bucketName || !accessKeyId || !region) {
|
|
@@ -201,7 +201,7 @@ export async function onInit(defaultEngine) {
|
|
|
201
201
|
} else {
|
|
202
202
|
// Si la clé secrète n'est pas fournie, on ne la modifie pas.
|
|
203
203
|
// Si tu veux permettre de la supprimer, il faudrait une logique explicite.
|
|
204
|
-
// Pour l'instant, on ne touche pas à s3Config.secretAccessKey si req.
|
|
204
|
+
// Pour l'instant, on ne touche pas à s3Config.secretAccessKey si req.fields.secretAccessKey est vide.
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
const result = await engine.userProvider.updateUser(
|