Spaces:
Sleeping
Sleeping
Upload 239 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .env +12 -0
- api_key.txt +2 -0
- ca.pem +26 -0
- controller/CallbackController.py +212 -0
- controller/ChatController.py +224 -0
- controller/GroupOrderController.py +25 -0
- controller/RecommendController.py +25 -0
- controller/RedirectController.py +96 -0
- controller/RefundController.py +150 -0
- controller/__init__.py +0 -0
- controller/__pycache__/CallbackController.cpython-311.pyc +0 -0
- controller/__pycache__/ChatController.cpython-311.pyc +0 -0
- controller/__pycache__/GroupOrderController.cpython-311.pyc +0 -0
- controller/__pycache__/RecommendController.cpython-311.pyc +0 -0
- controller/__pycache__/RedirectController.cpython-311.pyc +0 -0
- controller/__pycache__/RefundController.cpython-311.pyc +0 -0
- controller/__pycache__/__init__.cpython-311.pyc +0 -0
- controller/__pycache__/task_manager.cpython-311.pyc +0 -0
- function/__init__.py +1 -0
- function/__pycache__/__init__.cpython-311.pyc +0 -0
- function/__pycache__/__init__.cpython-312.pyc +0 -0
- function/__pycache__/chat.cpython-311.pyc +0 -0
- function/__pycache__/chat.cpython-312.pyc +0 -0
- function/advance_shopping/call_gemini/__pycache__/tool_call.cpython-311.pyc +0 -0
- function/advance_shopping/call_gemini/tool_call.py +44 -0
- function/advance_shopping/function/__pycache__/test.cpython-311.pyc +0 -0
- function/advance_shopping/function/test.py +615 -0
- function/advance_shopping/prompt/__pycache__/context_prompt.cpython-311.pyc +0 -0
- function/advance_shopping/prompt/__pycache__/multitool_prompt.cpython-311.pyc +0 -0
- function/advance_shopping/prompt/__pycache__/user_intent.cpython-311.pyc +0 -0
- function/advance_shopping/prompt/context_prompt.py +38 -0
- function/advance_shopping/prompt/multitool_prompt.py +83 -0
- function/advance_shopping/prompt/user_intent.py +127 -0
- function/advance_shopping/server_java/__pycache__/server_java.cpython-311.pyc +0 -0
- function/advance_shopping/server_java/config.yaml +53 -0
- function/advance_shopping/server_java/server_java.py +338 -0
- function/agent/__init__.py +0 -0
- function/agent/function_call.py +190 -0
- function/agent/pipeline_agent.py +136 -0
- function/analyze/__init__.py +0 -0
- function/analyze/__pycache__/__init__.cpython-311.pyc +0 -0
- function/analyze/__pycache__/execute_query.cpython-311.pyc +0 -0
- function/analyze/__pycache__/main.cpython-311.pyc +0 -0
- function/analyze/__pycache__/merge_output.cpython-311.pyc +0 -0
- function/analyze/__pycache__/pipeline.cpython-311.pyc +0 -0
- function/analyze/__pycache__/prompt_databse.cpython-311.pyc +0 -0
- function/analyze/__pycache__/prompt_query.cpython-311.pyc +0 -0
- function/analyze/__pycache__/return_code_python.cpython-311.pyc +0 -0
- function/analyze/execute_query.py +394 -0
- 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
|