fix
This commit is contained in:
130
src/lib/server/parser.ts
Normal file
130
src/lib/server/parser.ts
Normal 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: (°F–32)×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);
|
||||
}
|
||||
Reference in New Issue
Block a user