kltn21110 commited on
Commit
325b400
·
verified ·
1 Parent(s): e6f777b

Upload 239 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +12 -0
  2. api_key.txt +2 -0
  3. ca.pem +26 -0
  4. controller/CallbackController.py +212 -0
  5. controller/ChatController.py +224 -0
  6. controller/GroupOrderController.py +25 -0
  7. controller/RecommendController.py +25 -0
  8. controller/RedirectController.py +96 -0
  9. controller/RefundController.py +150 -0
  10. controller/__init__.py +0 -0
  11. controller/__pycache__/CallbackController.cpython-311.pyc +0 -0
  12. controller/__pycache__/ChatController.cpython-311.pyc +0 -0
  13. controller/__pycache__/GroupOrderController.cpython-311.pyc +0 -0
  14. controller/__pycache__/RecommendController.cpython-311.pyc +0 -0
  15. controller/__pycache__/RedirectController.cpython-311.pyc +0 -0
  16. controller/__pycache__/RefundController.cpython-311.pyc +0 -0
  17. controller/__pycache__/__init__.cpython-311.pyc +0 -0
  18. controller/__pycache__/task_manager.cpython-311.pyc +0 -0
  19. function/__init__.py +1 -0
  20. function/__pycache__/__init__.cpython-311.pyc +0 -0
  21. function/__pycache__/__init__.cpython-312.pyc +0 -0
  22. function/__pycache__/chat.cpython-311.pyc +0 -0
  23. function/__pycache__/chat.cpython-312.pyc +0 -0
  24. function/advance_shopping/call_gemini/__pycache__/tool_call.cpython-311.pyc +0 -0
  25. function/advance_shopping/call_gemini/tool_call.py +44 -0
  26. function/advance_shopping/function/__pycache__/test.cpython-311.pyc +0 -0
  27. function/advance_shopping/function/test.py +615 -0
  28. function/advance_shopping/prompt/__pycache__/context_prompt.cpython-311.pyc +0 -0
  29. function/advance_shopping/prompt/__pycache__/multitool_prompt.cpython-311.pyc +0 -0
  30. function/advance_shopping/prompt/__pycache__/user_intent.cpython-311.pyc +0 -0
  31. function/advance_shopping/prompt/context_prompt.py +38 -0
  32. function/advance_shopping/prompt/multitool_prompt.py +83 -0
  33. function/advance_shopping/prompt/user_intent.py +127 -0
  34. function/advance_shopping/server_java/__pycache__/server_java.cpython-311.pyc +0 -0
  35. function/advance_shopping/server_java/config.yaml +53 -0
  36. function/advance_shopping/server_java/server_java.py +338 -0
  37. function/agent/__init__.py +0 -0
  38. function/agent/function_call.py +190 -0
  39. function/agent/pipeline_agent.py +136 -0
  40. function/analyze/__init__.py +0 -0
  41. function/analyze/__pycache__/__init__.cpython-311.pyc +0 -0
  42. function/analyze/__pycache__/execute_query.cpython-311.pyc +0 -0
  43. function/analyze/__pycache__/main.cpython-311.pyc +0 -0
  44. function/analyze/__pycache__/merge_output.cpython-311.pyc +0 -0
  45. function/analyze/__pycache__/pipeline.cpython-311.pyc +0 -0
  46. function/analyze/__pycache__/prompt_databse.cpython-311.pyc +0 -0
  47. function/analyze/__pycache__/prompt_query.cpython-311.pyc +0 -0
  48. function/analyze/__pycache__/return_code_python.cpython-311.pyc +0 -0
  49. function/analyze/execute_query.py +394 -0
  50. function/analyze/gemini/__init__.py +0 -0
