diff --git a/src/lib/server/ai/gemini-client.ts b/src/lib/server/ai/gemini-client.ts index 2f45c4d..a25cdc5 100644 --- a/src/lib/server/ai/gemini-client.ts +++ b/src/lib/server/ai/gemini-client.ts @@ -114,6 +114,7 @@ export async function extractRecipeFromImage( imageBuffer: Buffer, mimeType: string ): Promise { + let firstMsg: string | null = null; try { return await callGemini(imageBuffer, mimeType); } catch (e) { @@ -132,6 +133,9 @@ export async function extractRecipeFromImage( : new GeminiError('AI_FAILED', String(e)); } + firstMsg = e instanceof Error ? e.message : String(e); + console.warn(`[gemini-client] first attempt failed, retrying: ${firstMsg}`); + await new Promise((r) => setTimeout(r, 500)); try { return await callGemini( @@ -140,11 +144,23 @@ export async function extractRecipeFromImage( 'Dein vorheriger Output war ungültig. Bitte antworte ausschließlich mit JSON gemäß Schema.' ); } catch (retryErr) { - if (retryErr instanceof GeminiError) throw retryErr; + const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr); + if (retryErr instanceof GeminiError) { + if (retryErr.code === 'AI_FAILED') { + throw new GeminiError( + 'AI_FAILED', + `retry failed: ${retryMsg} (first: ${firstMsg})` + ); + } + throw retryErr; + } const retryStatus = getStatus(retryErr); if (retryStatus === 429) throw new GeminiError('AI_RATE_LIMITED', 'Gemini rate limit on retry'); - throw new GeminiError('AI_FAILED', String(retryErr)); + throw new GeminiError( + 'AI_FAILED', + `retry failed: ${retryMsg} (first: ${firstMsg})` + ); } } } diff --git a/src/routes/api/recipes/extract-from-photo/+server.ts b/src/routes/api/recipes/extract-from-photo/+server.ts index 75d84b8..1fd25e6 100644 --- a/src/routes/api/recipes/extract-from-photo/+server.ts +++ b/src/routes/api/recipes/extract-from-photo/+server.ts @@ -121,9 +121,11 @@ export const POST: RequestHandler = async ({ request, getClientAddress }) => { : e.code === 'AI_NOT_CONFIGURED' ? 503 : 503; - // Nur Code + Meta loggen, niemals Prompt/Response-Inhalt. + // Nur Code + Meta + Error-Message loggen, niemals Prompt/Response-Inhalt. + // e.message enthaelt z.B. Zod-Validierungspfade oder "non-JSON output" -- + // kein AI-Content, aber die Diagnose-Info, warum AI_FAILED kam. console.warn( - `[extract-from-photo] ${e.code} after ${Date.now() - startedAt}ms, ${preprocessed.buffer.byteLength} bytes` + `[extract-from-photo] ${e.code} after ${Date.now() - startedAt}ms, ${preprocessed.buffer.byteLength} bytes: ${e.message}` ); return errJson(status, e.code, 'Die Bild-Analyse ist fehlgeschlagen.'); }