truglpk3 commited on
Commit
77eb34e
·
verified ·
1 Parent(s): ee06b79

Upload chatbot

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. chatbot/__init__.py +0 -0
  2. chatbot/__pycache__/__init__.cpython-310.pyc +0 -0
  3. chatbot/__pycache__/config.cpython-310.pyc +0 -0
  4. chatbot/__pycache__/main.cpython-310.pyc +0 -0
  5. chatbot/agents/graphs/__pycache__/chatbot_graph.cpython-310.pyc +0 -0
  6. chatbot/agents/graphs/__pycache__/food_similarity_graph.cpython-310.pyc +0 -0
  7. chatbot/agents/graphs/__pycache__/meal_similarity_graph.cpython-310.pyc +0 -0
  8. chatbot/agents/graphs/__pycache__/meal_suggestion_graph.cpython-310.pyc +0 -0
  9. chatbot/agents/graphs/__pycache__/meal_suggestion_json_graph.cpython-310.pyc +0 -0
  10. chatbot/agents/graphs/chatbot_graph.py +43 -0
  11. chatbot/agents/graphs/food_similarity_graph.py +23 -0
  12. chatbot/agents/graphs/meal_similarity_graph.py +23 -0
  13. chatbot/agents/graphs/meal_suggestion_graph.py +21 -0
  14. chatbot/agents/graphs/meal_suggestion_json_graph.py +23 -0
  15. chatbot/agents/nodes/__pycache__/classify_topic.cpython-310.pyc +0 -0
  16. chatbot/agents/nodes/__pycache__/food_query.cpython-310.pyc +0 -0
  17. chatbot/agents/nodes/__pycache__/general_chat.cpython-310.pyc +0 -0
  18. chatbot/agents/nodes/__pycache__/meal_identify.cpython-310.pyc +0 -0
  19. chatbot/agents/nodes/__pycache__/select_food_plan.cpython-310.pyc +0 -0
  20. chatbot/agents/nodes/__pycache__/suggest_meal_node.cpython-310.pyc +0 -0
  21. chatbot/agents/nodes/classify_topic.py +60 -0
  22. chatbot/agents/nodes/food_query.py +31 -0
  23. chatbot/agents/nodes/functions/__init__.py +25 -0
  24. chatbot/agents/nodes/functions/__pycache__/__init__.cpython-310.pyc +0 -0
  25. chatbot/agents/nodes/functions/__pycache__/enrich_food_with_nutrition.cpython-310.pyc +0 -0
  26. chatbot/agents/nodes/functions/__pycache__/enrich_meal_plan_with_nutrition.cpython-310.pyc +0 -0
  27. chatbot/agents/nodes/functions/__pycache__/enrich_meal_plan_with_nutrition_2.cpython-310.pyc +0 -0
  28. chatbot/agents/nodes/functions/__pycache__/generate_best_food_choice.cpython-310.pyc +0 -0
  29. chatbot/agents/nodes/functions/__pycache__/generate_food_plan.cpython-310.pyc +0 -0
  30. chatbot/agents/nodes/functions/__pycache__/generate_food_similarity.cpython-310.pyc +0 -0
  31. chatbot/agents/nodes/functions/__pycache__/generate_food_similarity_2.cpython-310.pyc +0 -0
  32. chatbot/agents/nodes/functions/__pycache__/generate_meal_plan.cpython-310.pyc +0 -0
  33. chatbot/agents/nodes/functions/__pycache__/generate_meal_plan_day_json.cpython-310.pyc +0 -0
  34. chatbot/agents/nodes/functions/__pycache__/generate_meal_plan_json_2.cpython-310.pyc +0 -0
  35. chatbot/agents/nodes/functions/__pycache__/get_user_profile.cpython-310.pyc +0 -0
  36. chatbot/agents/nodes/functions/enrich_food_with_nutrition.py +54 -0
  37. chatbot/agents/nodes/functions/enrich_meal_plan_with_nutrition.py +72 -0
  38. chatbot/agents/nodes/functions/enrich_meal_plan_with_nutrition_2.py +72 -0
  39. chatbot/agents/nodes/functions/generate_best_food_choice.py +79 -0
  40. chatbot/agents/nodes/functions/generate_food_plan.py +42 -0
  41. chatbot/agents/nodes/functions/generate_food_similarity.py +39 -0
  42. chatbot/agents/nodes/functions/generate_food_similarity_2.py +45 -0
  43. chatbot/agents/nodes/functions/generate_meal_plan.py +66 -0
  44. chatbot/agents/nodes/functions/generate_meal_plan_day_json.py +83 -0
  45. chatbot/agents/nodes/functions/generate_meal_plan_json_2.py +94 -0
  46. chatbot/agents/nodes/functions/get_user_profile.py +35 -0
  47. chatbot/agents/nodes/general_chat.py +40 -0
  48. chatbot/agents/nodes/meal_identify.py +64 -0
  49. chatbot/agents/nodes/select_food_plan.py +51 -0
  50. chatbot/agents/nodes/suggest_meal_node.py +72 -0
chatbot/__init__.py ADDED
File without changes
chatbot/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (183 Bytes). View file
 
chatbot/__pycache__/config.cpython-310.pyc ADDED
Binary file (393 Bytes). View file
 
chatbot/__pycache__/main.cpython-310.pyc ADDED
Binary file (1.66 kB). View file
 
chatbot/agents/graphs/__pycache__/chatbot_graph.cpython-310.pyc ADDED
Binary file (1.32 kB). View file
 
chatbot/agents/graphs/__pycache__/food_similarity_graph.cpython-310.pyc ADDED
Binary file (998 Bytes). View file
 
chatbot/agents/graphs/__pycache__/meal_similarity_graph.cpython-310.pyc ADDED
Binary file (1.02 kB). View file
 
chatbot/agents/graphs/__pycache__/meal_suggestion_graph.cpython-310.pyc ADDED
Binary file (877 Bytes). View file
 
chatbot/agents/graphs/__pycache__/meal_suggestion_json_graph.cpython-310.pyc ADDED
Binary file (1.01 kB). View file
 