.env ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DB_HOST=mysql-22f29707-student-4328.h.aivencloud.com
2
+ DB_USER=avnadmin
3
+ DB_PASSWORD=AVNS_D-m-KFb1aPKlV4JBIw7
4
+ DB_NAME=demohmdrinks
5
+ DB_PORT=18340
6
+ MONGO_URI = mongodb+srv://muyxue:389126@cluster0.190kw.mongodb.net/hmdrinks?retryWrites=true&w=majority&appName=Cluster0
7
+ GROQ_API_KEY=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
8
+ COHERE_API_KEY=v34SUVXvG16NlmEZQt1mtwSeUbbdbxCy5KCFue11
9
+ GOOGLE_API_KEY_1=AIzaSyCO-RlqYewC4e9BEPoC8m-AxHUY7J3_o2E
10
+ COHERE_API_KEY_2= v34SUVXvG16NlmEZQt1mtwSeUbbdbxCy5KCFue11
11
+ COHERE_API_KEY_3= v34SUVXvG16NlmEZQt1mtwSeUbbdbxCy5KCFue11
12
+ GOOGLE_API_KEY=AIzaSyCO-RlqYewC4e9BEPoC8m-AxHUY7J3_o2E
api_key.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac
2
+ AIzaSyAW23eFBB3u2bUfM0sxk-WS_nS2-sV4cUA
ca.pem ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEUDCCArigAwIBAgIUfxHeji94PVdRnLYTj22XRvvdXXwwDQYJKoZIhvcNAQEM
3
+ BQAwQDE+MDwGA1UEAww1NDY4MDI2MDYtZGM1OC00NDM4LTg0ODctNzQxNWE5MjVl
4
+ YjJmIEdFTiAxIFByb2plY3QgQ0EwHhcNMjUwNjE5MDkyNzM2WhcNMzUwNjE3MDky
5
+ NzM2WjBAMT4wPAYDVQQDDDU0NjgwMjYwNi1kYzU4LTQ0MzgtODQ4Ny03NDE1YTky
6
+ NWViMmYgR0VOIDEgUHJvamVjdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCC
7
+ AYoCggGBAMqawd0fx+VhLXb1sOcvAOJAjnybF4phtphIZywDc+wrSiJKmKpFdIEV
8
+ 9d4hyzHqby7uyFvXp4qe710D/1vYzTfAtWyYra3Fq5FAR+rZoQfKeBJneLvWr4OM
9
+ O4nhntrXLWoz5z5dvrwwMQdFXK5zWL776gsWo9Zfved1/VrBma0PMnLWGCxBhe8D
10
+ CUvFwj9WDLcBu1Wpemj5hNUYV7HrIKYKnRXT6LsW8LAcwdVvwkFMeTYXoAvxZime
11
+ jgdm0l9aBHmkxVQB4yV2jzSjYl+RfBv9Fr0sFsTjYRhWTnqgotwQ/kWtV2lPvTC8
12
+ 9RrR+Zv4Npr5GFva856J8F7vJYE4u8iXtcW88/8R2BD6X7Is0bNSXN4ddb4e1uG1
13
+ xibOwNdFAbaiRfQpdg5uA87LCbb4JsU0WRe705qJHOPynVMQsQHDqioexVkJwNot
14
+ FhRG7j9rQLMihC5IRbxYql0FLamm0xb32Z4bSTqmwQacQF+uB27i6rxB3w8wV2C0
15
+ tXmBa9siVwIDAQABo0IwQDAdBgNVHQ4EFgQUHgH+oY6nEHz0RQ7Kov6/1L1/scYw
16
+ EgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEMBQAD
17
+ ggGBAJSFK9FoV5d/PxtrP2z+BViDjgXeoJ6tLVykLjpVEr0OCIzwgi3F8O4a1U7P
18
+ 2BQu5URiiiC28aoIvDXcmowQFJBw6DaVIadGACifNORCWals+gT+4l2kdnRFW8fy
19
+ gP+cHjj9IBLtmcgttMTTRArTYgYO9GfuK1PMD8srG/syue5V/bQ6+vmO6gAtuTs1
20
+ cnQXzzMyZKNLb90MERfojegLXCyoI2O5nC604fhDSgnVUKzw7g6vRPHAWPQGCZn1
21
+ DwpI7BydRRs57F/Tigz8EgbzC2cuTQaaWr3oOiBTsGl5Nkc2iPxKXzoPHMUT3MD3
22
+ vRosEkl+LN5hupYbaHagHT50n9j+AG5KXiRoN4S0IxJZWSGMpYMYEQ4Qkd3AvRnW
23
+ ECdT5ZXXm19a4teTZIk1fZ9dITnn/vobfW0MhpG4kkHIOBHVy4VJrLqFrQfLOVQM
24
+ qVgKdc0K6FfDnfNdvkrfzJoLoR0X6AtVS60pfpolBXNQEPNWcdKDL0DSBbZ+rnid
25
+ 9nYErA==
26
+ -----END CERTIFICATE-----
controller/CallbackController.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request,HTTPException
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from fastapi.requests import Request
4
+ import yaml
5
+ from typing import Dict
6
+ from fastapi.responses import RedirectResponse
7
+ from service import CallBackService
8
+ import json
9
+ from fastapi.responses import JSONResponse
10
+ from models.Database_Entity import PaymentCallbackLog
11
+ with open("config.yaml", "r") as f:
12
+ config = yaml.safe_load(f)
13
+ base_backend_java = config["callback_urls"]
14
+ base_frontend = config["frontend"]
15
+
16
+ router = APIRouter()
17
+
18
+ class JWTBearer(HTTPBearer):
19
+ def __init__(self, auto_error: bool = True):
20
+ super(JWTBearer, self).__init__(auto_error=auto_error)
21
+
22
+ async def __call__(self, request: Request):
23
+ credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
24
+ if credentials:
25
+ if credentials.scheme != "Bearer":
26
+ raise HTTPException(status_code=401, detail="Invalid authentication scheme.")
27
+ return credentials.credentials
28
+ else:
29
+ raise HTTPException(status_code=401, detail="Invalid authorization code.")
30
+
31
+ jwt_bearer = JWTBearer()
32
+
33
+
34
+
35
+ @router.post("/android/zalo")
36
+ async def zalo_callback_post(request: Request):
37
+ try:
38
+ json_data = await request.json()
39
+ data_str =json_data.get("data")
40
+
41
+ if data_str:
42
+ data_dict = json.loads(data_str)
43
+ app_trans_id = data_dict.get("app_trans_id")
44
+ PaymentCallbackLog(
45
+ type="zalopay",
46
+ app_trans_id=data_dict.get("app_trans_id"),
47
+ raw_data=data_dict,
48
+ status="success" if data_dict.get("status") == "1" else "fail",
49
+ is_refund = False
50
+ ).save()
51
+
52
+ print(app_trans_id)
53
+ data = await CallBackService.zalo_callback(str(app_trans_id))
54
+ return data
55
+ except Exception as e:
56
+ print("Lỗi khi gọi callback web zalo:", e)
57
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
58
+
59
+
60
+ @router.post("/android/group/zalo")
61
+ async def zalo_callback_post(request: Request):
62
+ try:
63
+ json_data = await request.json()
64
+ data_str =json_data.get("data")
65
+ if data_str:
66
+ data_dict = json.loads(data_str)
67
+ app_trans_id = data_dict.get("app_trans_id")
68
+ PaymentCallbackLog(
69
+ type="zalopay",
70
+ app_trans_id=data_dict.get("app_trans_id"),
71
+ raw_data=data_dict,
72
+ status="success" if data_dict.get("status") == "1" else "fail",
73
+ is_refund = False
74
+ ).save()
75
+ print(app_trans_id)
76
+ data = await CallBackService.zalo_group_callback(str(app_trans_id))
77
+ return data
78
+ except Exception as e:
79
+ print("Lỗi khi gọi callback group ảndroid zalo:", e)
80
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
81
+
82
+
83
+ @router.get("/android/payOs")
84
+ async def return_url_handler(code: str = None, id: str = None, cancel: bool = None, status: str = None, orderCode: str = None):
85
+ try:
86
+ return await CallBackService.android_payos_callback(status,orderCode)
87
+ except Exception as e:
88
+ print("Lỗi khi gọi callback android payos:", e)
89
+ # Trả về lỗi 500 - Internal Server Error
90
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
91
+
92
+
93
+ @router.get("/android/group/payOs")
94
+ async def return_url_handler1(code: str = None, id: str = None, cancel: bool = None, status: str = None, orderCode: str = None):
95
+ try:
96
+ return await CallBackService.android_group_payOs_callback(status,orderCode)
97
+ except Exception as e:
98
+ print("Lỗi khi gọi callback group payos:", e)
99
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
100
+
101
+
102
+ @router.get("/web/payOs")
103
+ async def return_url_handler(code: str = None, id: str = None, cancel: bool = None, status: str = None, orderCode: str = None):
104
+ try:
105
+ return await CallBackService.web_payos_callback(code, id, cancel, status, orderCode)
106
+ except Exception as e:
107
+ print("Lỗi khi gọi callback web payos:", e)
108
+ # Trả về lỗi 500 - Internal Server Error
109
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
110
+
111
+
112
+ @router.post("/android/momo")
113
+ async def return_url_handler(request: Request):
114
+ json_data = await request.json()
115
+ print(json_data)
116
+
117
+ data_str =json_data.get("data")
118
+ order_id_original = json_data.get("orderId")
119
+ order_id = ""
120
+ if order_id_original.startswith("MOMO-"):
121
+ order_id = order_id_original[len("MOMO-"):]
122
+ result_code = json_data.get("resultCode")
123
+ PaymentCallbackLog(
124
+ type="momo",
125
+ order_id=order_id,
126
+ raw_data=json_data,
127
+ status="success" if result_code == 0 else "fail",
128
+ is_refund = False
129
+ ).save()
130
+ print(data_str)
131
+
132
+ try:
133
+ result = await CallBackService.momo_callback(order_id_original,result_code)
134
+ print(result)
135
+ except Exception as e:
136
+ print("Lỗi khi gọi call back android momo:", e)
137
+
138
+ if 0 != int(result_code):
139
+ return RedirectResponse(url=f"{base_frontend['android_redirect_base']}?status=-49")
140
+ else :
141
+ return RedirectResponse(url=f"{base_frontend['android_redirect_base']}?status=1")
142
+
143
+
144
+ @router.post("/android/group/momo")
145
+ async def return_url_handler(request: Request):
146
+ json_data = await request.json()
147
+ print(json_data)
148
+
149
+ data_str =json_data.get("data")
150
+ order_id = json_data.get("orderId")
151
+ if order_id.startswith("MOMO-"):
152
+ order_id = order_id[len("MOMO-"):]
153
+
154
+ result_code = json_data.get("resultCode")
155
+ print(data_str)
156
+ PaymentCallbackLog(
157
+ type="momo",
158
+ order_id=order_id,
159
+ raw_data=json_data,
160
+ status="success" if result_code == 0 else "fail",
161
+ is_refund = False
162
+ ).save()
163
+
164
+ try:
165
+ result = await CallBackService.momo_group_callback(order_id,result_code)
166
+ print(result)
167
+ except Exception as e:
168
+ print("Lỗi khi gọi callback android group momo:", e)
169
+ if 0 != int(result_code):
170
+ return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=-49""")
171
+ else :
172
+ return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=1""")
173
+
174
+
175
+ @router.get("/android/vnpay_ipn")
176
+ async def vnpay_ipn(request: Request):
177
+ params: Dict[str, str] = dict(request.query_params)
178
+ # Lưu callback vnpay
179
+ PaymentCallbackLog(
180
+ type="vnpay",
181
+ txn_ref=params.get("vnp_TxnRef"),
182
+ raw_data=params,
183
+ status="success" if params.get("vnp_ResponseCode") == "00" else "fail",
184
+ is_refund = False
185
+ ).save()
186
+
187
+ try:
188
+ return await CallBackService.vnpay_callbackk(params)
189
+ except Exception as e:
190
+ print("Lỗi khi gọi callback vnpay:", e)
191
+ # Trả về lỗi 500 - Internal Server Error
192
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
193
+
194
+
195
+ @router.get("/android/group/vnpay_ipn")
196
+ async def vnpay_ipn(request: Request):
197
+ params: Dict[str, str] = dict(request.query_params)
198
+ # Lưu callback vnpay
199
+ PaymentCallbackLog(
200
+ type="vnpay",
201
+ txn_ref=params.get("vnp_TxnRef"),
202
+ raw_data=params,
203
+ status="success" if params.get("vnp_ResponseCode") == "00" else "fail",
204
+ is_refund = False
205
+ ).save()
206
+
207
+ try:
208
+ return await CallBackService.vnpay_group_callback(params)
209
+ except Exception as e:
210
+ print("Lỗi khi gọi callback group vnpay ipn:", e)
211
+ # Trả về lỗi 500 - Internal Server Error
212
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
controller/ChatController.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Form, Request,Depends,HTTPException,BackgroundTasks
2
+ from service import ChatService
3
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4
+ from request import RequestChat
5
+ from fastapi.requests import Request
6
+ from fastapi.responses import JSONResponse
7
+ import asyncio
8
+ router = APIRouter()
9
+ import asyncio
10
+ # Lưu task đang chạy theo chat_history_id
11
+ task_registry: dict[str, asyncio.Task] = {}
12
+ # Lưu stop_event để dừng từng task
13
+ stop_events: dict[str, asyncio.Event] = {}
14
+ import decode_token
15
+ from models.Database_Entity import StopSignal
16
+ from models.Database_Entity import ChatHistory, User
17
+ from repository.MySQL import UserRepository
18
+
19
+ class JWTBearer(HTTPBearer):
20
+ def __init__(self, auto_error: bool = True):
21
+ super(JWTBearer, self).__init__(auto_error=auto_error)
22
+
23
+ async def __call__(self, request: Request):
24
+ credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
25
+ if credentials:
26
+ if credentials.scheme != "Bearer":
27
+ raise HTTPException(status_code=401, detail="Invalid authentication scheme.")
28
+ return credentials.credentials
29
+ else:
30
+ raise HTTPException(status_code=401, detail="Invalid authorization code.")
31
+
32
+ jwt_bearer = JWTBearer()
33
+
34
+
35
+ @router.get("/chat/user_history")
36
+ async def get_user_chat_history(token: str = Depends(jwt_bearer)):
37
+ try:
38
+ user_id_token = decode_token.JwtService.extract_user_id(token)
39
+ return await ChatService.get_user_chat_history(user_id_token)
40
+ except Exception as e:
41
+ print("Lỗi khi gọi user history chat:", e)
42
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
43
+
44
+
45
+ @router.post("/new_chat/create/")
46
+ async def create_chat(token: str = Depends(jwt_bearer)):
47
+ try:
48
+ user_id_token = decode_token.JwtService.extract_user_id(token)
49
+ new_chat = await ChatService.create_new_chat_history(user_id_token)
50
+ return new_chat
51
+ except Exception as e:
52
+ print("Lỗi khi gọi create new chat:", e)
53
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
54
+
55
+
56
+ from bson import ObjectId
57
+ @router.post("/question")
58
+ async def question(
59
+ request: RequestChat.ChatWithServer,
60
+ background_tasks: BackgroundTasks,
61
+ token: str = Depends(jwt_bearer)
62
+ ):
63
+ try:
64
+ user_id_token = decode_token.JwtService.extract_user_id(token)
65
+ user_role = decode_token.JwtService.extract_user_role(token)
66
+ chat_id = request.chat_history_id
67
+
68
+ stop_event = asyncio.Event()
69
+ stop_events[chat_id] = stop_event
70
+ print(f"[CREATE] stop_event id: {id(stop_event)} for chat_id: {chat_id}")
71
+ chat_history = ChatHistory.objects(pk=ObjectId(chat_id)).first()
72
+
73
+ if chat_history:
74
+ signal = StopSignal.objects(chat_history=chat_history).first()
75
+ if not signal:
76
+ signal = StopSignal(chat_history=chat_history)
77
+ signal.is_stopped = False
78
+ signal.stopped_at = None
79
+ signal.save()
80
+
81
+ async def run_chat():
82
+ try:
83
+ result = await ChatService.chat_with_user(
84
+ request.user_input,
85
+ user_id_token,
86
+ request.language,
87
+ user_role,
88
+ token,
89
+ chat_id,
90
+ stop_event
91
+ )
92
+ return result
93
+ except asyncio.CancelledError:
94
+ print(f"🛑 Task {chat_id} was cancelled by asyncio.")
95
+ return {"status": "cancelled"}
96
+ except Exception as e:
97
+ print(f"❌ Lỗi trong task {chat_id}:", e)
98
+ return {"error": str(e)}
99
+ finally:
100
+ # Dọn dẹp
101
+ stop_events.pop(chat_id, None)
102
+ task_registry.pop(chat_id, None)
103
+
104
+ task = asyncio.create_task(run_chat())
105
+ task_registry[chat_id] = task
106
+ return await task
107
+
108
+ except Exception as e:
109
+ print("Lỗi khi chạy:", e)
110
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
111
+
112
+
113
+ from datetime import datetime
114
+ @router.post("/stop-task/{chat_history_id}")
115
+ async def stop_task(chat_history_id: str, token: str = Depends(jwt_bearer)):
116
+ user_id = int(decode_token.JwtService.extract_user_id(token))
117
+ if not isinstance(user_id, int) or user_id <= 0:
118
+ raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer")
119
+ check = UserRepository.getUserByUserId(user_id)
120
+ if check is None:
121
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
122
+
123
+ check_history_id = UserRepository.getChatHistory(user_id,chat_history_id)
124
+ if check_history_id is None:
125
+ raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL")
126
+
127
+ user = User.objects(user_id=user_id).first()
128
+ if not user:
129
+ return {"error": "User not found or has been deleted in MongoDB"}
130
+ event = stop_events.get(chat_history_id)
131
+ task = task_registry.get(chat_history_id)
132
+ print(f"🚨 Đã vào stop-task với chat_history_id = {chat_history_id}")
133
+ print(f"🔎 stop_event: {event}, task: {task}")
134
+ # Set event trong RAM
135
+ if event:
136
+ print(f"🛑 Setting stop_event for {chat_history_id}")
137
+ event.set()
138
+ # Cancel task
139
+ if task:
140
+ print(f"🔪 Cancelling task for {chat_history_id}")
141
+ task.cancel()
142
+
143
+ # Cập nhật trạng thái stop vào MongoDB
144
+ from bson import ObjectId
145
+
146
+ chat_history = ChatHistory.objects(pk=ObjectId(chat_history_id)).first()
147
+
148
+ if chat_history:
149
+ signal = StopSignal.objects(chat_history=chat_history).first()
150
+ if not signal:
151
+ signal = StopSignal(chat_history=chat_history)
152
+ signal.is_stopped = True
153
+ signal.stopped_at = datetime.utcnow()
154
+ signal.save()
155
+ return {"message": f"Stop signal sent for chat_history_id {chat_history_id}"}
156
+
157
+
158
+ @router.put("/regenerate")
159
+ async def regenerate(request: RequestChat.Regenerate, token: str = Depends(jwt_bearer)):
160
+ try:
161
+ user_id_token = decode_token.JwtService.extract_user_id(token)
162
+ user_role = decode_token.JwtService.extract_user_role(token)
163
+ chat_id = request.chat_id
164
+ stop_event = asyncio.Event()
165
+ stop_events[chat_id] = stop_event
166
+ print(f"[CREATE] stop_event id: {id(stop_event)} for chat_id: {chat_id}")
167
+ chat_history = ChatHistory.objects(pk=ObjectId(chat_id)).first()
168
+
169
+ if chat_history:
170
+ signal = StopSignal.objects(chat_history=chat_history).first()
171
+ if not signal:
172
+ signal = StopSignal(chat_history=chat_history)
173
+ signal.is_stopped = False
174
+ signal.stopped_at = None
175
+ signal.save()
176
+
177
+ async def run_chat():
178
+ try:
179
+ new_chat = await ChatService.regenerate(request.question_new ,user_id_token,request.languages ,user_role,token,request.chat_id,stop_event)
180
+ return new_chat
181
+ except asyncio.CancelledError:
182
+ print(f"🛑 Task {chat_id} was cancelled by asyncio.")
183
+ return {"status": "cancelled"}
184
+ except Exception as e:
185
+ print(f"❌ Lỗi trong task {chat_id}:", e)
186
+ return {"error": str(e)}
187
+ task = asyncio.create_task(run_chat())
188
+ task_registry[chat_id] = task
189
+ return await task
190
+ except Exception as e:
191
+ print("Lỗi khi gọi regenerate:", e)
192
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
193
+
194
+
195
+ @router.put("/update")
196
+ async def update_chat_name(request: RequestChat.UpdateNameChat, token: str = Depends(jwt_bearer)):
197
+ try:
198
+ user_id_token = decode_token.JwtService.extract_user_id(token)
199
+ updated_chat = await ChatService.update_chat_name(request.chat_id, request.name_chat,user_id_token)
200
+ return updated_chat
201
+ except Exception as e:
202
+ print("Lỗi khi gọi update:", e)
203
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
204
+
205
+
206
+ @router.delete("/delete")
207
+ async def delete_chat(request: RequestChat.DeleteChatRequest, token: str = Depends(jwt_bearer)):
208
+ try:
209
+ user_id_token = decode_token.JwtService.extract_user_id(token)
210
+ deleted_chat = await ChatService.soft_delete_chat(request.chat_id,user_id_token)
211
+ return deleted_chat
212
+ except Exception as e:
213
+ print("Lỗi khi gọi deleted:", e)
214
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
215
+
216
+
217
+ @router.get("/list_detail_chat/{chat_id}")
218
+ async def get_chat_details(chat_id: str, token: str = Depends(jwt_bearer)):
219
+ try:
220
+ user_id_token = decode_token.JwtService.extract_user_id(token)
221
+ return await ChatService.get_chat_details(chat_id,user_id_token)
222
+ except Exception as e:
223
+ print("Lỗi khi gọi list_detail_chat:", e)
224
+ return JSONResponse(content={"error": "Internal server error"}, status_code=500)
controller/GroupOrderController.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Form, Request,Depends,HTTPException
2
+ from service import ChatService
3
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4
+ from request import RequestChat
5
+ from typing import Optional
6
+ from fastapi.requests import Request
7
+ from fastapi.responses import JSONResponse
8
+
9
+ router = APIRouter()
10
+
11
+ class JWTBearer(HTTPBearer):
12
+ def __init__(self, auto_error: bool = True):
13
+ super(JWTBearer, self).__init__(auto_error=auto_error)
14
+
15
+ async def __call__(self, request: Request):
16
+ credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
17
+ if credentials:
18
+ if credentials.scheme != "Bearer":
19
+ raise HTTPException(status_code=401, detail="Invalid authentication scheme.")
20
+ return credentials.credentials
21
+ else:
22
+ raise HTTPException(status_code=401, detail="Invalid authorization code.")
23
+
24
+ jwt_bearer = JWTBearer()
25
+ import decode_token
controller/RecommendController.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Form, Request,Depends,HTTPException
2
+ from service import ChatService
3
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4
+ from request import RequestChat
5
+ from typing import Optional
6
+ from fastapi.requests import Request
7
+
8
+
9
+ router = APIRouter()
10
+
11
+ class JWTBearer(HTTPBearer):
12
+ def __init__(self, auto_error: bool = True):
13
+ super(JWTBearer, self).__init__(auto_error=auto_error)
14
+
15
+ async def __call__(self, request: Request):
16
+ credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
17
+ if credentials:
18
+ if credentials.scheme != "Bearer":
19
+ raise HTTPException(status_code=401, detail="Invalid authentication scheme.")
20
+ return credentials.credentials
21
+ else:
22
+ raise HTTPException(status_code=401, detail="Invalid authorization code.")
23
+
24
+ jwt_bearer = JWTBearer()
25
+ import decode_token
controller/RedirectController.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Form, Request,Depends,HTTPException
2
+ from service import ChatService
3
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4
+ from request import RequestChat
5
+ from typing import Optional
6
+ from fastapi.requests import Request
7
+
8
+
9
+ router = APIRouter()
10
+
11
+ class JWTBearer(HTTPBearer):
12
+ def __init__(self, auto_error: bool = True):
13
+ super(JWTBearer, self).__init__(auto_error=auto_error)
14
+
15
+ async def __call__(self, request: Request):
16
+ credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
17
+ if credentials:
18
+ if credentials.scheme != "Bearer":
19
+ raise HTTPException(status_code=401, detail="Invalid authentication scheme.")
20
+ return credentials.credentials
21
+ else:
22
+ raise HTTPException(status_code=401, detail="Invalid authorization code.")
23
+
24
+ jwt_bearer = JWTBearer()
25
+ import decode_token
26
+ from service import RedirectService
27
+ from typing import Optional
28
+ from fastapi import Request, Query
29
+ from fastapi.responses import JSONResponse
30
+ @router.get("/android/zalo")
31
+ async def zalo_callback_get(
32
+ amount: Optional[int] = Query(None),
33
+ appid: Optional[str] = Query(None),
34
+ apptransid: Optional[str] = Query(None),
35
+ bankcode: Optional[str] = Query(None),
36
+ checksum: Optional[str] = Query(None),
37
+ discountamount: Optional[int] = Query(None),
38
+ pmcid: Optional[int] = Query(None),
39
+ status: Optional[int] = Query(None)
40
+ ):
41
+ return await RedirectService.zalo_redirect(status,apptransid)
42
+
43
+
44
+ @router.get("/android/group/zalo")
45
+ async def zalo_callback_get(
46
+ amount: Optional[int] = Query(None),
47
+ appid: Optional[str] = Query(None),
48
+ apptransid: Optional[str] = Query(None),
49
+ bankcode: Optional[str] = Query(None),
50
+ checksum: Optional[str] = Query(None),
51
+ discountamount: Optional[int] = Query(None),
52
+ pmcid: Optional[int] = Query(None),
53
+ status: Optional[int] = Query(None)
54
+ ):
55
+ return await RedirectService.zalo_group_redirect(status,apptransid)
56
+
57
+ @router.get("/web/zalo")
58
+ async def zalo_web_callback_get(request: Request):
59
+ return await RedirectService.zalo_web_redirect(request.query_params)
60
+
61
+
62
+ @router.get("/android/momo")
63
+ async def return_url_handler_1(request: Request):
64
+ query_params = request.query_params
65
+ return await RedirectService.android_momo_redirect(query_params)
66
+
67
+ @router.get("/android/group/momo")
68
+ async def return_url_handler_group_momo(request: Request):
69
+ query_params = request.query_params
70
+ return await RedirectService.android_group_momo_redirect(query_params)
71
+
72
+
73
+ from urllib.parse import urlencode
74
+ @router.get("/web/momo")
75
+ async def return_url_handler_web(request: Request):
76
+ query_params = request.query_params
77
+ return await RedirectService.web_momo_redirect(query_params)
78
+
79
+
80
+
81
+ from typing import Dict
82
+ @router.get("/android/vnpay")
83
+ async def vnpay_ipn_redirect(request: Request):
84
+ params: Dict[str, str] = dict(request.query_params)
85
+ return await RedirectService.android_vnpay_ipn_redirect(params)
86
+
87
+
88
+ @router.get("/android/group/vnpay")
89
+ async def vnpay_ipn_redirect(request: Request):
90
+ params: Dict[str, str] = dict(request.query_params)
91
+ return await RedirectService.android_vnpay_group_ipn_redirect(params)
92
+
93
+ @router.get("/web/vnpay")
94
+ async def vnpay_ipn_redirect(request: Request):
95
+ query_params = request.query_params
96
+ return await RedirectService.web_vnpay_ipn_redirect(query_params)
controller/RefundController.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request, Depends, HTTPException
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from fastapi.requests import Request
4
+ from fastapi.responses import JSONResponse
5
+ import asyncio
6
+ router = APIRouter()
7
+ import asyncio
8
+ class JWTBearer(HTTPBearer):
9
+ def __init__(self, auto_error: bool = True):
10
+ super(JWTBearer, self).__init__(auto_error=auto_error)
11
+
12
+ async def __call__(self, request: Request):
13
+ credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
14
+ if credentials:
15
+ if credentials.scheme != "Bearer":
16
+ raise HTTPException(status_code=401, detail="Invalid authentication scheme.")
17
+ return credentials.credentials
18
+ else:
19
+ raise HTTPException(status_code=401, detail="Invalid authorization code.")
20
+ import sys,os
21
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
22
+ jwt_bearer = JWTBearer()
23
+ import decode_token
24
+
25
+ from models.Database_Entity import StopSignal
26
+ from models.Database_Entity import ChatHistory,PaymentCallbackLog
27
+ from service.RefundService import *
28
+ from models.Database_Entity import PaymentCallbackLog
29
+ from bson import ObjectId
30
+ from mongoengine import connect
31
+ from dotenv import load_dotenv
32
+ load_dotenv()
33
+ MONGO_URI = os.getenv("MONGO_URI", "")
34
+ connect("chatbot_hmdrinks", host=MONGO_URI)
35
+
36
+
37
+ async def refund_by_type_and_code(type: str, code: str):
38
+ if type.lower() == "zalopay":
39
+ log = PaymentCallbackLog.objects(type="zalopay", app_trans_id=code).first()
40
+ if not log:
41
+ return {"code": -3, "message": "Không tìm thấy giao dịch ZaloPay"}
42
+ data = call_zalopay_refund(
43
+ app_trans_id=code,
44
+ amount=log.raw_data.get("amount", 0),
45
+ description="Hoàn tiền ZaloPay"
46
+ )
47
+ return_code = data.get("return_code")
48
+ if return_code == 3:
49
+ log.is_refund = True
50
+ log.save()
51
+ return {"code": 0 , "message": f"Hoàn tiền thành công", "data": data}
52
+ else:
53
+ log.is_refund = False
54
+ log.save()
55
+ return {"code": -1 , "message": f"Hoàn tiền thất bại", "data": data}
56
+
57
+
58
+ elif type.lower() == "vnpay":
59
+ log = PaymentCallbackLog.objects(type="vnpay", txn_ref=code).first()
60
+ if not log:
61
+ return {"code": -2, "message": "Không tìm thấy giao dịch VNPay"}
62
+ raw = log.raw_data
63
+ data = call_vnpay_refund(
64
+ txn_ref=code,
65
+ amount=int(raw.get("vnp_Amount", 0)), # VNPAY trả về x100
66
+ transaction_date=raw.get("vnp_PayDate"),
67
+ transaction_no=raw.get("vnp_TransactionNo"),
68
+ create_by="system_refund",
69
+ order_info=raw.get("vnp_OrderInfo", "Hoàn tiền VNPay")
70
+ )
71
+
72
+ resultCode = data.get("data").get("vnp_ResponseCode")
73
+ message = data.get("data").get("vnp_Message")
74
+ if resultCode == "00":
75
+ log.is_refund = True
76
+ log.save()
77
+ return {"code": 0 , "message": f"Hoàn tiền thành công {message}", "data": data}
78
+ else:
79
+ log.is_refund = False
80
+ log.save()
81
+ return {"code": -1 , "message": f"Hoàn tiền thất bại {message}", "data": data}
82
+
83
+ elif type.lower() == "momo":
84
+ log = PaymentCallbackLog.objects(type="momo", order_id=code).first()
85
+ if not log:
86
+ return {"code": -3, "message": "Không tìm thấy giao dịch MoMo"}
87
+ if log.raw_data.get("payType", "") != "qr":
88
+ return {"code": -2 , "message": f"Payment không thể hoàn tiền với ", "data": data}
89
+ data = call_momo_refund(
90
+ trans_id=log.raw_data.get("transId", 0),
91
+ amount=log.raw_data.get("amount", 0),
92
+ description=f"Hoàn tiền MoMo cho giao dịch {code}"
93
+ )
94
+ resultCode = data.get("resultCode")
95
+ if resultCode == 0:
96
+ log.is_refund = True
97
+ log.save()
98
+ return {"code": 0 , "message": f"Hoàn tiền thành công", "data": data}
99
+ else:
100
+ log.is_refund = False
101
+ log.save()
102
+ return {"code": -1 , "message": f"Hoàn tiền thất bại", "data": data}
103
+ else:
104
+ return {"code": -4, "message": f"Không hỗ trợ cổng thanh toán '{type}'"}
105
+
106
+ from fastapi import HTTPException
107
+ from pydantic import BaseModel
108
+
109
+
110
+ class Refund(BaseModel):
111
+ type: str
112
+ code: str
113
+
114
+
115
+ @router.post("/refund")
116
+ async def refund_endpoint(request: Refund,token: str = Depends(jwt_bearer)):
117
+ type = request.type
118
+ code = request.code
119
+ user_role = decode_token.JwtService.extract_user_role(token)
120
+ if user_role != "ADMIN":
121
+ return JSONResponse(content={"message": "Not allow"}, status_code=404)
122
+
123
+ data = asyncio.run(refund_by_type_and_code(type = type,code=code))
124
+ if data.get("code") != 0 :
125
+ return JSONResponse(content={"message": f"Not allow type {type}","status": -1, "data":data}, status_code=404)
126
+ else:
127
+ return JSONResponse(
128
+ content={
129
+ "status": 0,
130
+ "data": data
131
+ },
132
+ status_code=200
133
+ )
134
+
135
+
136
+ @router.post("/refund-schedule")
137
+ async def refund_endpoint_schedule(request: Refund):
138
+ type = request.type
139
+ code = request.code
140
+ data = asyncio.run(refund_by_type_and_code(type = type,code=code))
141
+ if data.get("code") != 0 :
142
+ return JSONResponse(content={"message": f"Not allow type {type}","status": -1, "data":data}, status_code=404)
143
+ else:
144
+ return JSONResponse(
145
+ content={
146
+ "status": 0,
147
+ "data": data
148
+ },
149
+ status_code=200
150
+ )
controller/__init__.py ADDED
File without changes
controller/__pycache__/CallbackController.cpython-311.pyc ADDED
Binary file (13.3 kB). View file
 
