Skip to content

Commit

Permalink
refactor: remove unused ingredient validation and extraction logic; s…
Browse files Browse the repository at this point in the history
…treamline recipe generation process
  • Loading branch information
Glider6014 committed Nov 26, 2024
1 parent 63fe96d commit b849577
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 412 deletions.
33 changes: 24 additions & 9 deletions app/api/recipes/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { NextRequest, NextResponse } from "next/server";
import { getServerSessionAuth } from "@/lib/nextauth";
import { generateRecipes } from "@/lib/Recipe/generateRecipes";
import connectDB from "@/lib/connectToDatabase";
import { extractIngredients } from "@/lib/langchain/extractIngredients";
import { generateIngredient } from "@/lib/Ingredients/generateIngredeints";
import mongoose from "mongoose";
import RecipeModel from "@/models/Recipe";

export async function POST(req: NextRequest) {
const session = await getServerSessionAuth();

Expand All @@ -28,19 +29,31 @@ export async function POST(req: NextRequest) {
}

try {
const ingredients = await extractIngredients(ingredientsInput.toString());
await connectDB();

if (!ingredients.length) {
// Split ingredients string into array and process each ingredient
const ingredientsList = ingredientsInput
.toString()
.split(",")
.map((i: string) => i.trim());
const generatedIngredients = await Promise.all(
ingredientsList.map(async (ingredientName: string) => {
const ingredient = await generateIngredient(ingredientName);
if (!ingredient) {
throw new Error(`Failed to generate ingredient: ${ingredientName}`);
}
return ingredient;
})
);

if (!generatedIngredients.length) {
return NextResponse.json(
{ error: "No ingredients found" },
{ error: "No ingredients could be generated" },
{ status: 400 }
);
}

await connectDB();
await Promise.all(ingredients.map((ing) => ing.save()));

const recipes = await generateRecipes(ingredients, count);
const recipes = await generateRecipes(generatedIngredients, count);

if (!recipes || !recipes.length) {
return NextResponse.json(
Expand All @@ -65,7 +78,9 @@ export async function POST(req: NextRequest) {
} catch (error) {
console.error("Error:", error);
return NextResponse.json(
{ error: "Server error occurred" },
{
error: error instanceof Error ? error.message : "Server error occurred",
},
{ status: 500 }
);
}
Expand Down
122 changes: 104 additions & 18 deletions lib/Ingredients/generateIngredeints.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// lib/langchain/generateIngredient.ts

import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
Expand All @@ -8,16 +6,56 @@ import connectDB from "@/lib/connectToDatabase";
import Ingredient, { IngredientType } from "@/models/Ingredient";

const ingredientSchema = z.object({
name: z.string(),
unit: z.enum(["g", "ml", "piece"]),
name: z.string({
required_error: "Name is required",
}),
unit: z.enum(["g", "ml", "piece"], {
required_error: "Unit is required",
invalid_type_error: "Invalid unit type. Must be 'g', 'ml', or 'piece'",
}),
nutrition: z.object({
calories: z.number(),
protein: z.number(),
fats: z.number(),
carbs: z.number(),
fiber: z.number(),
sugar: z.number(),
sodium: z.number(),
calories: z
.number({
required_error: "Calories value is required",
invalid_type_error: "Calories must be a number",
})
.nonnegative("Calories cannot be negative"),
protein: z
.number({
required_error: "Protein value is required",
invalid_type_error: "Protein must be a number",
})
.nonnegative("Protein cannot be negative"),
fats: z
.number({
required_error: "Fats value is required",
invalid_type_error: "Fats must be a number",
})
.nonnegative("Fats cannot be negative"),
carbs: z
.number({
required_error: "Carbs value is required",
invalid_type_error: "Carbs must be a number",
})
.nonnegative("Carbs cannot be negative"),
fiber: z
.number({
required_error: "Fiber value is required",
invalid_type_error: "Fiber must be a number",
})
.nonnegative("Fiber cannot be negative"),
sugar: z
.number({
required_error: "Sugar value is required",
invalid_type_error: "Sugar must be a number",
})
.nonnegative("Sugar cannot be negative"),
sodium: z
.number({
required_error: "Sodium value is required",
invalid_type_error: "Sodium must be a number",
})
.nonnegative("Sodium cannot be negative"),
}),
});

Expand All @@ -32,9 +70,39 @@ const model = new ChatOpenAI({
const prompt = ChatPromptTemplate.fromTemplate(`
Analyze the given food ingredient and provide its nutritional information.
Required format:
{{
"name": "ingredient name",
"unit": "g" | "ml" | "piece",
"nutrition": {{
"calories": number (per 100g/ml),
"protein": number (g per 100g/ml),
"fats": number (g per 100g/ml),
"carbs": number (g per 100g/ml),
"fiber": number (g per 100g/ml),
"sugar": number (g per 100g/ml),
"sodium": number (mg per 100g/ml)
}}
}}
Example response:
{{
"name": "apple",
"unit": "piece",
"nutrition": {{
"calories": 52,
"protein": 0.3,
"fats": 0.2,
"carbs": 14,
"fiber": 2.4,
"sugar": 10.4,
"sodium": 1
}}
}}
Ingredient to analyze: {ingredient}
{format_instructions}
Respond ONLY with a valid JSON object.
`);

const chain = prompt.pipe(model).pipe(parser);
Expand All @@ -53,23 +121,41 @@ export async function generateIngredient(
return existingIngredient;
}

console.log("Generating nutrition info for:", ingredientName);

const result = await chain.invoke({
ingredient: ingredientName,
format_instructions: parser.getFormatInstructions(),
});

const parsedResult = ingredientSchema.parse(result);
console.log("AI Response:", result);

const parsedResult = JSON.parse(result);
console.log("Parsed Result:", JSON.stringify(parsedResult, null, 2));

const validation = ingredientSchema.safeParse(parsedResult);

if (!validation.success) {
console.error(
"Validation errors:",
validation.error.flatten().fieldErrors
);
return null;
}

const newIngredient = new Ingredient({
name: parsedResult.name.toLowerCase(),
unit: parsedResult.unit,
nutrition: parsedResult.nutrition,
name: validation.data.name.toLowerCase(),
unit: validation.data.unit,
nutrition: validation.data.nutrition,
});

await newIngredient.save();
return newIngredient;
} catch (error) {
console.error("Error generating/saving ingredient:", error);
if (error instanceof z.ZodError) {
console.error("Validation errors:", error.flatten().fieldErrors);
} else {
console.error("Error generating/saving ingredient:", error);
}
return null;
}
}
Expand Down
7 changes: 0 additions & 7 deletions lib/Recipe/generateRecipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,11 @@ export async function generateRecipes(
.map((ing) => `- ${ing.name} (unit: ${ing.unit})`)
.join("\n");

console.log("Generating recipes with prompt:", {
ingredients: ingredientsString,
count,
});

const results = await chain.invoke({
ingredients: ingredientsString,
count,
});

console.log("AI Response:", results);

const parsedResults = JSON.parse(results);
console.log("Parsed Results:", JSON.stringify(parsedResults, null, 2));

Expand Down
5 changes: 0 additions & 5 deletions lib/cleanJsonString.ts

This file was deleted.

Loading

0 comments on commit b849577

Please sign in to comment.