CognxSafeTrack commited on
Commit
e6d84cb
·
1 Parent(s): 77c96f8

feat(ai): coaching refinements - temperature support, memory filter, and flexible guardrails for Day 6/7

Browse files
apps/api/src/services/ai/gemini-provider.ts CHANGED
@@ -14,7 +14,7 @@ export class GeminiProvider implements LLMProvider {
14
  this.proModel = this.genAI.getGenerativeModel({ model: 'gemini-1.5-pro' });
15
  }
16
 
17
- async generateStructuredData<T>(prompt: string, _schema: z.ZodSchema<T>): Promise<T> {
18
  // Use Flash for standard feedback/chat (fast)
19
  // Use Pro for complex docs (OnePager/PitchDeck) - detected by prompt length or keyword
20
  const isComplex = prompt.includes('PITCH_DECK') || prompt.includes('ONE_PAGER') || prompt.length > 2000;
@@ -28,6 +28,7 @@ export class GeminiProvider implements LLMProvider {
28
  contents: [{ role: 'user', parts: [{ text: prompt }] }],
29
  generationConfig: {
30
  responseMimeType: 'application/json',
 
31
  },
32
  });
33
 
 
14
  this.proModel = this.genAI.getGenerativeModel({ model: 'gemini-1.5-pro' });
15
  }
16
 
17
+ async generateStructuredData<T>(prompt: string, _schema: z.ZodSchema<T>, temperature?: number): Promise<T> {
18
  // Use Flash for standard feedback/chat (fast)
19
  // Use Pro for complex docs (OnePager/PitchDeck) - detected by prompt length or keyword
20
  const isComplex = prompt.includes('PITCH_DECK') || prompt.includes('ONE_PAGER') || prompt.length > 2000;
 
28
  contents: [{ role: 'user', parts: [{ text: prompt }] }],
29
  generationConfig: {
30
  responseMimeType: 'application/json',
31
+ temperature: temperature ?? 0.2, // Default to 0.2
32
  },
33
  });
34
 