controller/__pycache__/ChatController.cpython-311.pyc ADDED
Binary file (16 kB). View file
 
controller/__pycache__/GroupOrderController.cpython-311.pyc ADDED
Binary file (2.15 kB). View file
 
controller/__pycache__/RecommendController.cpython-311.pyc ADDED
Binary file (2.08 kB). View file
 
controller/__pycache__/RedirectController.cpython-311.pyc ADDED
Binary file (7.01 kB). View file
 
controller/__pycache__/RefundController.cpython-311.pyc ADDED
Binary file (8.95 kB). View file
 
controller/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (167 Bytes). View file
 
controller/__pycache__/task_manager.cpython-311.pyc ADDED
Binary file (414 Bytes). View file
 
function/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from function import *
function/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (198 Bytes). View file
 
function/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (152 Bytes). View file
 
function/__pycache__/chat.cpython-311.pyc ADDED
Binary file (46.2 kB). View file
 
function/__pycache__/chat.cpython-312.pyc ADDED
Binary file (27.7 kB). View file
 
function/advance_shopping/call_gemini/__pycache__/tool_call.cpython-311.pyc ADDED
Binary file (2.25 kB). View file
 
function/advance_shopping/call_gemini/tool_call.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+ import json
4
+ from google import genai
5
+ from google.genai import types
6
+
7
+ thinking_config = types.ThinkingConfig(include_thoughts=True, thinking_budget=5000)
8
+
9
+ def generate(prompt: str, max_retries: int = 3):
10
+ client = genai.Client(
11
+ api_key="AIzaSyA4eTxl-suN_D8COMiRA5UmrF-6vW7etys",
12
+ )
13
+ model = "gemini-2.5-flash-preview-05-20"
14
+ contents = [
15
+ types.Content(
16
+ role="user",
17
+ parts=[
18
+ types.Part.from_text(text=prompt),
19
+ ],
20
+ ),
21
+ ]
22
+ generate_content_config = types.GenerateContentConfig(
23
+ response_mime_type="application/json",
24
+ thinking_config=thinking_config
25
+ )
26
+
27
+ for attempt in range(max_retries + 1):
28
+ result = ""
29
+ try:
30
+ for chunk in client.models.generate_content_stream(
31
+ model=model,
32
+ contents=contents,
33
+ config=generate_content_config,
34
+ ):
35
+ if chunk.text: # Đảm bảo chunk.text không rỗng
36
+ result += chunk.text
37
+
38
+ if result.strip(): # Nếu kết quả không rỗng
39
+ return json.loads(result)
40
+
41
+ except Exception as e:
42
+ print(f"[Lần {attempt + 1}] Lỗi khi gọi Gemini hoặc phân tích JSON: {e}")
43
+
44
+ raise RuntimeError("Không thể tạo nội dung sau nhiều lần thử.")
function/advance_shopping/function/__pycache__/test.cpython-311.pyc ADDED
Binary file (23.9 kB). View file
 
