File size: 3,892 Bytes
f498d44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import google.generativeai as genai
from core.config import settings
from typing import Dict, Any
import json

# 1. Cấu hình Gemini ngay khi load file
try:
    genai.configure(api_key=settings.GEMINI_API_KEY)
    # Cấu hình model với chế độ JSON cưỡng ép
    model = genai.GenerativeModel(
        model_name=settings.GEMINI_MODEL_NAME,
        generation_config={
            "temperature": 0.7,
            "top_p": 0.95,
            "top_k": 64,
            "max_output_tokens": 9000,
            "response_mime_type": "application/json", # <--- TÍNH NĂNG QUAN TRỌNG NHẤT
        }
    )
except Exception as e:
    print(f"⚠️ Cảnh báo: Không thể cấu hình Gemini. Kiểm tra API KEY. Lỗi: {e}")

def build_rag_prompt(context: str, user_query: str) -> str:
    template = """
    Bạn là trợ lý du lịch AI chuyên về Việt Nam.
    
    Nhiệm vụ: Dựa vào danh sách địa điểm trong phần "Context", hãy chọn ra 3 địa điểm phù hợp nhất với "User Query".

    User Query: "{user_query}"
    
    Context (Dữ liệu DB):
    ---
    {context}
    ---
    
    QUY TẮC BẮT BUỘC:
    1. NGÔN NGỮ: 100% Tiếng Việt. Nếu Context có từ tiếng Anh (ví dụ: "Hiking"), PHẢI DỊCH sang tiếng Việt ("Leo núi").
    2. TRUNG THỰC VỚI DỮ LIỆU:
       - Tên địa điểm ("name") phải COPY Y NGUYÊN từ Context (kể cả viết hoa/thường). KHÔNG được sửa tên.
    3. OUTPUT FORMAT: Bạn đang chạy ở chế độ JSON Output, hãy trả về JSON khớp với Schema sau:
    
    {{
        "recommendations": [
            {{
                "rank": 1,
                "name": "Tên y hệt trong Context",
                "justification_summary": "Giải thích lý do chọn địa điểm này dựa trên context bằng tiếng Việt",
                "suggested_activities": ["Hoạt động 1 (Tiếng Việt)", "Hoạt động 2"]
            }},
             {{
                "rank": 2,
                "name": "Tên y hệt trong Context",
                "justification_summary": "Giải thích lý do chọn địa điểm này dựa trên context bằng tiếng Việt",
                "suggested_activities": ["Hoạt động 1 (Tiếng Việt)", "Hoạt động 2"]
            }},
             {{
                "rank": 3,
                "name": "Tên y hệt trong Context",
                "justification_summary": "Giải thích lý do chọn địa điểm này dựa trên context bằng tiếng Việt",
                "suggested_activities": ["Hoạt động 1 (Tiếng Việt)", "Hoạt động 2"]
            }}
        ]
    }}
    """
    return template.format(user_query=user_query, context=context)

def call_llm_api(prompt: str) -> str:
    """
    Gọi Gemini API
    """
    try:
        # Gemini chat session hoặc generate_content
        response = model.generate_content(prompt)
        return response.text
    except Exception as e:
        print(f"Error calling Gemini API: {e}")
        # Trả về JSON rỗng báo lỗi để hàm parse xử lý
        return json.dumps({"error": f"Gemini API Error: {str(e)}"})

def parse_llm_response(response_str: str) -> Dict[str, Any]:
    """
    Parse kết quả từ Gemini.
    Vì Gemini đã bật 'response_mime_type': 'application/json', 
    nó đảm bảo 99.99% trả về JSON chuẩn, không cần cắt gọt chuỗi phức tạp.
    """
    try:
        # Gemini đôi khi trả về dư khoảng trắng, strip cho chắc
        clean_str = response_str.strip()
        return json.loads(clean_str)
    except json.JSONDecodeError:
        print(f"JSON Error. Raw response: {response_str}")
        return {"error": "Invalid JSON from Gemini", "raw_response": response_str}
    except Exception as e:
        return {"error": f"Parse Error: {str(e)}"}