All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 30s
Neue SPRACHE-Sektion weist Gemini explizit darauf hin, dass die Texte ausschliesslich deutsch sind -- Umlaute, deutsche Zutaten, deutsche Masseinheiten als Prior fuer die Zeichen-Rekonstruktion. Soll die "Kontext-Detektiv"-Logik bei handgeschriebenen oder verblassten Rezepten verbessern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
4.2 KiB
TypeScript
95 lines
4.2 KiB
TypeScript
import { z } from 'zod';
|
|
import { SchemaType } from '@google/generative-ai';
|
|
|
|
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.
|
|
|
|
SPRACHE:
|
|
- Die Texte sind ausschließlich auf Deutsch. Nutze deutsches Sprachverständnis (Umlaute ä/ö/ü/ß, deutsche Zutatennamen, deutsche Maßeinheiten) als starken Prior bei der Rekonstruktion unklarer Zeichen. Gib die Ausgabe vollständig auf Deutsch zurück.
|
|
|
|
LOGIK-REGELN FÜR SCHWER LESBARE TEXTE:
|
|
- Handle als "Kontext-Detektiv": Wenn Zeichen unklar sind, nutze kulinarisches Wissen zur Rekonstruktion (z.B. "Pr-se" -> "Prise").
|
|
- Bei absoluter Unleserlichkeit eines Wortes: Nutze "[?]".
|
|
- 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.
|
|
- Zeit: Alle Angaben strikt in Minuten (Integer). "1 Stunde" = 60.
|
|
- Rauschen ignorieren: Keine Werbung, Einleitungstexte oder Bildunterschriften extrahieren.
|
|
|
|
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
|
|
// übergeben; Gemini respektiert die Struktur und liefert valides JSON.
|
|
export const GEMINI_RESPONSE_SCHEMA = {
|
|
type: SchemaType.OBJECT,
|
|
properties: {
|
|
title: { type: SchemaType.STRING, nullable: false },
|
|
servings_default: { type: SchemaType.INTEGER, nullable: true },
|
|
servings_unit: { type: SchemaType.STRING, nullable: true },
|
|
prep_time_min: { type: SchemaType.INTEGER, nullable: true },
|
|
cook_time_min: { type: SchemaType.INTEGER, nullable: true },
|
|
total_time_min: { type: SchemaType.INTEGER, nullable: true },
|
|
ingredients: {
|
|
type: SchemaType.ARRAY,
|
|
items: {
|
|
type: SchemaType.OBJECT,
|
|
properties: {
|
|
quantity: { type: SchemaType.NUMBER, nullable: true },
|
|
unit: { type: SchemaType.STRING, nullable: true },
|
|
name: { type: SchemaType.STRING, nullable: false },
|
|
note: { type: SchemaType.STRING, nullable: true }
|
|
},
|
|
required: ['name']
|
|
}
|
|
},
|
|
steps: {
|
|
type: SchemaType.ARRAY,
|
|
items: {
|
|
type: SchemaType.OBJECT,
|
|
properties: {
|
|
text: { type: SchemaType.STRING, nullable: false }
|
|
},
|
|
required: ['text']
|
|
}
|
|
}
|
|
},
|
|
required: ['title', 'ingredients', 'steps']
|
|
} as const;
|
|
|
|
// Zod-Spiegel des Schemas. .strict() verhindert, dass Gemini zusätzliche Keys
|
|
// unbemerkt durchschmuggelt.
|
|
const ingredientSchema = z
|
|
.object({
|
|
quantity: z.number().nullable(),
|
|
unit: z.string().max(30).nullable(),
|
|
name: z.string().min(1).max(200),
|
|
note: z.string().max(300).nullable()
|
|
})
|
|
.strict();
|
|
|
|
const stepSchema = z
|
|
.object({
|
|
text: z.string().min(1).max(4000)
|
|
})
|
|
.strict();
|
|
|
|
export const extractionResponseSchema = z
|
|
.object({
|
|
title: z.string().min(1).max(200),
|
|
servings_default: z.number().int().nonnegative().nullable(),
|
|
servings_unit: z.string().max(30).nullable(),
|
|
prep_time_min: z.number().int().nonnegative().nullable(),
|
|
cook_time_min: z.number().int().nonnegative().nullable(),
|
|
total_time_min: z.number().int().nonnegative().nullable(),
|
|
ingredients: z.array(ingredientSchema),
|
|
steps: z.array(stepSchema)
|
|
})
|
|
.strict();
|
|
|
|
export type ExtractionResponse = z.infer<typeof extractionResponseSchema>;
|