function/advance_shopping/function/test.py ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # def extract_quantity(input: str, full_test, data) -> str:
2
+ # import base64
3
+ # import os
4
+ # from google import genai
5
+ # from google.genai import types
6
+ # client = genai.Client(
7
+ # api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac",
8
+ # )
9
+
10
+ # model = "gemini-2.5-flash-preview-05-20"
11
+ # prompt = f"""
12
+ # Bạn là một chuyên gia trong việc trích xuất và lấy số lượng trong câu hỏi đồng thời lấy size sản phẩm ngoài ra bạn sẽ lấy tên sản phẩm kèm theo.
13
+ # Bạn sẽ thực hiện lấy đúng, chính xác số lượng sản phẩm đó.
14
+ # Ngoài ra hãy tham khảo thêm: {full_test} để xác xem liệu là sản phẩm nào thực sự bạn không cần phải lấy trong này có đề cập các sản phẩm không tồn tại. Nếu gặp không cần phải trích xuất dữ liệu chúng. Hãy đọc kĩ phân tích để tránh việc lấy nhầm sản phẩm không tồn tại.
15
+ # Phân biệt rõ ràng dù là Trà đào hay trà đào thì nó giống nhau khi viết thường vậy thì các sản phẩm khác cũng giống vậy. Không phải ưng cái thì trà chanh, cái thì Trà chanh điều này là sai hoàn toàn.
16
+ # Nhớ kĩ câu hỏi đầu vào của người dùng trong đó Người dùng hỏi: là câu hỏi trước đó người dùng nói khi đó trợ lý thấy thiếu sót điều gì sẽ hỏi lại và người dùng trả lời tiếp. Hãy nhớ Người dùng trả lời luôn là câu trả lời cuối cùng.
17
+ # Khi thực hiện phân tích bạn hãy xem thêm phần data này: {data}. Đây là các trích xuất bản ghi trước đó khi câu hỏi người dùng đưa vào bạn phải vận dụng để tạo ra list phù hợp mà không làm hao phí data này.
18
+ # - Nếu data không có, không cần check nhưng nếu có data vui lòng check kĩ càng
19
+ # **Yêu cầu phản hồi:**
20
+ # - Chỉ trả về số lượng duy nhất sau khi thực hiện. Yêu cầu không giải thích gì thêm.
21
+ # - Nếu sản phẩm đó trùng tên nhung khác size thì vẫn để riêng ra ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"']
22
+ # - Chỉ trả về kiểu số ví dụ 1,2,3,... Nếu không có bắt được thì vui lòng để None
23
+ # - Trả về Size duy nhất của sản phẩm. Nếu có nhiều size của sản phẩm thì phải trích xuất ra đúng ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"'] là cùng trà đào mà có sie khác nhau. Vui lòng không tự sinh ra size của sản phẩm chỉ khi trong đó có thông tin về size của đúng sản phẩm đó thì mới lấy ra size của sản phẩm đó.
24
+ # - Vui lòng để nguyên tên sản phẩm ví dụ trà đào là trà đào nếu họ ghi Trà đào thì mới lấy Trà đào tức là không được thay đổi tên sản phẩm, viết hoa hay viết thường
25
+ # - Formart trả về là: Json: "size": "M", "quantity": "5", name: "trà đào"
26
+ # - Nếu xóa sản phẩm đó thì vui lòng không hiển thị lại trong chuỗi. Xác định rõ sản phẩm nào cần xóa, sản phẩm nào cập nhật để điều chỉnh list một cách hợp lý
27
+ # - Ví dụ có nhiều sản phẩm thì json ghép trong 1 chuỗi
28
+
29
+ # **Văn bản đầu vào cần xử lý:**
30
+ # \"{input}\"
31
+ # """
32
+ # contents = [
33
+ # types.Content(
34
+ # role="user",
35
+ # parts=[
36
+ # types.Part.from_text(text=f"""{prompt}"""),
37
+ # ],
38
+ # ),
39
+ # ]
40
+ # generate_content_config = types.GenerateContentConfig(
41
+ # response_mime_type="application/json",
42
+ # )
43
+
44
+ # result = ""
45
+
46
+ # for chunk in client.models.generate_content_stream(
47
+ # model=model,
48
+ # contents=contents,
49
+ # config=generate_content_config,
50
+ # ):
51
+ # result += chunk.text # Gom tất cả vào 1 chuỗi
52
+
53
+ # result = result.strip()
54
+ # return result
55
+
56
+
57
+ def extract_quantity(input: str, full_test, data) -> str:
58
+ import base64
59
+ import os
60
+ import time
61
+ from google import genai
62
+ from google.genai import types
63
+
64
+ client = genai.Client(
65
+ api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac",
66
+ )
67
+
68
+ model = "gemini-2.5-flash-preview-05-20"
69
+
70
+ prompt = f"""
71
+ Bạn là một chuyên gia trong việc trích xuất và lấy số lượng trong câu hỏi đồng thời lấy size sản phẩm ngoài ra bạn sẽ lấy tên sản phẩm kèm theo.
72
+ Bạn sẽ thực hiện lấy đúng, chính xác số lượng sản phẩm đó.
73
+ Ngoài ra hãy tham khảo thêm: {full_test} để xác xem liệu là sản phẩm nào thực sự bạn không cần phải lấy trong này có đề cập các sản phẩm không tồn tại. Nếu gặp không cần phải trích xuất dữ liệu chúng. Hãy ��ọc kĩ phân tích để tránh việc lấy nhầm sản phẩm không tồn tại.
74
+ Phân biệt rõ ràng dù là Trà đào hay trà đào thì nó giống nhau khi viết thường vậy thì các sản phẩm khác cũng giống vậy. Không phải ưng cái thì trà chanh, cái thì Trà chanh điều này là sai hoàn toàn.
75
+ Nhớ kĩ câu hỏi đầu vào của người dùng trong đó Người dùng hỏi: là câu hỏi trước đó người dùng nói khi đó trợ lý thấy thiếu sót điều gì sẽ hỏi lại và người dùng trả lời tiếp. Hãy nhớ Người dùng trả lời luôn là câu trả lời cuối cùng.
76
+ Khi thực hiện phân tích bạn hãy xem thêm phần data này: {data}. Đây là các trích xuất bản ghi trước đó khi câu hỏi người dùng đưa vào bạn phải vận dụng để tạo ra list phù hợp mà không làm hao phí data này.
77
+ - Nếu data không có, không cần check nhưng nếu có data vui lòng check kĩ càng
78
+
79
+ **Yêu cầu phản hồi:**
80
+ - Chỉ trả về số lượng duy nhất sau khi thực hiện. Yêu cầu không giải thích gì thêm.
81
+ - Nếu sản phẩm đó trùng tên nhưng khác size thì vẫn để riêng ra ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"']
82
+ - Chỉ trả về kiểu số ví dụ 1,2,3,... Nếu không có bắt được thì vui lòng để None
83
+ - Trả về Size duy nhất của sản phẩm. Nếu có nhiều size của sản phẩm thì phải trích xuất ra đúng ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"'] là cùng trà đào mà có size khác nhau. Vui lòng không tự sinh ra size của sản phẩm chỉ khi trong đó có thông tin về size của đúng sản phẩm đó thì mới lấy ra size của sản phẩm đó.
84
+ - Vui lòng để nguyên tên sản phẩm ví dụ trà đào là trà đào nếu họ ghi Trà đào thì mới lấy Trà đào tức là không được thay đổi tên sản phẩm, viết hoa hay viết thường
85
+ - Format trả về là: Json: "size": "M", "quantity": "5", name: "trà đào"
86
+ - Nếu xóa sản phẩm đó thì vui lòng không hiển thị lại trong chuỗi. Xác định rõ sản phẩm nào cần xóa, sản phẩm nào cập nhật để điều chỉnh list một cách hợp lý
87
+ - Ví dụ có nhiều sản phẩm thì json ghép trong 1 chuỗi
88
+
89
+ **Văn bản đầu vào cần xử lý:**
90
+ \"{input}\""""
91
+
92
+ contents = [
93
+ types.Content(
94
+ role="user",
95
+ parts=[
96
+ types.Part.from_text(text=prompt),
97
+ ],
98
+ )
99
+ ]
100
+
101
+ generate_content_config = types.GenerateContentConfig(
102
+ response_mime_type="application/json",
103
+ )
104
+
105
+ retries = 0
106
+ max_retries = 2
107
+ while retries <= max_retries:
108
+ try:
109
+ result = ""
110
+ for chunk in client.models.generate_content_stream(
111
+ model=model,
112
+ contents=contents,
113
+ config=generate_content_config,
114
+ ):
115
+ result += chunk.text
116
+ result = result.strip()
117
+ if result: # Nếu có nội dung thì trả về luôn
118
+ return result
119
+ except Exception as e:
120
+ pass # Có thể log lỗi nếu muốn
121
+ retries += 1
122
+ time.sleep(1) # Nghỉ 1 giây trước khi thử lại
123
+
124
+ return "None"
125
+
126
+
127
+ def generate_question(user_input: str, data_cleant: list, data_check: list, full_test: str) -> str:
128
+ import base64
129
+ import os
130
+ import time
131
+ from google import genai
132
+ from google.genai import types
133
+
134
+ client = genai.Client(
135
+ api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac",
136
+ )
137
+
138
+ model = "gemini-2.5-flash-preview-05-20"
139
+
140
+ prompt = f"""
141
+ Bạn là một chuyên gia trong việc đặt câu hỏi làm rõ thông tin sản phẩm còn thiếu từ người dùng, dựa trên kết quả phân tích đầu vào.
142
+
143
+ ### 1. Mục tiêu:
144
+ - Từ danh sách sản phẩm người dùng yêu cầu (`data_cleant`), kết hợp với kết quả kiểm tra (`data_check`), bạn cần tạo **một câu hỏi duy nhất** để làm rõ những thông tin còn thiếu hoặc chưa chính xác.
145
+ - Hãy đọc kĩ phần mô tả: {full_test} để xác định xem liệu là với câu hỏi người dùng và data truyền vào các nhận xét này sẽ chỉ ra điểm yếu kém mã mỗi sản phầm còn tồn đọng. Nếu có hãy vui lòng tạo câu hỏi một cách chính xác, công tâm.
146
+ - Luôn đảm bảo câu hỏi bao quanh được các nhận xét của mình.
147
+ - Với các sản phẩm không tồn tại thì không cần hỏi nữa tuy nhiên nếu không còn sản phẩm gì thì hãy hỏi người dùng liệu có muốn mua sản phẩm khác hay không
148
+ - Nếu không có gì cần hỏi thêm, chỉ trả về chuỗi "None" (không có dấu ngoặc) khi cảm thấy tất cả đã đáp ứng được.
149
+
150
+ ### 2. Định nghĩa dữ liệu:
151
+ - `data_cleant`: danh sách sản phẩm đã được tách từ văn bản người dùng, mỗi phần tử gồm:
152
+ - "name": tên sản phẩm.
153
+ - "size": kích cỡ (có thể thiếu).
154
+ - "quantity": số lượng (có thể thiếu).
155
+
156
+ - `data_check`: danh sách tương ứng (cùng thứ tự) với `data_cleant`, mỗi phần tử chứa kết quả kiểm tra:
157
+ - Nếu bắt đầu bằng ❌: sản phẩm không tồn tại → loại khỏi danh sách hỏi.
158
+ - Nếu chứa ⚠️: sản phẩm có tồn tại nhưng thiếu thông tin → cần hỏi lại.
159
+ - Nếu chứa ✅: sản phẩm hợp lệ và đầy đủ → không cần hỏi lại.
160
+
161
+ ### 3. Yêu cầu tạo câu hỏi:
162
+ - Bỏ qua hoàn toàn các sản phẩm có `check` bắt đầu bằng ❌, không hỏi lại, chỉ có thể đề cập nhẹ rằng chúng đã bị loại.
163
+ - Với các sản phẩm còn lại (⚠️ hoặc ✅):
164
+ - Nếu thiếu `size` hoặc `quantity`, hãy đặt **một câu hỏi duy nhất** để làm rõ toàn bộ phần còn thiếu.
165
+ - Nếu mọi thứ đã đầy đủ và hợp lệ, chỉ trả về "None".
166
+ - Nếu data_cleant rỗng hoặc không có sản phẩm nào cần hỏi, hãy hỏi họ xem có muốn mua sản phẩm khác hay không
167
+
168
+ ### 4. Văn bản người dùng đã nhập:
169
+ "{user_input}"
170
+
171
+ ### 5. Danh sách sản phẩm đã xử lý (`data_cleant`):
172
+ {data_cleant}
173
+
174
+ ### 6. Kết quả kiểm tra từng sản phẩm (`data_check`):
175
+ {data_check}
176
+
177
+ ### 7. Tổng hợp kết quả kiểm tra:
178
+ {full_test}
179
+
180
+ ### ⚠️ Quy tắc bắt buộc:
181
+ - **Chỉ trả về một câu hỏi duy nhất** hoặc chuỗi "None".
182
+ - **Không giải thích, không liệt kê lại dữ liệu, không mô tả lại logic.**
183
+ """
184
+
185
+ contents = [
186
+ types.Content(
187
+ role="user",
188
+ parts=[
189
+ types.Part.from_text(text=prompt),
190
+ ],
191
+ )
192
+ ]
193
+
194
+ generate_content_config = types.GenerateContentConfig(
195
+ response_mime_type="application/json",
196
+ )
197
+
198
+ for attempt in range(3): # thử tối đa 3 lần
199
+ try:
200
+ result = ""
201
+ for chunk in client.models.generate_content_stream(
202
+ model=model,
203
+ contents=contents,
204
+ config=generate_content_config,
205
+ ):
206
+ if chunk.text:
207
+ result += chunk.text
208
+
209
+ return result.strip()
210
+ except Exception as e:
211
+ if attempt == 2:
212
+ raise e # nếu thử 3 lần vẫn lỗi, raise ra ngoài
213
+ time.sleep(1) # chờ 1s trước khi retry
214
+
215
+
216
+ # def generate_question(user_input: str, data_cleant: list, data_check: list, full_test:str) -> str:
217
+ # import base64
218
+ # import os
219
+ # from google import genai
220
+ # from google.genai import types
221
+
222
+ # client = genai.Client(
223
+ # api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac",
224
+ # )
225
+
226
+ # model = "gemini-2.5-flash-preview-05-20"
227
+
228
+ # prompt = f"""
229
+ # Bạn là một chuyên gia trong việc đặt câu hỏi làm rõ thông tin sản phẩm còn thiếu từ người dùng, dựa trên kết quả phân tích đầu vào.
230
+
231
+ # ### 1. Mục tiêu:
232
+ # - Từ danh sách sản phẩm người dùng yêu cầu (`data_cleant`), kết hợp với kết quả kiểm tra (`data_check`), bạn cần tạo **một câu hỏi duy nhất** để làm rõ những thông tin còn thiếu hoặc chưa chính xác.
233
+ # - Hãy đọc kĩ phần mô tả: {full_test} để xác định xem liệu là với câu hỏi người dùng và data truyền vào các nhận xét này sẽ chỉ ra điểm yếu kém mã mỗi sản phầm còn tồn đọng. Nếu có hãy vui lòng tạo câu hỏi một cách chính xác, công tâm.
234
+ # - Luôn đảm bảo câu hỏi bao quanh được các nhận xét của mình.
235
+ # - Với các sản phẩm không tồn tại thì không cần hỏi nữa tuy nhiên nếu không còn sản phẩm gì thì hãy hỏi người dùng liệu có muốn mua sản phẩm khác hay không
236
+ # - Nếu không có gì cần hỏi thêm, chỉ trả về chuỗi `"None"` (không có dấu ngoặc) khi cảm thấy tất cả đã đáp ứng được.
237
+
238
+ # ### 2. Định nghĩa dữ liệu:
239
+ # - `data_cleant`: danh sách sản phẩm đã được tách từ văn bản người dùng, mỗi phần tử gồm:
240
+ # - `"name"`: tên sản phẩm.
241
+ # - `"size"`: kích cỡ (có thể thiếu).
242
+ # - `"quantity"`: số lượng (có thể thiếu).
243
+
244
+ # - `data_check`: danh sách tương ứng (cùng thứ tự) với `data_cleant`, mỗi phần tử chứa kết quả kiểm tra:
245
+ # - Nếu bắt đầu bằng `❌`: sản phẩm không tồn tại → loại khỏi danh sách hỏi.
246
+ # - Nếu chứa `⚠️`: sản phẩm có tồn tại nhưng thiếu thông tin → cần hỏi lại.
247
+ # - Nếu chứa `✅`: sản phẩm hợp lệ và đầy đủ → không cần hỏi lại.
248
+
249
+ # ### 3. Yêu cầu tạo câu hỏi:
250
+ # - Bỏ qua hoàn toàn các sản phẩm có `check` bắt đầu bằng `❌`, không hỏi lại, chỉ có thể đề cập nhẹ rằng chúng đã bị loại.
251
+ # - Với các sản phẩm còn lại (⚠️ hoặc ✅):
252
+ # - Nếu thiếu `size` hoặc `quantity`, hãy đặt **một câu hỏi duy nhất** để làm rõ toàn bộ phần còn thiếu.
253
+ # - Nếu mọi thứ đã đầy đủ và hợp lệ, chỉ trả về `"None"`.
254
+ # - Nếu data_cleant rỗng hoặc không có sản phẩm nào cần hỏi, hãy hỏi họ xem có muốn mua sản phẩm khác hay không
255
+
256
+ # ### 4. Văn bản người dùng đã nhập:
257
+ # \"{user_input}\"
258
+
259
+ # ### 5. Danh sách sản phẩm đã xử lý (`data_cleant`):
260
+ # {data_cleant}
261
+
262
+ # ### 6. Kết quả kiểm tra từng sản phẩm (`data_check`):
263
+ # {data_check}
264
+
265
+ # ### 7. Tổng hợp kết quả kiểm tra:
266
+ # {full_test}
267
+
268
+ # ### ⚠️ Quy tắc bắt buộc:
269
+ # - **Chỉ trả về một câu hỏi duy nhất** hoặc chuỗi `"None"`.
270
+ # - **Không giải thích, không liệt kê lại dữ liệu, không mô tả lại logic.**
271
+ # """
272
+
273
+ # contents = [
274
+ # types.Content(
275
+ # role="user",
276
+ # parts=[
277
+ # types.Part.from_text(text=prompt),
278
+ # ],
279
+ # )
280
+ # ]
281
+
282
+ # generate_content_config = types.GenerateContentConfig(
283
+ # response_mime_type="application/json",
284
+ # )
285
+
286
+ # result = ""
287
+
288
+ # for chunk in client.models.generate_content_stream(
289
+ # model=model,
290
+ # contents=contents,
291
+ # config=generate_content_config,
292
+ # ):
293
+ # result += chunk.text
294
+
295
+ # return result.strip()
296
+
297
+
298
+
299
+
300
+ example_json = [
301
+ { "name": "trà đào", "size": "S", "quantity": 2, "action": "update" },
302
+ { "name": "trà sữa", "size": "S", "action": "delete" },
303
+ { "name": "hồng trà", "size": "M", "quantity": 1, "action": "add" }
304
+ ]
305
+
306
+
307
+ def classify_data(input: str, structured_data,max_retries=3, retry_delay=1.0) -> str:
308
+ import base64
309
+ import os
310
+ from google import genai
311
+ from google.genai import types
312
+ client = genai.Client(
313
+ api_key="AIzaSyA4eTxl-suN_D8COMiRA5UmrF-6vW7etys",
314
+ )
315
+
316
+ model = "gemini-2.5-flash-preview-05-20"
317
+ prompt = f"""
318
+ Bạn là một chuyên gia xử lý ngôn ngữ, có nhiệm vụ xác định **ý định** của người dùng đối với các sản phẩm (thêm, cập nhật hoặc xóa) và trích xuất chính xác thông tin liên quan.
319
+ - Bên này chỉ được hỏi các sản phẩm liên quan đến đồ uống, nc ngọt, không đc phép thêm các sản phẩm thể loại khác nhu sách, phim, movie
320
+ ### Dữ liệu đầu vào:
321
+ - Câu query mới: "{input}"
322
+ - Dữ liệu sản phẩm cũ: {structured_data}
323
+ ---
324
+
325
+ ### Yêu cầu xử lý:
326
+
327
+ 1. So sánh **câu query mới** và **Dữ liệu sản phẩm cũ** để xác định **ý định chính** của người dùng là:
328
+ - Xác định đúng tên sản phẩm, size được đề cập đến và nếu có hành động xóa thì phải gán thêm action đúng chỗ không phải tạo ra đối tượng khác.
329
+ - `add`: thêm sản phẩm mới.
330
+ - `update`: cập nhật thông tin sản phẩm cũ (tên, size, số lượng).
331
+ - `delete`: xóa sản phẩm (có thể là một hoặc nhiều).
332
+ 2. Trích xuất dữ liệu dạng JSON list gồm các sản phẩm bị ảnh hưởng trong câu mới.
333
+ 2. Nếu là hành động thêm cần xác định rõ tên sản phẩm, size (nếu có) và số lượng (nếu có). Và ý nghĩa câu {input} ví dụ họ cũng thêm vào sản phẩm đã tồn tại đó thì quantity phải cộng lên hoặc giảm xuống.(Quan trọng)
334
+ 3. Mỗi sản phẩm trong JSON phải có các trường:
335
+ - `name`: tên sản phẩm.
336
+ - `size`: nếu có, ghi size. Nếu không có ghi "None". Phân biệt rõ size này là của sản phẩm nào.
337
+ - `quantity`: nếu có, ghi số lượng. Nếu không có ghi là 1. Nếu không có số lượng thì ghi là 1. Số lượng không bao giờ được phép ấm nếu trừ hay giảm mà âm hoặc bằng 0 thì phải gán là 0.
338
+ - `action`: hành động tương ứng (add / update / delete).
339
+ - size_old: Nếu là hành động `update`, cần xác định rõ **size cũ** của sản phẩm để cập nhật.(Chỉ khi nào có yêu cầu thay thế size(thay thế size mà không đổi số lượng thì số lượng phải giữ nguyên), kích cỡ)
340
+ 4. Nếu là hành động `delete`, cần xác định rõ **tên và size** của sản phẩm cần xóa.
341
+ 6. Nếu có hành động thay sản phẩm này bằng sản phâm khác thì cần xác định rõ tên sản phẩm mới và size mới, khi đó sản phẩm mới size mới là add còn cái bị thay thế sẽ thành deleted.
342
+ 5. Nếu sản phẩm không đủ thông tin (thiếu tên), thì **bỏ qua** không thêm vào JSON.
343
+ 6. Nếu người dùng nói xóa toàn bộ sản phẩm, thì liệt kê tất cả sản phẩm hiện có trong structured_data với action là `delete`.
344
+ 7. Nếu không xác định được hành động nào, trả về `"None"`.
345
+ 8. Nếu đã có sản phẩm trong structured_data nhưng không được đề cập trong câu mới, thì giữ nguyên thông tin của sản phẩm đó. Nếu có đề cập thì xác định rõ ý định của người dùng
346
+ **Yêu cầu phản hồi phải tuân theo:
347
+ - Vui lòng trả về danh sách toàn bộ các sản phẩm gồm có các dữ liệu cũ và dữ liệu update, luôn thay dữ liệu cu c bằng dữ liệu mới nếu có update,delete nếu không có update,delete thì giữ nguyên dữ liệu cũ.
348
+ - Dữ liệu insert,add cũng phải được thêm vào danh sách này, không được tạo ra một đối tượng khác mà phải là một chuỗi JSON nối tiếp với các sản phẩm khác.
349
+ - Không được phép viết hoa tên sản phẩm, chỉ viết thường trừ khi bản gốc đã viết hoa. Không thêm dấu câu, không xuống dòng.
350
+ - Luôn trả về đầy đủ gồm các sản phẩm size delete, update, add, insert trong một chuỗi JSON nối tiếp.
351
+ ---
352
+ ### ✅ Output mẫu (phải đúng định dạng JSON array, không xuống dòng, không giải thích):
353
+
354
+ ```json
355
+ {example_json}
356
+ ```
357
+ """
358
+ contents = [
359
+ types.Content(
360
+ role="user",
361
+ parts=[
362
+ types.Part.from_text(text=f"""{prompt}"""),
363
+ ],
364
+ ),
365
+ ]
366
+ generate_content_config = types.GenerateContentConfig(
367
+ response_mime_type="application/json",
368
+ )
369
+
370
+ result = ""
371
+
372
+
373
+ for attempt in range(1, max_retries + 1):
374
+ try:
375
+ result = ""
376
+ for chunk in client.models.generate_content_stream(
377
+ model=model,
378
+ contents=contents,
379
+ config=generate_content_config,
380
+ ):
381
+ if chunk.text:
382
+ result += chunk.text
383
+ return result.strip()
384
+ except Exception as e:
385
+ if attempt == max_retries:
386
+ raise RuntimeError(f"[Gemini classify_data failed] After {max_retries} attempts: {str(e)}")
387
+ else:
388
+ import time
389
+ print(f"[Retry {attempt}/{max_retries}] Error: {e}. Retrying in {retry_delay} seconds...")
390
+ time.sleep(retry_delay)
391
+
392
+
393
+ def update_data(input: str, input_old, structured_data, max_retries: int = 2) -> str:
394
+ import base64
395
+ import os
396
+ import time
397
+ from google import genai
398
+ from google.genai import types
399
+
400
+ client = genai.Client(
401
+ api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac",
402
+ )
403
+
404
+ model = "gemini-2.5-flash-preview-05-20"
405
+
406
+ prompt = f"""
407
+ Bạn là một chuyên gia trong việc cập nhật lại sản phẩm từ yêu cầu mới của người dùng dựa trên yêu cầu trước đó.
408
+
409
+ - Câu query mới: {input}
410
+ - Câu query cũ: {input_old}
411
+ - Dữ liệu sản phẩm ban đầu được trích xuất từ câu cũ:
412
+ {structured_data}
413
+
414
+ **Yêu cầu xử lý:**
415
+
416
+ 1. So sánh giữa câu query mới và câu query cũ để xác định người dùng đã thay đổi gì: về tên sản phẩm, size, số lượng hoặc loại bỏ/thêm sản phẩm.
417
+ 2. Dựa vào structured_data, xác định các sản phẩm trong câu mới để cập nhật tương ứng.
418
+ 3. Nếu người dùng đổi tên sản phẩm, thì thay thế sản phẩm cũ gần nhất (vị trí hoặc tên) bằng sản phẩm mới đó.
419
+ 4. Nếu người dùng cập nhật size cho sản phẩm, thì ghi đè size mới.
420
+ 5. Nếu người dùng không nói rõ size, nhưng sản phẩm đó đã tồn tại trong structured_data, thì giữ nguyên size cũ.
421
+ 6. Nếu người dùng cập nhật số lượng, thì ghi đè lại số lượng theo câu mới.
422
+ 7. Nếu có sản phẩm mới hoàn toàn chưa có trong structured_data và đủ thông tin (tên, số lượng, size), thì thêm vào danh sách.
423
+ 8. Nếu sản phẩm mới không có đủ thông tin (thiếu tên, size hoặc số lượng), thì bỏ qua không thêm.
424
+ 9. Nếu một sản phẩm trong structured_data không bị thay đổi hoặc nhắc đến trong câu mới, thì giữ nguyên thông tin sản phẩm đó.
425
+ 10. Nếu không thể xác định được bất kỳ sản phẩm nào trong câu mới, thì trả về "None"
426
+ 11. Nếu người dùng có đề cập giữ lại thì xác định xem có sản phẩm nào trong structured_data không bị thay đổi thì giữ lại sản phẩm đó.
427
+ 12. Nếu người dùng nói mua thêm hay thêm sản phẩm(các từ có ngữ ý là mua sản phẩm khác) mà không có trong structured_data thì bạn sẽ thêm sản phẩm mới vào danh sách
428
+ **Phản hồi chỉ gồm chuỗi JSON không giải thích gì thêm. Mỗi sản phẩm là một object JSON trong chuỗi nối tiếp, ví dụ:**
429
+
430
+ **Tuyệt đối không viết hoa tên sản phẩm trừ khi bản gốc đã viết hoa. Không thêm dấu câu, không xuống dòng.**
431
+
432
+ **Văn bản đầu vào mới cần xử lý:**
433
+ "{input}"
434
+ """
435
+
436
+ contents = [
437
+ types.Content(
438
+ role="user",
439
+ parts=[types.Part.from_text(prompt)],
440
+ ),
441
+ ]
442
+ generate_content_config = types.GenerateContentConfig(
443
+ response_mime_type="application/json",
444
+ )
445
+
446
+ for attempt in range(max_retries + 1):
447
+ result = ""
448
+ try:
449
+ for chunk in client.models.generate_content_stream(
450
+ model=model,
451
+ contents=contents,
452
+ config=generate_content_config,
453
+ ):
454
+ if chunk.text:
455
+ result += chunk.text
456
+
457
+ result = result.strip()
458
+ if result:
459
+ return result
460
+
461
+ except Exception as e:
462
+ print(f"[Lần {attempt + 1}] Lỗi khi gọi Gemini: {e}")
463
+ time.sleep(1)
464
+
465
+ raise RuntimeError("Không thể tạo dữ liệu cập nhật sau nhiều lần thử.")
466
+
467
+
468
+ # def update_data(input: str, input_old, structured_data) -> str:
469
+ # import base64
470
+ # import os
471
+ # from google import genai
472
+ # from google.genai import types
473
+ # client = genai.Client(
474
+ # api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac",
475
+ # )
476
+
477
+ # model = "gemini-2.5-flash-preview-05-20"
478
+ # prompt = f"""
479
+ # Bạn là một chuyên gia trong việc cập nhật lại sản phẩm từ yêu cầu mới của người dùng dựa trên yêu cầu trước đó.
480
+
481
+ # - Câu query mới: {input}
482
+ # - Câu query cũ: {input_old}
483
+ # - Dữ liệu sản phẩm ban đầu được trích xuất từ câu cũ:
484
+ # {structured_data}
485
+
486
+ # **Yêu cầu xử lý:**
487
+
488
+ # 1. So sánh giữa câu query mới và câu query cũ để xác định người dùng đã thay đổi gì: về tên sản phẩm, size, số lượng hoặc loại bỏ/thêm sản phẩm.
489
+ # 2. Dựa vào structured_data, xác định các sản phẩm trong câu mới để cập nhật tương ứng.
490
+ # 3. Nếu người dùng đổi tên sản phẩm, thì thay thế sản phẩm cũ gần nhất (vị trí hoặc tên) bằng sản phẩm mới đó.
491
+ # 4. Nếu người dùng cập nhật size cho sản phẩm, thì ghi đè size mới.
492
+ # 5. Nếu người dùng không nói rõ size, nhưng sản phẩm đó đã tồn tại trong structured_data, thì giữ nguyên size cũ.
493
+ # 6. Nếu người dùng cập nhật số lượng, thì ghi đè lại số lượng theo câu mới.
494
+ # 7. Nếu có sản phẩm mới hoàn toàn chưa có trong structured_data và đủ thông tin (tên, số lượng, size), thì thêm vào danh sách.
495
+ # 8. Nếu sản phẩm mới không có đủ thông tin (thiếu tên, size hoặc số lượng), thì bỏ qua không thêm.
496
+ # 9. Nếu một sản phẩm trong structured_data không bị thay đổi hoặc nhắc đến trong câu mới, thì giữ nguyên thông tin sản phẩm đó.
497
+ # 10. Nếu không thể xác định được bất kỳ sản phẩm nào trong câu mới, thì trả về "None"
498
+ # 11. Nếu người dùng có đề cập giữ lại thì xác định xem có sản phẩm nào trong structured_data không bị thay đổi thì giữ lại sản phẩm đó.
499
+ # 12. Nếu người dùng nói mua thêm hay thêm sản phẩm(các từ có ngữ ý là mua sản phẩm khác) mà không có trong structured_data thì bạn sẽ thêm sản phẩm mới vào danh sách
500
+ # **Phản hồi chỉ gồm chuỗi JSON không giải thích gì thêm. Mỗi sản phẩm là một object JSON trong chuỗi nối tiếp, ví dụ:**
501
+
502
+
503
+ # **Tuyệt đối không viết hoa tên sản phẩm trừ khi bản gốc đã viết hoa. Không thêm dấu câu, không xuống dòng.**
504
+
505
+ # **Văn bản đầu vào mới cần xử lý:**
506
+ # "{input}"
507
+
508
+ # """
509
+ # contents = [
510
+ # types.Content(
511
+ # role="user",
512
+ # parts=[
513
+ # types.Part.from_text(text=f"""{prompt}"""),
514
+ # ],
515
+ # ),
516
+ # ]
517
+ # generate_content_config = types.GenerateContentConfig(
518
+ # response_mime_type="application/json",
519
+ # )
520
+
521
+ # result = ""
522
+
523
+ # for chunk in client.models.generate_content_stream(
524
+ # model=model,
525
+ # contents=contents,
526
+ # config=generate_content_config,
527
+ # ):
528
+ # result += chunk.text
529
+
530
+ # result = result.strip()
531
+ # return result
532
+
533
+
534
+ def validate_products(data_list, server_java):
535
+ validated_data = []
536
+
537
+ for product in data_list:
538
+ product_name = product.get("name")
539
+ product_size = product.get("size")
540
+
541
+ if product_name not in [None, "", "None"] and product_size not in [None, "", "None"]:
542
+ check = server_java.search_product(keyword=product_name, size=product_size)
543
+
544
+ if check is None:
545
+ # Không thêm sản phẩm này vào danh sách kết quả
546
+ print(f"❌ Không tồn tại sản phẩm '{product_name}' với size '{product_size}'")
547
+ continue # bỏ qua sản phẩm này
548
+
549
+ # Nếu tồn tại nhưng không có size
550
+ if isinstance(check, tuple) and (len(check) > 2 and check[2] in [None, "", "None"]):
551
+ product["check"] = f"⚠️ Sản phẩm '{product_name}' không có size '{product_size}'"
552
+ else:
553
+ product["check"] = f"✅ Sản phẩm '{product_name}' với size '{product_size}' tồn tại"
554
+
555
+ validated_data.append(product)
556
+
557
+ else:
558
+ product["check"] = "⚠️ Thiếu thông tin sản phẩm (name hoặc size)"
559
+ validated_data.append(product)
560
+
561
+ return validated_data
562
+
563
+
564
+ def validate_product_list(data_list, server_java):
565
+ print(data_list)
566
+ annotated_list = []
567
+ clean_list = []
568
+ full_test = ""
569
+
570
+ for product in data_list:
571
+ product_copy = product.copy()
572
+ product_name = product_copy.get("name")
573
+ product_size = product_copy.get("size")
574
+
575
+ if product_name not in [None, "", "None"]:
576
+ check = server_java.search_product(keyword=product_name, size=product_size)
577
+
578
+ if check is None:
579
+ full_test += f".Không tồn tại sản phẩm '{product_name}' với size '{product_size} \n"
580
+ product_copy["check"] = f"❌ Không tồn tại sản phẩm '{product_name}' với size '{product_size}'"
581
+ product_copy["deleted"] = True
582
+ elif isinstance(check, tuple) and (len(check) > 2 and check[2] in [None, "", "None"]):
583
+ product_copy["check"] = f"⚠️ Sản phẩm '{product_name}' không có size '{product_size}'"
584
+ full_test += f".Không tồn tại sản phẩm '{product_name}' không có size '{product_size}"
585
+ product_copy["deleted"] = True
586
+ else:
587
+ product_copy["check"] = f"✅ Sản phẩm '{product_name}' với size '{product_size}' tồn tại"
588
+ if int(check[3]) == 0:
589
+ full_test += f".Sản phẩm '{product_name}' size '{product_size} hiện tại không còn hàng"
590
+ product_copy["deleted"] = True
591
+ if product['quantity'] in [None, "", "None"]:
592
+ full_test += f".Sản phẩm '{product_name}' size '{product_size} chưa nhập số lượng"
593
+ elif int(check[3]) < int(product['quantity']):
594
+ full_test += f".Sản phẩm '{product_name}' size '{product_size} hiện tại không có đủ số lượng, số lượng còn lại chỉ là tầm {int(check[3])}"
595
+ else:
596
+ product_copy["check"] = "⚠️ Thiếu thông tin sản phẩm (name hoặc size)"
597
+
598
+ annotated_list.append(product_copy)
599
+
600
+ if not product_copy.get("deleted", False):
601
+ # Tạo một bản sao sạch chỉ chứa các trường cần thiết
602
+ clean_item = {
603
+ "name": product_copy.get("name"),
604
+ "size": product_copy.get("size"),
605
+ "quantity": product_copy.get("quantity"),
606
+ "check": product_copy.get("check")
607
+ }
608
+ clean_list.append(clean_item)
609
+
610
+ return annotated_list, clean_list, full_test
611
+
612
+
613
+
614
+
615
+
function/advance_shopping/prompt/__pycache__/context_prompt.cpython-311.pyc ADDED
Binary file (2.04 kB). View file
 
