CognxSafeTrack commited on
Commit
936cad5
·
1 Parent(s): ab37938

fix(ai): Sprint 41 - Détanchéité linguistique Wolof stricte et réinjection boucle Deep Dive

Browse files
apps/api/src/services/ai/index.ts CHANGED
@@ -146,7 +146,10 @@ class AIService {
146
  let searchResults: any[] | undefined = undefined;
147
  // 🚀 Brique 1: Activation du Browsing (Obligatoire)
148
  console.log(`[AI_SERVICE] 🔍 Triggering Market Search for Day ${dayNumber} (Always ON)...`);
149
- const query = `${activityLabel} ${region} Sénégal marché chiffres statistiques data`;
 
 
 
150
  try {
151
  const results = await searchService.search(query);
152
  if (results && results.length > 0) {
@@ -183,7 +186,7 @@ class AIService {
183
 
184
  1. Dis "Merci pour cette précision sur [Point abordé]. En intégrant cela, ton modèle devient encore plus solide car... [Donne l'Analyse]".
185
  2. Règle stricte d'Anti-Hallucination : CE QUE L'UTILISATEUR VIENT DE FOURNIR EST LA VÉRITÉ TERRAIN ET ANNULE TES SOURCES EN CAS DE CONFLIT.
186
- 3. Pose UNE SEULE question ciblée pour l'amener à réfléchir sur un sous-jacent (Fournisseur, coût caché, concurrence de quartier).
187
 
188
  ATTENTION : Ne pose AUCUNE question qui relève d'une leçon suivante (reste bloqué sur le périmètre du Jour ${dayNumber}).
189
  `;
@@ -236,11 +239,16 @@ class AIService {
236
 
237
  ${actionPrompt}
238
 
239
- PROTOCOLE DATA STEWARD (Intégrité Géographique) :
 
240
  - Si l'utilisateur est à ${region}, donne lui les vrais chiffres de ${region}. S'ils n'existent pas, back-up sur les chiffres du Sénégal.
241
  - TRANSPARENCE : Cite toujours la source formelle (ex: Agence Nationale de la Statistique et de la Démographie, Banque Mondiale, Direction du commerce, etc.).
242
 
243
- DÉTANCHÉITÉ LINGUISTIQUE : 100% ${userLanguage === 'WOLOF' ? 'WOLOF (ñ, ë, é)' : 'Français institutionnel'}. Zéro mélange.
 
 
 
 
244
  `;
245
 
246
  const feedback = await this.provider.generateStructuredData(prompt, FeedbackSchema);
 
146
  let searchResults: any[] | undefined = undefined;
147
  // 🚀 Brique 1: Activation du Browsing (Obligatoire)
148
  console.log(`[AI_SERVICE] 🔍 Triggering Market Search for Day ${dayNumber} (Always ON)...`);
149
+
150
+ // Remove hallucinatory generic fallback words
151
+ const cleanActivity = activityLabel.replace(/non précisé|e-commerce/i, '').trim() || 'Entrepreneuriat';
152
+ const query = `${cleanActivity} ${region} Sénégal marché chiffres statistiques data`;
153
  try {
154
  const results = await searchService.search(query);
155
  if (results && results.length > 0) {
 
186
 
187
  1. Dis "Merci pour cette précision sur [Point abordé]. En intégrant cela, ton modèle devient encore plus solide car... [Donne l'Analyse]".
188
  2. Règle stricte d'Anti-Hallucination : CE QUE L'UTILISATEUR VIENT DE FOURNIR EST LA VÉRITÉ TERRAIN ET ANNULE TES SOURCES EN CAS DE CONFLIT.
189
+ 3. Pose UNE SEULE question ciblée pour l'amener à réfléchir sur un sous-jacent lié EXCLUSIVEMENT à son secteur (${activityLabel}) (ex: 'Comment tes clients de ce secteur paient-ils ?', 'Ont-ils tous WhatsApp ?'). Ne pose JAMAIS de questions génériques de type 'Quoi d'autre ?'.
190
 
191
  ATTENTION : Ne pose AUCUNE question qui relève d'une leçon suivante (reste bloqué sur le périmètre du Jour ${dayNumber}).
192
  `;
 
239
 
240
  ${actionPrompt}
241
 
242
+ PROTOCOLE DATA STEWARD (Intégrité Géographique et Sectorielle) :
243
+ - Reste EXCLUSIVEMENT sur le secteur : ${activityLabel}. Ne fais AUCUNE supposition sur un secteur "e-commerce" par défaut si cela n'est pas stipulé.
244
  - Si l'utilisateur est à ${region}, donne lui les vrais chiffres de ${region}. S'ils n'existent pas, back-up sur les chiffres du Sénégal.
245
  - TRANSPARENCE : Cite toujours la source formelle (ex: Agence Nationale de la Statistique et de la Démographie, Banque Mondiale, Direction du commerce, etc.).
246
 
247
+ ÉTANCHÉITÉ LINGUISTIQUE (OBLIGATION ABSOLUE) :
248
+ Tu es configuré dans le mode linguistique suivant : ${userLanguage}.
249
+ ${userLanguage === 'WOLOF' ?
250
+ `ALERTE MAXIMALE : Tu as l'INTERDICTION FORMELLE ET DÉFINITIVE d'utiliser ne serait-ce qu'UN SEUL MOT de Français. TOUTE TA RÉPONSE DOIT ÊTRE EN WOLOF STANDARD. Utilise exclusivement le Glossaire Wolof Officiel (v4.0). Si tu ne connais pas le mot technique en wolof, utilise une périphrase ou une structure simple mais RESTE EN WOLOF (ex: "njàngat" au lieu de "analyse"). Utilise bien les caractères ñ, ë, é.` :
251
+ `ALERTE MAXIMALE : Tu as l'INTERDICTION FORMELLE d'utiliser du Wolof. Reste dans un Français institutionnel et pragmatique.`}
252
  `;
253
 
254
  const feedback = await this.provider.generateStructuredData(prompt, FeedbackSchema);
apps/whatsapp-worker/src/index.ts CHANGED
@@ -92,10 +92,25 @@ const worker = new Worker('whatsapp-queue', async (job: Job) => {
92
 
93
  if (feedbackRes.ok) {
94
  feedbackData = await feedbackRes.json();
 
 
 
 
 
95
  if (feedbackData.validation && feedbackData.enrichedVersion && feedbackData.actionableAdvice) {
96
  feedbackMsg = `🌟 ${feedbackData.validation}\n\n🚀 ${feedbackData.enrichedVersion}\n\n💡 Conseil de Terrain :\n${feedbackData.actionableAdvice}`;
 
 
 
 
 
97
  } else if (feedbackData.text) {
98
  feedbackMsg = feedbackData.text;
 
 
 
 
 
99
  } else {
100
  feedbackMsg = '✅ Analyse terminée.';
101
  }
 
92
 
93
  if (feedbackRes.ok) {
94
  feedbackData = await feedbackRes.json();
95
+
96
+ const callToAction = language === 'WOLOF'
97
+ ? "\n\nSu nga bëggee yokk leneen ci li nga xam, bindal 1️⃣ APPROFONDIR, wala nga bind 2️⃣ SUITE."
98
+ : "\n\nSi tu veux affiner ce point avec une donnée de ton propre terrain, tape 1️⃣ APPROFONDIR, sinon tape 2️⃣ SUITE.";
99
+
100
  if (feedbackData.validation && feedbackData.enrichedVersion && feedbackData.actionableAdvice) {
101
  feedbackMsg = `🌟 ${feedbackData.validation}\n\n🚀 ${feedbackData.enrichedVersion}\n\n💡 Conseil de Terrain :\n${feedbackData.actionableAdvice}`;
102
+ if (!isDeepDive || (isDeepDive && iterationCount < 3 && !feedbackData.isForcedClosure)) {
103
+ feedbackMsg += callToAction;
104
+ } else if (feedbackData.isForcedClosure) {
105
+ feedbackMsg += (language === 'WOLOF' ? "\n\nBindal 2️⃣ SUITE ngir wéy." : "\n\nTape 2️⃣ SUITE pour continuer.");
106
+ }
107
  } else if (feedbackData.text) {
108
  feedbackMsg = feedbackData.text;
109
+ if (!isDeepDive || (isDeepDive && iterationCount < 3 && !feedbackData.isForcedClosure)) {
110
+ feedbackMsg += callToAction;
111
+ } else if (feedbackData.isForcedClosure) {
112
+ feedbackMsg += (language === 'WOLOF' ? "\n\nBindal 2️⃣ SUITE ngir wéy." : "\n\nTape 2️⃣ SUITE pour continuer.");
113
+ }
114
  } else {
115
  feedbackMsg = '✅ Analyse terminée.';
116
  }
apps/whatsapp-worker/src/pedagogy.ts CHANGED
@@ -249,7 +249,7 @@ export async function sendLessonDay(userId: string, trackId: string, dayNumber:
249
  // Send the text as a separate short message
250
  // ─── Format & Send 🌍 Bilingue v1.0 🌍 ─────────────
251
  let textFR = '';
252
- if ((trackDay as any).buttonsJson?.content?.FR) {
253
  textFR = (trackDay as any).buttonsJson.content.FR.lessonText;
254
  }
255
 
@@ -271,7 +271,7 @@ export async function sendLessonDay(userId: string, trackId: string, dayNumber:
271
  const alertMsg = isWolof ? "⚠️ Kàddu gi mënul a yónnee. Làng gi a ngi nii ci mbind:" : "⚠️ Impossible de charger l'audio de la leçon. Voici le contenu au format texte :";
272
  await sendTextMessage(user.phone, alertMsg);
273
  let textFR = '';
274
- if ((trackDay as any).buttonsJson?.content?.FR) {
275
  textFR = (trackDay as any).buttonsJson.content.FR.lessonText;
276
  }
277
  const lessonMsg = textFR ? `${lessonText}\n(FR) ${textFR}` : lessonText;
@@ -287,7 +287,7 @@ export async function sendLessonDay(userId: string, trackId: string, dayNumber:
287
  await sendTextMessage(user.phone, alertMsg);
288
 
289
  let textFR = '';
290
- if ((trackDay as any).buttonsJson?.content?.FR) {
291
  textFR = (trackDay as any).buttonsJson.content.FR.lessonText;
292
  }
293
 
 
249
  // Send the text as a separate short message
250
  // ─── Format & Send 🌍 Bilingue v1.0 🌍 ─────────────
251
  let textFR = '';
252
+ if (!isWolof && (trackDay as any).buttonsJson?.content?.FR) {
253
  textFR = (trackDay as any).buttonsJson.content.FR.lessonText;
254
  }
255
 
 
271
  const alertMsg = isWolof ? "⚠️ Kàddu gi mënul a yónnee. Làng gi a ngi nii ci mbind:" : "⚠️ Impossible de charger l'audio de la leçon. Voici le contenu au format texte :";
272
  await sendTextMessage(user.phone, alertMsg);
273
  let textFR = '';
274
+ if (!isWolof && (trackDay as any).buttonsJson?.content?.FR) {
275
  textFR = (trackDay as any).buttonsJson.content.FR.lessonText;
276
  }
277
  const lessonMsg = textFR ? `${lessonText}\n(FR) ${textFR}` : lessonText;
 
287
  await sendTextMessage(user.phone, alertMsg);
288
 
289
  let textFR = '';
290
+ if (!isWolof && (trackDay as any).buttonsJson?.content?.FR) {
291
  textFR = (trackDay as any).buttonsJson.content.FR.lessonText;
292
  }
293
 
patch.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const file = 'apps/api/src/services/ai/index.ts';
3
+ let content = fs.readFileSync(file, 'utf8');
4
+
5
+ content = content.replace(
6
+ '3. Pose UNE SEULE question ciblée pour l\\'amener à réfléchir sur un sous-jacent (Fournisseur, coût caché, concurrence de quartier).',
7
+ '3. Pose UNE SEULE question ciblée pour l\\'amener à réfléchir sur un sous-jacent lié EXCLUSIVEMENT à son secteur (${activityLabel}) (ex: "Comment tes clients de ce secteur paient-ils ?", "Ont-ils tous WhatsApp ?"). Ne pose JAMAIS de questions génériques de type "Quoi d\\'autre ?".'
8
+ );
9
+
10
+ fs.writeFileSync(file, content);