""" Tools Service for LLM Function Calling HuggingFace-compatible với prompt engineering """ import httpx from typing import List, Dict, Any, Optional import json import asyncio class ToolsService: """ Manages external API tools that LLM can call via prompt engineering """ def __init__(self, base_url: str = "https://www.festavenue.site"): self.base_url = base_url self.client = httpx.AsyncClient(timeout=10.0) def get_tools_prompt(self) -> str: """ Return prompt instruction for HuggingFace LLM về available tools """ return """ AVAILABLE TOOLS: Bạn có thể sử dụng các công cụ sau để lấy thông tin chi tiết: 1. get_event_details(event_code: str) - Mô tả: Lấy thông tin đầy đủ về một sự kiện từ hệ thống - Khi nào dùng: Khi user hỏi về ngày giờ chính xác, địa điểm cụ thể, thông tin liên hệ, hoặc chi tiết khác về một sự kiện - Tham số: event_code (mã sự kiện, ví dụ: "Y-CONCERT", "EVT001") - Ví dụ: User hỏi "Ngày tổ chức Y-CONCERT là khi nào?" → Dùng get_event_details("Y-CONCERT") CÚ PHÁP GỌI TOOL: Khi bạn cần gọi tool, hãy trả lời CHÍNH XÁC theo format JSON này: ```json { "tool_call": true, "function_name": "get_event_details", "arguments": { "event_code": "Y-CONCERT" }, "reason": "Cần lấy thông tin chính xác về ngày giờ tổ chức" } ``` QUAN TRỌNG: - CHỈ trả JSON khi BẮT BUỘC cần gọi tool - Nếu có thể trả lời từ context sẵn có, đừng gọi tool - Sau khi nhận kết quả từ tool, hãy trả lời user bằng ngôn ngữ tự nhiên """ async def parse_and_execute(self, llm_response: str) -> Optional[Dict[str, Any]]: """ Parse LLM response và execute tool nếu có Returns: None nếu không có tool call Dict với tool result nếu có tool call """ # Try to extract JSON from response try: # Tìm JSON block trong response if "```json" in llm_response: json_start = llm_response.find("```json") + 7 json_end = llm_response.find("```", json_start) json_str = llm_response[json_start:json_end].strip() elif "{" in llm_response and "}" in llm_response: # Fallback: tìm JSON object đầu tiên json_start = llm_response.find("{") json_end = llm_response.rfind("}") + 1 json_str = llm_response[json_start:json_end] else: return None tool_call = json.loads(json_str) # Validate tool call structure if not tool_call.get("tool_call"): return None function_name = tool_call.get("function_name") arguments = tool_call.get("arguments", {}) # Execute tool if function_name == "get_event_details": result = await self._get_event_details(arguments.get("event_code")) return { "function": function_name, "arguments": arguments, "result": result } else: return { "function": function_name, "arguments": arguments, "result": {"success": False, "error": f"Unknown function: {function_name}"} } except (json.JSONDecodeError, KeyError, ValueError) as e: # Không phải tool call, response bình thường return None async def _get_event_details(self, event_code: str) -> Dict[str, Any]: """ Call getEventByEventCode API """ try: response = await self.client.get( f"{self.base_url}/event/get-event-by-event-code", params={"eventCode": event_code} ) response.raise_for_status() data = response.json() # Extract relevant fields event = data.get("data", {}) if not event: return { "success": False, "error": "Event not found", "message": f"Không tìm thấy sự kiện với mã {event_code}" } return { "success": True, "event_code": event.get("eventCode"), "event_name": event.get("eventName"), "description": event.get("description"), "short_description": event.get("shortDescription"), "start_time": event.get("startTimeEventTime"), "end_time": event.get("endTimeEventTime"), "start_sale": event.get("startTicketSaleTime"), "end_sale": event.get("endTicketSaleTime"), "location": { "address": event.get("location", {}).get("address"), "city": event.get("location", {}).get("city"), }, "contact": { "email": event.get("publicContactEmail"), "phone": event.get("publicContactPhone"), "website": event.get("website") }, "capacity": event.get("capacity"), "hashtags": event.get("hashtags", []) } except httpx.HTTPStatusError as e: return { "success": False, "error": f"HTTP {e.response.status_code}", "message": f"API trả về lỗi khi truy vấn sự kiện {event_code}" } except Exception as e: return { "success": False, "error": str(e), "message": "Không thể kết nối đến API để lấy thông tin sự kiện" } async def close(self): """Close HTTP client""" await self.client.aclose()