apps/api/src/services/ai/index.ts CHANGED
@@ -44,10 +44,11 @@ class AIService {
44
  */
45
  private async callWithFailover<T>(
46
  prompt: string,
47
- schema: z.ZodSchema<T>
 
48
  ): Promise<{ data: T, source: string }> {
49
  try {
50
- const data = await this.primaryProvider.generateStructuredData(prompt, schema);
51
  const source = (this.primaryProvider instanceof GeminiProvider) ? 'GEMINI' :
52
  (this.primaryProvider instanceof OpenAIProvider) ? 'OPENAI' : 'MOCK';
53
  console.log(`[AI_INFO] ${source} used successfully.`);
@@ -55,7 +56,7 @@ class AIService {
55
  } catch (err) {
56
  if (this.fallbackProvider) {
57
  console.warn('[AI_WARNING] Primary provider failed, falling back to OpenAI...', (err as Error).message);
58
- const data = await this.fallbackProvider.generateStructuredData(prompt, schema);
59
  console.log('[AI_INFO] OPENAI used as fallback.');
60
  return { data, source: 'OPENAI' };
61
  }
@@ -255,7 +256,14 @@ class AIService {
255
  🚨 DOUBLE INTENTION DÉTECTÉE : L'utilisateur a posé une question ou sollicité un avis stratégique.
256
  - TU DOIS répondre spécifiquement à sa question dans la section "🚀 Version Enrichée".
257
  - Compare son idée ou sa question (ex: prix, cible, stratégie) avec les standards du marché trouvés via tes recherches.
258
- - Sois tranché et expert.` : ''}
 
 
 
 
 
 
 
259
 
260
  LES 3 PILIERS OBLIGATOIRES DU FEEDBACK :
261
  1. 🌟 Validation (Pilier 1) : Félicite et valide l'idée de l'utilisateur avec l'enthousiasme d'un investisseur.
@@ -313,7 +321,7 @@ class AIService {
313
  `ALERTE MAXIMALE : Tu as l'INTERDICTION FORMELLE d'utiliser du Wolof. Reste dans un Français institutionnel et pragmatique.`}
314
  `;
315
 
316
- const { data, source } = await this.callWithFailover(prompt, FeedbackSchema);
317
  return {
318
  ...data,
319
  searchResults,
 
44
  */
45
  private async callWithFailover<T>(
46
  prompt: string,
47
+ schema: z.ZodSchema<T>,
48
+ temperature?: number
49
  ): Promise<{ data: T, source: string }> {
50
  try {
51
+ const data = await this.primaryProvider.generateStructuredData(prompt, schema, temperature);
52
  const source = (this.primaryProvider instanceof GeminiProvider) ? 'GEMINI' :
53
  (this.primaryProvider instanceof OpenAIProvider) ? 'OPENAI' : 'MOCK';
54
  console.log(`[AI_INFO] ${source} used successfully.`);
 
56
  } catch (err) {
57
  if (this.fallbackProvider) {
58
  console.warn('[AI_WARNING] Primary provider failed, falling back to OpenAI...', (err as Error).message);
59
+ const data = await this.fallbackProvider.generateStructuredData(prompt, schema, temperature);
60
  console.log('[AI_INFO] OPENAI used as fallback.');
61
  return { data, source: 'OPENAI' };
62
  }
 
256
  🚨 DOUBLE INTENTION DÉTECTÉE : L'utilisateur a posé une question ou sollicité un avis stratégique.
257
  - TU DOIS répondre spécifiquement à sa question dans la section "🚀 Version Enrichée".
258
  - Compare son idée ou sa question (ex: prix, cible, stratégie) avec les standards du marché trouvés via tes recherches.
259
+ - Sois tranché et expert (Senior Consultant). N'hésite pas à être critique si le modèle est risqué.
260
+ - Exemple : Si l'utilisateur propose une marge de 1-2%, préviens-le que les frais Mobile Money absorbent déjà 1% et qu'il risque de travailler à perte.` : ''}
261
+
262
+ 🧠 FILTRE DE MÉMOIRE (ANTI-RÉPÉTITION) :
263
+ - Voici ce que l'utilisateur a déjà partagé : ${previousResponsesContext}
264
+ - NE DONNE JAMAIS un conseil que tu as déjà donné ou qui est trop similaire aux points déjà validés.
265
+ - Diversifie tes angles : Si tu as déjà parlé de publicité, parle maintenant de Logistique, Psychologie client, Partenariats, ou Technique de vente terrain.
266
+ - Interdiction de répéter "Le créneau 6h-8h est critique pour la publicité" si cela a déjà été mentionné.
267
 
268
  LES 3 PILIERS OBLIGATOIRES DU FEEDBACK :
269
  1. 🌟 Validation (Pilier 1) : Félicite et valide l'idée de l'utilisateur avec l'enthousiasme d'un investisseur.
 
321
  `ALERTE MAXIMALE : Tu as l'INTERDICTION FORMELLE d'utiliser du Wolof. Reste dans un Français institutionnel et pragmatique.`}
322
  `;
323
 
324
+ const { data, source } = await this.callWithFailover(prompt, FeedbackSchema, 0.7);
325
  return {
326
  ...data,
327
  searchResults,
apps/api/src/services/ai/openai-provider.ts CHANGED
@@ -26,7 +26,7 @@ export class OpenAIProvider implements LLMProvider {
26
  });
27
  }
28
 
29
- async generateStructuredData<T>(prompt: string, schema: z.ZodSchema<T>): Promise<T> {
30
  console.log('[OPENAI] Generating structured data...');
31
 
32
  const timeout = new Promise<never>((_, reject) =>
@@ -44,6 +44,7 @@ export class OpenAIProvider implements LLMProvider {
44
  { role: 'user', content: prompt }
45
  ],
46
  response_format: responseFormat,
 
47
  }),
48
  timeout
49
  ]);
 
26
  });
27
  }
28
 
29
+ async generateStructuredData<T>(prompt: string, schema: z.ZodSchema<T>, temperature?: number): Promise<T> {
30
  console.log('[OPENAI] Generating structured data...');
31
 
32
  const timeout = new Promise<never>((_, reject) =>
 
44
  { role: 'user', content: prompt }
45
  ],
46
  response_format: responseFormat,
47
+ temperature: temperature ?? 0.2,
48
  }),
49
  timeout
50
  ]);
