feat: log raw AI JSON response to browser console

This commit is contained in:
2025-12-22 11:33:15 +01:00
parent 83229e9c67
commit 1d98bb9947
7 changed files with 56 additions and 38 deletions

View File

@@ -108,6 +108,9 @@ export default function ScanAndTasteFlow({ isOpen, onClose, imageFile }: ScanAnd
const startPrep = performance.now(); const startPrep = performance.now();
if (result.success && result.data) { if (result.success && result.data) {
console.log('[ScanFlow] magicScan success'); console.log('[ScanFlow] magicScan success');
if (result.raw) {
console.log('[ScanFlow] RAW AI RESPONSE:', result.raw);
}
setBottleMetadata(result.data); setBottleMetadata(result.data);
const endPrep = performance.now(); const endPrep = performance.now();

View File

@@ -1,40 +1,46 @@
export const getSystemPrompt = (availableTags: string, language: string) => ` export const getSystemPrompt = (availableTags: string, language: string) => `
You are a sommelier and database clerk for a premium whisky vault. Analyze the bottle image. TASK: Analyze this whisky bottle image. Return raw JSON.
PART 1: METADATA EXTRACTION STEP 1: IDENTIFICATION (OCR & EXTRACTION)
Extract precise metadata. If the image is NOT a whisky bottle or if you are very unsure, set "is_whisky" to false and provide a low "confidence" score. Extract exact text and details from the label. Look closely for specific dates and codes.
Use null for any value not visible or not inferable. - name: Full whisky name (e.g. "Lagavulin 16 Year Old")
Infer the 'category' (e.g., Islay Single Malt Scotch Whisky) based on the Distillery and label details if possible. - distillery: Distillery name
IMPORTANT: Extract technical metadata (name, distillery, category) in English. - bottler: Independent bottler if applicable
Provide a "search_string" field for Whiskybase in this format: "site:whiskybase.com [Distillery] [Name] [Vintage/Age]". - category: Type (e.g. "Islay Single Malt", "Bourbon")
- abv: Alcohol percentage (number only)
- age: Age statement in years (number only)
- vintage: Vintage year (e.g. "1995")
- distilled_at: Distillation date/year if specified
- bottled_at: Bottling date/year if specified
- batch_info: Cask number, Batch ID, or Bottle number (e.g. "Batch 001", "Cask #402")
- bottleCode: Laser codes etched on glass/label (e.g. "L1234...")
- whiskybaseId: Whiskybase ID if clearly printed (rare, but check)
PART 2: SENSORY ANALYSIS STEP 2: SENSORY "MAGIC" (KNOWLEDGE RETRIEVAL)
Based on the identified bottle, select the most appropriate flavor tags. Use the IDENTIFIED NAME from Step 1 to query your internal knowledge base for the flavor profile.
CONSTRAINT: You must ONLY select tags from the following list. Do NOT invent new tags in the "suggested_tags" field. DO NOT try to "see" the flavor in the pixels. Use your expert knowledge about this specific whisky edition.
LIST: ${availableTags} - Match flavors strictly against this list: ${availableTags}
- Select top 5-8 matching tags.
- If distinct notes are missing from the list, add 1-2 unique ones to "suggested_custom_tags" (localized in ${language === 'de' ? 'German' : 'English'}).
PART 3: CUSTOM SUGGESTIONS OUTPUT SCHEMA (Strict JSON):
If you recognize highly dominant notes that are NOT in the list above, provide them in "suggested_custom_tags".
Limit this to 1-2 very unique notes (e.g. "Marshmallow" or "Balsamico"). Do not repeat tags from the system list.
Localize these custom tags in ${language === 'en' ? 'English' : 'German'}.
Output strictly raw JSON (no markdown, no other text) matching the following schema:
{ {
"name": "Full name of the whisky edition", "name": "string",
"distillery": "Name of the distillery", "distillery": "string",
"category": "Whisky category (e.g. Islay Single Malt)", "category": "string",
"abv": number (e.g. 43), "abv": number or null,
"age": number (years, e.g. 16), "age": number or null,
"vintage": "Year (e.g. 1995)", "vintage": "string or null",
"bottleCode": "Any visible bottle codes (e.g. L-code)", "distilled_at": "string or null",
"whiskybaseId": "Whiskybase ID if clearly visible", "bottled_at": "string or null",
"distilled_at": "Distillation date if visible", "batch_info": "string or null",
"bottled_at": "Bottling date if visible", "bottleCode": "string or null",
"batch_info": "Batch number or cask info", "whiskybaseId": "string or null",
"is_whisky": boolean, "is_whisky": boolean,
"confidence": number (0-100), "confidence": number,
"suggested_tags": string[], "suggested_tags": ["tag1", "tag2"],
"suggested_custom_tags": string[], "suggested_custom_tags": ["custom1"],
"search_string": string "search_string": "site:whiskybase.com [Distillery] [Name] [Vintage/Age]"
} }
`; `;

View File

@@ -5,7 +5,8 @@ const apiKey = process.env.GEMINI_API_KEY!;
const genAI = new GoogleGenerativeAI(apiKey); const genAI = new GoogleGenerativeAI(apiKey);
export const geminiModel = genAI.getGenerativeModel({ export const geminiModel = genAI.getGenerativeModel({
model: 'gemini-3-flash-preview', //model: 'gemini-3-flash-preview',
model: 'gemini-2.5-flash',
generationConfig: { generationConfig: {
responseMimeType: 'application/json', responseMimeType: 'application/json',
}, },

View File

@@ -115,6 +115,7 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
} }
if (Array.isArray(jsonData)) jsonData = jsonData[0]; if (Array.isArray(jsonData)) jsonData = jsonData[0];
console.log('[Mistral AI] JSON Response:', jsonData);
// Extract search_string before validation // Extract search_string before validation
const searchString = jsonData.search_string; const searchString = jsonData.search_string;
@@ -156,7 +157,8 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
apiDuration: endApi - startApi, apiDuration: endApi - startApi,
parseDuration: endParse - startParse, parseDuration: endParse - startParse,
uploadSize: uploadSize uploadSize: uploadSize
} },
raw: jsonData
}; };
} catch (error) { } catch (error) {

View File

@@ -115,6 +115,7 @@ export async function analyzeBottle(input: any): Promise<AnalysisResponse> {
} }
if (Array.isArray(jsonData)) jsonData = jsonData[0]; if (Array.isArray(jsonData)) jsonData = jsonData[0];
console.log('[Gemini AI] JSON Response:', jsonData);
const searchString = jsonData.search_string; const searchString = jsonData.search_string;
delete jsonData.search_string; delete jsonData.search_string;
@@ -149,7 +150,8 @@ export async function analyzeBottle(input: any): Promise<AnalysisResponse> {
apiDuration: endApi - startApi, apiDuration: endApi - startApi,
parseDuration: endParse - startParse, parseDuration: endParse - startParse,
uploadSize: uploadSize uploadSize: uploadSize
} },
raw: jsonData
} as any; } as any;
} catch (error) { } catch (error) {

View File

@@ -80,7 +80,8 @@ export async function magicScan(input: any): Promise<AnalysisResponse & { wb_id?
return { return {
...aiResponse, ...aiResponse,
wb_id: cacheHit.wb_id, wb_id: cacheHit.wb_id,
perf: aiResponse.perf perf: aiResponse.perf,
raw: aiResponse.raw
}; };
} }
@@ -108,14 +109,16 @@ export async function magicScan(input: any): Promise<AnalysisResponse & { wb_id?
return { return {
...aiResponse, ...aiResponse,
wb_id: braveResult.id, wb_id: braveResult.id,
perf: aiResponse.perf perf: aiResponse.perf,
raw: aiResponse.raw
}; };
} }
return { return {
...aiResponse, ...aiResponse,
wb_id: undefined, wb_id: undefined,
perf: aiResponse.perf perf: aiResponse.perf,
raw: aiResponse.raw
}; };
} catch (error) { } catch (error) {

View File

@@ -101,4 +101,5 @@ export interface AnalysisResponse {
parseDuration: number; parseDuration: number;
uploadSize: number; uploadSize: number;
}; };
raw?: any;
} }