chatbot/agents/graphs/chatbot_graph.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import StateGraph, START, END
2
+ from chatbot.agents.states.state import AgentState
3
+
4
+ # Import các node
5
+ from chatbot.agents.nodes.classify_topic import classify_topic, route_by_topic
6
+ from chatbot.agents.nodes.meal_identify import meal_identify
7
+ from chatbot.agents.nodes.suggest_meal_node import suggest_meal_node
8
+ from chatbot.agents.nodes.food_query import food_query
9
+ from chatbot.agents.nodes.select_food_plan import select_food_plan
10
+ from chatbot.agents.nodes.general_chat import general_chat
11
+
12
+ def workflow_chatbot() -> StateGraph:
13
+ workflow_chatbot = StateGraph(AgentState)
14
+
15
+ workflow_chatbot.add_node("classify_topic", classify_topic)
16
+ workflow_chatbot.add_node("meal_identify", meal_identify)
17
+ workflow_chatbot.add_node("suggest_meal_node", suggest_meal_node)
18
+ workflow_chatbot.add_node("food_query", food_query)
19
+ workflow_chatbot.add_node("select_food_plan", select_food_plan)
20
+ workflow_chatbot.add_node("general_chat", general_chat)
21
+
22
+ workflow_chatbot.add_edge(START, "classify_topic")
23
+
24
+ workflow_chatbot.add_conditional_edges(
25
+ "classify_topic",
26
+ route_by_topic,
27
+ {
28
+ "meal_identify": "meal_identify",
29
+ "food_query": "food_query",
30
+ "general_chat": "general_chat",
31
+ }
32
+ )
33
+
34
+ workflow_chatbot.add_edge("meal_identify", "suggest_meal_node")
35
+ workflow_chatbot.add_edge("suggest_meal_node", END)
36
+
37
+ workflow_chatbot.add_edge("food_query", "select_food_plan")
38
+ workflow_chatbot.add_edge("select_food_plan", END)
39
+
40
+ workflow_chatbot.add_edge("general_chat", END)
41
+
42
+ graph = workflow_chatbot.compile()
43
+ return graph
chatbot/agents/graphs/food_similarity_graph.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import StateGraph, START, END
2
+ from chatbot.agents.states.state import AgentState
3
+
4
+ # Import các node
5
+ from chatbot.agents.nodes.functions import get_user_profile, generate_food_similarity, generate_best_food_choice, enrich_food_with_nutrition
6
+
7
+ def food_similarity_graph() -> StateGraph:
8
+ workflow = StateGraph(AgentState)
9
+
10
+ workflow.add_node("Get_User_Profile", get_user_profile)
11
+ workflow.add_node("Generate_Food_Similarity", generate_food_similarity)
12
+ workflow.add_node("Generate_Best_Food_Choice", generate_best_food_choice)
13
+ workflow.add_node("Enrich_Food_With_Nutrition", enrich_food_with_nutrition)
14
+
15
+ workflow.set_entry_point("Get_User_Profile")
16
+
17
+ workflow.add_edge("Get_User_Profile", "Generate_Food_Similarity")
18
+ workflow.add_edge("Generate_Food_Similarity", "Generate_Best_Food_Choice")
19
+ workflow.add_edge("Generate_Best_Food_Choice", "Enrich_Food_With_Nutrition")
20
+ workflow.add_edge("Enrich_Food_With_Nutrition", END)
21
+
22
+ graph = workflow.compile()
23
+ return graph
chatbot/agents/graphs/meal_similarity_graph.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import StateGraph, START, END
2
+ from chatbot.agents.states.state import AgentState
3
+
4
+ # Import các node
5
+ from chatbot.agents.nodes.functions import get_user_profile, generate_food_similarity_2, generate_meal_plan_json_2, enrich_meal_plan_with_nutrition_2
6
+
7
+ def meal_similarity_graph() -> StateGraph:
8
+ workflow = StateGraph(AgentState)
9
+
10
+ workflow.add_node("Get_User_Profile", get_user_profile)
11
+ workflow.add_node("Generate_Food_Similarity_2", generate_food_similarity_2)
12
+ workflow.add_node("Generate_Meal_Plan_Json_2", generate_meal_plan_json_2)
13
+ workflow.add_node("Enrich_Meal_Plan_With_Nutrition_2", enrich_meal_plan_with_nutrition_2)
14
+
15
+ workflow.set_entry_point("Get_User_Profile")
16
+
17
+ workflow.add_edge("Get_User_Profile", "Generate_Food_Similarity_2")
18
+ workflow.add_edge("Generate_Food_Similarity_2", "Generate_Meal_Plan_Json_2")
19
+ workflow.add_edge("Generate_Meal_Plan_Json_2", "Enrich_Meal_Plan_With_Nutrition_2")
20
+ workflow.add_edge("Enrich_Meal_Plan_With_Nutrition_2", END)
21
+
22
+ graph = workflow.compile()
23
+ return graph
chatbot/agents/graphs/meal_suggestion_graph.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import StateGraph, START, END
2
+ from chatbot.agents.states.state import AgentState
3
+
4
+ # Import các node
5
+ from chatbot.agents.nodes.functions import get_user_profile, generate_food_plan, generate_meal_plan
6
+
7
+ def workflow_meal_suggestion() -> StateGraph:
8
+ workflow = StateGraph(AgentState)
9
+
10
+ workflow.add_node("Get_User_Profile", get_user_profile)
11
+ workflow.add_node("Generate_Food_Plan", generate_food_plan)
12
+ workflow.add_node("Generate_Meal_Plan", generate_meal_plan)
13
+
14
+ workflow.set_entry_point("Get_User_Profile")
15
+
16
+ workflow.add_edge("Get_User_Profile", "Generate_Food_Plan")
17
+ workflow.add_edge("Generate_Food_Plan", "Generate_Meal_Plan")
18
+ workflow.add_edge("Generate_Meal_Plan", END)
19
+
20
+ graph = workflow.compile()
21
+ return graph
chatbot/agents/graphs/meal_suggestion_json_graph.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import StateGraph, START, END
2
+ from chatbot.agents.states.state import AgentState
3
+
4
+ # Import các node
5
+ from chatbot.agents.nodes.functions import get_user_profile, generate_food_plan, generate_meal_plan_day_json, enrich_meal_plan_with_nutrition
6
+
7
+ def workflow_meal_suggestion_json() -> StateGraph:
8
+ workflow = StateGraph(AgentState)
9
+
10
+ workflow.add_node("Get_User_Profile", get_user_profile)
11
+ workflow.add_node("Generate_Food_Plan", generate_food_plan)
12
+ workflow.add_node("Generate_Meal_Plan_Day_Json", generate_meal_plan_day_json)
13
+ workflow.add_node("Enrich_Meal_Plan_With_Nutrition", enrich_meal_plan_with_nutrition)
14
+
15
+ workflow.set_entry_point("Get_User_Profile")
16
+
17
+ workflow.add_edge("Get_User_Profile", "Generate_Food_Plan")
18
+ workflow.add_edge("Generate_Food_Plan", "Generate_Meal_Plan_Day_Json")
19
+ workflow.add_edge("Generate_Meal_Plan_Day_Json", "Enrich_Meal_Plan_With_Nutrition")
20
+ workflow.add_edge("Enrich_Meal_Plan_With_Nutrition", END)
21
+
22
+ graph = workflow.compile()
23
+ return graph
chatbot/agents/nodes/__pycache__/classify_topic.cpython-310.pyc ADDED
Binary file (2.46 kB). View file
 
chatbot/agents/nodes/__pycache__/food_query.cpython-310.pyc ADDED
Binary file (1.4 kB). View file
 
chatbot/agents/nodes/__pycache__/general_chat.cpython-310.pyc ADDED
Binary file (1.78 kB). View file
 
chatbot/agents/nodes/__pycache__/meal_identify.cpython-310.pyc ADDED
Binary file (2.72 kB). View file
 
chatbot/agents/nodes/__pycache__/select_food_plan.cpython-310.pyc ADDED
Binary file (2.49 kB). View file
 
chatbot/agents/nodes/__pycache__/suggest_meal_node.cpython-310.pyc ADDED
Binary file (2.48 kB). View file
 