function/advance_shopping/prompt/__pycache__/multitool_prompt.cpython-311.pyc ADDED
Binary file (5.26 kB). View file
 
function/advance_shopping/prompt/__pycache__/user_intent.cpython-311.pyc ADDED
Binary file (7.51 kB). View file
 
function/advance_shopping/prompt/context_prompt.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def build_context_prompt(chat_history: str, cart_exists: str) -> str:
2
+ """
3
+ Tạo prompt đầu vào cho LLM dựa trên đoạn hội thoại và trạng thái giỏ hàng.
4
+
5
+ Parameters:
6
+ - chat_history (str): Đoạn hội thoại giữa chatbot và người dùng.
7
+ - cart_exists (str): "Yes" nếu còn giỏ hàng, "No" nếu không.
8
+
9
+ Returns:
10
+ - str: Prompt hoàn chỉnh để gửi cho LLM.
11
+ """
12
+ return f"""
13
+ Bạn là một chuyên gia trong việc xử lý ngữ cảnh hội thoại để hỗ trợ mua sắm.
14
+
15
+ Tôi sẽ cung cấp cho bạn:
16
+ - Một đoạn hội thoại giữa chatbot và người dùng (chat_history).
17
+ - Trạng thái giỏ hàng (cart_exists), giá trị là "Yes" nếu người dùng đang còn giỏ hàng, "No" nếu không.
18
+
19
+ Yêu cầu:
20
+ - Nếu cart_exists là "Yes", bạn phải đưa ra **một câu hỏi duy nhất** để hướng người dùng quay lại việc mua sản phẩm, dựa vào đoạn hội thoại trước.
21
+ - Nếu cart_exists là "No", chỉ trả về "None", không thêm bất kỳ nội dung nào khác.
22
+ - Không tóm tắt, không giải thích, chỉ trả về câu hỏi hoặc "None".
23
+
24
+ Nếu câu trả lời bạn tạo ra có ý hỏi người dùng về việc thêm, xóa, sửa sản phẩm vào giỏ hay tạo thanh toán thì hãy trả thêm một biến gọi là is_question_orders và gán là True nếu không có hãy gán là False.
25
+ Hãy trả về format Json gồm có hai biến là question và is_question_orders
26
+
27
+
28
+ Dữ liệu đầu vào:
29
+ chat_history:
30
+ {chat_history}
31
+
32
+ cart_exists: {cart_exists}
33
+
34
+ Trả lời:
35
+ """.strip()
36
+
37
+
38
+
function/advance_shopping/prompt/multitool_prompt.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ example_json_multi_prompt = {
2
+ "question": "Thêm sản phẩm nước ép xoài size H vào giỏ hàng, xóa sản phẩm trà chanh khỏi giỏ hàng.",
3
+ "question_normal": {
4
+ "question_normal1": "Xem danh sách các bài viết mới."
5
+ },
6
+ "confirm_order": False
7
+ }
8
+
9
+
10
+ def build_multi_tool_prompt(question: str, user_answer: str, tools: list) -> str:
11
+ tool_list = ", ".join(tools)
12
+ prompt = f"""Bạn là một chuyên gia trong việc phân tích ý định người dùng để xác định **tất cả hành động** cần thực hiện trong hệ thống bán hàng.
13
+
14
+ ### Dữ liệu đầu vào:
15
+ - Một câu hỏi từ hệ thống gửi tới người dùng:
16
+ question: "{question}"
17
+
18
+ - Một câu trả lời từ người dùng:
19
+ user_answer: "{user_answer}"
20
+
21
+ - Danh sách các tool khả dụng:
22
+ {tool_list}
23
+
24
+ ---
25
+
26
+ ### Danh sách tool và chức năng:
27
+ - `insert_cart`: Thêm sản phẩm vào giỏ hàng
28
+ - `update_cart`: Cập nhật thông tin sản phẩm đã có trong giỏ hàng (ví dụ: size, số lượng)
29
+ - `delete_item_cart`: Xoá một sản phẩm cụ thể khỏi giỏ hàng
30
+ - `deleted all cart`: Xoá toàn bộ giỏ hàng
31
+ - `confirm_order`: Xác nhận đơn hàng hiện tại
32
+
33
+ ---
34
+
35
+ ### Yêu cầu:
36
+ 1. Phân tích kỹ câu trả lời của người dùng.
37
+ 2. Xác định **mọi hành động** mà người dùng mong muốn thực hiện.
38
+ 3. Gán đúng với một trong các tool trong danh sách.
39
+ 4. Với các tool `insert_cart`, `update_cart`, `delete_item_cart`, cần có các tham số:
40
+ - `product_name`: tên sản phẩm (bắt buộc)
41
+ - `size`: kích cỡ (nếu có)
42
+ - `quantity`: số lượng (nếu có)
43
+ 5. Các tool `confirm_order` và `deleted all cart` không cần tham số.
44
+ 7. Yêu cầu đặt hàng mà vẫn có các thông tin về tên sản phẩm hoặc size hay cả hai đều phải giữ lại trong question
45
+ 6. Các câu nói về xác nhận thanh toán, xem giỏ hàng đều không cần để question
46
+
47
+ ---
48
+
49
+ ### Định dạng đầu ra:
50
+ ạn chỉ cần viết lại một câu duy nhất mô tả lại các hành động bằng ngôn ngữ tự nhiên và xác định rõ tên sản phẩm mà họ đề cập. Vui lòng viết rõ ràng chính xác để có thể hiểu bạn nói gì nghĩa là dùng , cho các hành động khác. Nếu có yêu cầu xác nhận thanh toán thì thêm một trường là confirm_orders và câu hỏi sẽ đc đưa vào question. Sau đó hãy trả về Json
51
+ Bạn sẽ nhận được một câu mô tả hành động từ người dùng.
52
+ Hãy đọc kỹ và phân loại nội dung theo 3 yêu cầu sau:
53
+ 1. question:
54
+ - Viết lại duy nhất một câu mô tả đầy đủ các hành động liên quan đến việc mua hàng, bao gồm:
55
+ + Thêm/xóa sản phẩm khỏi giỏ hàng
56
+ + Chọn size, loại sản phẩm
57
+ + Đặt hàng, xác nhận đơn hàng
58
+ + Các thao tác với đơn hàng, giỏ hàng, thanh toán
59
+ + Hành động phải nối bằng dấu phẩy “,”, và ghi rõ tên sản phẩm, size nếu có.
60
+ + Các câu nói về xác nhận thanh toán, xem giỏ hàng, xóa vật phẩm trong giỏ hàng đều không cần để question
61
+ 2. question_normal:
62
+ - Tách các câu hỏi hoặc hành động không liên quan đến việc mua hàng, như:
63
+ + Xem bài viết, tra cứu thông tin, hỏi khuyến mãi
64
+ + Xem bài đăng, thời gian mở cửa, v.v.
65
+ + Trả về dưới dạng dictionary với các khóa: question_normal1, question_normal2, ...
66
+ + Nhưng nếu câu hỏi đó liên quan đến việc xem sản phẩm trong giỏ hàng, đơn hàng hay các hành động liện quan có giỏ hàng, đơn hàng thì vui lòng không bỏ vào question_normal
67
+ 3. confirm_order:
68
+ + Gán giá trị true nếu câu chứa hành động xác nhận đơn hàng hoặc thanh toán (ví dụ: “xác nhận đơn hàng”, “đặt mua”, “tôi muốn thanh toán”,...)
69
+ + Gán false nếu không có hành động đó.
70
+ . Trả về một JSON gồm 3 trường:
71
+ Ví dụ:
72
+ "question": theo mô tả của minh ỏ trên. Các câu nói về xác nhận thanh toán, xem giỏ hàng, xóa vật phẩm trong giỏ hàng đều không cần để question chỉ cần để. Yêu cầu đặt hàng mà vẫn có các thông tin về tên sản phẩm hoặc size hay cả hai đều phải giữ lại question ""
73
+ "question chỉ trống khi câu đó riêng lẻ là xác nhận mua hàng, tôi muốn mua hàng, đặt hàng, tiến hành thanh toán,..."
74
+ "question_normal": theo mô tả trên
75
+ "confirm_order": true hoặc false
76
+
77
+
78
+ Ví dụ trả về:
79
+ {example_json_multi_prompt}
80
+
81
+ Bắt đầu phân tích dữ liệu sau đây."""
82
+
83
+ return prompt
function/advance_shopping/prompt/user_intent.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ json_example = {
2
+ "type": """ "shopping_question" | "normal_question" | "unclear" """,
3
+ "clarification_question_if_unclear": """ "string | null" """,
4
+ "is_confirm_cart": """ Yes | No""",
5
+ "is_deleted_cart": """ Yes | No""",
6
+ "is_confirm_order": """Yes | No """,
7
+ "is_deleted_order": """ Yes| No""",
8
+ "is_view_cart": """ Yes| No""",
9
+ }
10
+
11
+
12
+
13
+
14
+ def build_detailed_user_intent_prompt(cart_status: str, chat_history: str, user_question: str) -> str:
15
+ prompt = f"""
16
+ Bạn là một hệ thống phân tích hội thoại thông minh trong lĩnh vực thương mại điện tử, có nhiệm vụ xác định ý định của người dùng dựa trên hội thoại với chatbot và trạng thái giỏ hàng hiện tại.
17
+
18
+ ### Mục tiêu:
19
+ Phân loại **câu hỏi mới nhất của người dùng** vào một trong ba nhóm ý định sau:
20
+
21
+ ---
22
+
23
+ ### 1. `shopping_question` – Câu hỏi mang ý định mua sắm
24
+
25
+ **Định nghĩa**: Là câu hỏi thể hiện mong muốn của người dùng muốn thực hiện một hành động mua sắm online hoặc tương tác với các chức năng thương mại điện tử.
26
+
27
+ **Các hành động phổ biến cần nhận diện:**
28
+ - Thêm sản phẩm vào giỏ hàng → `add_to_cart`
29
+ - Xác nhận đơn hàng → `confirm_order`
30
+ - Tiến hành thanh toán → `checkout`
31
+ - Kiểm tra giỏ hàng hiện tại → `view_cart`
32
+ - Xóa sản phẩm khỏi giỏ hàng → `remove_item`
33
+ - Đặt hàng → `place_order`
34
+
35
+ ** Các câu hỏi về sản phẩm, xem sản phẩm không liên quan tới loại này
36
+ ** Các từ hỏi về thông tin đơn hàng, tình trạng giao đơn hay hỏi về quá trình đơn không liên quan đến này
37
+
38
+ **Từ khóa thường gặp:**
39
+ - **Động từ**: mua, buy, đặt, order, xác nhận, thanh toán, kiểm tra
40
+ - **Đối tượng**: sản phẩm, món, đồ, item, giỏ hàng, đơn hàng, order(Vui lòng kèm theo động từ ở trên)
41
+
42
+ **Phân loại là `shopping_question` nếu**:
43
+ - Có ý định thực hiện hành động thương mại rõ ràng(tôi muốn mua, thêm sản phẩm, xóa sản phẩm, loại bỏ,..)
44
+ - Nhắc đến giỏ hàng
45
+ - Có động từ hành động và danh từ liên quan mua sắm
46
+ - **Các hành động liên quan đến việc xem thông tin, dữ liệu sản phẩm(xem sản phẩm trà chanh, thông tin về cà phê,...) không được tính là shopping_question`**. Quan trọng
47
+
48
+ ---
49
+
50
+ ### 2. `normal_question` – Câu hỏi thông thường không liên quan đến giao dịch
51
+
52
+ **Ví dụ**:
53
+ - Hỏi thông tin về thương hiệu, xuất xứ
54
+ - Hỏi về khuyến mãi, giờ mở cửa
55
+ - Tư vấn không đi kèm hành động mua cụ thể
56
+
57
+ ---
58
+
59
+ ### 3. `unclear` – Không rõ ý định
60
+
61
+ **Khi nào áp dụng?**
62
+ - Câu nói mơ hồ, không đủ thông tin để xác định
63
+ - Có thể là ý định mua nhưng chưa rõ ràng
64
+
65
+ **Yêu cầu bổ sung nếu chọn `unclear`:**
66
+ - Đề xuất một câu hỏi gợi mở giúp làm rõ thêm ý định người dùng
67
+ *(Ví dụ: "Bạn muốn mình thêm món đó vào giỏ luôn không ạ?")*
68
+
69
+ ---
70
+ ##is_confirm_cart: Yes | No
71
+ Mô tả: Xác định xem người dùng có yêu cầu xác nhận hoặc đồng ý với nội dung giỏ hàng hiện tại hay không (ví dụ: "giỏ hàng của tôi đã đúng chưa?", "tôi xác nhận giỏ hàng này").
72
+ Yes: Người dùng muốn xác nhận nội dung giỏ hàng.
73
+ No: Người dùng không xác nhận giỏ hàng (mà có thể thêm/xóa sản phẩm, xem giỏ, v.v.).
74
+ ###is_deleted_cart: Yes | No
75
+ Mô tả: Xác định xem người dùng có yêu cầu xóa toàn bộ giỏ hàng hiện tại hay không, xóa toàn bộ sản phẩm đã thêm vào hay không
76
+ Yes: Người dùng muốn xóa giỏ hàng.
77
+ No: Người dùng không muốn xóa giỏ hàng.
78
+ ###is_confirm_order: Yes | No
79
+ Mô tả: Xác định xem người dùng có ý định xác nhận, hoàn tất, hoặc tiến hành đặt/mua ngay lập tức một đơn hàng hoặc sản phẩm hay không. Đây là điểm mà bạn đã góp ý.
80
+ Yes: Nếu người dùng sử dụng các từ ngữ như "xác nhận đơn hàng", "đặt hàng", "mua luôn", "thanh toán", "quyết định mua", hoặc các câu lệnh rõ ràng thể hiện mong muốn hoàn tất giao dịch (chứ không chỉ dừng lại ở việc thêm vào giỏ hàng). Kể cả khi có yêu cầu "thêm vào giỏ" kèm theo một ý định mua sắm mạnh mẽ và hoàn tất (ví dụ: "thêm vào giỏ và mua luôn", "thêm vào giỏ, tôi quyết định mua sản phẩm này"), thì giá trị này vẫn là Yes.
81
+ No: Nếu người dùng chỉ muốn thêm sản phẩm vào giỏ hàng, xem giỏ hàng, xóa sản phẩm khỏi giỏ hàng, hoặc các hành động khác mà không đi kèm ý định hoàn tất giao dịch ngay lập tức.
82
+ ###is_deleted_order: Yes| No
83
+ Mô tả: Xác định xem người dùng có yêu cầu hủy bỏ một đơn hàng đã được đặt hoặc đang trong quá trình xử lý hay không.
84
+ Yes: Người dùng muốn hủy đơn hàng.
85
+ No: Người dùng không muốn hủy đơn hàng.
86
+ ###is_view_cart: Yes| No
87
+ Mô tả: Xác định xem người dùng có yêu cầu muốn xem thông tin về giỏ hàng, sản phẩm trong giỏ(Chỉ kèm theo khi có đề cập giỏ hàng, đơn hàng), sản phẫm nãy giờ nguời dùng đã thêm cập nhật vào giỏ
88
+ Yes: Người dùng muốn xem giỏ hàng, đơn hàng, danh sách sản phẩm nãy giờ mà người dùng đã thêm(Phải kiểm tra xem có kèm theo giỏ hàng, đơn hàng đồ không) .
89
+ No: Người dùng không muốn xem.
90
+
91
+ ### Phân tích bổ sung:
92
+ Ngoài phân loại chính, bạn cũng cần **trích xuất xem câu nói có chứa hành động nào sau đây không**:
93
+ - Tạo giỏ hàng mới
94
+ - Xóa giỏ hàng
95
+ - Xác nhận tạo đơn hàng
96
+ - Hủy đơn hàng
97
+ - Xem đơn hàng
98
+ - Nếu câu hỏi người dùng kết hợp nhiều ý ví dụ xem xong rồi xác nhận mua, xem xong rồi xóa thì hãy vui lòng đánh dấu thêm thứ tự thực hiện trong đó.
99
+
100
+ ---
101
+
102
+ ### DỮ LIỆU ĐẦU VÀO
103
+
104
+ **Trạng thái giỏ hàng hiện tại:**
105
+ {cart_status}
106
+
107
+ **Lịch sử hội thoại gần nhất:**
108
+ \"\"\"\n{chat_history.strip()}\n\"\"\"
109
+
110
+ **Câu hỏi hiện tại của người dùng:**
111
+ \"{user_question.strip()}\"
112
+
113
+ ---
114
+
115
+ ### ĐỊNH DẠNG ĐẦU RA (JSON)
116
+
117
+ ```json
118
+ {json_example}
119
+ Hãy phân tích cẩn thận dựa trên ngữ cảnh trước khi đưa ra kết luận.
120
+ Luôn Đảm bảo trong câu trả lời luôn đảm bảo trong json trả về có 'question'. Tuyệt đối không được phép trả dữ liệu mà không có trường question.
121
+ Hãy xem xét kĩ xem câu hỏi người dùng có phải mua sắm hay không hay chỉ đơn thuần là câu hỏi khác nếu có hãy tách chúng ra.
122
+ """
123
+ return prompt.strip()
124
+
125
+ # data = build_detailed_user_intent_prompt("No","","Xem giỏ hàng của tôi")
126
+ # with open("user_intent_prompt.txt", "w", encoding="utf-8") as f:
127
+ # f.write(data)
function/advance_shopping/server_java/__pycache__/server_java.cpython-311.pyc ADDED
Binary file (12.8 kB). View file
 
