CognxSafeTrack commited on
Commit
9f4922e
·
1 Parent(s): 2c344e4

fix: Resolve Production Crash (Gemini 404, OpenAI Zod, Job Payload mismatch)

Browse files
apps/api/src/services/ai/gemini-provider.ts CHANGED
@@ -13,7 +13,7 @@ export class GeminiProvider implements LLMProvider {
13
  // Standard model for normal requests
14
  this.flashModel = this.genAI.getGenerativeModel({ model: 'gemini-2.0-flash' });
15
  // Pro model for long context & complex doc generation
16
- this.proModel = this.genAI.getGenerativeModel({ model: 'gemini-1.5-pro' });
17
  }
18
 
19
  async generateStructuredData<T>(prompt: string, _schema: z.ZodSchema<T>, temperature?: number, imageUrl?: string): Promise<T> {
@@ -21,7 +21,7 @@ export class GeminiProvider implements LLMProvider {
21
  // Use Pro for complex docs (OnePager/PitchDeck) - detected by prompt length or keyword
22
  const isComplex = prompt.includes('PITCH_DECK') || prompt.includes('ONE_PAGER') || prompt.length > 2000;
23
  const model = isComplex ? this.proModel : this.flashModel;
24
- const modelName = isComplex ? 'gemini-1.5-pro' : 'gemini-2.0-flash';
25
 
26
  console.log(`[GEMINI] Generating structured data with ${modelName}... (Vision: ${!!imageUrl})`);
27
 
 
13
  // Standard model for normal requests
14
  this.flashModel = this.genAI.getGenerativeModel({ model: 'gemini-2.0-flash' });
15
  // Pro model for long context & complex doc generation
16
+ this.proModel = this.genAI.getGenerativeModel({ model: 'gemini-1.5-pro-latest' });
17
  }
18
 
19
  async generateStructuredData<T>(prompt: string, _schema: z.ZodSchema<T>, temperature?: number, imageUrl?: string): Promise<T> {
 
21
  // Use Pro for complex docs (OnePager/PitchDeck) - detected by prompt length or keyword
22
  const isComplex = prompt.includes('PITCH_DECK') || prompt.includes('ONE_PAGER') || prompt.length > 2000;
23
  const model = isComplex ? this.proModel : this.flashModel;
24
+ const modelName = isComplex ? 'gemini-1.5-pro-latest' : 'gemini-2.0-flash';
25
 
26
  console.log(`[GEMINI] Generating structured data with ${modelName}... (Vision: ${!!imageUrl})`);
27
 
apps/api/src/services/ai/types.ts CHANGED
@@ -70,8 +70,8 @@ export const FeedbackSchema = z.object({
70
  teamMembers: z.array(z.object({
71
  name: z.string(),
72
  role: z.string(),
73
- bio: z.string().optional(),
74
- photoUrl: z.string().optional()
75
  })).nullable().optional().describe("List of newly identified team members (Day 11) with their roles and photos"),
76
  fundingAsk: z.string().nullable().optional().describe("The Ask: amount and purpose (Day 12)"),
77
  aiSource: z.string().nullable().optional().describe("The AI provider used (GEMINI, OPENAI, MOCK)")
 
70
  teamMembers: z.array(z.object({
71
  name: z.string(),
72
  role: z.string(),
73
+ bio: z.string().nullable().optional(),
74
+ photoUrl: z.string().nullable().optional()
75
  })).nullable().optional().describe("List of newly identified team members (Day 11) with their roles and photos"),
76
  fundingAsk: z.string().nullable().optional().describe("The Ask: amount and purpose (Day 12)"),
77
  aiSource: z.string().nullable().optional().describe("The AI provider used (GEMINI, OPENAI, MOCK)")
apps/whatsapp-worker/src/index.ts CHANGED
@@ -64,7 +64,10 @@ const worker = new Worker('whatsapp-queue', async (job: Job) => {
64
  await WhatsAppLogic.handleIncomingMessage(phone, text, audioUrl, imageUrl);
65
  }
66
  else if (job.name === 'generate-feedback') {
67
- const { userId, text, trackId, exercisePrompt, lessonText, exerciseCriteria, currentDay, totalDays, language, userActivity, userRegion, previousResponses, isDeepDive, iterationCount, imageUrl } = job.data;
 
 
 
68
  const user = await prisma.user.findUnique({
69
  where: { id: userId },
70
  include: { businessProfile: true } as any
@@ -279,7 +282,7 @@ const worker = new Worker('whatsapp-queue', async (job: Job) => {
279
  // @ts-ignore
280
  await prisma.response.create({
281
  data: {
282
- enrollmentId: job.data.enrollmentId,
283
  userId: user.id,
284
  dayNumber: currentDay,
285
  content: feedbackMsg,
 
64
  await WhatsAppLogic.handleIncomingMessage(phone, text, audioUrl, imageUrl);
65
  }
66
  else if (job.name === 'generate-feedback') {
67
+ const { userId, text, trackId, exercisePrompt, lessonText, exerciseCriteria, totalDays, language, userActivity, userRegion, previousResponses, isDeepDive, iterationCount, imageUrl } = job.data;
68
+ const currentDay = Number(job.data.currentDay || job.data.dayNumber || 0);
69
+ const enrollmentId = job.data.enrollmentId;
70
+
71
  const user = await prisma.user.findUnique({
72
  where: { id: userId },
73
  include: { businessProfile: true } as any
 
282
  // @ts-ignore
283
  await prisma.response.create({
284
  data: {
285
+ enrollmentId: enrollmentId,
286
  userId: user.id,
287
  dayNumber: currentDay,
288
  content: feedbackMsg,
apps/whatsapp-worker/src/services/whatsapp-logic.ts CHANGED
@@ -298,9 +298,10 @@ export class WhatsAppLogic {
298
 
299
  await whatsappQueue.add('generate-feedback', {
300
  userId: user.id, text, trackId: activeEnrollment.trackId, trackDayId: trackDay.id,
 
301
  exercisePrompt: trackDay.exercisePrompt || '', lessonText: trackDay.lessonText || '',
302
  exerciseCriteria: trackDay.exerciseCriteria, pendingProgressId: pendingProgress.id,
303
- dayNumber: activeEnrollment.currentDay, totalDays: activeEnrollment.track.duration, language: user.language,
304
  userActivity: user.activity, userRegion: user.city, previousResponses,
305
  isDeepDive: isDeepDiveAction, iterationCount: currentIterationCount, imageUrl: imageUrl
306
  }, { attempts: 3, backoff: { type: 'exponential', delay: 2000 } });
 
298
 
299
  await whatsappQueue.add('generate-feedback', {
300
  userId: user.id, text, trackId: activeEnrollment.trackId, trackDayId: trackDay.id,
301
+ enrollmentId: activeEnrollment.id,
302
  exercisePrompt: trackDay.exercisePrompt || '', lessonText: trackDay.lessonText || '',
303
  exerciseCriteria: trackDay.exerciseCriteria, pendingProgressId: pendingProgress.id,
304
+ currentDay: activeEnrollment.currentDay, totalDays: activeEnrollment.track.duration, language: user.language,
305
  userActivity: user.activity, userRegion: user.city, previousResponses,
306
  isDeepDive: isDeepDiveAction, iterationCount: currentIterationCount, imageUrl: imageUrl
307
  }, { attempts: 3, backoff: { type: 'exponential', delay: 2000 } });
tasks/lessons.md CHANGED
@@ -9,5 +9,13 @@ Ce fichier archive les échecs et solutions liées à la **stabilité technique*
9
  - **[STT] Whisper Auto-Validation** : Si l'indice de confiance est <= 80%, intercepter le flux et basculer en `PENDING_REVIEW` pour validation humaine sans crash.
10
  - **[NON-BLOQUANT] Processus FFMPEG** : Les conversions audio doivent tourner dans des sous-processus.
11
 
 
 
 
 
12
  ## 🛡️ Règle d'Or de l'Intégrité
13
  **Les correctifs techniques ne doivent JAMAIS impacter la logique de personnalisation du prompt `generatePersonalizedLesson` ou des feedbacks.** Toute simplification technique qui réduit la spécificité de l'IA est un échec.
 
 
 
 
 
9
  - **[STT] Whisper Auto-Validation** : Si l'indice de confiance est <= 80%, intercepter le flux et basculer en `PENDING_REVIEW` pour validation humaine sans crash.
10
  - **[NON-BLOQUANT] Processus FFMPEG** : Les conversions audio doivent tourner dans des sous-processus.
11
 
12
+ - **[AI] OpenAI Zod Schemas** : Pour les Structured Outputs, OpenAI exige que TOUS les champs d'un schéma soient obligatoires OU explicitement `.nullable().optional()`. Les `.optional()` seuls sur les sous-objets provoquent une erreur.
13
+ - **[QUEUES] Payload Sync** : S'assurer que les noms de champs dans `whatsappQueue.add` (producteur) matchent exactement le destructuring dans le worker (consommateur). Préférer `currentDay` partout pour la consistance.
14
+ - **[DB] Enrollment ID** : Toujours passer l'ID d'inscription (`enrollmentId`) au job de feedback pour permettre la persistance des réponses sans crash Prisma.
15
+
16
  ## 🛡️ Règle d'Or de l'Intégrité
17
  **Les correctifs techniques ne doivent JAMAIS impacter la logique de personnalisation du prompt `generatePersonalizedLesson` ou des feedbacks.** Toute simplification technique qui réduit la spécificité de l'IA est un échec.
18
+
19
+ ## 📅 Historique des Apprentissages
20
+ - [24/03/2026] | Gemini 404 | Utiliser `gemini-1.5-pro-latest` au lieu de `gemini-1.5-pro` sur l'API `v1beta`.
21
+ - [24/03/2026] | Missing dayNumber | `currentDay` vs `dayNumber` mismatch dans le job payload. Toujours vérifier le destructuring.