chatbot/agents/nodes/classify_topic.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.prompts import PromptTemplate
2
+ import json
3
+ from pydantic import BaseModel, Field
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.models.llm_setup import llm
6
+
7
+ class Topic(BaseModel):
8
+ name: str = Field(
9
+ description=(
10
+ "Tên chủ đề mà người dùng đang hỏi. "
11
+ "Các giá trị hợp lệ: 'nutrition_analysis', 'meal_suggestion', 'general_chat'."
12
+ )
13
+ )
14
+
15
+ def classify_topic(state: AgentState):
16
+ print("---CLASSIFY TOPIC---")
17
+ llm_with_structure_op = llm.with_structured_output(Topic)
18
+
19
+ prompt = PromptTemplate(
20
+ template="""
21
+ Bạn là bộ phân loại chủ đề câu hỏi người dùng trong hệ thống chatbot dinh dưỡng.
22
+
23
+ Nhiệm vụ:
24
+ - Phân loại câu hỏi vào một trong các nhóm:
25
+ 1. "meal_suggestion": khi người dùng muốn gợi ý thực đơn cho một bữa ăn, khẩu phần, hoặc chế độ ăn (chỉ cho bữa ăn, không cho món ăn đơn lẻ).
26
+ 2. "food_query": khi người dùng tìm kiếm, gợi ý một món ăn hoặc muốn biết thành phần dinh dưỡng của món ăn hoặc khẩu phần cụ thể.
27
+ 3. "general_chat": khi câu hỏi không thuộc hai nhóm trên.
28
+
29
+ Câu hỏi người dùng: {question}
30
+
31
+ Hãy trả lời dưới dạng JSON phù hợp với schema sau:
32
+ {format_instructions}
33
+ """
34
+ )
35
+
36
+ messages = state["messages"]
37
+ user_message = messages[-1].content if messages else state.question
38
+
39
+ format_instructions = json.dumps(llm_with_structure_op.output_schema.model_json_schema(), ensure_ascii=False, indent=2)
40
+
41
+ chain = prompt | llm_with_structure_op
42
+
43
+ topic_result = chain.invoke({
44
+ "question": user_message,
45
+ "format_instructions": format_instructions
46
+ })
47
+
48
+ print("Topic:", topic_result.name)
49
+
50
+ return {"topic": topic_result.name}
51
+
52
+ def route_by_topic(state: AgentState):
53
+ topic = state["topic"]
54
+ if topic == "meal_suggestion":
55
+ return "meal_identify"
56
+ elif topic == "food_query":
57
+ return "food_query"
58
+ else:
59
+ return "general_chat"
60
+
chatbot/agents/nodes/food_query.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from chatbot.agents.states.state import AgentState
2
+ from chatbot.utils.user_profile import get_user_by_id
3
+ from chatbot.agents.tools.food_retriever import food_retriever, food_retriever_top3, query_constructor
4
+
5
+
6
+ def food_query(state: AgentState):
7
+ print("---FOOD QUERY---")
8
+
9
+ user_id = state.get("user_id", {})
10
+ messages = state["messages"]
11
+ user_message = messages[-1].content if messages else state.question
12
+
13
+ user_profile = get_user_by_id(user_id)
14
+
15
+ suggested_meals = []
16
+
17
+ prompt = f"""
18
+ Người dùng có khẩu phần: {user_profile["khẩu phần"]}.
19
+ Câu hỏi: "{user_message}".
20
+ Hãy tìm các món ăn phù hợp với khẩu phần và yêu cầu này, cho phép sai lệch không quá 20%.
21
+ """
22
+
23
+ query_ans = query_constructor.invoke(prompt)
24
+ print(f"🔍 Dạng truy vấn: {food_retriever.structured_query_translator.visit_structured_query(structured_query=query_ans)}")
25
+ foods = food_retriever_top3.invoke(prompt)
26
+ print(f"🔍 Kết quả truy vấn: ")
27
+ for i, food in enumerate(foods):
28
+ print(f"{i} - {food.metadata['name']}")
29
+ suggested_meals.append(food)
30
+
31
+ return {"suggested_meals": suggested_meals, "user_profile": user_profile}
chatbot/agents/nodes/functions/__init__.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .enrich_food_with_nutrition import enrich_food_with_nutrition
2
+ from .enrich_meal_plan_with_nutrition import enrich_meal_plan_with_nutrition
3
+ from .enrich_meal_plan_with_nutrition_2 import enrich_meal_plan_with_nutrition_2
4
+ from .generate_best_food_choice import generate_best_food_choice
5
+ from .generate_food_plan import generate_food_plan
6
+ from .generate_food_similarity import generate_food_similarity
7
+ from .generate_food_similarity_2 import generate_food_similarity_2
8
+ from .generate_meal_plan_day_json import generate_meal_plan_day_json
9
+ from .generate_meal_plan_json_2 import generate_meal_plan_json_2
10
+ from .generate_meal_plan import generate_meal_plan
11
+ from .get_user_profile import get_user_profile
12
+
13
+ __all__ = [
14
+ "enrich_food_with_nutrition",
15
+ "enrich_meal_plan_with_nutrition",
16
+ "enrich_meal_plan_with_nutrition_2",
17
+ "generate_best_food_choice",
18
+ "generate_food_plan",
19
+ "generate_food_similarity",
20
+ "generate_food_similarity_2",
21
+ "generate_meal_plan_day_json",
22
+ "generate_meal_plan_json_2",
23
+ "generate_meal_plan",
24
+ "get_user_profile",
25
+ ]
chatbot/agents/nodes/functions/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (806 Bytes). View file
 