function/advance_shopping/server_java/config.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ frontend:
2
+ web_payment_base: "http://localhost:5173"
3
+ base: "http://localhost:5173"
4
+ web_redirect_paths:
5
+ zalo: "payment-online-status"
6
+ momo: "payment-online-status-momo"
7
+ payos: "payment-online-status-payos"
8
+ vnpay: "payment-online-status-vnpay"
9
+ android_redirect_base: "myapp://open/order-complete"
10
+ android_group_redirect_base: "myapp://open/order-group-complete"
11
+
12
+
13
+ callback_urls:
14
+ base: http://localhost:1010
15
+ zalo:
16
+ single:
17
+ web:
18
+ callback: api/payment/zalo/callback
19
+ android:
20
+ callback: api/payment/zalo/callback
21
+ group:
22
+ android:
23
+ callback: api/payment-group/zalo/callback
24
+
25
+ momo:
26
+ single:
27
+ web:
28
+ callback: api/payment/momo/callback
29
+ android:
30
+ callback: api/payment/momo/callback
31
+ group:
32
+ android:
33
+ callback: api/payment-group/momo/callback
34
+
35
+ payos:
36
+ single:
37
+ web:
38
+ callback: api/payment/payOS/callback
39
+ android:
40
+ callback: api/payment/payOS/callback
41
+ group:
42
+ android:
43
+ callback: api/payment-group/payOS/callback
44
+
45
+ vnpay:
46
+ single:
47
+ web:
48
+ callback: api/payment/vnpay_ipn
49
+ android:
50
+ callback: api/payment/vnpay_ipn
51
+ group:
52
+ android:
53
+ callback: api/payment-group/vnpay_ipn
function/advance_shopping/server_java/server_java.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import urllib.parse
3
+ import yaml
4
+ with open("config.yaml", "r") as f:
5
+ config = yaml.safe_load(f)
6
+ base_backend_java = config["callback_urls"]["base"]
7
+ base_frontend = config["frontend"]["base"]
8
+ api = base_backend_java
9
+ def search_product(keyword, page=1, limit=5, language="VN", size=None):
10
+ encoded_keyword = urllib.parse.quote(keyword)
11
+ print(api)
12
+ url = f"{api}/api/product/search?keyword={encoded_keyword}&page={page}&limit={limit}&language={language}"
13
+ print(url)
14
+ try:
15
+ response = requests.get(url)
16
+ response.raise_for_status()
17
+ data = response.json()
18
+
19
+ # Lấy danh sách sản phẩm
20
+ products = data.get("productResponseList", [])
21
+ if not products:
22
+ print("⚠️ Không tìm thấy sản phẩm.")
23
+ return None
24
+
25
+ # Lấy sản phẩm đầu tiên
26
+ first_product = products[0]
27
+ pro_id = first_product.get("proId")
28
+ pro_name = first_product.get("proName")
29
+ variants = first_product.get("listProductVariants", [])
30
+
31
+
32
+
33
+ size_check = ""
34
+ stock_check = 0
35
+ price_check = 0
36
+ for v in variants:
37
+ if v['size'] == size:
38
+ size_check = v['size']
39
+ stock_check = v['stock']
40
+ price_check = v['price']
41
+ break
42
+ # print(f" + Size: {v['size']}, Giá: {v['price']} VND, Tồn kho: {v['stock']}")
43
+
44
+ return pro_id, pro_name, size_check, stock_check, price_check
45
+
46
+ except requests.RequestException as e:
47
+ print(f"❌ Lỗi kết nối: {e}")
48
+ except ValueError:
49
+ print("❌ Lỗi parse JSON.")
50
+
51
+
52
+ def insert_cartItem(userId: int, cart_id: int, proId:int, size, quantity, token:str = "", language="VN"):
53
+ url = f"{api}/api/cart-item/insert"
54
+ headers = {
55
+ "Authorization": f"Bearer {token}",
56
+ "Content-Type": "application/json",
57
+ }
58
+
59
+ body = {
60
+ "userId": userId,
61
+ "cartId": cart_id,
62
+ "proId": proId,
63
+ "size": size,
64
+ "quantity": quantity,
65
+ "language": language
66
+ }
67
+
68
+ try:
69
+ response = requests.post(url, json=body, headers=headers)
70
+ # response.raise_for_status()
71
+ if not response.ok:
72
+ print("❌ Lỗi HTTP", response.status_code)
73
+ try:
74
+ print("📦 Nội dung lỗi JSON:", response.json())
75
+ except Exception:
76
+ print("📜 Nội dung lỗi (text):", response.text)
77
+ return None
78
+ return response.json()
79
+
80
+ except requests.exceptions.RequestException as e:
81
+ print("Lỗi khi gọi API:", e)
82
+ return None
83
+
84
+
85
+ def update_cartItem_quantity(userId: int, cartItemId: int, quantity, token):
86
+ url = f"{api}/api/cart-item/update"
87
+ headers = {
88
+ "Authorization": f"Bearer {token}",
89
+ "Content-Type": "application/json",
90
+ }
91
+
92
+ body = {
93
+ "userId": userId,
94
+ "cartItemId": cartItemId,
95
+ "quantity": quantity,
96
+ }
97
+ response = requests.put(url, json=body, headers=headers)
98
+ try:
99
+
100
+ # response.raise_for_status()
101
+ if not response.ok:
102
+ print("❌ Lỗi HTTP", response.status_code)
103
+ try:
104
+ print("📦 Nội dung lỗi JSON:", response.json())
105
+ except Exception:
106
+ print("📜 Nội dung lỗi (text):", response.text)
107
+ return None
108
+ return response.json()
109
+ except requests.exceptions.RequestException as e:
110
+ print("Lỗi khi gọi API:", e)
111
+ return response.content
112
+
113
+
114
+ def update_cartItem_size(userId: int, cartItemId: int, size, token):
115
+ url = f"{api}/api/cart-item/change-size"
116
+ headers = {
117
+ "Authorization": f"Bearer {token}",
118
+ "Content-Type": "application/json",
119
+ }
120
+
121
+ body = {
122
+ "userId": userId,
123
+ "cartItemId": cartItemId,
124
+ "size": size,
125
+ }
126
+ try:
127
+ response = requests.put(url, json=body, headers=headers)
128
+
129
+ if not response.ok:
130
+ print("❌ Lỗi HTTP", response.status_code)
131
+ try:
132
+
133
+ return response.json()
134
+ except Exception:
135
+ return response.text
136
+ return response.json()
137
+ except requests.exceptions.RequestException as e:
138
+ print("Lỗi khi gọi API:", e)
139
+
140
+
141
+ def remove_one_cartItem( userId, cartItemId,token):
142
+ url = f"{api}/api/cart-item/delete/{cartItemId}"
143
+ headers = {
144
+ "Authorization": f"Bearer {token}",
145
+ "Content-Type": "application/json",
146
+ }
147
+
148
+ body = {
149
+ "userId": userId,
150
+ "cartItemId": cartItemId,
151
+ }
152
+
153
+ try:
154
+ response = requests.delete(url, json=body, headers=headers)
155
+ response.raise_for_status()
156
+ if not response.ok:
157
+ print("❌ Lỗi HTTP", response.status_code)
158
+ try:
159
+ print("📦 Nội dung lỗi JSON:", response.json())
160
+ return response.json()
161
+ except Exception:
162
+
163
+ print("📜 Nội dung lỗi (text):", response.text)
164
+ return response.text
165
+ return None
166
+ return response.json()
167
+ except requests.exceptions.RequestException as e:
168
+ print("Lỗi khi gọi API:", e)
169
+ return None
170
+
171
+
172
+ def remove_all_cartItem(userId, cartId,token):
173
+ url = f"{api}/api/cart/delete-allItem/{cartId}"
174
+ headers = {
175
+ "Authorization": f"Bearer {token}",
176
+ "Content-Type": "application/json",
177
+ }
178
+
179
+ body = {
180
+ "userId": userId,
181
+ "cartId": cartId,
182
+ }
183
+
184
+ try:
185
+ response = requests.delete(url, json=body, headers=headers)
186
+ if not response.ok:
187
+ print("❌ Lỗi HTTP", response.status_code)
188
+ try:
189
+ print("📦 Nội dung lỗi JSON:", response.json())
190
+ return response.json()
191
+ except Exception:
192
+
193
+ print("📜 Nội dung lỗi (text):", response.text)
194
+ return response.text
195
+ return response.json()
196
+ except requests.exceptions.RequestException as e:
197
+ print("Lỗi khi gọi API:", e)
198
+ return None
199
+
200
+
201
+ def create_orders(user_id, cartId, token, language="VN"):
202
+ url = f"{api}/api/orders/create"
203
+ headers = {
204
+ "Authorization": f"Bearer {token}",
205
+ "Content-Type": "application/json",
206
+ }
207
+
208
+ body = {
209
+
210
+ "userId": user_id,
211
+ "cartId": cartId,
212
+ "voucherId": "string",
213
+ "pointCoinUse": 0,
214
+ "note": "string",
215
+ "language": language
216
+
217
+ }
218
+
219
+ try:
220
+ response = requests.post(url, json=body, headers=headers)
221
+ if not response.ok:
222
+ print("❌ Lỗi HTTP", response.status_code)
223
+ try:
224
+ print("📦 Nội dung lỗi JSON:", response.json())
225
+ return response.json()
226
+ except Exception:
227
+ print("📜 Nội dung lỗi (text):", response.text)
228
+ return response.text
229
+ return response.json()
230
+ except requests.exceptions.RequestException as e:
231
+ print("Lỗi khi gọi API:", e)
232
+ return None
233
+
234
+
235
+ def confirm_orders_AI(user_id,token, orderId):
236
+ url = f"{api}/api/orders/confirm"
237
+ headers = {
238
+ "Authorization": f"Bearer {token}",
239
+ "Content-Type": "application/json",
240
+ }
241
+ body = {
242
+
243
+ "userId": user_id,
244
+ "orderId": orderId,
245
+
246
+ }
247
+
248
+ try:
249
+ response = requests.post(url, json=body, headers=headers)
250
+ if not response.ok:
251
+ print("❌ Lỗi HTTP", response.status_code)
252
+ try:
253
+ print("📦 Nội dung lỗi JSON:", response.json())
254
+ return response.json()
255
+ except Exception:
256
+ print("📜 Nội dung lỗi (text):", response.text)
257
+ return response.text
258
+ return response.json()
259
+ except requests.exceptions.RequestException as e:
260
+ print("Lỗi khi gọi API:", e)
261
+ return None
262
+
263
+
264
+ def confirm_cancel_AI(user_id,token, orderId):
265
+ url = f"{api}/api/orders/confirm-cancel-ai"
266
+ headers = {
267
+ "Authorization": f"Bearer {token}",
268
+ "Content-Type": "application/json",
269
+ }
270
+ body = {
271
+
272
+ "userId": user_id,
273
+ "orderId": orderId,
274
+
275
+ }
276
+
277
+ try:
278
+ response = requests.post(url, json=body, headers=headers)
279
+ if not response.ok:
280
+ print("❌ Lỗi HTTP", response.status_code)
281
+ try:
282
+ print("📦 Nội dung lỗi JSON:", response.json())
283
+ return response.json()
284
+ except Exception:
285
+ print("📜 Nội dung lỗi (text):", response.text)
286
+ return response.text
287
+ return response.json()
288
+ except requests.exceptions.RequestException as e:
289
+ print("Lỗi khi gọi API:", e)
290
+ return None
291
+
292
+
293
+ def create_cart(user_id, token):
294
+ print(token)
295
+ print(user_id)
296
+ url = f"{api}/api/cart/createAI"
297
+ headers = {
298
+ "Authorization": f"Bearer {token}",
299
+ "Content-Type": "application/json",
300
+ }
301
+
302
+ body = {
303
+ "userId": user_id
304
+ }
305
+ try:
306
+ response = requests.post(url, json=body, headers=headers)
307
+ response.raise_for_status()
308
+ return response.json()
309
+ except requests.exceptions.RequestException as e:
310
+ print("Lỗi khi gọi API:", e)
311
+ return None
312
+
313
+
314
+ # print(search_product(keyword="trà gừng",size="L"))
315
+
316
+ # data = create_cart(4, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg1NDE4ODgsImV4cCI6MTc0OTQ0MTg4OH0.6pEg1hdSDRvfAQK5YYJsQY_OfZPV9_EqqVvTGbHftvc")
317
+ # print(data)
318
+
319
+ # data1 = insert_cartItem(4, 775, 38, "L", 2, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ")
320
+ # print(data1)
321
+
322
+ # data2 = update_cartItem_quantity(4, 1226, 5, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ")
323
+ # print(data2)
324
+
325
+ # data3 = update_cartItem_size(4, 1226, "K", "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ")
326
+ # print(data3)
327
+
328
+ # data4 = remove_one_cartItem(4, 1226, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ")
329
+ # print(data4)
330
+
331
+ # data5 = remove_all_cartItem(4, 773, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ")
332
+ # print(data5)
333
+
334
+ # data6 = create_orders(4, 787, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg1NDE4ODgsImV4cCI6MTc0OTQ0MTg4OH0.6pEg1hdSDRvfAQK5YYJsQY_OfZPV9_EqqVvTGbHftvc")
335
+ # print(data6)
336
+
337
+ # data7 = confirm_orders_AI(4, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg1NDE4ODgsImV4cCI6MTc0OTQ0MTg4OH0.6pEg1hdSDRvfAQK5YYJsQY_OfZPV9_EqqVvTGbHftvc", 652)
338
+ # print(data7)
function/agent/__init__.py ADDED
File without changes
function/agent/function_call.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from langchain_openai import ChatOpenAI
3
+ import os
4
+ import os,sys
5
+ BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
6
+ sys.path.insert(0, BASE_DIR)
7
+ import function.gemini_response.filter_query_internal as filter_query
8
+
9
+
10
+ #Test OpenAI
11
+ os.environ["OPENAI_API_KEY"] = "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"
12
+ llm = ChatOpenAI(model="gpt-4.1")
13
+
14
+ #Test google
15
+ os.environ["GOOGLE_API_KEY"] = "AIzaSyB_7ahCuAOZU0UKcON_A00ya5breHTEgQM"
16
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
17
+ llm1 = ChatGoogleGenerativeAI(model='gemini-2.5-flash-preview-05-20',temperature=0.6)
18
+
19
+ from langchain_core.messages import HumanMessage, ToolMessage
20
+
21
+
22
+ @tool
23
+ def question_information(query: str) -> str:
24
+ """
25
+ Trả lời các câu hỏi về thông tin liên hệ của cửa hàng, thông tin về sinh viên, tên đề tài các nội dung liên quan mà không phải sql
26
+
27
+ Hàm này chỉ phục vụ mục đích cung cấp thông tin liên lạc của cửa hàng, bao gồm:
28
+ - Số điện thoại liên hệ.
29
+ - Địa chỉ cửa hàng.
30
+ - Giờ làm việc.
31
+ - Email hỗ trợ.
32
+ - Các kênh liên hệ khác như Facebook, Zalo, v.v.
33
+
34
+ ⚠️ Lưu ý:
35
+ - Hàm này KHÔNG trả lời các câu hỏi khác như: gợi ý món ăn, tư vấn sản phẩm, giá cả, chương trình khuyến mãi, cách pha chế đồ uống, v.v.
36
+ - Nếu truy vấn không liên quan đến thông tin cửa hàng, hàm sẽ từ chối trả lời.
37
+
38
+ Args:
39
+ query (str): Câu hỏi của người dùng liên quan đến thông tin liên hệ của cửa hàng. bao gồm:
40
+ - Số điện thoại liên hệ.
41
+ - Địa chỉ cửa hàng.
42
+ - Giờ làm việc.
43
+ - Email hỗ trợ.
44
+ - Các kênh liên hệ khác như Facebook, Zalo, v.v.
45
+
46
+ Returns:
47
+ str: Thông tin liên hệ của cửa hàng hoặc thông báo từ chối nếu câu hỏi không phù hợp. Xác định rõ câu hỏi không viết gọn
48
+ """
49
+ return query
50
+
51
+
52
+ @tool(description="Vui lòng giữ đúng truy vấn người dùng không tạo ra câu truy vấn khác ý nghĩa câu hỏi người dùng định hỏi, (**yêu cầu phải bám sát không được tự sinh câu hỏi)")
53
+ def question_sql(query: str)->str:
54
+ """
55
+ Hỗ trợ truy xuất schema của database để giúp tạo truy vấn SQL cho các bảng sau:
56
+
57
+ - **Giỏ hàng (cart, cart_item)**
58
+ - **Danh mục sản phẩm(tên sản phẩm) (category, category_translation)**
59
+ - **Liên hệ và yêu thích (contact, favourite, favourite_item)**
60
+ - **Thông báo (notification)**
61
+ - **Đơn hàng (orders, order_item, shipment)**
62
+ - **Thanh toán (otp, payments)**
63
+ - **Bài viết & tin tức (post, post_translation)**
64
+ - **Lịch sử giá (price_history)**
65
+ - **Sản phẩm (product, product_translation, product_variants)**
66
+ - **Đánh giá sản phẩm (review)**
67
+ - **Người dùng (user, user_coin, token)**
68
+ - **Mã giảm giá & phiếu giảm giá (voucher, user_voucher)**
69
+ - **Xác nhận về mua/xem, xóa giỏ hàng, đơn hàng**(Hãy lưu ý những câu hỏi về này)
70
+
71
+ ⚠ **Lưu ý**:
72
+ - Chỉ hỗ trợ truy vấn liên quan đến các bảng trong database.
73
+ - Không trả lời các câu hỏi về thông tin cửa hàng như số điện thoại, email, giờ làm việc.
74
+
75
+ Args:
76
+ query (str): Câu hỏi liên quan đến việc truy vấn dữ liệu từ database.
77
+
78
+ Returns:
79
+ str: Mô tả bảng tương ứng hoặc thông báo từ chối nếu câu hỏi không phù hợp.
80
+ """
81
+ return query
82
+
83
+
84
+ @tool
85
+ def question_general(query: str) -> str:
86
+ """
87
+ Xử lý các câu hỏi không liên quan đến các công cụ có sẵn.
88
+
89
+ Hàm này nhận vào một câu hỏi dưới dạng chuỗi và trả về câu hỏi đó
90
+ mà không thực hiện bất kỳ xử lý nào khác. Nó có thể được sử dụng như
91
+ một phương án dự phòng khi không có tool nào phù hợp để xử lý câu hỏi.
92
+
93
+ Args:
94
+ query (str): Câu hỏi đầu vào từ người dùng.
95
+
96
+ Returns:
97
+ str: Trả về chính câu hỏi mà không thay đổi.
98
+ """
99
+ return query
100
+
101
+
102
+
103
+
104
+ @tool(description="""ác định xem truy vấn có phải là câu chào hỏi hay không.
105
+ Câu chào hỏi bao gồm các từ hoặc cụm từ thể hiện sự chào đón, bắt đầu cuộc trò chuyện, chẳng hạn như:
106
+ - "Xin chào", "Chào bạn", "Hello", "Hi", "Hey", "Good morning", "Good evening", ...
107
+ - Cụm từ mang ý nghĩa xã giao: "Rất vui ��ược gặp bạn", "Hôm nay bạn thế nào?", "Chúc một ngày tốt lành", ...
108
+
109
+ Lưu ý:
110
+ - Chỉ xử lý các câu hỏi thuộc nhóm chào hỏi.
111
+ - Không liên quan đến **question_sql** hoặc các câu hỏi về dữ liệu.
112
+
113
+ Args:
114
+ query (str): Câu hỏi đầu vào của người dùng.
115
+
116
+ Returns:
117
+ str: Trả về câu hỏi nếu nó thuộc nhóm chào hỏi, ngược lại có thể xử lý theo yêu cầu cụ thể.""")
118
+ def question_hello(query: str)->str:
119
+ """
120
+ Xác định xem truy vấn có phải là câu chào hỏi hay không.
121
+ Câu chào hỏi bao gồm các từ hoặc cụm từ thể hiện sự chào đón, bắt đầu cuộc trò chuyện, chẳng hạn như:
122
+ - "Xin chào", "Chào bạn", "Hello", "Hi", "Hey", "Good morning", "Good evening", ...
123
+ - Cụm từ mang ý nghĩa xã giao: "Rất vui được gặp bạn", "Hôm nay bạn thế nào?", "Chúc một ngày tốt lành", ...
124
+
125
+ Lưu ý:
126
+ - Chỉ xử lý các câu hỏi thuộc nhóm chào hỏi.
127
+ - Không liên quan đến **question_sql** hoặc các câu hỏi về dữ liệu.
128
+
129
+ Args:
130
+ query (str): Câu hỏi đầu vào của người dùng.
131
+
132
+ Returns:
133
+ str: Trả về câu hỏi nếu nó thuộc nhóm chào hỏi, ngược lại có thể xử lý theo yêu cầu cụ thể.
134
+ """
135
+ return query
136
+
137
+
138
+ import asyncio
139
+ import importlib
140
+ import function.chat as chat_module
141
+ async def update_tool_calls(tool_calls, chat_id,user_id,user_input):
142
+
143
+ updated_calls = []
144
+
145
+ for tool in tool_calls:
146
+ query = tool['args']['query']
147
+ list_history = await chat_module.get_chat_details_text(chat_id, user_id)
148
+ new_question = await filter_query.response_rename_question(user_input, list_history, query)
149
+ tool['args']['query'] = new_question
150
+ new_name = await filter_query.response_general(new_question, list_history)
151
+ if new_name is not None and new_name == "question_sql":
152
+ tool['name'] = new_name.strip()
153
+ updated_calls.append(tool)
154
+ if new_name == "question_general":
155
+ tool['name'] = new_name.strip()
156
+ updated_calls.append(tool)
157
+ return updated_calls
158
+ else:
159
+ return tool_calls
160
+
161
+ return updated_calls
162
+
163
+
164
+ async def process_user_query(user_input, user_id, role, language,chat_id):
165
+ always_call_tool_llm = llm.bind_tools([question_information, question_sql, question_hello], tool_choice=True,strict=True)
166
+ tool_calls = always_call_tool_llm.invoke(f"{user_input}").tool_calls
167
+
168
+ for tc in tool_calls:
169
+ tc["user_id"] = user_id
170
+ tc["role"] = role
171
+ tc["language"] = language
172
+ print("Tool gốc:",tool_calls)
173
+ data = await update_tool_calls(tool_calls,chat_id,user_id,user_input)
174
+
175
+ for item in data:
176
+ item['chat_id'] = chat_id
177
+ print("Tool update: ", data)
178
+ return data
179
+
180
+
181
+ # import asyncio
182
+
183
+ async def main():
184
+ await process_user_query("Tôi muốn thêm trà chanh size L vào giỏ hàng", 4, "CUSTOMER", "VN", "67d2fc6607b51a01e3beb501")
185
+
186
+ if __name__ == "__main__":
187
+ loop = asyncio.new_event_loop()
188
+ asyncio.set_event_loop(loop)
189
+ loop.run_until_complete(main())
190
+ loop.close()
function/agent/pipeline_agent.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os,sys
2
+ BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
3
+ sys.path.insert(0, BASE_DIR)
4
+ from function.file import extract_file as extract_file
5
+ import function.agent.function_call as fc
6
+ import function.function_agent.multi_agent as multi_agent
7
+ import function.function_agent.map_toolcall as map_toolcall
8
+ import function.gemini_response.response_all as response_all
9
+ from models.Database_Entity import StopSignal
10
+ from advance_shopping.server_java import server_java
11
+ async def check_should_stop(chat_id: str, stop_event: object = None):
12
+ # Trường hợp dừng qua RAM (in-memory)
13
+ await asyncio.sleep(0.1)
14
+ if stop_event and stop_event.is_set():
15
+ print("🛑 Dừng qua stop_event.")
16
+ return {"status": "cancelled"}
17
+
18
+ # Trường hợp dừng qua MongoDB
19
+ await asyncio.sleep(0.1)
20
+ if StopSignal.objects(chat_history=chat_id, is_stopped=True).first():
21
+ print("🛑 Dừng vì có StopSignal trong DB.")
22
+ return {"status": "cancelled"}
23
+
24
+ return None # Không bị dừng
25
+
26
+
27
+ async def rollback_cart_if_stopped(stop_event, result_multi_task, chat_id: str, user_id: int, token: str):
28
+ if stop_event and stop_event.is_set():
29
+ for task_id, task_result in result_multi_task.items():
30
+ if task_result.get("is_shopping"):
31
+ print(f"INFO: Bắt đầu rollback task mua sắm: {task_id}")
32
+ chat_cart = ChatCart()
33
+ latest_unconfirmed_cart = ChatCart.objects(chat_history=chat_id).order_by('-created_at').first()
34
+ if latest_unconfirmed_cart:
35
+ chat_cart = latest_unconfirmed_cart
36
+
37
+ # Khôi phục trạng thái backup
38
+ chat_cart.cart_products = chat_cart.backup_cart_products
39
+ chat_cart.status = chat_cart.backup_status
40
+ chat_cart.confirmed_order = chat_cart.backup_confirmed_order
41
+
42
+ chat_cart.save()
43
+ cart_id = chat_cart.cart_id
44
+ order_id = chat_cart.order_id
45
+
46
+ if order_id:
47
+ try:
48
+ server_java.confirm_cancel_AI(int(user_id), token, order_id)
49
+ print(f"✅ Đã huỷ đơn hàng: {order_id}")
50
+ except Exception as e:
51
+ print(f"ERROR khi huỷ đơn hàng {order_id}: {e}")
52
+ if cart_id:
53
+ try:
54
+ server_java.remove_all_cartItem(int(user_id), cart_id, token)
55
+ print(f"✅ Đã xoá item trong giỏ hàng: {cart_id}")
56
+ except Exception as e:
57
+ print(f"ERROR khi xoá cart item {cart_id}: {e}")
58
+
59
+ # Reset các ID để tránh nhầm lẫn về sau
60
+ chat_cart.order_id = None
61
+ chat_cart.cart_id = None
62
+ chat_cart.save()
63
+
64
+ else:
65
+ print(f"Task {task_id} không phải mua sắm, không cần rollback.")
66
+
67
+
68
+ from typing import Optional
69
+ import asyncio
70
+ from models.Database_Entity import StopSignal,ChatCart
71
+ async def multi_query_user(
72
+ user_input: str,
73
+ user_id: int,
74
+ role,
75
+ languages: str,
76
+ chat_id,
77
+ token,
78
+ stop_event: Optional[asyncio.Event] = None
79
+ ) -> str:
80
+
81
+ chat_cart = ChatCart()
82
+ latest_unconfirmed_cart = ChatCart.objects(chat_history=chat_id,status ="pending").order_by('-created_at').first()
83
+ if latest_unconfirmed_cart:
84
+ chat_cart = latest_unconfirmed_cart
85
+ chat_cart.backup_cart_products = chat_cart.cart_products
86
+ chat_cart.backup_status = chat_cart.status
87
+ chat_cart.backup_confirmed_order = chat_cart.confirmed_order
88
+ chat_cart.save()
89
+
90
+ result_check = await check_should_stop(chat_id, stop_event)
91
+ if result_check:
92
+ return result_check
93
+
94
+ multi_tasks = await fc.process_user_query(user_input, user_id, role, languages, chat_id)
95
+
96
+ result_check = await check_should_stop(chat_id, stop_event)
97
+ if result_check:
98
+ return result_check
99
+
100
+ result_multi_task = await multi_agent.process_toolcalls_with_order(multi_tasks, token, stop_event,chat_id)
101
+ chat_cart = ChatCart()
102
+ if stop_event and stop_event.is_set():
103
+ await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token)
104
+
105
+
106
+ result_check = await check_should_stop(chat_id, stop_event)
107
+ if result_check:
108
+ await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token)
109
+ return result_check
110
+
111
+ map_tool_result = map_toolcall.map_toolcalls_with_results(multi_tasks, result_multi_task)
112
+
113
+ result_check = await check_should_stop(chat_id, stop_event)
114
+ if result_check:
115
+ await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token)
116
+ return result_check
117
+
118
+ result = await response_all.response_all(user_input, map_tool_result)
119
+
120
+ result_check = await check_should_stop(chat_id, stop_event)
121
+ if result_check:
122
+ await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token)
123
+ return result_check
124
+
125
+ return result if result is not None else {"status": "empty"}
126
+
127
+
128
+
129
+ # import asyncio
130
+ # # if __name__ == "__main__":
131
+ # # token = "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInNlc3Npb25JZCI6ImJkM2M5MmNkLTczMzgtNDlmNS04NzIyLTUwMDU0Zjk5MjRhZSIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg3NjcwNDUsImV4cCI6MTc0OTY2NzA0NX0.X4d4GcstDhaVC1nbPWWkN3sUEHNUltDtD5KgP_mzPrM"
132
+ # # result = asyncio.run(multi_query_user("Tôi muốn thêm sản phẩm Yogurt Đào size S vào giỏ hàng và tôi quyết định mua sản phẩm này", 4 , "CUSTOMER", "VN","67d009fee0a638763885c195",token=token))
133
+ # # print(result)
134
+ # if __name__ == "__main__":
135
+ # result = asyncio.run(multi_query_user("Tôi muốn thêm trà chanh size L vào giỏ", 4, "CUSTOMER", "ADMIN","67d2fc6607b51a01e3beb501",""))
136
+ # print(result)
function/analyze/__init__.py ADDED
File without changes
function/analyze/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (188 Bytes). View file
 
