Spaces:
Sleeping
Sleeping
Commit
Β·
8a8f67f
1
Parent(s):
cb4c2f7
π€ Enhanced chatbot with two-option system and reinforcement learning
Browse files⨠Features:
β’ Two-option chatbot flow: Nutrition recommendations OR Recipe recommendations
β’ Nutrition info with trustworthy sources (NIH, CDC, Nutrition.gov)
β’ User feedback system for reinforcement learning
β’ Personalized recipe recommendations that improve over time
π§ New API endpoints:
β’ /api/chatbot-options - Initial option selection
β’ /api/nutrition-info - Evidence-based nutrition information
β’ /api/user-feedback - Records user feedback for ML improvements
π― This update transforms the chatbot into an intelligent assistant that learns user preferences and provides credible nutrition information.
app.py
CHANGED
|
@@ -47,19 +47,35 @@ class RecipeRequest(BaseModel):
|
|
| 47 |
ingredients: str
|
| 48 |
preferences: Optional[str] = ""
|
| 49 |
max_minutes: int = 30
|
| 50 |
-
|
| 51 |
# Conversation intelligence fields
|
| 52 |
user_id: Optional[str] = None
|
| 53 |
session_id: Optional[str] = None
|
| 54 |
conversation_context: Optional[dict] = None
|
| 55 |
user_preferences: Optional[dict] = None
|
| 56 |
-
|
| 57 |
-
# Personalization fields
|
| 58 |
liked_recipe_ids: List[int] = []
|
| 59 |
disliked_recipe_ids: List[int] = []
|
| 60 |
dietary_restrictions: List[str] = []
|
| 61 |
preferred_cuisines: List[str] = []
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
class DatabaseRecipe(BaseModel):
|
| 64 |
id: int
|
| 65 |
name: str
|
|
@@ -78,6 +94,29 @@ class RecipeResponse(BaseModel):
|
|
| 78 |
query: RecipeRequest
|
| 79 |
error: Optional[str] = None
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
def safe_eval_list(x):
|
| 82 |
"""Safely parse string representations of lists"""
|
| 83 |
if isinstance(x, list):
|
|
@@ -567,6 +606,196 @@ def search_recipes(query_features, request_data=None, top_k=10):
|
|
| 567 |
|
| 568 |
return filtered_df.head(top_k)
|
| 569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
# Load model on startup
|
| 571 |
@app.on_event("startup")
|
| 572 |
async def load_model():
|
|
|
|
| 47 |
ingredients: str
|
| 48 |
preferences: Optional[str] = ""
|
| 49 |
max_minutes: int = 30
|
| 50 |
+
|
| 51 |
# Conversation intelligence fields
|
| 52 |
user_id: Optional[str] = None
|
| 53 |
session_id: Optional[str] = None
|
| 54 |
conversation_context: Optional[dict] = None
|
| 55 |
user_preferences: Optional[dict] = None
|
| 56 |
+
|
| 57 |
+
# Personalization fields
|
| 58 |
liked_recipe_ids: List[int] = []
|
| 59 |
disliked_recipe_ids: List[int] = []
|
| 60 |
dietary_restrictions: List[str] = []
|
| 61 |
preferred_cuisines: List[str] = []
|
| 62 |
|
| 63 |
+
class NutritionRequest(BaseModel):
|
| 64 |
+
query: str
|
| 65 |
+
user_id: Optional[str] = None
|
| 66 |
+
previous_queries: List[str] = []
|
| 67 |
+
|
| 68 |
+
class ChatbotOptionRequest(BaseModel):
|
| 69 |
+
user_input: str
|
| 70 |
+
user_id: Optional[str] = None
|
| 71 |
+
session_id: Optional[str] = None
|
| 72 |
+
|
| 73 |
+
class UserFeedbackRequest(BaseModel):
|
| 74 |
+
user_id: str
|
| 75 |
+
recipe_id: int
|
| 76 |
+
feedback_type: str # "like", "dislike", "save"
|
| 77 |
+
interaction_context: Optional[dict] = None
|
| 78 |
+
|
| 79 |
class DatabaseRecipe(BaseModel):
|
| 80 |
id: int
|
| 81 |
name: str
|
|
|
|
| 94 |
query: RecipeRequest
|
| 95 |
error: Optional[str] = None
|
| 96 |
|
| 97 |
+
class NutritionResponse(BaseModel):
|
| 98 |
+
status: str
|
| 99 |
+
topic: str
|
| 100 |
+
summary: str
|
| 101 |
+
key_points: List[str]
|
| 102 |
+
trusted_sources: List[dict]
|
| 103 |
+
error: Optional[str] = None
|
| 104 |
+
|
| 105 |
+
class ChatbotOptionResponse(BaseModel):
|
| 106 |
+
status: str
|
| 107 |
+
response_type: str # "options", "nutrition", "recipe"
|
| 108 |
+
message: str
|
| 109 |
+
options: Optional[List[str]] = None
|
| 110 |
+
nutrition_info: Optional[dict] = None
|
| 111 |
+
recipes: Optional[List[DatabaseRecipe]] = None
|
| 112 |
+
error: Optional[str] = None
|
| 113 |
+
|
| 114 |
+
class UserFeedbackResponse(BaseModel):
|
| 115 |
+
status: str
|
| 116 |
+
message: str
|
| 117 |
+
updated_preferences: Optional[dict] = None
|
| 118 |
+
error: Optional[str] = None
|
| 119 |
+
|
| 120 |
def safe_eval_list(x):
|
| 121 |
"""Safely parse string representations of lists"""
|
| 122 |
if isinstance(x, list):
|
|
|
|
| 606 |
|
| 607 |
return filtered_df.head(top_k)
|
| 608 |
|
| 609 |
+
# New enhanced chatbot endpoint - option selection
|
| 610 |
+
@app.post("/api/chatbot-options", response_model=ChatbotOptionResponse)
|
| 611 |
+
async def chatbot_options(request: ChatbotOptionRequest):
|
| 612 |
+
"""
|
| 613 |
+
Enhanced chatbot that gives users option between nutrition recommendations and recipes
|
| 614 |
+
"""
|
| 615 |
+
try:
|
| 616 |
+
user_input = request.user_input.lower().strip()
|
| 617 |
+
|
| 618 |
+
# Check if user is asking for specific type of help
|
| 619 |
+
if any(word in user_input for word in ["nutrition", "healthy", "vitamin", "mineral", "diet", "health"]):
|
| 620 |
+
return ChatbotOptionResponse(
|
| 621 |
+
status="success",
|
| 622 |
+
response_type="nutrition",
|
| 623 |
+
message="I can help you with nutrition information! What specific topic would you like to learn about?",
|
| 624 |
+
options=["Vitamins & Minerals", "Heart Health", "Weight Management", "Diabetes Nutrition", "General Nutrition Tips"]
|
| 625 |
+
)
|
| 626 |
+
elif any(word in user_input for word in ["recipe", "cook", "meal", "food", "ingredients"]):
|
| 627 |
+
return ChatbotOptionResponse(
|
| 628 |
+
status="success",
|
| 629 |
+
response_type="recipe",
|
| 630 |
+
message="I can help you find recipes! Tell me what ingredients you have or what type of meal you'd like.",
|
| 631 |
+
options=["Quick Meals (15-30 min)", "Healthy Options", "Comfort Food", "Vegetarian", "Use My Ingredients"]
|
| 632 |
+
)
|
| 633 |
+
else:
|
| 634 |
+
# Initial greeting - present both options
|
| 635 |
+
return ChatbotOptionResponse(
|
| 636 |
+
status="success",
|
| 637 |
+
response_type="options",
|
| 638 |
+
message="Hello! I'm your nutrition and recipe assistant. How can I help you today?",
|
| 639 |
+
options=["π Get nutrition recommendations", "π³ Find recipe recommendations"]
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
except Exception as e:
|
| 643 |
+
return ChatbotOptionResponse(
|
| 644 |
+
status="error",
|
| 645 |
+
response_type="options",
|
| 646 |
+
message="Sorry, I encountered an error. Please try again.",
|
| 647 |
+
error=str(e)
|
| 648 |
+
)
|
| 649 |
+
|
| 650 |
+
# Nutrition information endpoint
|
| 651 |
+
@app.post("/api/nutrition-info", response_model=NutritionResponse)
|
| 652 |
+
async def get_nutrition_info(request: NutritionRequest):
|
| 653 |
+
"""
|
| 654 |
+
Provides nutritional recommendations with trustworthy sources
|
| 655 |
+
"""
|
| 656 |
+
try:
|
| 657 |
+
query = request.query.lower().strip()
|
| 658 |
+
|
| 659 |
+
# Generate nutrition response based on query
|
| 660 |
+
nutrition_info = generate_nutrition_response(query)
|
| 661 |
+
|
| 662 |
+
return NutritionResponse(
|
| 663 |
+
status="success",
|
| 664 |
+
topic=nutrition_info["topic"],
|
| 665 |
+
summary=nutrition_info["summary"],
|
| 666 |
+
key_points=nutrition_info["key_points"],
|
| 667 |
+
trusted_sources=nutrition_info["sources"]
|
| 668 |
+
)
|
| 669 |
+
|
| 670 |
+
except Exception as e:
|
| 671 |
+
return NutritionResponse(
|
| 672 |
+
status="error",
|
| 673 |
+
topic="Error",
|
| 674 |
+
summary="Failed to retrieve nutrition information",
|
| 675 |
+
key_points=[],
|
| 676 |
+
trusted_sources=[],
|
| 677 |
+
error=str(e)
|
| 678 |
+
)
|
| 679 |
+
|
| 680 |
+
# User feedback endpoint for reinforcement learning
|
| 681 |
+
@app.post("/api/user-feedback", response_model=UserFeedbackResponse)
|
| 682 |
+
async def record_user_feedback(request: UserFeedbackRequest):
|
| 683 |
+
"""
|
| 684 |
+
Records user feedback for reinforcement learning improvements
|
| 685 |
+
"""
|
| 686 |
+
try:
|
| 687 |
+
# In a real implementation, this would store feedback in a database
|
| 688 |
+
# For now, we'll log it and return success
|
| 689 |
+
|
| 690 |
+
print(f"π User feedback: User {request.user_id} {request.feedback_type} recipe {request.recipe_id}")
|
| 691 |
+
|
| 692 |
+
# Here you would typically:
|
| 693 |
+
# 1. Store the feedback in a database
|
| 694 |
+
# 2. Update user preference models
|
| 695 |
+
# 3. Trigger retraining of recommendation models
|
| 696 |
+
|
| 697 |
+
return UserFeedbackResponse(
|
| 698 |
+
status="success",
|
| 699 |
+
message=f"Thank you for your feedback! Your {request.feedback_type} has been recorded.",
|
| 700 |
+
updated_preferences={"learning": True}
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
except Exception as e:
|
| 704 |
+
return UserFeedbackResponse(
|
| 705 |
+
status="error",
|
| 706 |
+
message="Failed to record feedback",
|
| 707 |
+
error=str(e)
|
| 708 |
+
)
|
| 709 |
+
|
| 710 |
+
def generate_nutrition_response(query: str) -> dict:
|
| 711 |
+
"""
|
| 712 |
+
Generate nutrition information with trusted sources
|
| 713 |
+
"""
|
| 714 |
+
|
| 715 |
+
# Define trusted sources
|
| 716 |
+
trusted_sources = [
|
| 717 |
+
{
|
| 718 |
+
"title": "Nutrition.gov - Official Nutrition Information",
|
| 719 |
+
"url": "https://www.nutrition.gov/",
|
| 720 |
+
"domain": "nutrition.gov",
|
| 721 |
+
"credibility_score": 0.95
|
| 722 |
+
},
|
| 723 |
+
{
|
| 724 |
+
"title": "NIH Office of Dietary Supplements",
|
| 725 |
+
"url": "https://ods.od.nih.gov/",
|
| 726 |
+
"domain": "nih.gov",
|
| 727 |
+
"credibility_score": 0.98
|
| 728 |
+
},
|
| 729 |
+
{
|
| 730 |
+
"title": "CDC Nutrition Guidelines",
|
| 731 |
+
"url": "https://www.cdc.gov/nutrition/",
|
| 732 |
+
"domain": "cdc.gov",
|
| 733 |
+
"credibility_score": 0.95
|
| 734 |
+
}
|
| 735 |
+
]
|
| 736 |
+
|
| 737 |
+
# Generate topic-specific responses
|
| 738 |
+
if any(word in query for word in ["vitamin", "mineral"]):
|
| 739 |
+
return {
|
| 740 |
+
"topic": "Vitamins and Minerals",
|
| 741 |
+
"summary": "Vitamins and minerals are essential micronutrients that support various body functions including immune health, energy production, and disease prevention.",
|
| 742 |
+
"key_points": [
|
| 743 |
+
"Get nutrients from whole foods when possible",
|
| 744 |
+
"Fat-soluble vitamins (A,D,E,K) are stored in body fat",
|
| 745 |
+
"Water-soluble vitamins (B,C) need regular replenishment",
|
| 746 |
+
"Consult healthcare providers before taking supplements"
|
| 747 |
+
],
|
| 748 |
+
"sources": trusted_sources
|
| 749 |
+
}
|
| 750 |
+
elif any(word in query for word in ["heart", "cardiovascular"]):
|
| 751 |
+
return {
|
| 752 |
+
"topic": "Heart-Healthy Nutrition",
|
| 753 |
+
"summary": "A heart-healthy diet emphasizes fruits, vegetables, whole grains, lean proteins, and healthy fats while limiting saturated fat, trans fat, and sodium.",
|
| 754 |
+
"key_points": [
|
| 755 |
+
"Limit saturated fat to <10% of daily calories",
|
| 756 |
+
"Choose omega-3 rich foods like fish and walnuts",
|
| 757 |
+
"Eat 5-9 servings of fruits and vegetables daily",
|
| 758 |
+
"Limit sodium to 2,300mg per day (1,500mg if at risk)"
|
| 759 |
+
],
|
| 760 |
+
"sources": trusted_sources
|
| 761 |
+
}
|
| 762 |
+
elif any(word in query for word in ["diabetes", "blood sugar"]):
|
| 763 |
+
return {
|
| 764 |
+
"topic": "Diabetes Nutrition Management",
|
| 765 |
+
"summary": "Managing diabetes involves choosing foods that help maintain stable blood sugar levels through balanced meals with appropriate carbohydrates, protein, and healthy fats.",
|
| 766 |
+
"key_points": [
|
| 767 |
+
"Monitor carbohydrate intake and choose complex carbs",
|
| 768 |
+
"Include protein and healthy fats with meals",
|
| 769 |
+
"Eat at consistent times to help manage blood sugar",
|
| 770 |
+
"Stay hydrated and limit sugary beverages"
|
| 771 |
+
],
|
| 772 |
+
"sources": trusted_sources
|
| 773 |
+
}
|
| 774 |
+
elif any(word in query for word in ["weight", "lose", "management"]):
|
| 775 |
+
return {
|
| 776 |
+
"topic": "Weight Management Nutrition",
|
| 777 |
+
"summary": "Healthy weight management focuses on creating a sustainable calorie balance through nutrient-dense foods and portion control.",
|
| 778 |
+
"key_points": [
|
| 779 |
+
"Create a moderate calorie deficit for gradual weight loss",
|
| 780 |
+
"Focus on nutrient-dense, filling foods",
|
| 781 |
+
"Include protein at each meal to support satiety",
|
| 782 |
+
"Stay hydrated and get adequate sleep"
|
| 783 |
+
],
|
| 784 |
+
"sources": trusted_sources
|
| 785 |
+
}
|
| 786 |
+
else:
|
| 787 |
+
return {
|
| 788 |
+
"topic": "General Nutrition Guidelines",
|
| 789 |
+
"summary": "A balanced diet includes a variety of nutrient-dense foods from all food groups, adequate hydration, and appropriate portion sizes.",
|
| 790 |
+
"key_points": [
|
| 791 |
+
"Eat a variety of colorful fruits and vegetables",
|
| 792 |
+
"Choose whole grains over refined grains",
|
| 793 |
+
"Include lean proteins and healthy fats",
|
| 794 |
+
"Limit processed foods and added sugars"
|
| 795 |
+
],
|
| 796 |
+
"sources": trusted_sources
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
# Load model on startup
|
| 800 |
@app.on_event("startup")
|
| 801 |
async def load_model():
|