|
| 1 | +import logging |
| 2 | +import google.generativeai as genai |
| 3 | +from typing import Dict, Any, List |
| 4 | +from ..dependencies import get_settings |
| 5 | + |
| 6 | +log = logging.getLogger(__name__) |
| 7 | + |
| 8 | +class AnswerComposerService: |
| 9 | + def __init__(self): |
| 10 | + settings = get_settings() |
| 11 | + if not settings.gemini_api_key: |
| 12 | + raise ValueError("GEMINI_API_KEY not found") |
| 13 | + genai.configure(api_key=settings.gemini_api_key) |
| 14 | + self.model_name = 'gemini-2.5-flash' # Or settings.GEMINI_COMPOSER_MODEL |
| 15 | + |
| 16 | + def compose( |
| 17 | + self, |
| 18 | + question: str, |
| 19 | + graph_result: Dict[str, Any], |
| 20 | + web_context: str = "" |
| 21 | + ) -> str: |
| 22 | + """ |
| 23 | + Tổng hợp câu trả lời cuối cùng. |
| 24 | + """ |
| 25 | + final_choice = graph_result.get("final_choice") |
| 26 | + |
| 27 | + # Xây dựng context từ Graph |
| 28 | + graph_context_str = "" |
| 29 | + if final_choice: |
| 30 | + graph_context_str = ( |
| 31 | + f"THÔNG TIN TỪ CƠ SỞ DỮ LIỆU LUẬT (Tin cậy cao):\n" |
| 32 | + f"- Nguồn: {final_choice['source_type']} (Node ID: {final_choice['source_node_id']})\n" |
| 33 | + f"- Nội dung:\n{final_choice['chosen_snippet']}\n" |
| 34 | + ) |
| 35 | + else: |
| 36 | + graph_context_str = "Không tìm thấy thông tin trong cơ sở dữ liệu luật nội bộ." |
| 37 | + |
| 38 | + # Xây dựng context từ Web (nếu có) |
| 39 | + web_context_str = "" |
| 40 | + if web_context: |
| 41 | + web_context_str = f"\nTHÔNG TIN BỔ SUNG TỪ WEB (Tham khảo):\n{web_context}\n" |
| 42 | + |
| 43 | + prompt = f""" |
| 44 | +Bạn là Trợ lý Luật sư AI chuyên nghiệp (Legal Assistant). |
| 45 | +
|
| 46 | +NHIỆM VỤ: |
| 47 | +Trả lời câu hỏi của người dùng dựa trên các nguồn thông tin được cung cấp dưới đây. |
| 48 | +
|
| 49 | +CÂU HỎI: "{question}" |
| 50 | +
|
| 51 | +NGUỒN THÔNG TIN: |
| 52 | +{graph_context_str} |
| 53 | +{web_context_str} |
| 54 | +
|
| 55 | +YÊU CẦU TRẢ LỜI: |
| 56 | +1. **Chính xác & Cẩn trọng**: Chỉ trả lời dựa trên thông tin được cung cấp. Nếu thông tin mâu thuẫn, ưu tiên "THÔNG TIN TỪ CƠ SỞ DỮ LIỆU LUẬT". |
| 57 | +2. **Trích dẫn rõ ràng**: |
| 58 | + - Luôn trích dẫn Điều/Khoản/Luật nếu có trong nội dung (ví dụ: "Theo Khoản 1 Điều 60 Luật BHXH..."). |
| 59 | + - Nếu dùng thông tin từ Web, hãy nói rõ "Theo thông tin tham khảo từ nguồn web...". |
| 60 | +3. **Cấu trúc mạch lạc**: |
| 61 | + - Mở đầu: Trả lời trực tiếp vào vấn đề. |
| 62 | + - Thân bài: Giải thích chi tiết, nêu căn cứ pháp lý. |
| 63 | + - Kết luận: Tóm tắt lại hoặc đưa ra lời khuyên (nếu phù hợp). |
| 64 | +4. **Không bịa đặt**: Nếu không có đủ thông tin để trả lời, hãy thành thật xin lỗi và đề xuất người dùng cung cấp thêm chi tiết hoặc tra cứu nguồn khác. |
| 65 | +
|
| 66 | +HÃY VIẾT CÂU TRẢ LỜI HOÀN CHỈNH: |
| 67 | +""" |
| 68 | + model = genai.GenerativeModel( |
| 69 | + self.model_name, |
| 70 | + generation_config={"temperature": 0.2} # Hơi sáng tạo một chút để viết văn mượt mà |
| 71 | + ) |
| 72 | + |
| 73 | + try: |
| 74 | + response = model.generate_content(prompt) |
| 75 | + return response.text.strip() |
| 76 | + except Exception as e: |
| 77 | + log.error(f"Lỗi compose answer: {e}") |
| 78 | + return "Xin lỗi, hệ thống gặp sự cố khi tổng hợp câu trả lời." |
| 79 | + |
| 80 | + def compose_stream( |
| 81 | + self, |
| 82 | + question: str, |
| 83 | + graph_result: Dict[str, Any], |
| 84 | + web_context: str = "" |
| 85 | + ): |
| 86 | + """ |
| 87 | + Tổng hợp câu trả lời cuối cùng (Streaming). |
| 88 | + """ |
| 89 | + final_choice = graph_result.get("final_choice") |
| 90 | + |
| 91 | + # Xây dựng context từ Graph |
| 92 | + graph_context_str = "" |
| 93 | + if final_choice: |
| 94 | + graph_context_str = ( |
| 95 | + f"THÔNG TIN TỪ CƠ SỞ DỮ LIỆU LUẬT (Tin cậy cao):\n" |
| 96 | + f"- Nguồn: {final_choice['source_type']} (Node ID: {final_choice['source_node_id']})\n" |
| 97 | + f"- Nội dung:\n{final_choice['chosen_snippet']}\n" |
| 98 | + ) |
| 99 | + else: |
| 100 | + graph_context_str = "Không tìm thấy thông tin trong cơ sở dữ liệu luật nội bộ." |
| 101 | + |
| 102 | + # Xây dựng context từ Web (nếu có) |
| 103 | + web_context_str = "" |
| 104 | + if web_context: |
| 105 | + web_context_str = f"\nTHÔNG TIN TỪ WEB (Tham khảo):\n{web_context}\n" |
| 106 | + |
| 107 | + prompt = f""" |
| 108 | +Bạn là Trợ lý Luật sư AI chuyên nghiệp (Legal Assistant). |
| 109 | +
|
| 110 | +NHIỆM VỤ: |
| 111 | +Trả lời câu hỏi của người dùng dựa trên các nguồn thông tin được cung cấp dưới đây. |
| 112 | +
|
| 113 | +CÂU HỎI: "{question}" |
| 114 | +
|
| 115 | +NGUỒN THÔNG TIN: |
| 116 | +{graph_context_str} |
| 117 | +{web_context_str} |
| 118 | +
|
| 119 | +YÊU CẦU TRẢ LỜI: |
| 120 | +1. **Chính xác & Cẩn trọng**: Chỉ trả lời dựa trên thông tin được cung cấp. Nếu thông tin mâu thuẫn, ưu tiên "THÔNG TIN TỪ CƠ SỞ DỮ LIỆU LUẬT". |
| 121 | +2. **Trích dẫn rõ ràng**: |
| 122 | + - Luôn trích dẫn Điều/Khoản/Luật nếu có trong nội dung (ví dụ: "Theo Khoản 1 Điều 60 Luật BHXH..."). |
| 123 | + - Nếu dùng thông tin từ Web, hãy nói rõ "Theo thông tin tham khảo từ nguồn web...". |
| 124 | +3. **Cấu trúc mạch lạc**: |
| 125 | + - Mở đầu: Trả lời trực tiếp vào vấn đề. |
| 126 | + - Thân bài: Giải thích chi tiết, nêu căn cứ pháp lý. |
| 127 | + - Kết luận: Tóm tắt lại hoặc đưa ra lời khuyên (nếu phù hợp). |
| 128 | +4. **Không bịa đặt**: Nếu không có đủ thông tin để trả lời, hãy thành thật xin lỗi và đề xuất người dùng cung cấp thêm chi tiết hoặc tra cứu nguồn khác. |
| 129 | +
|
| 130 | +HÃY VIẾT CÂU TRẢ LỜI HOÀN CHỈNH: |
| 131 | +""" |
| 132 | + model = genai.GenerativeModel( |
| 133 | + self.model_name, |
| 134 | + generation_config={"temperature": 0.2} |
| 135 | + ) |
| 136 | + |
| 137 | + try: |
| 138 | + response = model.generate_content(prompt, stream=True) |
| 139 | + for chunk in response: |
| 140 | + if chunk.text: |
| 141 | + yield chunk.text |
| 142 | + except Exception as e: |
| 143 | + log.error(f"Lỗi compose answer stream: {e}") |
| 144 | + yield "Xin lỗi, hệ thống gặp sự cố khi tổng hợp câu trả lời." |
0 commit comments