fix retry error
Browse files- app/embedding.py +3 -2
- app/facebook.py +4 -4
- app/main.py +2 -2
- app/sheets.py +6 -6
- app/supabase_db.py +4 -4
- app/utils.py +18 -4
app/embedding.py
CHANGED
|
@@ -4,7 +4,7 @@ from loguru import logger
|
|
| 4 |
import httpx
|
| 5 |
from tenacity import retry, stop_after_attempt, wait_exponential
|
| 6 |
|
| 7 |
-
from .utils import
|
| 8 |
|
| 9 |
class EmbeddingClient:
|
| 10 |
def __init__(self):
|
|
@@ -15,7 +15,7 @@ class EmbeddingClient:
|
|
| 15 |
"""
|
| 16 |
self._client = httpx.AsyncClient()
|
| 17 |
|
| 18 |
-
@
|
| 19 |
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10), reraise=True)
|
| 20 |
async def create_embedding(self, text: str) -> List[float]:
|
| 21 |
"""
|
|
@@ -45,6 +45,7 @@ class EmbeddingClient:
|
|
| 45 |
logger.error(f"Error creating embedding: {e}")
|
| 46 |
raise
|
| 47 |
|
|
|
|
| 48 |
def cosine_similarity(self, embedding1: List[float], embedding2: List[float]) -> float:
|
| 49 |
"""
|
| 50 |
Tính cosine similarity giữa hai embedding.
|
|
|
|
| 4 |
import httpx
|
| 5 |
from tenacity import retry, stop_after_attempt, wait_exponential
|
| 6 |
|
| 7 |
+
from .utils import timing_decorator_async, timing_decorator_sync
|
| 8 |
|
| 9 |
class EmbeddingClient:
|
| 10 |
def __init__(self):
|
|
|
|
| 15 |
"""
|
| 16 |
self._client = httpx.AsyncClient()
|
| 17 |
|
| 18 |
+
@timing_decorator_async
|
| 19 |
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10), reraise=True)
|
| 20 |
async def create_embedding(self, text: str) -> List[float]:
|
| 21 |
"""
|
|
|
|
| 45 |
logger.error(f"Error creating embedding: {e}")
|
| 46 |
raise
|
| 47 |
|
| 48 |
+
@timing_decorator_sync
|
| 49 |
def cosine_similarity(self, embedding1: List[float], embedding2: List[float]) -> float:
|
| 50 |
"""
|
| 51 |
Tính cosine similarity giữa hai embedding.
|
app/facebook.py
CHANGED
|
@@ -6,7 +6,7 @@ import httpx
|
|
| 6 |
from fastapi import HTTPException, Request
|
| 7 |
from loguru import logger
|
| 8 |
|
| 9 |
-
from .utils import
|
| 10 |
|
| 11 |
class FacebookClient:
|
| 12 |
def __init__(self, app_secret: str):
|
|
@@ -18,7 +18,7 @@ class FacebookClient:
|
|
| 18 |
self.app_secret = app_secret
|
| 19 |
self._client = httpx.AsyncClient()
|
| 20 |
|
| 21 |
-
@
|
| 22 |
async def verify_webhook(self, token: str, challenge: str, verify_token: str) -> int:
|
| 23 |
"""
|
| 24 |
Xác thực webhook Facebook bằng verify_token và trả về challenge.
|
|
@@ -47,7 +47,7 @@ class FacebookClient:
|
|
| 47 |
|
| 48 |
return hmac.compare_digest(signature[7:], expected)
|
| 49 |
|
| 50 |
-
@
|
| 51 |
async def send_message(self, page_access_token: str, recipient_id: str, message: str) -> Dict[str, Any]:
|
| 52 |
"""
|
| 53 |
Gửi message tới user qua Facebook Messenger API.
|
|
@@ -70,7 +70,7 @@ class FacebookClient:
|
|
| 70 |
logger.error(f"Error sending message to Facebook: {e}")
|
| 71 |
raise HTTPException(status_code=500, detail="Failed to send message to Facebook")
|
| 72 |
|
| 73 |
-
@
|
| 74 |
def parse_message(self, body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
| 75 |
"""
|
| 76 |
Parse message từ payload Facebook webhook.
|
|
|
|
| 6 |
from fastapi import HTTPException, Request
|
| 7 |
from loguru import logger
|
| 8 |
|
| 9 |
+
from .utils import timing_decorator_async, timing_decorator_sync
|
| 10 |
|
| 11 |
class FacebookClient:
|
| 12 |
def __init__(self, app_secret: str):
|
|
|
|
| 18 |
self.app_secret = app_secret
|
| 19 |
self._client = httpx.AsyncClient()
|
| 20 |
|
| 21 |
+
@timing_decorator_async
|
| 22 |
async def verify_webhook(self, token: str, challenge: str, verify_token: str) -> int:
|
| 23 |
"""
|
| 24 |
Xác thực webhook Facebook bằng verify_token và trả về challenge.
|
|
|
|
| 47 |
|
| 48 |
return hmac.compare_digest(signature[7:], expected)
|
| 49 |
|
| 50 |
+
@timing_decorator_async
|
| 51 |
async def send_message(self, page_access_token: str, recipient_id: str, message: str) -> Dict[str, Any]:
|
| 52 |
"""
|
| 53 |
Gửi message tới user qua Facebook Messenger API.
|
|
|
|
| 70 |
logger.error(f"Error sending message to Facebook: {e}")
|
| 71 |
raise HTTPException(status_code=500, detail="Failed to send message to Facebook")
|
| 72 |
|
| 73 |
+
@timing_decorator_sync
|
| 74 |
def parse_message(self, body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
| 75 |
"""
|
| 76 |
Parse message từ payload Facebook webhook.
|
app/main.py
CHANGED
|
@@ -12,7 +12,7 @@ from .facebook import FacebookClient
|
|
| 12 |
from .sheets import SheetsClient
|
| 13 |
from .supabase_db import SupabaseClient
|
| 14 |
from .embedding import EmbeddingClient
|
| 15 |
-
from .utils import setup_logging, extract_command, extract_keywords,
|
| 16 |
from .constants import VEHICLE_KEYWORDS, SHEET_RANGE
|
| 17 |
from .health import router as health_router
|
| 18 |
|
|
@@ -88,7 +88,7 @@ async def verify_webhook(request: Request):
|
|
| 88 |
)
|
| 89 |
|
| 90 |
@app.post("/webhook")
|
| 91 |
-
@
|
| 92 |
async def webhook(request: Request):
|
| 93 |
"""
|
| 94 |
Nhận và xử lý message từ Facebook Messenger webhook.
|
|
|
|
| 12 |
from .sheets import SheetsClient
|
| 13 |
from .supabase_db import SupabaseClient
|
| 14 |
from .embedding import EmbeddingClient
|
| 15 |
+
from .utils import setup_logging, extract_command, extract_keywords, timing_decorator_async, timing_decorator_sync, ensure_log_dir, validate_config
|
| 16 |
from .constants import VEHICLE_KEYWORDS, SHEET_RANGE
|
| 17 |
from .health import router as health_router
|
| 18 |
|
|
|
|
| 88 |
)
|
| 89 |
|
| 90 |
@app.post("/webhook")
|
| 91 |
+
@timing_decorator_async
|
| 92 |
async def webhook(request: Request):
|
| 93 |
"""
|
| 94 |
Nhận và xử lý message từ Facebook Messenger webhook.
|
app/sheets.py
CHANGED
|
@@ -8,7 +8,7 @@ import pickle
|
|
| 8 |
from datetime import datetime
|
| 9 |
from loguru import logger
|
| 10 |
|
| 11 |
-
from .utils import
|
| 12 |
from .constants import SHEET_RANGE
|
| 13 |
|
| 14 |
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
|
|
@@ -26,7 +26,7 @@ class SheetsClient:
|
|
| 26 |
self.creds = None
|
| 27 |
self.service = None
|
| 28 |
|
| 29 |
-
@
|
| 30 |
def authenticate(self) -> None:
|
| 31 |
"""
|
| 32 |
Xác thực với Google Sheets API, tạo self.service.
|
|
@@ -50,8 +50,8 @@ class SheetsClient:
|
|
| 50 |
|
| 51 |
self.service = build('sheets', 'v4', credentials=self.creds)
|
| 52 |
|
| 53 |
-
@
|
| 54 |
-
|
| 55 |
"""
|
| 56 |
Lấy lịch sử hội thoại của user từ Google Sheets.
|
| 57 |
Input: user_id (str), page_id (str)
|
|
@@ -87,8 +87,8 @@ class SheetsClient:
|
|
| 87 |
logger.error(f"Error getting conversation history: {e}")
|
| 88 |
return []
|
| 89 |
|
| 90 |
-
@
|
| 91 |
-
|
| 92 |
self,
|
| 93 |
user_id: str,
|
| 94 |
page_id: str,
|
|
|
|
| 8 |
from datetime import datetime
|
| 9 |
from loguru import logger
|
| 10 |
|
| 11 |
+
from .utils import timing_decorator_sync
|
| 12 |
from .constants import SHEET_RANGE
|
| 13 |
|
| 14 |
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
|
|
|
|
| 26 |
self.creds = None
|
| 27 |
self.service = None
|
| 28 |
|
| 29 |
+
@timing_decorator_sync
|
| 30 |
def authenticate(self) -> None:
|
| 31 |
"""
|
| 32 |
Xác thực với Google Sheets API, tạo self.service.
|
|
|
|
| 50 |
|
| 51 |
self.service = build('sheets', 'v4', credentials=self.creds)
|
| 52 |
|
| 53 |
+
@timing_decorator_sync
|
| 54 |
+
def get_conversation_history(self, user_id: str, page_id: str) -> List[Dict[str, Any]]:
|
| 55 |
"""
|
| 56 |
Lấy lịch sử hội thoại của user từ Google Sheets.
|
| 57 |
Input: user_id (str), page_id (str)
|
|
|
|
| 87 |
logger.error(f"Error getting conversation history: {e}")
|
| 88 |
return []
|
| 89 |
|
| 90 |
+
@timing_decorator_sync
|
| 91 |
+
def log_conversation(
|
| 92 |
self,
|
| 93 |
user_id: str,
|
| 94 |
page_id: str,
|
app/supabase_db.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Any, Dict, List, Optional
|
|
| 2 |
from supabase.client import create_client, Client
|
| 3 |
from loguru import logger
|
| 4 |
|
| 5 |
-
from .utils import
|
| 6 |
|
| 7 |
class SupabaseClient:
|
| 8 |
def __init__(self, url: str, key: str):
|
|
@@ -13,7 +13,7 @@ class SupabaseClient:
|
|
| 13 |
"""
|
| 14 |
self.client: Client = create_client(url, key)
|
| 15 |
|
| 16 |
-
@
|
| 17 |
def get_page_token(self, page_id: str):
|
| 18 |
"""
|
| 19 |
Lấy access token của Facebook page từ Supabase.
|
|
@@ -29,7 +29,7 @@ class SupabaseClient:
|
|
| 29 |
logger.error(f"Error getting page token: {e}")
|
| 30 |
return None
|
| 31 |
|
| 32 |
-
@
|
| 33 |
def match_documents(self, embedding: List[float], match_count: int = 5):
|
| 34 |
"""
|
| 35 |
Truy vấn vector similarity search qua RPC match_documents.
|
|
@@ -53,7 +53,7 @@ class SupabaseClient:
|
|
| 53 |
logger.error(f"Error matching documents: {e}")
|
| 54 |
return []
|
| 55 |
|
| 56 |
-
@
|
| 57 |
def store_embedding(self, text: str, embedding: List[float], metadata: Dict[str, Any]):
|
| 58 |
"""
|
| 59 |
Lưu embedding vào Supabase.
|
|
|
|
| 2 |
from supabase.client import create_client, Client
|
| 3 |
from loguru import logger
|
| 4 |
|
| 5 |
+
from .utils import timing_decorator_sync
|
| 6 |
|
| 7 |
class SupabaseClient:
|
| 8 |
def __init__(self, url: str, key: str):
|
|
|
|
| 13 |
"""
|
| 14 |
self.client: Client = create_client(url, key)
|
| 15 |
|
| 16 |
+
@timing_decorator_sync
|
| 17 |
def get_page_token(self, page_id: str):
|
| 18 |
"""
|
| 19 |
Lấy access token của Facebook page từ Supabase.
|
|
|
|
| 29 |
logger.error(f"Error getting page token: {e}")
|
| 30 |
return None
|
| 31 |
|
| 32 |
+
@timing_decorator_sync
|
| 33 |
def match_documents(self, embedding: List[float], match_count: int = 5):
|
| 34 |
"""
|
| 35 |
Truy vấn vector similarity search qua RPC match_documents.
|
|
|
|
| 53 |
logger.error(f"Error matching documents: {e}")
|
| 54 |
return []
|
| 55 |
|
| 56 |
+
@timing_decorator_sync
|
| 57 |
def store_embedding(self, text: str, embedding: List[float], metadata: Dict[str, Any]):
|
| 58 |
"""
|
| 59 |
Lưu embedding vào Supabase.
|
app/utils.py
CHANGED
|
@@ -4,11 +4,10 @@ from loguru import logger
|
|
| 4 |
from typing import Any, Callable
|
| 5 |
import os
|
| 6 |
|
| 7 |
-
def
|
| 8 |
"""
|
| 9 |
Decorator đo thời gian thực thi của hàm async, log thời lượng xử lý.
|
| 10 |
-
|
| 11 |
-
Output: Kết quả trả về của func.
|
| 12 |
"""
|
| 13 |
@wraps(func)
|
| 14 |
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
@@ -16,7 +15,22 @@ def timing_decorator(func: Callable) -> Callable:
|
|
| 16 |
result = await func(*args, **kwargs)
|
| 17 |
end_time = time.time()
|
| 18 |
duration = end_time - start_time
|
| 19 |
-
logger.info(f"{func.__name__} took {duration:.2f} seconds to execute")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
return result
|
| 21 |
return wrapper
|
| 22 |
|
|
|
|
| 4 |
from typing import Any, Callable
|
| 5 |
import os
|
| 6 |
|
| 7 |
+
def timing_decorator_async(func: Callable) -> Callable:
|
| 8 |
"""
|
| 9 |
Decorator đo thời gian thực thi của hàm async, log thời lượng xử lý.
|
| 10 |
+
Dùng cho async def.
|
|
|
|
| 11 |
"""
|
| 12 |
@wraps(func)
|
| 13 |
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
|
|
| 15 |
result = await func(*args, **kwargs)
|
| 16 |
end_time = time.time()
|
| 17 |
duration = end_time - start_time
|
| 18 |
+
logger.info(f"[TIMING][async] {func.__name__} took {duration:.2f} seconds to execute")
|
| 19 |
+
return result
|
| 20 |
+
return wrapper
|
| 21 |
+
|
| 22 |
+
def timing_decorator_sync(func: Callable) -> Callable:
|
| 23 |
+
"""
|
| 24 |
+
Decorator đo thời gian thực thi của hàm sync, log thời lượng xử lý.
|
| 25 |
+
Dùng cho def thường.
|
| 26 |
+
"""
|
| 27 |
+
@wraps(func)
|
| 28 |
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
| 29 |
+
start_time = time.time()
|
| 30 |
+
result = func(*args, **kwargs)
|
| 31 |
+
end_time = time.time()
|
| 32 |
+
duration = end_time - start_time
|
| 33 |
+
logger.info(f"[TIMING][sync] {func.__name__} took {duration:.2f} seconds to execute")
|
| 34 |
return result
|
| 35 |
return wrapper
|
| 36 |
|