feat: log raw AI JSON response to browser console
This commit is contained in:
@@ -108,6 +108,9 @@ export default function ScanAndTasteFlow({ isOpen, onClose, imageFile }: ScanAnd
|
||||
const startPrep = performance.now();
|
||||
if (result.success && result.data) {
|
||||
console.log('[ScanFlow] magicScan success');
|
||||
if (result.raw) {
|
||||
console.log('[ScanFlow] RAW AI RESPONSE:', result.raw);
|
||||
}
|
||||
setBottleMetadata(result.data);
|
||||
|
||||
const endPrep = performance.now();
|
||||
|
||||
@@ -1,40 +1,46 @@
|
||||
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
|
||||
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.
|
||||
Use null for any value not visible or not inferable.
|
||||
Infer the 'category' (e.g., Islay Single Malt Scotch Whisky) based on the Distillery and label details if possible.
|
||||
IMPORTANT: Extract technical metadata (name, distillery, category) in English.
|
||||
Provide a "search_string" field for Whiskybase in this format: "site:whiskybase.com [Distillery] [Name] [Vintage/Age]".
|
||||
STEP 1: IDENTIFICATION (OCR & EXTRACTION)
|
||||
Extract exact text and details from the label. Look closely for specific dates and codes.
|
||||
- name: Full whisky name (e.g. "Lagavulin 16 Year Old")
|
||||
- distillery: Distillery name
|
||||
- bottler: Independent bottler if applicable
|
||||
- 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
|
||||
Based on the identified bottle, select the most appropriate flavor tags.
|
||||
CONSTRAINT: You must ONLY select tags from the following list. Do NOT invent new tags in the "suggested_tags" field.
|
||||
LIST: ${availableTags}
|
||||
STEP 2: SENSORY "MAGIC" (KNOWLEDGE RETRIEVAL)
|
||||
Use the IDENTIFIED NAME from Step 1 to query your internal knowledge base for the flavor profile.
|
||||
DO NOT try to "see" the flavor in the pixels. Use your expert knowledge about this specific whisky edition.
|
||||
- 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
|
||||
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:
|
||||
OUTPUT SCHEMA (Strict JSON):
|
||||
{
|
||||
"name": "Full name of the whisky edition",
|
||||
"distillery": "Name of the distillery",
|
||||
"category": "Whisky category (e.g. Islay Single Malt)",
|
||||
"abv": number (e.g. 43),
|
||||
"age": number (years, e.g. 16),
|
||||
"vintage": "Year (e.g. 1995)",
|
||||
"bottleCode": "Any visible bottle codes (e.g. L-code)",
|
||||
"whiskybaseId": "Whiskybase ID if clearly visible",
|
||||
"distilled_at": "Distillation date if visible",
|
||||
"bottled_at": "Bottling date if visible",
|
||||
"batch_info": "Batch number or cask info",
|
||||
"name": "string",
|
||||
"distillery": "string",
|
||||
"category": "string",
|
||||
"abv": number or null,
|
||||
"age": number or null,
|
||||
"vintage": "string or null",
|
||||
"distilled_at": "string or null",
|
||||
"bottled_at": "string or null",
|
||||
"batch_info": "string or null",
|
||||
"bottleCode": "string or null",
|
||||
"whiskybaseId": "string or null",
|
||||
"is_whisky": boolean,
|
||||
"confidence": number (0-100),
|
||||
"suggested_tags": string[],
|
||||
"suggested_custom_tags": string[],
|
||||
"search_string": string
|
||||
"confidence": number,
|
||||
"suggested_tags": ["tag1", "tag2"],
|
||||
"suggested_custom_tags": ["custom1"],
|
||||
"search_string": "site:whiskybase.com [Distillery] [Name] [Vintage/Age]"
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
@@ -5,7 +5,8 @@ const apiKey = process.env.GEMINI_API_KEY!;
|
||||
const genAI = new GoogleGenerativeAI(apiKey);
|
||||
|
||||
export const geminiModel = genAI.getGenerativeModel({
|
||||
model: 'gemini-3-flash-preview',
|
||||
//model: 'gemini-3-flash-preview',
|
||||
model: 'gemini-2.5-flash',
|
||||
generationConfig: {
|
||||
responseMimeType: 'application/json',
|
||||
},
|
||||
|
||||
@@ -115,6 +115,7 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
|
||||
}
|
||||
|
||||
if (Array.isArray(jsonData)) jsonData = jsonData[0];
|
||||
console.log('[Mistral AI] JSON Response:', jsonData);
|
||||
|
||||
// Extract search_string before validation
|
||||
const searchString = jsonData.search_string;
|
||||
@@ -156,7 +157,8 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
}
|
||||
},
|
||||
raw: jsonData
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -115,6 +115,7 @@ export async function analyzeBottle(input: any): Promise<AnalysisResponse> {
|
||||
}
|
||||
|
||||
if (Array.isArray(jsonData)) jsonData = jsonData[0];
|
||||
console.log('[Gemini AI] JSON Response:', jsonData);
|
||||
|
||||
const searchString = jsonData.search_string;
|
||||
delete jsonData.search_string;
|
||||
@@ -149,7 +150,8 @@ export async function analyzeBottle(input: any): Promise<AnalysisResponse> {
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
}
|
||||
},
|
||||
raw: jsonData
|
||||
} as any;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -80,7 +80,8 @@ export async function magicScan(input: any): Promise<AnalysisResponse & { wb_id?
|
||||
return {
|
||||
...aiResponse,
|
||||
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 {
|
||||
...aiResponse,
|
||||
wb_id: braveResult.id,
|
||||
perf: aiResponse.perf
|
||||
perf: aiResponse.perf,
|
||||
raw: aiResponse.raw
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...aiResponse,
|
||||
wb_id: undefined,
|
||||
perf: aiResponse.perf
|
||||
perf: aiResponse.perf,
|
||||
raw: aiResponse.raw
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -101,4 +101,5 @@ export interface AnalysisResponse {
|
||||
parseDuration: number;
|
||||
uploadSize: number;
|
||||
};
|
||||
raw?: any;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user