File size: 5,009 Bytes
b5961aa
 
 
 
445252b
b5961aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29b313e
 
b5961aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445252b
b5961aa
445252b
 
b5961aa
 
29b313e
b5961aa
 
 
 
29b313e
b5961aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29b313e
b5961aa
 
 
 
 
29b313e
 
b5961aa
 
 
 
29b313e
 
b5961aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29b313e
b5961aa
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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}