feat(ai): OCR-Experten-Framing + expliziter User-Prompt
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m18s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m18s
Auf Gemini-Empfehlung: System-Instruction als OCR-Experte fuer kulinarische Dokumente, mit "Kontext-Detektiv"-Regel fuer schwer lesbare Zeichen, "[?]" fuer Unleserliches und strikter "keine Halluzination"-Regel. User-Prompt wird jetzt als eigene text-part bei jedem Call mitgeschickt (Bild + User-Prompt + bei Retry die Korrektur-Note). Inline-Schema aus dem Prompt entfernt, da es mit unserem responseSchema konfligierte (servings vs servings_default+unit, times-nested vs flat, instructions vs steps, kein note-Feld) -- das kann die beobachteten AI_FAILED-Schema-Validation-Fehler beguenstigt haben. Struktur wird jetzt ausschliesslich ueber responseSchema enforced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { GoogleGenerativeAI } from '@google/generative-ai';
|
|||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
import {
|
import {
|
||||||
RECIPE_EXTRACTION_SYSTEM_PROMPT,
|
RECIPE_EXTRACTION_SYSTEM_PROMPT,
|
||||||
|
RECIPE_EXTRACTION_USER_PROMPT,
|
||||||
GEMINI_RESPONSE_SCHEMA,
|
GEMINI_RESPONSE_SCHEMA,
|
||||||
extractionResponseSchema,
|
extractionResponseSchema,
|
||||||
type ExtractionResponse
|
type ExtractionResponse
|
||||||
@@ -84,7 +85,10 @@ async function callGemini(
|
|||||||
|
|
||||||
const parts: Array<
|
const parts: Array<
|
||||||
{ inlineData: { data: string; mimeType: string } } | { text: string }
|
{ inlineData: { data: string; mimeType: string } } | { text: string }
|
||||||
> = [{ inlineData: { data: imageBuffer.toString('base64'), mimeType } }];
|
> = [
|
||||||
|
{ inlineData: { data: imageBuffer.toString('base64'), mimeType } },
|
||||||
|
{ text: RECIPE_EXTRACTION_USER_PROMPT }
|
||||||
|
];
|
||||||
if (appendUserNote) parts.push({ text: appendUserNote });
|
if (appendUserNote) parts.push({ text: appendUserNote });
|
||||||
|
|
||||||
const result = await withTimeout(
|
const result = await withTimeout(
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { SchemaType } from '@google/generative-ai';
|
import { SchemaType } from '@google/generative-ai';
|
||||||
|
|
||||||
export const RECIPE_EXTRACTION_SYSTEM_PROMPT = `Du bist ein Rezept-Extraktions-Assistent.
|
export const RECIPE_EXTRACTION_SYSTEM_PROMPT = `Du bist ein hochpräziser OCR-Experte für kulinarische Dokumente (Rezepte). Deine Aufgabe ist die Extraktion von Rezeptdaten (Titel, Zutaten, Zubereitungsschritte, Zeiten, Portionen) in valides JSON gemäß dem vorgegebenen Schema.
|
||||||
Du bekommst ein Foto eines gedruckten oder handgeschriebenen Rezepts und gibst ein strukturiertes JSON zurück.
|
|
||||||
|
|
||||||
Regeln:
|
LOGIK-REGELN FÜR SCHWER LESBARE TEXTE:
|
||||||
- Extrahiere nur, was tatsächlich auf dem Bild lesbar ist. Sonst Feld auf null (oder leeres Array).
|
- Handle als "Kontext-Detektiv": Wenn Zeichen unklar sind, nutze kulinarisches Wissen zur Rekonstruktion (z.B. "Pr-se" -> "Prise").
|
||||||
- Zutaten: quantity als Zahl (Bruchteile wie ½, ¼, 1 ½ als Dezimalzahl 0.5, 0.25, 1.5), unit separat
|
- Bei absoluter Unleserlichkeit eines Wortes: Nutze "[?]".
|
||||||
(g, ml, l, kg, EL, TL, Stück, Prise, Msp, …).
|
- Halluziniere keine fehlenden Werte: Wenn eine Mengenangabe komplett fehlt, setze 'quantity' auf null. Was nicht auf dem Bild steht, ist null (oder leeres Array).
|
||||||
|
|
||||||
|
FORMATIERUNGS-REGELN:
|
||||||
|
- Zutaten: quantity (Zahl) separat von unit (String). Brüche (½, ¼, 1 ½) strikt in Dezimalzahlen (0.5, 0.25, 1.5).
|
||||||
|
- Einheiten: Normalisiere auf (g, ml, l, kg, EL, TL, Stück, Prise, Msp).
|
||||||
- Zubereitungsschritte: pro erkennbarer Nummerierung oder Absatz EIN Schritt.
|
- Zubereitungsschritte: pro erkennbarer Nummerierung oder Absatz EIN Schritt.
|
||||||
- Zeiten in Minuten (ganze Zahl). "1 Stunde" = 60.
|
- Zeit: Alle Angaben strikt in Minuten (Integer). "1 Stunde" = 60.
|
||||||
- Ignoriere Werbung, Foto-Bildunterschriften, Einleitungstexte. Nur das Rezept selbst.
|
- Rauschen ignorieren: Keine Werbung, Einleitungstexte oder Bildunterschriften extrahieren.
|
||||||
- Denke dir NICHTS dazu aus. Was nicht auf dem Bild steht, ist null.
|
|
||||||
- Antworte ausschließlich im vorgegebenen JSON-Schema. Kein Markdown, kein Prosa-Text.`;
|
STRIKTE ANWEISUNG: Gib ausschließlich das rohe JSON-Objekt gemäß Schema zurück. Kein Markdown-Code-Block, kein Einleitungstext, keine Prosa.`;
|
||||||
|
|
||||||
|
export const RECIPE_EXTRACTION_USER_PROMPT =
|
||||||
|
'Analysiere dieses Bild hochauflösend. Extrahiere alle rezeptrelevanten Informationen gemäß deiner System-Instruktion. Achte besonders auf schwache Handschriften oder verblassten Text und stelle sicher, dass die Zuordnung von Menge zu Zutat logisch korrekt ist.';
|
||||||
|
|
||||||
// Gemini responseSchema (Subset von OpenAPI). Wird an GenerativeModel.generateContent
|
// Gemini responseSchema (Subset von OpenAPI). Wird an GenerativeModel.generateContent
|
||||||
// übergeben; Gemini respektiert die Struktur und liefert valides JSON.
|
// übergeben; Gemini respektiert die Struktur und liefert valides JSON.
|
||||||
|
|||||||
Reference in New Issue
Block a user