datafreak commited on
Commit
39bf3ec
·
verified ·
1 Parent(s): b0f9b51

prototype (initial looks)

Browse files
Files changed (2) hide show
  1. app.py +271 -0
  2. requirements.txt +6 -0
app.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ import gradio as gr
4
+ from string import Template
5
+ from typing import List
6
+ from pydantic import BaseModel, Field
7
+
8
+ # ----- Load API Keys -----
9
+ load_dotenv()
10
+ google_api_key = os.getenv('GOOGLE_API_KEY')
11
+ tavily_api_key = os.getenv('TAVILY_API_KEY')
12
+
13
+ # ----- Pydantic Models -----
14
+ class NutritionInfo(BaseModel):
15
+ calories: str = Field(..., description="Total calories in the meal")
16
+ protein: str = Field(..., description="Protein content in grams")
17
+ carbs: str = Field(..., description="Carbohydrates content in grams")
18
+ fat: str = Field(..., description="Fat content in grams")
19
+
20
+ class BudgetMeal(BaseModel):
21
+ meal_name: str = Field(..., description="Name of the meal")
22
+ ingredients: List[str] = Field(..., description="List of ingredients with quantities")
23
+ cooking_steps: List[str] = Field(..., description="Step-by-step cooking instructions")
24
+ nutrition_info: NutritionInfo = Field(..., description="Nutritional breakdown of the meal")
25
+ reason_for_selection: str = Field(..., description="Explanation for why this meal was chosen")
26
+
27
+ class BudgetBasedMeal(BaseModel):
28
+ low_budget: BudgetMeal = Field(..., description="Low-budget version of the meal")
29
+ medium_budget: BudgetMeal = Field(..., description="Medium-budget version of the meal")
30
+ high_budget: BudgetMeal = Field(..., description="High-budget version of the meal")
31
+
32
+ class ThreeMealPlan(BaseModel):
33
+ meal_type: str
34
+ breakfast: BudgetBasedMeal
35
+ lunch: BudgetBasedMeal
36
+ dinner: BudgetBasedMeal
37
+
38
+ class FourMealPlan(BaseModel):
39
+ meal_type: str
40
+ breakfast: BudgetBasedMeal
41
+ lunch: BudgetBasedMeal
42
+ snack: BudgetBasedMeal
43
+ dinner: BudgetBasedMeal
44
+
45
+ class IntermittentFastingPlan(BaseModel):
46
+ meal_type: str
47
+ lunch: BudgetBasedMeal
48
+ dinner: BudgetBasedMeal
49
+
50
+ # ----- Prompt Template -----
51
+ prompt_template = Template(
52
+ """
53
+ You are a top nutritionist. Based on the user's profile, create a personalized one-day meal plan. The user follows the "$meal_plan_type" pattern, so design the plan accordingly.
54
+
55
+ Meal Plan Type Guide:
56
+ - "3 meals/day" → breakfast, lunch, dinner
57
+ - "4 meals/day" → breakfast, lunch, snack, dinner
58
+ - "Intermittent fasting (2 meals)" → lunch, dinner
59
+
60
+ Each meal should have **3 flexible options**:
61
+ - low budget
62
+ - medium budget
63
+ - high budget
64
+
65
+ Each option must include:
66
+ - Meal name
67
+ - Ingredients
68
+ - Cooking steps
69
+ - Basic nutrition info (calories, protein, carbs, fat)
70
+ - Short reason for choosing this meal based on the user's chosen package
71
+
72
+ User Profile:
73
+ - Age group: $age_group
74
+ - Height: $height inches
75
+ - Weight: $weight lbs
76
+ - Gender: $gender
77
+ - Meal Plan Type: $meal_plan_type
78
+ - Dietary Preference: $dietary_preference
79
+ - Allergies or restrictions: $allergies
80
+ - Goal/package: $package
81
+
82
+ Output format must be clean and JSON-like, without extra keys. Just the meals as per plan type with 3 budget-based options each.
83
+ """
84
+ )
85
+
86
+ # ----- Tavily Tool -----
87
+ from langchain_tavily import TavilySearch
88
+ tavily_tool = TavilySearch(
89
+ max_results=20,
90
+ topic="general",
91
+ include_answer=True,
92
+ include_raw_content=True,
93
+ search_depth="advanced",
94
+ tavily_api_key=tavily_api_key,
95
+ include_domains=[
96
+ "https://www.nutritionvalue.org/",
97
+ "https://www.walmart.com/search?q=",
98
+ "https://www.healthline.com/nutrition",
99
+ "https://www.healthline.com/nutrition/meal-kits",
100
+ "https://www.healthline.com/nutrition/meal-kits/diets",
101
+ "https://www.healthline.com/nutrition/special-diets",
102
+ "https://www.healthline.com/nutrition/healthy-eating",
103
+ "https://www.healthline.com/nutrition/food-freedom",
104
+ "https://www.healthline.com/nutrition/feel-good-food",
105
+ "https://www.healthline.com/nutrition/products",
106
+ "https://www.healthline.com/nutrition/vitamins-supplements",
107
+ "https://www.healthline.com/nutrition/sustain",
108
+ ],
109
+ )
110
+
111
+ # ----- LLM + Agents -----
112
+ from langchain_google_genai import ChatGoogleGenerativeAI
113
+ llm = ChatGoogleGenerativeAI(model="gemini-2.5-pro-exp-03-25", google_api_key=google_api_key)
114
+
115
+ from langgraph.prebuilt import create_react_agent
116
+ agent_3_meals = create_react_agent(
117
+ llm,
118
+ tools=[tavily_tool],
119
+ response_format=ThreeMealPlan
120
+ )
121
+
122
+ agent_4_meals = create_react_agent(
123
+ llm,
124
+ tools=[tavily_tool],
125
+ response_format=FourMealPlan
126
+ )
127
+
128
+ agent_intermittent = create_react_agent(
129
+ llm,
130
+ tools=[tavily_tool],
131
+ response_format=IntermittentFastingPlan
132
+ )
133
+
134
+ # ----- Render Functions -----
135
+ def render_meal(meal: BudgetMeal, budget_label: str) -> str:
136
+ ingredients_str = "\n- ".join(meal.ingredients)
137
+ steps_str = "\n1. ".join(meal.cooking_steps)
138
+ return (
139
+ f"### {budget_label} Option\n\n"
140
+ f"**🍽️ Meal Name**: {meal.meal_name}\n\n"
141
+ f"**📝 Ingredients**:\n- {ingredients_str}\n\n"
142
+ f"**����‍🍳 Cooking Steps**:\n1. {steps_str}\n\n"
143
+ f"**🍎 Nutrition Info**:\n"
144
+ f"- Calories: {meal.nutrition_info.calories}\n"
145
+ f"- Protein: {meal.nutrition_info.protein}\n"
146
+ f"- Carbs: {meal.nutrition_info.carbs}\n"
147
+ f"- Fat: {meal.nutrition_info.fat}\n\n"
148
+ f"**💡 Why this meal?**\n{meal.reason_for_selection}\n"
149
+ )
150
+
151
+ def render_budget_meal(meal_obj: BudgetBasedMeal, meal_type: str) -> str:
152
+ return (
153
+ f"## 🍱 {meal_type.title()}\n\n"
154
+ f"{render_meal(meal_obj.low_budget, 'Low Budget')}\n"
155
+ f"{render_meal(meal_obj.medium_budget, 'Medium Budget')}\n"
156
+ f"{render_meal(meal_obj.high_budget, 'High Budget')}\n"
157
+ )
158
+
159
+ def format_three_meal_plan(plan: ThreeMealPlan) -> str:
160
+ return (
161
+ f"# 🧾 Meal Plan: {plan.meal_type}\n\n"
162
+ f"{render_budget_meal(plan.breakfast, 'Breakfast')}\n"
163
+ f"{render_budget_meal(plan.lunch, 'Lunch')}\n"
164
+ f"{render_budget_meal(plan.dinner, 'Dinner')}\n"
165
+ )
166
+
167
+ def format_four_meal_plan(plan: FourMealPlan) -> str:
168
+ return (
169
+ f"# 🧾 Meal Plan: {plan.meal_type}\n\n"
170
+ f"{render_budget_meal(plan.breakfast, 'Breakfast')}\n"
171
+ f"{render_budget_meal(plan.lunch, 'Lunch')}\n"
172
+ f"{render_budget_meal(plan.snack, 'Snack')}\n"
173
+ f"{render_budget_meal(plan.dinner, 'Dinner')}\n"
174
+ )
175
+
176
+ def format_if_plan(plan: IntermittentFastingPlan) -> str:
177
+ return (
178
+ f"# 🧾 Meal Plan: {plan.meal_type}\n\n"
179
+ f"{render_budget_meal(plan.lunch, 'Lunch')}\n"
180
+ f"{render_budget_meal(plan.dinner, 'Dinner')}\n"
181
+ )
182
+
183
+ # ----- Generate Meal Plan Function -----
184
+ def generate_meal_plan(age_group, feet, inches, weight, gender, meal_plan_type, dietary_preference, allergies, package):
185
+ # Compute total height in inches:
186
+ total_height = int(feet) * 12 + int(inches)
187
+ user_input = {
188
+ "age_group": age_group,
189
+ "height": str(total_height),
190
+ "weight": str(weight),
191
+ "gender": gender,
192
+ "meal_plan_type": meal_plan_type,
193
+ "dietary_preference": dietary_preference,
194
+ "allergies": allergies,
195
+ "package": package
196
+ }
197
+ filled_prompt = prompt_template.substitute(**user_input)
198
+ inputs = {"messages": [("user", filled_prompt)]}
199
+
200
+ if meal_plan_type.startswith("3 meals/day"):
201
+ result = agent_3_meals.invoke(inputs)["structured_response"]
202
+ formatted = format_three_meal_plan(result)
203
+ elif meal_plan_type.startswith("4 meals/day"):
204
+ result = agent_4_meals.invoke(inputs)["structured_response"]
205
+ formatted = format_four_meal_plan(result)
206
+ else: # Intermittent Fasting
207
+ result = agent_intermittent.invoke(inputs)["structured_response"]
208
+ formatted = format_if_plan(result)
209
+
210
+ return formatted
211
+
212
+ # ----- Gradio UI -----
213
+ demo = gr.Blocks()
214
+
215
+ with demo:
216
+ gr.Markdown("## 🍽️ Personalized Meal Plan Generator")
217
+ with gr.Row():
218
+ age_group = gr.Dropdown(choices=["18-24", "25-30", "31-40", "41-50", "51+"], label="Age Group")
219
+ gender = gr.Dropdown(choices=["male", "female", "other"], label="Gender")
220
+ with gr.Row():
221
+ feet = gr.Number(label="Height (feet)")
222
+ inches = gr.Number(label="Height (inches)")
223
+ weight = gr.Number(label="Weight (lbs)")
224
+ meal_plan_type = gr.Radio(
225
+ choices=[
226
+ "3 meals/day (Breakfast, Lunch, Dinner)",
227
+ "4 meals/day (Breakfast, Lunch, Snack, Dinner)",
228
+ "Intermittent fasting (2 meals)"
229
+ ],
230
+ label="Meal Plan Type"
231
+ )
232
+ dietary_preference = gr.Dropdown(
233
+ choices=["Keto", "Vegan", "Vegetarian", "Low-Carb", "High-Protein", "Balanced"],
234
+ label="Dietary Preference"
235
+ )
236
+ allergies = gr.Textbox(label="Allergies or Restrictions (e.g., Gluten, Dairy, Nuts or 'None')")
237
+ package = gr.Dropdown(
238
+ choices=["Fitness and Mobility", "Focus Flow", "No More Insomnia"],
239
+ label="Goal/Package"
240
+ )
241
+
242
+ with gr.Row():
243
+ # Add a status indicator
244
+ status_indicator = gr.Markdown("Status: Ready")
245
+
246
+ generate_btn = gr.Button("Generate Meal Plan")
247
+ output_display = gr.Markdown()
248
+
249
+ # Add the loading indicator logic
250
+ def generate_with_loading(age_group, feet, inches, weight, gender, meal_plan_type, dietary_preference, allergies, package):
251
+ # Return a loading message for the status indicator
252
+ return "Status: Generating your meal plan... This may take a moment! ⏳"
253
+
254
+ def finalize_generation(age_group, feet, inches, weight, gender, meal_plan_type, dietary_preference, allergies, package):
255
+ # Generate the meal plan
256
+ result = generate_meal_plan(age_group, feet, inches, weight, gender, meal_plan_type, dietary_preference, allergies, package)
257
+ # Update the status indicator
258
+ return "Status: Ready", result
259
+
260
+ # Connect the button click to both functions in sequence
261
+ generate_btn.click(
262
+ fn=generate_with_loading,
263
+ inputs=[age_group, feet, inches, weight, gender, meal_plan_type, dietary_preference, allergies, package],
264
+ outputs=status_indicator,
265
+ ).then(
266
+ fn=finalize_generation,
267
+ inputs=[age_group, feet, inches, weight, gender, meal_plan_type, dietary_preference, allergies, package],
268
+ outputs=[status_indicator, output_display],
269
+ )
270
+
271
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ langchain-google-genai
2
+ langchain-tavily
3
+ python-dotenv
4
+ langchain-core
5
+ langgraph
6
+ gradio