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 +12 -4
- apps/whatsapp-worker/src/index.ts +15 -0
- apps/whatsapp-worker/src/pedagogy.ts +3 -3
- patch.js +10 -0
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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 (
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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);
|