From bab2132d5160c83e703782b3d85bc55de355593d Mon Sep 17 00:00:00 2001 From: Steve Freeman Date: Sun, 22 Mar 2026 11:17:39 -0400 Subject: [PATCH 1/2] Improved gemini error logging --- packages/backend/src/ai/ai.service.ts | 48 +++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/ai/ai.service.ts b/packages/backend/src/ai/ai.service.ts index e1dd61ab..cd20145d 100644 --- a/packages/backend/src/ai/ai.service.ts +++ b/packages/backend/src/ai/ai.service.ts @@ -34,6 +34,7 @@ import type { ResponseOutputText, ResponseOutputRefusal, } from 'openai/resources/responses/responses'; +import type { Part } from '@google/genai'; import { GoogleGenAI } from '@google/genai'; interface ExtractionResult { @@ -155,10 +156,32 @@ export class AIService { }, }) .then((response) => { + this.aiServiceLogger.debug('Gemini response structure:', { + hasCandidates: !!response.candidates, + candidatesLength: response.candidates?.length, + firstCandidateKeys: response.candidates?.[0] ? Object.keys(response.candidates[0]) : null, + contentKeys: response.candidates?.[0]?.content ? Object.keys(response.candidates[0].content) : null, + partsLength: response.candidates?.[0]?.content?.parts?.length, + fullResponse: JSON.stringify(response, null, 2), + }); + let imageBytes = Buffer.from([]); - response.candidates?.[0].content?.parts?.forEach((part) => { + if (!response.candidates || response.candidates.length === 0) { + this.aiServiceLogger.warn('No candidates in Gemini response'); + return ''; + } + + const parts = response.candidates[0].content?.parts; + if (!parts || parts.length === 0) { + this.aiServiceLogger.warn('No parts in first candidate'); + return ''; + } + + parts.forEach((part: Part) => { if (part.inlineData?.data) { imageBytes = Buffer.concat([imageBytes, Buffer.from(part.inlineData.data, 'base64')]); + } else { + this.aiServiceLogger.debug('Part does not have inlineData.data:', Object.keys(part)); } }); @@ -215,10 +238,31 @@ export class AIService { }, }) .then((response) => { + this.aiServiceLogger.debug('Gemini generateImage response structure:', { + hasCandidates: !!response.candidates, + candidatesLength: response.candidates?.length, + firstCandidateKeys: response.candidates?.[0] ? Object.keys(response.candidates[0]) : null, + contentKeys: response.candidates?.[0]?.content ? Object.keys(response.candidates[0].content) : null, + partsLength: response.candidates?.[0]?.content?.parts?.length, + }); + let imageBytes = Buffer.from([]); - response.candidates?.[0].content?.parts?.forEach((part) => { + if (!response.candidates || response.candidates.length === 0) { + this.aiServiceLogger.warn('No candidates in Gemini response for generateImage'); + return ''; + } + + const parts = response.candidates[0].content?.parts; + if (!parts || parts.length === 0) { + this.aiServiceLogger.warn('No parts in first candidate for generateImage'); + return ''; + } + + parts.forEach((part: Part) => { if (part.inlineData?.data) { imageBytes = Buffer.concat([imageBytes, Buffer.from(part.inlineData.data, 'base64')]); + } else { + this.aiServiceLogger.debug('GenerateImage part does not have inlineData.data:', Object.keys(part)); } }); From 94967a42b04c0a057a8d58d48a7da482196f9a40 Mon Sep 17 00:00:00 2001 From: Steve Freeman Date: Sun, 22 Mar 2026 11:20:53 -0400 Subject: [PATCH 2/2] Changed .debug to .info --- packages/backend/src/ai/ai.service.spec.ts | 1 + packages/backend/src/ai/ai.service.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/ai/ai.service.spec.ts b/packages/backend/src/ai/ai.service.spec.ts index e20e2016..b810e3e1 100644 --- a/packages/backend/src/ai/ai.service.spec.ts +++ b/packages/backend/src/ai/ai.service.spec.ts @@ -60,6 +60,7 @@ const buildAiService = (): AIService => { error: jest.fn(), warn: jest.fn(), info: jest.fn(), + debug: jest.fn(), } as unknown as AIService['aiServiceLogger']; return ai; diff --git a/packages/backend/src/ai/ai.service.ts b/packages/backend/src/ai/ai.service.ts index cd20145d..ba7ebaaf 100644 --- a/packages/backend/src/ai/ai.service.ts +++ b/packages/backend/src/ai/ai.service.ts @@ -156,7 +156,7 @@ export class AIService { }, }) .then((response) => { - this.aiServiceLogger.debug('Gemini response structure:', { + this.aiServiceLogger.info('Gemini response structure:', { hasCandidates: !!response.candidates, candidatesLength: response.candidates?.length, firstCandidateKeys: response.candidates?.[0] ? Object.keys(response.candidates[0]) : null, @@ -181,7 +181,7 @@ export class AIService { if (part.inlineData?.data) { imageBytes = Buffer.concat([imageBytes, Buffer.from(part.inlineData.data, 'base64')]); } else { - this.aiServiceLogger.debug('Part does not have inlineData.data:', Object.keys(part)); + this.aiServiceLogger.info('Part does not have inlineData.data:', Object.keys(part)); } }); @@ -238,7 +238,7 @@ export class AIService { }, }) .then((response) => { - this.aiServiceLogger.debug('Gemini generateImage response structure:', { + this.aiServiceLogger.info('Gemini generateImage response structure:', { hasCandidates: !!response.candidates, candidatesLength: response.candidates?.length, firstCandidateKeys: response.candidates?.[0] ? Object.keys(response.candidates[0]) : null, @@ -262,7 +262,7 @@ export class AIService { if (part.inlineData?.data) { imageBytes = Buffer.concat([imageBytes, Buffer.from(part.inlineData.data, 'base64')]); } else { - this.aiServiceLogger.debug('GenerateImage part does not have inlineData.data:', Object.keys(part)); + this.aiServiceLogger.info('GenerateImage part does not have inlineData.data:', Object.keys(part)); } });