This commit is contained in:
Giancarmine Salucci
2025-12-21 02:03:05 +01:00
parent 167cd1f4bb
commit 9357bd483a
36 changed files with 6251 additions and 1547 deletions

130
src/lib/server/parser.ts Normal file
View File

@@ -0,0 +1,130 @@
import { createLLM } from './llm';
import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod';
const RecipeSchema = z.object({
name: z.string(),
servings: z.number().nullable(),
description: z.string().nullable(),
ingredients: z.array(
z.object({
item: z.string(),
amount: z.string(),
unit: z.string()
})
).nullable(),
steps: z.array(z.string()).nullable(),
image: z.string().nullable().optional()
});
export type Recipe = z.infer<typeof RecipeSchema>;
/**
* Detect if the text contains a recipe using binary classification
* @param text - The text to analyze
* @returns True if a recipe is detected, false otherwise
*/
export async function detectRecipe(text: string): Promise<boolean> {
try {
const { client, model } = createLLM();
const detectionResponse = await client.chat.completions.create({
model,
messages: [
{
role: 'system',
content:
"You are a recipe detector. Answer with ONLY 'yes' or 'no' - nothing else. A recipe MUST have: (1) name/title, (2) ingredients with quantities, (3) numbered cooking steps. If ANY are missing, answer 'no'."
},
{
role: 'user',
content: `Does this text contain a recipe?\n\n${text}`
}
],
max_tokens: 10
});
const detectionResult = detectionResponse.choices[0].message.content?.toLowerCase() ?? '';
return detectionResult.includes('yes');
} catch (e) {
console.error('Recipe detection error:', e);
throw new Error('Failed to detect recipe');
}
}
/**
* Extract recipe data from text using LLM structured output
* @param text - The text containing the recipe
* @returns Parsed recipe object
*/
export async function parseRecipe(text: string): Promise<Recipe> {
try {
const { client, model } = createLLM();
const completion = await client.beta.chat.completions.parse({
model,
messages: [
{
role: 'system',
content: `You are a RECIPE EXTRACTOR. Extract the recipe from the provided text.
✅ REQUIREMENTS:
1. Extract the exact recipe name from the text
2. List all ingredients with their quantities and units
3. List all cooking steps in order
4. Translate everything to Italian
5. Convert measurements to SI units (g, mL, °C)
📋 CONVERSION TABLE:
- 1 cup = 240 mL, 1 tbsp = 15 mL, 1 tsp = 5 mL
- 1 oz = 28.35 g, 1 lb = 453.59 g
- 1 stick butter = 113 g
- °F→°C: (°F32)×5/9
🔄 OUTPUT FORMAT:
{
"name": "recipe name in Italian",
"servings": number or null,
"description": "description in Italian or null",
"ingredients": [{"item": "ingredient name", "amount": "quantity", "unit": "SI unit"}],
"steps": ["1. First step", "2. Second step", ...]
}
Extract ONLY what's explicitly in the text. Be accurate and literal.
`
},
{
role: 'user',
content: `Extract the recipe from this text:\n\n${text}`
}
],
response_format: zodResponseFormat(RecipeSchema, 'recipe')
});
const recipe = completion.choices[0].message.parsed;
if (!recipe || !recipe.name) {
throw new Error('Failed to extract recipe - missing name');
}
return recipe;
} catch (e) {
console.error('Recipe parsing error:', e);
throw new Error('Failed to parse recipe');
}
}
/**
* Complete workflow: detect recipe and parse if found
* @param text - The text to analyze
* @returns Parsed recipe object if detected, null otherwise
*/
export async function extractRecipe(text: string): Promise<Recipe | null> {
const isRecipe = await detectRecipe(text);
if (!isRecipe) {
return null;
}
return parseRecipe(text);
}