truglpk3's picture
.
445252b
from langchain_core.pydantic_v1 import BaseModel, Field
from chatbot.models.llm_setup import llm
from chatbot.agents.states.state import SwapState
import logging
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ChefDecision(BaseModel):
# Thay đổi tên trường cho rõ nghĩa
selected_meal_id: int = Field(description="ID (meal_id) của món ăn được chọn từ danh sách")
reason: str = Field(description="Lý do ẩm thực ngắn gọn")
def llm_finalize_choice(state: SwapState):
logger.info("---NODE: LLM FINAL SELECTION (BY REAL MEAL_ID)---")
top_candidates = state["top_candidates"]
food_old = state["food_old"]
if not top_candidates: return {"best_replacement": None}
# 1. Format danh sách hiển thị kèm Real ID
options_text = ""
for item in top_candidates:
real_id = item.get("meal_id")
options_text += (
f"ID [{real_id}] - {item['name']}\n"
f" - Số liệu: {item['final_kcal']} Kcal | P:{item['final_protein']}g | L:{item['final_totalfat']}g | C:{item['final_carbs']}g\n"
f" - Độ lệch (Loss): {item['optimization_loss']}\n"
)
# 2. Prompt cập nhật
system_prompt = f"""
Bạn là Bếp trưởng. Người dùng muốn đổi món '{food_old.get('name')}'.
Dưới đây là các ứng viên thay thế:
{options_text}
NHIỆM VỤ:
1. Chọn ra 1 món thay thế tốt nhất về mặt ẩm thực.
2. Trả về chính xác ID (số trong ngoặc vuông []) của món đó.
"""
# 3. Gọi LLM
try:
llm_structured = llm.with_structured_output(ChefDecision)
time_start = time.time()
decision = llm_structured.invoke(system_prompt)
time_end = time.time()
logger.info(f"⏱️ Thời gian LLM: {time_end - time_start:.2f} giây")
target_id = decision.selected_meal_id
except Exception as e:
logger.info(f"⚠️ Lỗi LLM: {e}. Fallback về option đầu tiên.")
# Fallback lấy ID của món đầu tiên
target_id = top_candidates[0].get("meal_id")
decision = ChefDecision(selected_meal_id=target_id, reason="Fallback do lỗi hệ thống.")
# 4. Mapping lại bằng meal_id
selected_full_candidate = None
for item in top_candidates:
if int(item.get("meal_id")) == int(target_id):
selected_full_candidate = item
break
# Fallback an toàn
if not selected_full_candidate:
logger.info(f"⚠️ ID {target_id} không tồn tại trong list. Chọn món Top 1.")
selected_full_candidate = top_candidates[0]
# Bổ sung lý do
selected_full_candidate["chef_reason"] = decision.reason
#-------------------------------------------------------------------
# --- PHẦN MỚI: IN BẢNG SO SÁNH (VISUAL COMPARISON) ---
logger.info(f"✅ CHEF SELECTED: {selected_full_candidate['name']} (ID: {selected_full_candidate['meal_id']})")
logger.info(f"📝 Lý do: {decision.reason}")
# Lấy thông tin món cũ (đã scale ở menu gốc)
old_kcal = float(food_old.get('final_kcal', food_old['kcal']))
old_pro = float(food_old.get('final_protein', food_old['protein']))
old_fat = float(food_old.get('final_totalfat', food_old['totalfat']))
old_carb = float(food_old.get('final_carbs', food_old['carbs']))
# Lấy thông tin món mới (đã re-scale bởi Scipy)
new_kcal = selected_full_candidate['final_kcal']
new_pro = selected_full_candidate['final_protein']
new_fat = selected_full_candidate['final_totalfat']
new_carb = selected_full_candidate['final_carbs']
scale = selected_full_candidate['portion_scale']
# In bảng
logger.info("\n 📊 BẢNG SO SÁNH THAY THẾ:")
headers = ["Chỉ số", "Món Cũ (Gốc)", "Món Mới (Re-scale)", "Chênh lệch"]
row_fmt = " | {:<10} | {:<15} | {:<20} | {:<12} |"
logger.info(" " + "-"*68)
logger.info(row_fmt.format(*headers))
logger.info(" " + "-"*68)
def print_row(label, old_val, new_val, unit=""):
diff = new_val - old_val
diff_str = f"{diff:+.1f}"
# Đánh dấu màu (Logic text)
status = "✅"
# Nếu lệch > 20% thì cảnh báo
if old_val > 0 and abs(diff)/old_val > 0.2: status = "⚠️"
logger.info(row_fmt.format(
label,
f"{old_val:.0f} {unit}",
f"{new_val:.0f} {unit} (x{scale} suất)",
f"{diff_str} {status}"
))
print_row("Năng lượng", old_kcal, new_kcal, "Kcal")
print_row("Protein", old_pro, new_pro, "g")
print_row("TotalFat", old_fat, new_fat, "g")
print_row("Carb", old_carb, new_carb, "g")
logger.info(" " + "-"*68)
logger.info(f"✅ Chef Selected: ID {selected_full_candidate['meal_id']} - {selected_full_candidate['name']}")
return {"best_replacement": selected_full_candidate}