Spaces:
Running
Running
Upload chatbot
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- chatbot/__init__.py +0 -0
- chatbot/__pycache__/__init__.cpython-310.pyc +0 -0
- chatbot/__pycache__/config.cpython-310.pyc +0 -0
- chatbot/__pycache__/main.cpython-310.pyc +0 -0
- chatbot/agents/graphs/__pycache__/chatbot_graph.cpython-310.pyc +0 -0
- chatbot/agents/graphs/__pycache__/food_similarity_graph.cpython-310.pyc +0 -0
- chatbot/agents/graphs/__pycache__/meal_similarity_graph.cpython-310.pyc +0 -0
- chatbot/agents/graphs/__pycache__/meal_suggestion_graph.cpython-310.pyc +0 -0
- chatbot/agents/graphs/__pycache__/meal_suggestion_json_graph.cpython-310.pyc +0 -0
- chatbot/agents/graphs/chatbot_graph.py +43 -0
- chatbot/agents/graphs/food_similarity_graph.py +23 -0
- chatbot/agents/graphs/meal_similarity_graph.py +23 -0
- chatbot/agents/graphs/meal_suggestion_graph.py +21 -0
- chatbot/agents/graphs/meal_suggestion_json_graph.py +23 -0
- chatbot/agents/nodes/__pycache__/classify_topic.cpython-310.pyc +0 -0
- chatbot/agents/nodes/__pycache__/food_query.cpython-310.pyc +0 -0
- chatbot/agents/nodes/__pycache__/general_chat.cpython-310.pyc +0 -0
- chatbot/agents/nodes/__pycache__/meal_identify.cpython-310.pyc +0 -0
- chatbot/agents/nodes/__pycache__/select_food_plan.cpython-310.pyc +0 -0
- chatbot/agents/nodes/__pycache__/suggest_meal_node.cpython-310.pyc +0 -0
- chatbot/agents/nodes/classify_topic.py +60 -0
- chatbot/agents/nodes/food_query.py +31 -0
- chatbot/agents/nodes/functions/__init__.py +25 -0
- chatbot/agents/nodes/functions/__pycache__/__init__.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/enrich_food_with_nutrition.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/enrich_meal_plan_with_nutrition.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/enrich_meal_plan_with_nutrition_2.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/generate_best_food_choice.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/generate_food_plan.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/generate_food_similarity.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/generate_food_similarity_2.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/generate_meal_plan.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/generate_meal_plan_day_json.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/generate_meal_plan_json_2.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/__pycache__/get_user_profile.cpython-310.pyc +0 -0
- chatbot/agents/nodes/functions/enrich_food_with_nutrition.py +54 -0
- chatbot/agents/nodes/functions/enrich_meal_plan_with_nutrition.py +72 -0
- chatbot/agents/nodes/functions/enrich_meal_plan_with_nutrition_2.py +72 -0
- chatbot/agents/nodes/functions/generate_best_food_choice.py +79 -0
- chatbot/agents/nodes/functions/generate_food_plan.py +42 -0
- chatbot/agents/nodes/functions/generate_food_similarity.py +39 -0
- chatbot/agents/nodes/functions/generate_food_similarity_2.py +45 -0
- chatbot/agents/nodes/functions/generate_meal_plan.py +66 -0
- chatbot/agents/nodes/functions/generate_meal_plan_day_json.py +83 -0
- chatbot/agents/nodes/functions/generate_meal_plan_json_2.py +94 -0
- chatbot/agents/nodes/functions/get_user_profile.py +35 -0
- chatbot/agents/nodes/general_chat.py +40 -0
- chatbot/agents/nodes/meal_identify.py +64 -0
- chatbot/agents/nodes/select_food_plan.py +51 -0
- 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!!!"}
|