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 |
-
|
|
|
|
| 158 |
await scheduleMessage(user.id, user.language === 'WOLOF'
|
| 159 |
-
? "Dama lay xaar nga wax ma lu gën a yaatu ci sa mbir
|
| 160 |
-
: "Je n'ai pas bien compris
|
| 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
|