chatbot/agents/nodes/functions/__pycache__/enrich_food_with_nutrition.cpython-310.pyc ADDED
Binary file (1.53 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/enrich_meal_plan_with_nutrition.cpython-310.pyc ADDED
Binary file (1.78 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/enrich_meal_plan_with_nutrition_2.cpython-310.pyc ADDED
Binary file (1.79 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/generate_best_food_choice.cpython-310.pyc ADDED
Binary file (2.97 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/generate_food_plan.cpython-310.pyc ADDED
Binary file (1.93 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/generate_food_similarity.cpython-310.pyc ADDED
Binary file (1.7 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/generate_food_similarity_2.cpython-310.pyc ADDED
Binary file (1.91 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/generate_meal_plan.cpython-310.pyc ADDED
Binary file (3.31 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/generate_meal_plan_day_json.cpython-310.pyc ADDED
Binary file (3.81 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/generate_meal_plan_json_2.cpython-310.pyc ADDED
Binary file (3.74 kB). View file
 
chatbot/agents/nodes/functions/__pycache__/get_user_profile.cpython-310.pyc ADDED
Binary file (1.69 kB). View file
 
chatbot/agents/nodes/functions/enrich_food_with_nutrition.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ def enrich_food_with_nutrition(state: AgentState):
12
+ food_new_raw = state["food_new_raw"]
13
+ suggested_meals = state.get("suggested_meals", [])
14
+
15
+ # Map ID → món gốc
16
+ meal_map = {str(m["meal_id"]): m for m in suggested_meals}
17
+
18
+ # Những field KHÔNG nhân portion
19
+ skip_scale_fields = {
20
+ "meal_id", "name", "ingredients", "ingredients_text",
21
+ "difficulty", "servings", "cooking_time_minutes"
22
+ }
23
+
24
+
25
+ meal_id = str(food_new_raw["id"])
26
+ portion = float(food_new_raw["portion"])
27
+
28
+ base = meal_map.get(meal_id)
29
+
30
+ enriched_food = {}
31
+
32
+ if base:
33
+ enriched_food = {
34
+ "id": meal_id,
35
+ "name": food_new_raw["name"],
36
+ "portion": portion
37
+ }
38
+
39
+ for key, value in base.items():
40
+ # Nếu không nhân portion
41
+ if key in skip_scale_fields:
42
+ enriched_food[key] = value
43
+ continue
44
+
45
+ # Nếu là số → nhân portion
46
+ if isinstance(value, (int, float)):
47
+ enriched_food[key] = round(value * portion, 4)
48
+ else:
49
+ # Các field text, list thì giữ nguyên
50
+ enriched_food[key] = value
51
+
52
+ return {"food_new": enriched_food}
53
+ else:
54
+ return {"food_new": food_new_raw}
chatbot/agents/nodes/functions/enrich_meal_plan_with_nutrition.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ def enrich_meal_plan_with_nutrition(state: AgentState):
12
+ meal_plan = state["meal_plan"]
13
+ suggested_meals = state.get("suggested_meals", [])
14
+
15
+ # Map ID → món gốc
16
+ meal_map = {str(m["meal_id"]): m for m in suggested_meals}
17
+
18
+ # Những field KHÔNG nhân portion
19
+ skip_scale_fields = {
20
+ "meal_id", "name", "ingredients", "ingredients_text",
21
+ "difficulty", "servings", "cooking_time_minutes"
22
+ }
23
+
24
+ enriched_meals = []
25
+
26
+ for meal in meal_plan.get("meals", []):
27
+ enriched_items = []
28
+
29
+ for item in meal.get("items", []):
30
+ meal_id = str(item["id"])
31
+ portion = float(item["portion"])
32
+
33
+ base = meal_map.get(meal_id)
34
+
35
+ if base:
36
+ enriched_item = {
37
+ "id": meal_id,
38
+ "name": item["name"],
39
+ "portion": portion
40
+ }
41
+
42
+ for key, value in base.items():
43
+ # Nếu không nhân portion
44
+ if key in skip_scale_fields:
45
+ enriched_item[key] = value
46
+ continue
47
+
48
+ # Nếu là số → nhân portion
49
+ if isinstance(value, (int, float)):
50
+ enriched_item[key] = round(value * portion, 4)
51
+ else:
52
+ # Các field text, list thì giữ nguyên
53
+ enriched_item[key] = value
54
+
55
+ enriched_items.append(enriched_item)
56
+
57
+ else:
58
+ enriched_items.append(item)
59
+
60
+ enriched_meals.append({
61
+ "meal_name": meal["meal_name"],
62
+ "items": enriched_items
63
+ })
64
+
65
+ meal_plan_day = {
66
+ "meals": enriched_meals,
67
+ "reason": meal_plan.get("reason", "")
68
+ }
69
+
70
+ return {
71
+ "meal_plan_day": meal_plan_day
72
+ }
chatbot/agents/nodes/functions/enrich_meal_plan_with_nutrition_2.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ def enrich_meal_plan_with_nutrition_2(state: AgentState):
12
+ meal_new_raw = state["meal_new_raw"]
13
+ suggested_meals = state.get("suggested_meals", [])
14
+
15
+ # Map ID → món gốc
16
+ meal_map = {str(m["meal_id"]): m for m in suggested_meals}
17
+
18
+ # Những field KHÔNG nhân portion
19
+ skip_scale_fields = {
20
+ "meal_id", "name", "ingredients", "ingredients_text",
21
+ "difficulty", "servings", "cooking_time_minutes"
22
+ }
23
+
24
+ enriched_meals = []
25
+
26
+ for meal in meal_new_raw.get("meals", []):
27
+ enriched_items = []
28
+
29
+ for item in meal.get("items", []):
30
+ meal_id = str(item["id"])
31
+ portion = float(item["portion"])
32
+
33
+ base = meal_map.get(meal_id)
34
+
35
+ if base:
36
+ enriched_item = {
37
+ "id": meal_id,
38
+ "name": item["name"],
39
+ "portion": portion
40
+ }
41
+
42
+ for key, value in base.items():
43
+ # Nếu không nhân portion
44
+ if key in skip_scale_fields:
45
+ enriched_item[key] = value
46
+ continue
47
+
48
+ # Nếu là số → nhân portion
49
+ if isinstance(value, (int, float)):
50
+ enriched_item[key] = round(value * portion, 4)
51
+ else:
52
+ # Các field text, list thì giữ nguyên
53
+ enriched_item[key] = value
54
+
55
+ enriched_items.append(enriched_item)
56
+
57
+ else:
58
+ enriched_items.append(item)
59
+
60
+ enriched_meals.append({
61
+ "meal_name": meal["meal_name"],
62
+ "items": enriched_items
63
+ })
64
+
65
+ meal_new = {
66
+ "meals": enriched_meals,
67
+ "reason": meal_new_raw.get("reason", "")
68
+ }
69
+
70
+ return {
71
+ "meal_new": meal_new
72
+ }
chatbot/agents/nodes/functions/generate_best_food_choice.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ import json
12
+
13
+ def generate_best_food_choice(state: AgentState):
14
+ print("---GENERATE BEST FOOD CHOICE---")
15
+
16
+ user_profile = state["user_profile"]
17
+ food_old = state["food_old"]
18
+ suggested_meals = state["suggested_meals"]
19
+
20
+ if not suggested_meals:
21
+ return {"error": "Không có món để chọn"}
22
+
23
+ # Chuẩn bị danh sách món đã retriever được
24
+ candidate_text = "\n".join([
25
+ f"- ID: {m['meal_id']}, {m['name']} | "
26
+ f"{m.get('kcal',0)} kcal, {m.get('protein',0)} protein, "
27
+ f"{m.get('lipid',0)} lipid, {m.get('carbohydrate',0)} carbohydrate | "
28
+ f"tags: {', '.join(m.get('tags', []))}"
29
+ for m in suggested_meals
30
+ ])
31
+
32
+ # Prompt chọn món tốt nhất
33
+ prompt = f"""
34
+ Bạn là AI chuyên gia dinh dưỡng.
35
+
36
+ Nhiệm vụ: Trong danh sách các món sau đây, hãy CHỌN RA 1 MÓN TƯƠNG TỰ NHẤT
37
+ để thay thế món: {food_old['name']}.
38
+
39
+ Giá trị dinh dưỡng món cũ:
40
+ - kcal: {food_old['kcal']}
41
+ - protein: {food_old['protein']}
42
+ - lipid: {food_old['lipid']}
43
+ - carbohydrate: {food_old['carbohydrate']}
44
+ - tags: {', '.join(food_old['tags'])}
45
+
46
+ Danh sách món gợi ý:
47
+ {candidate_text}
48
+
49
+ --- QUY TẮC ---
50
+ 1. Chọn món có dinh dưỡng gần nhất với món cũ (±20%).
51
+ 2. Ưu tiên món có nhiều tag trùng với món cũ.
52
+ 3. Đề xuất khẩu phần (portion) phù hợp để tổng năng lượng món gần với món cũ (ví dụ: 0.5, 1, 1.2).
53
+ 4. Chỉ trả JSON duy nhất, không viết gì thêm.
54
+
55
+ --- ĐỊNH DẠNG JSON TRẢ VỀ ---
56
+ {{
57
+ "id": <ID>,
58
+ "name": "<Tên món>",
59
+ "portion": số_lượng_khẩu_phần (float)
60
+ }}
61
+
62
+ KHÔNG VIẾT GÌ NGOÀI JSON.
63
+ """
64
+
65
+
66
+ print("---Prompt---")
67
+ print(prompt)
68
+
69
+ result = llm.invoke(prompt)
70
+ output = result.content
71
+
72
+ # Parse JSON an toàn
73
+ try:
74
+ food_new_raw = json.loads(output)
75
+ except Exception as e:
76
+ print("❌ JSON parse error:", e)
77
+ return {"response": "LLM trả về JSON không hợp lệ", "raw_output": output}
78
+
79
+ return {"food_new_raw": food_new_raw}
chatbot/agents/nodes/functions/generate_food_plan.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ # --- Cấu hình logging ---
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # --- Generate food plan ---
16
+ def generate_food_plan(state: AgentState) -> Dict[str, Any]:
17
+ logger.info("--- GENERATE_FOOD_PLAN ---")
18
+ meals_to_generate: List[str] = state.get("meals_to_generate", [])
19
+ user_profile: Dict[str, Any] = state.get("user_profile", {})
20
+
21
+ if not meals_to_generate:
22
+ logger.warning("meals_to_generate rỗng, sử dụng mặc định ['sáng', 'trưa', 'tối']")
23
+ meals_to_generate = ["sáng", "trưa", "tối"]
24
+
25
+ meals_text = ", ".join(meals_to_generate)
26
+
27
+ query_text = (
28
+ f"Tìm các món ăn phù hợp với người dùng có chế độ ăn: {user_profile.get('khẩu phần', 'ăn chay')}. "
29
+ f"Ưu tiên món phổ biến, cân bằng dinh dưỡng, cho bữa {meals_text}."
30
+ )
31
+ logger.info(f"Query: {query_text}")
32
+
33
+ try:
34
+ foods = food_retriever.invoke(query_text)
35
+ except Exception as e:
36
+ logger.error(f"Lỗi khi truy vấn món ăn: {e}")
37
+ foods = []
38
+
39
+ suggested_meals = [food.metadata for food in foods] if foods else []
40
+ logger.info(f"Số món được gợi ý: {len(suggested_meals)}")
41
+
42
+ return {"suggested_meals": suggested_meals}
chatbot/agents/nodes/functions/generate_food_similarity.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ def generate_food_similarity(state: AgentState):
12
+ print("---GENERATE FOOD SIMILARITY---")
13
+ meals_to_generate = state.get("meals_to_generate", ["sáng"])
14
+ user_profile = state["user_profile"]
15
+ food_old = state["food_old"]
16
+
17
+ suggested_meals = []
18
+ meals_text = ", ".join(meals_to_generate)
19
+
20
+ query = (
21
+ f"Tìm món ăn tương tự món {food_old['name']} dựa trên: "
22
+ f"kcal ~{food_old['kcal']} (±20%), "
23
+ f"protein ~{food_old['protein']}g (±20%), "
24
+ f"lipid ~{food_old['lipid']}g (±20%), "
25
+ f"carbohydrate ~{food_old['carbohydrate']}g (±20%). "
26
+ f"Ưu tiên các món có tags: {', '.join(food_old['tags'])}. "
27
+ f"Phù hợp khẩu phần: {user_profile['khẩu phần']}, "
28
+ f"phục vụ cho bữa {meals_text}."
29
+ )
30
+
31
+ print("Query: " + query)
32
+
33
+ foods = food_retriever.invoke(query)
34
+ print(f"🔍 Kết quả truy vấn: ")
35
+ for i, food in enumerate(foods):
36
+ print(f"{i} - {food.metadata['name']}")
37
+ suggested_meals.append(food.metadata)
38
+
39
+ return {"suggested_meals": suggested_meals}
chatbot/agents/nodes/functions/generate_food_similarity_2.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ def generate_food_similarity_2(state: AgentState):
12
+ print("---GENERATE FOOD SIMILARITY---")
13
+ meals_to_generate = state.get("meals_to_generate", ["sáng"])
14
+ user_profile = state["user_profile"]
15
+ meal_old = state["meal_old"]
16
+
17
+ suggested_meals = []
18
+ food_name_text = ", ".join([meal['name'] for meal in meal_old])
19
+
20
+ all_tags = [
21
+ tag
22
+ for meal in meal_old
23
+ for tag in meal.get("tags", [])
24
+ if isinstance(tag, str)
25
+ ]
26
+ unique_tags = list(set(all_tags))
27
+ food_tag_text = ", ".join(unique_tags)
28
+
29
+ meals_text = ", ".join(meals_to_generate)
30
+
31
+ query = (
32
+ f"Tìm món ăn ưu tiên các món có tags: {food_tag_text}. "
33
+ f"Phù hợp khẩu phần: {user_profile['khẩu phần']}, "
34
+ f"phục vụ cho bữa {meals_text}."
35
+ )
36
+
37
+ print("Query: " + query)
38
+
39
+ foods = food_retriever.invoke(query)
40
+ print(f"🔍 Kết quả truy vấn: ")
41
+ for i, food in enumerate(foods):
42
+ print(f"{i} - {food.metadata['name']}")
43
+ suggested_meals.append(food.metadata)
44
+
45
+ return {"suggested_meals": suggested_meals}
chatbot/agents/nodes/functions/generate_meal_plan.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ # --- Cấu hình logging ---
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ def generate_meal_plan(state: AgentState):
16
+ logger.info("--- GENERATE_MEAL_PLAN ---")
17
+ user_profile = state.get("user_profile", {})
18
+ suggested_meals = state.get("suggested_meals", [])
19
+ meals_to_generate = state.get("meals_to_generate", [])
20
+ question = state.get("question", "Hãy tạo thực đơn cho tôi.")
21
+
22
+ meals_text = ", ".join(meals_to_generate)
23
+
24
+ suggested_meals_text = "\n".join(
25
+ [f"- {meal['name']}: {meal.get('kcal', 0)} kcal, "
26
+ f"{meal.get('protein', 0)}g protein, "
27
+ f"{meal.get('lipid', 0)}g chất béo, "
28
+ f"{meal.get('carbohydrate', 0)}g carbohydrate"
29
+ for meal in suggested_meals]
30
+ ) if suggested_meals else "Chưa có món ăn gợi ý."
31
+
32
+ prompt = f"""
33
+ Bạn có thể sử dụng thông tin người dùng có hồ sơ dinh dưỡng sau nếu cần thiết cho câu hỏi của người dùng:
34
+ - Tổng năng lượng mục tiêu: {user_profile['kcal']} kcal/ngày
35
+ - Protein: {user_profile['protein']}g
36
+ - Chất béo (lipid): {user_profile['lipid']}g
37
+ - Carbohydrate: {user_profile['carbohydrate']}g
38
+ - Chế độ ăn: {user_profile['khẩu phần']}
39
+
40
+ Câu hỏi của người dùng: "{question}"
41
+
42
+ Các bữa cần xây dựng:
43
+ {meals_text}
44
+
45
+ Danh sách món ăn hiện có để chọn:
46
+ {suggested_meals_text}
47
+
48
+ Yêu cầu:
49
+ 1. Hãy tổ hợp các món ăn trên để tạo thực đơn cho từng bữa (chỉ chọn trong danh sách có sẵn).
50
+ 2. Mỗi bữa gồm 1 đến 3 món, tổng năng lượng và dinh dưỡng xấp xỉ giá trị yêu cầu của bữa đó (±15%).
51
+ 3. Nếu cần, hãy ước tính khẩu phần mỗi món (ví dụ: 0.5 khẩu phần hoặc 1.2 khẩu phần) để đạt cân bằng chính xác.
52
+ 4. Đảm bảo tổng giá trị dinh dưỡng toàn ngày gần với hồ sơ người dùng.
53
+ 5. Chỉ chọn những món phù hợp với chế độ ăn: {user_profile['khẩu phần']}.
54
+ """
55
+
56
+ logger.info(prompt)
57
+
58
+ try:
59
+ result = llm.invoke(prompt, timeout=60)
60
+ response_content = getattr(result, "content", str(result))
61
+ except Exception as e:
62
+ logger.error(f"Lỗi khi gọi LLM: {e}")
63
+ response_content = "Không thể tạo thực đơn lúc này, vui lòng thử lại sau."
64
+
65
+ logger.info("Meal plan suggestion generated.")
66
+ return {"response": response_content}
chatbot/agents/nodes/functions/generate_meal_plan_day_json.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ import json
12
+
13
+ def generate_meal_plan_day_json(state: AgentState):
14
+ # logger.info("--- GENERATE_MEAL_PLAN ---")
15
+ user_profile = state.get("user_profile", {})
16
+ suggested_meals = state.get("suggested_meals", [])
17
+
18
+ suggested_meals_text = "\n".join(
19
+ [f"- ID: {meal['meal_id']} {meal['name']}: {meal.get('kcal', 0)} kcal, "
20
+ f"{meal.get('protein', 0)}g protein, "
21
+ f"{meal.get('lipid', 0)}g chất béo, "
22
+ f"{meal.get('carbohydrate', 0)}g carbohydrate"
23
+ for meal in suggested_meals]
24
+ ) if suggested_meals else "Chưa có món ăn gợi ý."
25
+
26
+ prompt = f"""
27
+ Bạn là AI chuyên gia dinh dưỡng. Nhiệm vụ: Tạo thực đơn cho một ngày theo dạng JSON.
28
+
29
+ Hồ sơ dinh dưỡng người dùng:
30
+ - Năng lượng mục tiêu: {user_profile['kcal']} kcal/ngày
31
+ - Protein: {user_profile['protein']}g
32
+ - Lipid: {user_profile['lipid']}g
33
+ - Carbohydrate: {user_profile['carbohydrate']}g
34
+ - Chế độ ăn: {user_profile['khẩu phần']}
35
+
36
+ Danh sách món ăn hiện có:
37
+ {suggested_meals_text}
38
+
39
+ --- YÊU CẦU ---
40
+ Trả về duy nhất một JSON theo cấu trúc sau:
41
+
42
+ {{
43
+ "meals": [
44
+ {{
45
+ "meal_name": "Tên bữa",
46
+ "items": [
47
+ {{
48
+ "id": "ID món",
49
+ "name": "Tên món",
50
+ "portion": số_lượng_khẩu_phần (float)
51
+ }}
52
+ ]
53
+ }}
54
+ ],
55
+ "reason": "Lý do xây dựng thực đơn"
56
+ }}
57
+
58
+ --- QUY TẮC ---
59
+ 1. Hãy tổ hợp các món ăn trên để tạo thực đơn cho từng bữa (chỉ chọn trong danh sách có sẵn).
60
+ 2. Mỗi bữa gồm 1 đến 3 món, tổng năng lượng và dinh dưỡng xấp xỉ giá trị yêu cầu của bữa đó (±15%).
61
+ 3. Mỗi món phải có cả "id", "name", "portion".
62
+ 4. Điều chỉnh "portion" có thể là 0.5, 1, 1.2... sao cho tổng năng lượng của một ngày đạt cân bằng chính xác.
63
+ 5. Đảm bảo tổng giá trị dinh dưỡng toàn ngày gần với hồ sơ người dùng.
64
+ 6. "reason" là lý do xây dựng thực đơn bao gồm dinh dưỡng của từng bữa sáng, trưa, tối và tổng lại cả ngày.
65
+ 7. Chỉ chọn món đúng chế độ ăn: {user_profile['khẩu phần']}.
66
+ 8. KHÔNG được trả lời thêm văn bản nào ngoài JSON.
67
+ """
68
+
69
+ print("---Prompt---")
70
+ print(prompt)
71
+
72
+ result = llm.invoke(prompt)
73
+ output = result.content
74
+
75
+ # Parse JSON an toàn
76
+ try:
77
+ meal_plan = json.loads(output)
78
+ logging.info("Lý do: " + meal_plan.get("reason", "Không có lý do"))
79
+ except Exception as e:
80
+ print("❌ JSON parse error:", e)
81
+ return {"response": "LLM trả về JSON không hợp lệ", "raw_output": output}
82
+
83
+ return {"meal_plan": meal_plan}
chatbot/agents/nodes/functions/generate_meal_plan_json_2.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+ import json
11
+
12
+ def generate_meal_plan_json_2(state: AgentState):
13
+ # logger.info("--- GENERATE_MEAL_PLAN ---")
14
+ user_profile = state.get("user_profile", {})
15
+ suggested_meals = state.get("suggested_meals", [])
16
+ meals_to_generate = state.get("meals_to_generate", ["sáng"])
17
+
18
+ meal_text = ", ".join(meals_to_generate)
19
+
20
+ suggested_meals_text = "\n".join(
21
+ [f"- ID: {meal['meal_id']} {meal['name']}: {meal.get('kcal', 0)} kcal, "
22
+ f"{meal.get('protein', 0)}g protein, "
23
+ f"{meal.get('lipid', 0)}g chất béo, "
24
+ f"{meal.get('carbohydrate', 0)}g carbohydrate"
25
+ for meal in suggested_meals]
26
+ ) if suggested_meals else "Chưa có món ăn gợi ý."
27
+
28
+ meal_old = state["meal_old"]
29
+
30
+ meal_old_text = "\n".join([
31
+ f"- {item['name']}: {item.get('kcal',0)} kcal, "
32
+ f"{item.get('protein',0)}g protein, "
33
+ f"{item.get('lipid',0)}g lipid, "
34
+ f"{item.get('carbohydrate',0)}g carbohydrate, "
35
+ f"tương đương: {item.get('portion', 1)} khẩu phần."
36
+ for item in meal_old
37
+ ])
38
+
39
+ prompt = f"""
40
+ Bạn là AI chuyên gia dinh dưỡng.
41
+
42
+ Nhiệm vụ: Tạo lại thực đơn cho bữa {meal_text} bằng cách chọn MÓN MỚI
43
+ từ danh sách gợi ý bên dưới sao cho:
44
+ - Dinh dưỡng gần nhất với bữa cũ (±15%)
45
+ - Món phù hợp khẩu phần: {user_profile['khẩu phần']}
46
+ - Chỉ được chọn món có trong danh sách gợi ý
47
+
48
+ --- THÔNG TIN BỮA CŨ ---
49
+ Bao gồm các món sau:
50
+ {meal_old_text}
51
+
52
+ Danh sách món gợi ý:
53
+ {suggested_meals_text}
54
+
55
+ --- QUY TẮC ---
56
+ 1. Chỉ chọn trong danh sách gợi ý.
57
+ 2. Mỗi bữa gồm 1–3 món.
58
+ 3. Mỗi món phải trả về đầy đủ: "id", "name", "portion".
59
+ 4. "portion" có thể là số thực: 0.5, 1.0, 1.2...
60
+ 5. Tổng dinh dưỡng bữa mới phải gần với tổng dinh dưỡng bữa cũ (±15%).
61
+ 6. Không được thêm mô tả ngoài JSON.
62
+
63
+ --- JSON TRẢ VỀ ---
64
+ {{
65
+ "meals": [
66
+ {{
67
+ "meal_name": "{meal_text}",
68
+ "items": [
69
+ {{
70
+ "id": "ID món",
71
+ "name": "Tên món",
72
+ "portion": số_lượng_khẩu_phần
73
+ }}
74
+ ]
75
+ }}
76
+ ],
77
+ "reason": "Giải thích vì sao chọn các món và khẩu phần"
78
+ }}
79
+ """
80
+
81
+ print("---Prompt---")
82
+ print(prompt)
83
+
84
+ result = llm.invoke(prompt)
85
+ output = result.content
86
+
87
+ # Parse JSON an toàn
88
+ try:
89
+ meal_new_raw = json.loads(output)
90
+ except Exception as e:
91
+ print("❌ JSON parse error:", e)
92
+ return {"response": "LLM trả về JSON không hợp lệ", "raw_output": output}
93
+
94
+ return {"meal_new_raw": meal_new_raw}
chatbot/agents/nodes/functions/get_user_profile.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.agents.tools.food_retriever import query_constructor, food_retriever
6
+ from langgraph.graph import END, StateGraph
7
+ from chatbot.models.llm_setup import llm
8
+ from langchain.tools import tool
9
+ from chatbot.utils.user_profile import get_user_by_id
10
+
11
+ # --- Cấu hình logging ---
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ def get_user_profile(state: AgentState) -> Dict[str, Any]:
16
+ """
17
+ Node: Lấy profile người dùng và chuẩn hóa key.
18
+ """
19
+ logger.info("--- GET_USER_PROFILE ---")
20
+ user_id = state.get("user_id", "")
21
+ user_profile = get_user_by_id(user_id)
22
+
23
+ # Chuẩn hóa khóa
24
+ mapping = {"fat": "lipid", "carbs": "carbohydrate", "protein": "protein",
25
+ "kcal": "kcal", "lipid": "lipid", "carbohydrate": "carbohydrate"}
26
+ normalized_profile = {mapping.get(k.lower(), k.lower()): v for k, v in user_profile.items()}
27
+
28
+ # Fallback default nếu thiếu
29
+ defaults = {"kcal": 1700, "protein": 120, "lipid": 56, "carbohydrate": 170, "khẩu phần": "ăn chay"}
30
+ for key, val in defaults.items():
31
+ normalized_profile.setdefault(key, val)
32
+
33
+ logger.info(f"User profile chuẩn hóa: {normalized_profile}")
34
+
35
+ return {"user_profile": normalized_profile, "daily_goal": normalized_profile, "suggested_meals": []}
chatbot/agents/nodes/general_chat.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from chatbot.agents.states.state import AgentState
2
+ from chatbot.models.llm_setup import llm
3
+ from langchain.schema.messages import SystemMessage, HumanMessage
4
+ from chatbot.utils.user_profile import get_user_by_id
5
+
6
+ def general_chat(state: AgentState):
7
+ print("---GENERAL CHAT---")
8
+
9
+ user_id = state.get("user_id", {})
10
+ messages = state["messages"]
11
+ user_message = messages[-1].content if messages else state.question
12
+
13
+ user_profile = get_user_by_id(user_id)
14
+
15
+ system_prompt = f"""
16
+ Bạn là một chuyên gia dinh dưỡng và ẩm thực AI.
17
+ Hãy trả lời các câu hỏi về:
18
+ - món ăn, thành phần, dinh dưỡng, calo, protein, chất béo, carb,
19
+ - chế độ ăn (ăn chay, keto, giảm cân, tăng cơ...),
20
+ - sức khỏe, lối sống, chế độ tập luyện liên quan đến ăn uống.
21
+ Một số thông tin về người dùng có thể dùng đến như sau:
22
+ - Tổng năng lượng mục tiêu: {user_profile['kcal']} kcal/ngày
23
+ - Protein: {user_profile['protein']}g
24
+ - Chất béo (lipid): {user_profile['lipid']}g
25
+ - Carbohydrate: {user_profile['carbohydrate']}g
26
+ - Chế độ ăn: {user_profile['khẩu phần']}
27
+ Không trả lời các câu hỏi ngoài chủ đề này.
28
+ Giải thích ngắn gọn, tự nhiên, rõ ràng.
29
+ """
30
+
31
+ messages = [
32
+ SystemMessage(content=system_prompt),
33
+ HumanMessage(content=user_message),
34
+ ]
35
+
36
+ response = llm.invoke(messages)
37
+
38
+ print(response.content if hasattr(response, "content") else response)
39
+
40
+ return {"response": response.content}
chatbot/agents/nodes/meal_identify.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.prompts import PromptTemplate
2
+ import json
3
+ from pydantic import BaseModel, Field
4
+ from chatbot.agents.states.state import AgentState
5
+ from chatbot.models.llm_setup import llm
6
+ from typing import List
7
+
8
+ class MealIntent(BaseModel):
9
+ intent: str = Field(
10
+ description=(
11
+ "Loại yêu cầu của người dùng, có thể là:\n"
12
+ "- 'full_day_meal': khi người dùng chưa ăn bữa nào và muốn gợi ý thực đơn cho cả ngày.\n"
13
+ "- 'not_full_day_meal': khi người dùng đã ăn một vài bữa và muốn gợi ý một bữa cụ thể hoặc các bữa còn lại."
14
+ )
15
+ )
16
+ meals_to_generate: List[str] = Field(
17
+ description="Danh sách các bữa được người dùng muốn gợi ý: ['sáng', 'trưa', 'tối']."
18
+ )
19
+
20
+
21
+ def meal_identify(state: AgentState):
22
+ print("---MEAL IDENTIFY---")
23
+
24
+ llm_with_structure_op = llm.with_structured_output(MealIntent)
25
+
26
+ # Lấy câu hỏi mới nhất từ lịch sử hội thoại
27
+ messages = state["messages"]
28
+ user_message = messages[-1].content if messages else state.question
29
+
30
+ format_instructions = json.dumps(llm_with_structure_op.output_schema.model_json_schema(), ensure_ascii=False, indent=2)
31
+
32
+ prompt = PromptTemplate(
33
+ template="""
34
+ Bạn là bộ phân tích yêu cầu gợi ý bữa ăn trong hệ thống chatbot dinh dưỡng.
35
+
36
+ Dựa trên câu hỏi của người dùng, hãy xác định:
37
+ 1. Người dùng muốn gợi ý cho **cả ngày**, **một hoặc một vài bữa cụ thể**.
38
+ 2. Danh sách các bữa người dùng muốn gợi ý (nếu có).
39
+
40
+ Quy tắc:
41
+ - Nếu người dùng muốn gợi ý thực đơn cho cả ngày → intent = "full_day_meal".
42
+ - Nếu họ nói đã ăn một bữa nào đó, muốn gợi ý một hoặc các bữa còn lại → intent = "not_full_day_meal".
43
+ - Các bữa người dùng có thể muốn gợi ý: ["sáng", "trưa", "tối"].
44
+
45
+ Câu hỏi người dùng: {question}
46
+
47
+ Hãy xuất kết quả dưới dạng JSON theo schema sau:
48
+ {format_instructions}
49
+ """
50
+ )
51
+
52
+ chain = prompt | llm_with_structure_op
53
+
54
+ result = chain.invoke({
55
+ "question": user_message,
56
+ "format_instructions": format_instructions
57
+ })
58
+
59
+ print("Bữa cần gợi ý: " + ", ".join(result.meals_to_generate))
60
+
61
+ return {
62
+ "meal_intent": result.intent,
63
+ "meals_to_generate": result.meals_to_generate,
64
+ }
chatbot/agents/nodes/select_food_plan.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from chatbot.agents.states.state import AgentState
2
+ from chatbot.models.llm_setup import llm
3
+
4
+ def select_food_plan(state: AgentState):
5
+ print("---SELECT FOOD PLAN---")
6
+
7
+ user_profile = state["user_profile"]
8
+ suggested_meals = state["suggested_meals"]
9
+ messages = state["messages"]
10
+ user_message = messages[-1].content if messages else state.question
11
+
12
+ suggested_meals_text = "\n".join(
13
+ f"{i+1}. {doc.metadata.get('name', 'Không rõ')} - "
14
+ f"{doc.metadata.get('kcal', '?')} kcal, "
15
+ f"Protein:{doc.metadata.get('protein', '?')}g, "
16
+ f"Chất béo:{doc.metadata.get('lipid', '?')}g, "
17
+ f"Carbohydrate:{doc.metadata.get('carbohydrate', '?')}g\n"
18
+ f"Mô tả: {doc.page_content}"
19
+ for i, doc in enumerate(suggested_meals)
20
+ )
21
+
22
+ prompt = f"""
23
+ Bạn là chuyên gia dinh dưỡng AI.
24
+ Bạn có thể sử dụng thông tin người dùng có hồ sơ dinh dưỡng sau nếu cần thiết cho câu hỏi của người dùng:
25
+ - Tổng năng lượng mục tiêu: {user_profile['kcal']} kcal/ngày
26
+ - Protein: {user_profile['protein']}g
27
+ - Chất béo (lipid): {user_profile['lipid']}g
28
+ - Carbohydrate: {user_profile['carbohydrate']}g
29
+ - Chế độ ăn: {user_profile['khẩu phần']}
30
+
31
+ Câu hỏi của người dùng: "{user_message}"
32
+
33
+ Danh sách món ăn hiện có để chọn:
34
+ {suggested_meals_text}
35
+
36
+ Yêu cầu:
37
+ 1. Chọn một món ăn phù hợp nhất với yêu cầu của người dùng, dựa trên dinh dưỡng và chế độ ăn.
38
+ 2. Nếu không có món nào phù hợp, hãy trả về:
39
+ "Không tìm thấy món phù hợp trong danh sách hiện có."
40
+ 3. Không tự tạo thêm món mới hoặc tên món không có trong danh sách.
41
+ 4. Nếu có nhiều món gần giống nhau, hãy chọn món có năng lượng và thành phần dinh dưỡng gần nhất với mục tiêu người dùng.
42
+ """
43
+
44
+ print("Prompt:")
45
+ print(prompt)
46
+
47
+ result = llm.invoke(prompt)
48
+
49
+ print(result.content if hasattr(result, "content") else result)
50
+
51
+ return {"response": result.content}
chatbot/agents/nodes/suggest_meal_node.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage
2
+ from chatbot.agents.states.state import AgentState
3
+ from chatbot.models.llm_setup import llm
4
+ from chatbot.agents.tools.daily_meal_suggestion import daily_meal_suggestion
5
+
6
+ def suggest_meal_node(state: AgentState):
7
+ print("---SUGGEST MEAL NODE---")
8
+
9
+ # 🧠 Lấy dữ liệu từ state
10
+ user_id = state.get("user_id", 0)
11
+ question = state.get("messages")
12
+ meals_to_generate = state.get("meals_to_generate", [])
13
+
14
+ # 🧩 Chuẩn bị prompt mô tả yêu cầu
15
+ system_prompt = """
16
+ Bạn là một chuyên gia gợi ý thực đơn AI.
17
+ Bạn không được tự trả lời hay đặt câu hỏi thêm.
18
+ Nếu người dùng yêu cầu gợi ý món ăn, bắt buộc gọi tool 'daily_meal_suggestion'.
19
+ với các tham số:
20
+ - user_id: ID người dùng hiện tại
21
+ - question: nội dung câu hỏi họ vừa hỏi
22
+ - meals_to_generate: danh sách các bữa cần sinh thực đơn (nếu có)
23
+
24
+ Nếu bạn không chắc bữa nào cần sinh, vẫn gọi tool này — phần xử lý sẽ lo chi tiết sau.
25
+ """
26
+
27
+ user_prompt = f"""
28
+ Người dùng có ID: {user_id}
29
+ Yêu cầu: "{question}"
30
+ Danh sách các bữa cần gợi ý: {meals_to_generate}
31
+ """
32
+
33
+ # 🚀 Gọi LLM và Tools
34
+ tools = [daily_meal_suggestion]
35
+ llm_with_tools = llm.bind_tools(tools)
36
+
37
+ response = llm_with_tools.invoke(
38
+ [
39
+ SystemMessage(content=system_prompt),
40
+ HumanMessage(content=user_prompt)
41
+ ]
42
+ )
43
+
44
+ print("===== DEBUG =====")
45
+ print("Response type:", type(response))
46
+ print("Tool calls:", getattr(response, "tool_calls", None))
47
+ print("Message content:", response.content)
48
+ print("=================")
49
+
50
+ if isinstance(response, AIMessage) and response.tool_calls:
51
+ tool_call = response.tool_calls[0]
52
+ tool_name = tool_call["name"]
53
+ tool_args = tool_call["args"]
54
+ tool_call_id = tool_call["id"]
55
+
56
+ print(f"👉 Executing tool: {tool_name} with args: {tool_args}")
57
+
58
+ # Bổ sung tham số nếu LLM quên
59
+ tool_args.setdefault("user_id", user_id)
60
+ tool_args.setdefault("question", question)
61
+ tool_args.setdefault("meals_to_generate", meals_to_generate)
62
+
63
+ if tool_name == "daily_meal_suggestion":
64
+ result = daily_meal_suggestion.invoke(tool_args)
65
+ elif tool_name == "fallback":
66
+ result = {"message": "Không có tool phù hợp.", "reason": tool_args.get("reason", "")}
67
+ else:
68
+ result = {"message": f"Tool '{tool_name}' chưa được định nghĩa."}
69
+
70
+ tool_message = ToolMessage(content=str(result), name=tool_name, tool_call_id=tool_call_id)
71
+ return {"messages": state["messages"] + [response, tool_message], "response": result}
72
+ return {"response": "Lỗi!!!"}