apps/api/src/services/ai/types.ts CHANGED
@@ -7,7 +7,7 @@ export interface TranscriptionResult {
7
 
8
  // Base interface for all LLM Providers
9
  export interface LLMProvider {
10
- generateStructuredData<T>(prompt: string, schema: z.ZodSchema<T>): Promise<T>;
11
  transcribeAudio(audioBuffer: Buffer, filename: string, language?: string): Promise<TranscriptionResult>;
12
  generateSpeech(text: string): Promise<Buffer>;
13
  generateImage(prompt: string): Promise<string>;
 
7
 
8
  // Base interface for all LLM Providers
9
  export interface LLMProvider {
10
+ generateStructuredData<T>(prompt: string, schema: z.ZodSchema<T>, temperature?: number): Promise<T>;
11
  transcribeAudio(audioBuffer: Buffer, filename: string, language?: string): Promise<TranscriptionResult>;
12
  generateSpeech(text: string): Promise<Buffer>;
13
  generateImage(prompt: string): Promise<string>;
apps/api/src/services/whatsapp.ts CHANGED
@@ -149,15 +149,14 @@ export class WhatsAppService {
149
  return;
150
  }
151
 
152
- // 🚨 Guardrail "Contenu Vide" / Gibberish (UX Engineer Requirement)
153
- const wordCount = (text || '').trim().split(/\s+/).length;
154
  const systemCommands = ['1', '2', 'SUITE', 'APPROFONDIR', 'INSCRIPTION', 'SEED'];
155
  const isSystemCommand = systemCommands.some(cmd => this.isFuzzyMatch(normalizedText, cmd)) || normalizedText.includes('INSCRI');
156
 
157
- if (wordCount < 3 && !isSystemCommand) {
 
158
  await scheduleMessage(user.id, user.language === 'WOLOF'
159
- ? "Dama lay xaar nga wax ma lu gën a yaatu ci sa mbir (mbebetu 3 baat). Waxtaanal ak man !"
160
- : "Je n'ai pas bien compris ton activité. Peux-tu me réexpliquer en quelques mots ce que tu fais ? (Minimum 3 mots)");
161
  return;
162
  }
163
 
@@ -454,6 +453,22 @@ export class WhatsAppService {
454
  });
455
 
456
  if (trackDay) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  await scheduleMessage(user.id, user.language === 'WOLOF' ? "⏳ Defar ak sa tontu..." : "⏳ Analyse de votre réponse...");
458
 
459
  // Update iteration count if it's a deep dive
 
149
  return;
150
  }
151
 
 
 
152
  const systemCommands = ['1', '2', 'SUITE', 'APPROFONDIR', 'INSCRIPTION', 'SEED'];
153
  const isSystemCommand = systemCommands.some(cmd => this.isFuzzyMatch(normalizedText, cmd)) || normalizedText.includes('INSCRI');
154
 
155
+ // 🚨 Guardrail "Gibberish" Lite (Global)
156
+ if (text.length < 2 && !isSystemCommand) {
157
  await scheduleMessage(user.id, user.language === 'WOLOF'
158
+ ? "Dama lay xaar nga wax ma lu gën a yaatu ci sa mbir. Waxtaanal ak man !"
159
+ : "Je n'ai pas bien compris. Peux-tu me réexpliquer en quelques mots ?");
160
  return;
161
  }
162
 
 
453
  });
454
 
455
  if (trackDay) {
456
+ // 🚨 Flexible Guardrail (Day 7 Fix)
457
+ const wordCount = (text || '').trim().split(/\s+/).length;
458
+ const validationKeyword = trackDay.validationKeyword || '';
459
+ const isOptionMatch = validationKeyword && this.isFuzzyMatch(normalizedText, validationKeyword);
460
+
461
+ // Specific bypass for known short answers in modules (WhatsApp, Boutique, etc.)
462
+ const commonOptions = ['WHATSAPP', 'BOUTIQUE', 'APPEL', 'VENTE', 'SERVICE', 'PRODUCTION'];
463
+ const isCommonOption = commonOptions.some(opt => this.isFuzzyMatch(normalizedText, opt));
464
+
465
+ if (wordCount < 3 && !isOptionMatch && !isCommonOption) {
466
+ await scheduleMessage(user.id, user.language === 'WOLOF'
467
+ ? "Dama lay xaar nga wax ma lu gën a yaatu ci sa mbir (mbebetu 3 baat). Waxtaanal ak man !"
468
+ : "Ta réponse est un peu courte. Peux-tu m'en dire plus ? (Minimum 3 mots)");
469
+ return;
470
+ }
471
+
472
  await scheduleMessage(user.id, user.language === 'WOLOF' ? "⏳ Defar ak sa tontu..." : "⏳ Analyse de votre réponse...");
473
 
474
  // Update iteration count if it's a deep dive