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,
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 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 |
-
|
| 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.
|