function/analyze/__pycache__/execute_query.cpython-311.pyc ADDED
Binary file (22.1 kB). View file
 
function/analyze/__pycache__/main.cpython-311.pyc ADDED
Binary file (5.8 kB). View file
 
function/analyze/__pycache__/merge_output.cpython-311.pyc ADDED
Binary file (1.04 kB). View file
 
function/analyze/__pycache__/pipeline.cpython-311.pyc ADDED
Binary file (9.04 kB). View file
 
function/analyze/__pycache__/prompt_databse.cpython-311.pyc ADDED
Binary file (37.5 kB). View file
 
function/analyze/__pycache__/prompt_query.cpython-311.pyc ADDED
Binary file (8.22 kB). View file
 
function/analyze/__pycache__/return_code_python.cpython-311.pyc ADDED
Binary file (5.96 kB). View file
 
function/analyze/execute_query.py ADDED
@@ -0,0 +1,394 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+
4
+ # Thêm thư mục gốc (chứa thư mục "function") vào sys.path
5
+ sys.path.append(os.path.abspath(os.path.join(os.getcwd(), "..")))
6
+ import os
7
+ from dotenv import load_dotenv
8
+
9
+ # Load biến môi trường từ file .env
10
+ load_dotenv()
11
+
12
+ DB_HOST = os.getenv("DB_HOST")
13
+ DB_USER = os.getenv("DB_USER")
14
+ DB_PASSWORD = os.getenv("DB_PASSWORD")
15
+ DB_NAME = os.getenv("DB_NAME")
16
+ DB_PORT = os.getenv("DB_PORT")
17
+
18
+ import os
19
+ from urllib.parse import quote
20
+
21
+ password = os.getenv("DB_PASSWORD") # VD: 'Yahana0509@'
22
+ DB_PASSWORD = quote(password)
23
+ # Tạo connection string
24
+ connection_uri = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
25
+ from langchain_community.utilities.sql_database import SQLDatabase
26
+ from langchain_experimental.sql import SQLDatabaseChain
27
+ import sys
28
+ import os
29
+ import pymysql
30
+ from fastapi import HTTPException
31
+ import sys
32
+ import os
33
+
34
+
35
+ from fastapi.encoders import jsonable_encoder
36
+ import re
37
+ from function.prompt import prompt_main as prompt
38
+ import function.prompt.prompt_custom as prompt_cus
39
+ os.environ["GOOGLE_API_KEY"] = "AIzaSyCO-RlqYewC4e9BEPoC8m-AxHUY7J3_o2E"
40
+ from bson import ObjectId
41
+ db = SQLDatabase.from_uri(connection_uri)
42
+ from dotenv import load_dotenv
43
+ import filter.filter_role as filter_role_1
44
+ import filter.filter_sql_injection as filter_sql_injection_1
45
+ import filter.result as query_result_1
46
+ import response.ResponseChat as res_chat
47
+ from datetime import datetime
48
+ import pytz
49
+ from mongoengine import connect
50
+ import os
51
+ import nltk
52
+ import function.agent.pipeline_agent as pipeline_agent
53
+ nltk.download('punkt')
54
+ from models.Database_Entity import User, ChatHistory, DetailChat
55
+ from dotenv import load_dotenv
56
+ load_dotenv()
57
+ MONGO_URI = os.getenv("MONGO_URI", "")
58
+ connect("chatbot_hmdrinks", host=MONGO_URI)
59
+ import re
60
+
61
+ def contains_delete(sql: str) -> bool:
62
+ return bool(re.search(r'\bdelete\b', sql, re.IGNORECASE))
63
+ load_dotenv()
64
+
65
+ #setup model
66
+ from bson import ObjectId
67
+ import random
68
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
69
+ llm1 = ChatGoogleGenerativeAI(model='gemini-2.5-flash-preview-05-20',temperature=0.5)
70
+
71
+ #setup kết nối database
72
+ db = SQLDatabase.from_uri(connection_uri)
73
+ db_chain = SQLDatabaseChain.from_llm(llm=llm1,db=db,prompt= prompt.PROMPT)
74
+
75
+ from prompt.prompt_syntax_insert import is_insert_related_to_product_category_variant, filter_syntax_sql
76
+ import sqlparse
77
+
78
+
79
+ import sys
80
+ import os
81
+ sys.path.append(os.path.abspath(os.path.join(os.getcwd(), '..')))
82
+ from prompt import prompt_detail_table
83
+
84
+ schema_mapping = {
85
+ "payments": prompt_detail_table.prompt_payments,
86
+ "user": prompt_detail_table.prompt_users,
87
+ "user_voucher": prompt_detail_table.prompt_user_voucher,
88
+ "category": prompt_detail_table.prompt_categort,
89
+ "category_translation": prompt_detail_table.prompt_category_translation,
90
+ "cart": prompt_detail_table.prompt_cart,
91
+ "cart_item": prompt_detail_table.prompt_cart_item,
92
+ "orders":prompt_detail_table.prompt_orders,
93
+ "order_item": prompt_detail_table.prompt_order_item,
94
+ "payment": prompt_detail_table.prompt_payments,
95
+ "favourite": prompt_detail_table.prompt_favourite,
96
+ "favourite_item": prompt_detail_table.prompt_fav_item,
97
+ "post": prompt_detail_table.prompt_post,
98
+ "post_translation": prompt_detail_table.prompt_post_translation,
99
+ "product": prompt_detail_table.prompt_product,
100
+ "product_translation": prompt_detail_table.prompt_product_translation,
101
+ "shipment": prompt_detail_table.prompt_shipment,
102
+ "product_variants": prompt_detail_table.prompt_product_variants,
103
+ "review": prompt_detail_table.prompt_review,
104
+ "user_coin": prompt_detail_table.prompt_user_coin,
105
+ "absence": prompt_detail_table.prompt_absence,
106
+ "cart_group": prompt_detail_table.prompt_cart_group,
107
+ "cart_item_group": prompt_detail_table.prompt_cartitem_group,
108
+ "group_orders": prompt_detail_table.prompt_group_orders,
109
+ "payments_group": prompt_detail_table.prompt_payments_group,
110
+ "group_order_members":prompt_detail_table.prompt_group_orders_member,
111
+ "shipment_group":prompt_detail_table.prompt_shipment_group,
112
+ "shipper_attendance":prompt_detail_table.prompt_shipper_attendance,
113
+ "shipper_commission_detail":prompt_detail_table.prompt_shipper_commission_detail,
114
+ "shipper_salary_summary":prompt_detail_table.prompt_shipper_salary_summary,
115
+ "voucher": prompt_detail_table.prompt_voucher
116
+ }
117
+
118
+
119
+ def get_schemas_from_sql(sql_query: str, schema_mapping: dict):
120
+ import sqlglot
121
+ print("SQL query:", sql_query)
122
+ parsed_query = sqlglot.parse_one(sql_query, read="mysql")
123
+
124
+ # Lấy danh sách bảng duy nhất trong query
125
+ table_names = list({t.name for t in parsed_query.find_all(sqlglot.exp.Table)})
126
+
127
+ schemas_used = {}
128
+ for table in table_names:
129
+ if table in schema_mapping:
130
+ schemas_used[table] = schema_mapping[table]
131
+ else:
132
+ print(f"⚠️ Warning: Table '{table}' not found in schema_mapping")
133
+
134
+ # Gom toàn bộ schema thành 1 chuỗi duy nhất
135
+ all_schemas = "\n\n".join(
136
+ [f"Schema for table '{table}':\n{schemas_used[table]}" for table in schemas_used]
137
+ )
138
+
139
+ return all_schemas
140
+
141
+
142
+ def build_sql_fix_prompt(schemas_result: dict, sql: str,user_id: int) -> str:
143
+
144
+ prompt = f"""
145
+ Bạn là một chuyên gia cơ sở dữ liệu.
146
+
147
+ Dưới đây là mô tả schema chi tiết của các bảng có trong hệ thống:
148
+
149
+ {schemas_result}
150
+
151
+ ---
152
+
153
+ Dưới đây là một câu SQL đang bị lỗi do không đúng tên bảng hoặc tên cột:
154
+
155
+ ```sql
156
+ {sql.strip()}
157
+ Yêu cầu của bạn là:
158
+ User Id: {user_id}
159
+ Dựa trên các schema ở trên, hãy kiểm tra và chỉnh sửa câu SQL sao cho:
160
+ Tên bảng, tên cột phải chính xác theo schema.
161
+ Logic và mục đích của truy vấn được giữ nguyên.
162
+ Chỉ trả lại phần SQL đã được chỉnh sửa (không giải thích, không chú thích, không thêm nhận xét).
163
+ Loại bỏ các câu comment chỉ trả lại câu SQL chính xác sau khi bạn đã chỉnh sửa.
164
+ Vui lòng chỉnh sửa một cách chính xác nhất.
165
+ Trả lời dưới dạng một truy vấn SQL duy nhất.
166
+ ** Tham khảo thêm các lỗi sau để tránh:
167
+ - "- Tránh các lỗi như :\n"
168
+ " (1054, \"Unknown column 'oi.pro_id' in 'field list'\")\n . Luôn đảm bảo bạn không bao giờ bị lỗi này"
169
+ " (1054, \"Unknown column 'oi.note' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này\n"
170
+ " (1054, \"Unknown column 'oi.size' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này \n"
171
+ " (1054, \"Unknown column 'c.is_deleted' in 'on clause'\"). Luôn đảm bảo bạn không bao giờ bị lỗi này\n"
172
+ """
173
+ return prompt
174
+
175
+
176
+
177
+
178
+ async def execute_query_user(user_input: str, user_id: int, languages: str, role: str):
179
+ PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_input)
180
+ check_insert = is_insert_related_to_product_category_variant(user_input)
181
+
182
+ db_config = {
183
+ "host": os.getenv("DB_HOST"),
184
+ "user": os.getenv("DB_USER"),
185
+ "database": os.getenv("DB_NAME"),
186
+ "password": os.getenv("DB_PASSWORD"),
187
+ "port": int(os.getenv("DB_PORT", 3306)),
188
+ "charset": "utf8mb4",
189
+ "cursorclass": pymysql.cursors.DictCursor,
190
+ }
191
+
192
+
193
+ def regenerate_sql_until_safe():
194
+ max_retry = 5
195
+ retry_count = 0
196
+
197
+ while retry_count < max_retry:
198
+ try:
199
+ regenerated_data = db_chain.run(f"""
200
+ Role: {text_role}
201
+ Language: {languages}
202
+ Question: {user_input}.
203
+ """)
204
+ regenerated_sql = extract_sql_from_response(regenerated_data)
205
+ if regenerated_sql:
206
+ regenerated_sql = clean_sql(regenerated_sql)
207
+ if not re.search(r"%{1,2}s", regenerated_sql): # đã sạch
208
+ return regenerated_sql
209
+ retry_count += 1
210
+ except Exception as e:
211
+ return f"❌ Lỗi khi tạo lại truy vấn lần {retry_count + 1}: {str(e)}"
212
+
213
+ return "❌ Lỗi: Không thể tạo được truy vấn an toàn sau nhiều lần thử."
214
+
215
+
216
+ def execute_query_with_pymysql(query, multi=False):
217
+ connection = pymysql.connect(**db_config)
218
+ try:
219
+ with connection.cursor() as cursor:
220
+ results = []
221
+ if multi:
222
+ # Dùng sqlparse để chia câu truy vấn cho an toàn
223
+ statements = sqlparse.split(query)
224
+ for stmt in statements:
225
+ stmt = stmt.strip()
226
+ if stmt:
227
+ cursor.execute(stmt)
228
+ results.append(cursor.fetchall())
229
+ else:
230
+ cursor.execute(query)
231
+ results = cursor.fetchall()
232
+ connection.commit()
233
+ return results
234
+ except pymysql.MySQLError as e:
235
+ return str(e)
236
+ finally:
237
+ connection.close()
238
+
239
+ def clean_sql(sql) -> str:
240
+ print("Clean sql1:", sql)
241
+ if isinstance(sql, dict) and sql:
242
+ first_value = next(iter(sql.values()))
243
+ sql = first_value
244
+ sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE)
245
+ sql = sql.replace("```sql", "")
246
+ sql = re.sub(r"```", "", sql)
247
+ return sql.strip()
248
+ # sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE)
249
+ # sql = sql.replace("```sql", "")
250
+ # # sql = re.sub(r'%%s', r'%s', sql)
251
+ # sql = re.sub(r"```", "", sql)
252
+ # return sql.strip()
253
+
254
+ def extract_sql_from_response(data):
255
+ match = re.search(r"\[SQL:\s*```sql\s*(.*?)\s*```]", data, re.DOTALL)
256
+ if match:
257
+ return clean_sql(match.group(1))
258
+ match = re.search(r"SQLQuery:\s*(.*)", data, re.DOTALL)
259
+ if match:
260
+ return clean_sql(match.group(1))
261
+
262
+ return None
263
+
264
+ def extract_sql_from_error(error_msg):
265
+ # Case 1: [SQL: ```sql ... ```]
266
+ match = re.search(r"\[SQL:\s*```sql\s*(.*?)\s*```]", error_msg, re.DOTALL)
267
+ if match:
268
+ return clean_sql(match.group(1))
269
+
270
+
271
+ match = re.search(r"```(?:sql)?\s*\r?\n(.*?)```", error_msg, re.DOTALL)
272
+ if match:
273
+ return clean_sql(match.group(1))
274
+
275
+ return None
276
+
277
+ def process_and_execute_sql(sql):
278
+ print("NHận sql: ",sql )
279
+ data = sql
280
+ if isinstance(data, dict) and data:
281
+ print("Là dict")
282
+ first_key, first_value = next(iter(data.items()))
283
+ sql = first_value
284
+ elif isinstance(data, list) and data:
285
+ sql = "\n\n".join(data)
286
+
287
+ sql_clean = clean_sql(sql)
288
+ print("SQL Clean: ", sql_clean)
289
+ if re.search(r"%{1,2}s", sql_clean):
290
+ regenerated_sql = regenerate_sql_until_safe()
291
+ sql_clean = clean_sql(regenerated_sql)
292
+ result = get_schemas_from_sql(sql_clean, schema_mapping)
293
+ prompt = build_sql_fix_prompt(schemas_result=result,sql = str(sql_clean),user_id =user_id)
294
+ from advance_shopping.call_gemini import tool_call
295
+ data = tool_call.generate(prompt = prompt)
296
+ sql_clean = clean_sql(data)
297
+ print("SQL step2: ", sql_clean)
298
+ if contains_delete(sql_clean):
299
+ return "Lỗi: Bạn không dược phép thực hiện truy vấn DELETE trong hệ thống này."
300
+
301
+ if re.search(r"\bIF\b.*\bTHEN\b", sql_clean, re.IGNORECASE):
302
+ return "❌ Lỗi: Không được dùng IF...THEN trong SQL. Vui lòng chia nhỏ truy vấn."
303
+
304
+ if check_insert:
305
+ check_syntax = filter_syntax_sql(sql_clean, PROMPT_CUSTOM, user_input)
306
+ if check_syntax is True:
307
+ # Tách từng câu và thực thi tuần tự
308
+ try:
309
+ connection = pymysql.connect(**db_config)
310
+ with connection.cursor() as cursor:
311
+ statements = sqlparse.split(sql_clean)
312
+ results = []
313
+ for stmt in statements:
314
+ stmt = stmt.strip()
315
+ if stmt:
316
+ cursor.execute(stmt)
317
+ try:
318
+ results.append(cursor.fetchall())
319
+ except:
320
+ results.append("✅ OK") # Có thể là câu SET hoặc INSERT
321
+ connection.commit()
322
+ return results
323
+ except Exception as e:
324
+ return f"❌ Lỗi khi thực thi từng truy vấn: {str(e)}"
325
+ finally:
326
+ connection.close()
327
+ else:
328
+ try:
329
+ regenerated_data = db_chain.run(f"""
330
+ Role: {text_role}
331
+ Language: {languages}
332
+ Question: {user_input}.
333
+ """)
334
+ regenerated_sql = extract_sql_from_response(regenerated_data)
335
+ if regenerated_sql:
336
+ return process_and_execute_sql(regenerated_sql)
337
+ else:
338
+ return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ."
339
+ except Exception as regen_error:
340
+ return f"❌ Lỗi khi tạo lại truy vấn: {str(regen_error)}"
341
+ else:
342
+ return execute_query_with_pymysql(sql_clean, multi=True)
343
+
344
+
345
+
346
+ PROMPT_CUSTOM.template += (
347
+ "\n\n⚠️ Lưu ý: Do NOT use IF...THEN...ELSE...END in SQL. "
348
+ "Only use plain SELECT, INSERT, UPDATE, JOIN, SET statements. "
349
+ "Tuyệt đối cấm dùng %%s hoặc %s để lấy giá trị từ biến. "
350
+ "SQL phải chứa giá trị cố định, không dùng placeholder hay biến bind."
351
+ )
352
+
353
+ db_chain = SQLDatabaseChain.from_llm(llm=llm1, db=db, prompt=PROMPT_CUSTOM)
354
+ text_role = f"{role} (userId = {user_id})" if role == "ADMIN" else f"{role} (userId = {user_id}), not role ADMIN"
355
+
356
+ try:
357
+ data = db_chain.run(f"""
358
+ Role: {text_role}
359
+ Language: {languages}
360
+ Question: {user_input}.
361
+ """)
362
+ extracted_sql = extract_sql_from_response(data)
363
+ if extracted_sql:
364
+ return process_and_execute_sql(extracted_sql)
365
+ else:
366
+ return data
367
+
368
+ except Exception as e:
369
+ error_message = str(e)
370
+ extracted_sql = extract_sql_from_error(error_message)
371
+
372
+ if extracted_sql:
373
+ fix_sql = re.sub(r"```sql", "", extracted_sql)
374
+ fix_sql = extracted_sql.replace("```", "")
375
+ fix_sql = extracted_sql.replace("```sql", "")
376
+ # fix_sql = re.sub(r'%%s', r'%s', fix_sql)
377
+ if contains_delete(fix_sql):
378
+ return "Lỗi: Bạn không dược phép thực hiện truy vấn DELETE trong hệ thống này."
379
+ return process_and_execute_sql(fix_sql)
380
+ else:
381
+ # Không trích xuất được SQL -> Thử tạo lại truy vấn từ đầu
382
+ try:
383
+ regenerated_data = db_chain.run(f"""
384
+ Role: {text_role}
385
+ Language: {languages}
386
+ Question: {user_input}.
387
+ """)
388
+ regenerated_sql = extract_sql_from_response(regenerated_data)
389
+ if regenerated_sql:
390
+ return process_and_execute_sql(regenerated_sql)
391
+ else:
392
+ return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ sau lỗi."
393
+ except Exception as regen_error:
394
+ return f"❌ Lỗi khi tạo lại truy vấn từ lỗi: {str(regen_error)}"
function/analyze/gemini/__init__.py ADDED
File without changes