File size: 8,823 Bytes
75033ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
"""

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

QUAN TRỌNG:
- event_code PHẢI LÀ metadata.id_use từ context (dạng MongoDB ObjectId)
- KHÔNG dùng tên sự kiện như "Y-CONCERT" làm event_code
- 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)

            

            # Handle multiple JSON formats from LLM

            

            # Format 1: HF API nested wrapper

            # {"name": "tool_call", "arguments": {"tool_call": true, ...}}

            if "name" in tool_call and "arguments" in tool_call and isinstance(tool_call["arguments"], dict):

                if "tool_call" in tool_call["arguments"]:

                    tool_call = tool_call["arguments"]  # Unwrap

            

            # Format 2: Direct tool name format

            # {"name": "tool.get_event_details", "arguments": {"event_code": "..."}}

            if "name" in tool_call and "arguments" in tool_call:

                function_name = tool_call["name"]

                # Remove "tool." prefix if exists

                if function_name.startswith("tool."):

                    function_name = function_name.replace("tool.", "")

                

                # Convert to standard format

                tool_call = {

                    "tool_call": True,

                    "function_name": function_name,

                    "arguments": tool_call["arguments"],

                    "reason": "Converted from alternate format"

                }

            

            # 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
        """

        print(f"\n=== CALLING API get_event_details ===")

        print(f"Event Code: {event_code}")

        

        try:

            url = f"https://hoalacrent.io.vn/api/v0/event/get-event-by-event-code"

            params = {"eventCode": event_code}

            

            print(f"URL: {url}")

            print(f"Params: {params}")

            

            response = await self.client.get(url, params=params)

            

            print(f"Status Code: {response.status_code}")

            

            # Log raw response for debugging

            raw_text = response.text

            print(f"Raw Response Length: {len(raw_text)} chars")

            print(f"Raw Response Preview (first 200 chars): {raw_text[:200]}")

            

            response.raise_for_status()

            

            # Try to parse JSON

            try:

                data = response.json()

            except json.JSONDecodeError as e:

                print(f"JSON Decode Error: {e}")

                print(f"Full Raw Response: {raw_text}")

                return {

                    "success": False,

                    "error": f"Invalid JSON response from API",

                    "message": "API trả về dữ liệu không hợp lệ (không phải JSON)",

                    "raw_response_preview": raw_text[:500]

                }

            

            print(f"Response Data Keys: {list(data.keys()) if data else 'None'}")

            print(f"Has 'data' field: {'data' in data}")

            

            # 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}"

                }

            

            # Extract location với nested address structure

            location_data = event.get("location", {})

            location = {

                "address": {

                    "street": location_data.get("address", {}).get("street", ""),

                    "city": location_data.get("address", {}).get("city", ""),

                    "state": location_data.get("address", {}).get("state", ""),

                    "postalCode": location_data.get("address", {}).get("postalCode", ""),

                    "country": location_data.get("address", {}).get("country", "")

                },

                "coordinates": {

                    "latitude": location_data.get("coordinates", {}).get("latitude"),

                    "longitude": location_data.get("coordinates", {}).get("longitude")

                }

            }

            

            # Build event URL

            event_code = event.get("eventCode")

            event_url = f"https://www.festavenue.site/user/event/{event_code}" if event_code else None

            

            return {

                "success": True,

                "event_code": event_code,

                "event_name": event.get("eventName"),

                "event_url": event_url,  # NEW: Direct link to event page

                "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": location,  # Full nested structure

                "contact": {

                    "email": event.get("publicContactEmail"),

                    "phone": event.get("publicContactPhone"),

                    "website": event.get("website")

                },

                "capacity": event.get("capacity"),

                "hashtags": event.get("hashtags", [])

            }

            

            print(f"Successfully extracted event data for: {event.get('eventName')}")

            print(f"=== API CALL COMPLETE ===")

            return result

            

        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()