Commit 6d94f832 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

refactor: optimize server setup (dev/prod mode), fix hybrid search columns, debug query logging

parent 1b9277bb
......@@ -2,3 +2,4 @@
# Ignore embedded repo
preference/
query.txt
# Python 3.11 slim (nhẹ, ít file)
FROM python:3.11-slim
# Thư mục làm việc
WORKDIR /app
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
# Copy requirements rồi cài package
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy code
COPY . .
# Mở port (sẽ được override bởi docker-compose)
EXPOSE 5004
# Chạy uvicorn với auto workers (2 * CPU cores + 1)
CMD ["sh", "-c", "uvicorn server:app --host 0.0.0.0 --port ${PORT:-5004} --workers $(( $(nproc) * 2 + 1 ))"]
\ No newline at end of file
CMD ["python", "server.py"]
\ No newline at end of file
......@@ -37,6 +37,7 @@ __all__ = [
"MONGODB_URI",
"OPENAI_API_KEY",
"PORT",
"ENV_MODE",
"REDIS_HOST",
"REDIS_PASSWORD",
"REDIS_PORT",
......@@ -74,6 +75,7 @@ JWT_ALGORITHM: str | None = os.getenv("JWT_ALGORITHM")
# ====================== SERVER CONFIG ======================
PORT: int = int(os.getenv("PORT", "5004"))
ENV_MODE: str = os.getenv("ENV_MODE", "dev").lower() # dev or prod
FIRECRAWL_API_KEY: str | None = os.getenv("FIRECRAWL_API_KEY")
# ====================== LANGFUSE CONFIGURATION (DEPRECATED) ======================
......
......@@ -127,7 +127,6 @@ async def hybrid_search(
/*+ SET_VAR(ann_params='{{"ef_search":{ef_search}}}') */
internal_ref_code,
product_name,
description_text,
product_image_url,
product_web_url,
sale_price,
......@@ -147,18 +146,12 @@ async def hybrid_search(
SELECT
internal_ref_code,
MAX_BY(product_name, similarity_score) as product_name,
MAX_BY(description_text, similarity_score) as description_text,
MAX_BY(product_image_url, similarity_score) as product_image_url,
MAX_BY(product_web_url, similarity_score) as product_web_url,
MAX_BY(sale_price, similarity_score) as sale_price,
MAX_BY(original_price, similarity_score) as original_price,
MAX_BY(discount_amount, similarity_score) as discount_amount,
GROUP_CONCAT(DISTINCT master_color ORDER BY master_color SEPARATOR ', ') as available_colors,
MAX_BY(season, similarity_score) as season,
MAX_BY(gender_by_product, similarity_score) as gender_by_product,
MAX_BY(age_by_product, similarity_score) as age_by_product,
MAX_BY(product_line_vn, similarity_score) as product_line_vn,
MAX_BY(style, similarity_score) as style,
MAX(similarity_score) as similarity_score
FROM semantic_candidates
{where_clause}
......@@ -169,6 +162,20 @@ async def hybrid_search(
logger.info(f"SQL Query:\n{sql}")
# Ghi query ra file query.txt ở thư mục backend (parent của thư mục search)
try:
import os
# backend/search/hybrid_search.py -> backend/query.txt
current_dir = os.path.dirname(os.path.abspath(__file__))
backend_dir = os.path.dirname(current_dir)
query_file_path = os.path.join(backend_dir, "query.txt")
with open(query_file_path, "w", encoding="utf-8") as f:
f.write(sql)
logger.info(f"Query saved to: {query_file_path}")
except Exception as e:
logger.error(f"Failed to write query to file: {e}")
# ═══════════════════════════════════════════════════════════════════
# STEP 6: Execute query
# ═══════════════════════════════════════════════════════════════════
......@@ -230,7 +237,6 @@ async def semantic_only_search(
season,
gender_by_product,
age_by_product,
product_line_vn,
approx_cosine_similarity(vector, {v_str}) as similarity_score
FROM shared_source.magento_product_dimension_with_text_embedding
ORDER BY similarity_score DESC
......
"""
FastAPI main application - Contract AI Service
Architecture:
- REST API Routes: FastAPI routers for HTTP endpoints
- SSE (Server-Sent Events): Real-time streaming for AI Contract and AI Legal
Modules:
- ai_contract: Contract generation and composition (SSE streaming)
- ai_legal: Legal Q&A and search (SSE streaming)
- conversation: Conversation history management
FastAPI Server - Canifa Product Recommendation API
"""
import os
import logging
# Configure Logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse
from config import (
PORT,
ENV_MODE,
LANGSMITH_API_KEY,
LANGSMITH_ENDPOINT,
LANGSMITH_PROJECT,
LANGSMITH_TRACING,
)
from api.recommend_text import router as recommend_text_router
# Logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
......@@ -22,35 +28,20 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
from config import LANGSMITH_API_KEY, LANGSMITH_ENDPOINT, LANGSMITH_PROJECT, LANGSMITH_TRACING
# Ensure LangSmith Env Vars are set for the process
# LangSmith environment
os.environ["LANGSMITH_TRACING"] = LANGSMITH_TRACING
os.environ["LANGSMITH_ENDPOINT"] = LANGSMITH_ENDPOINT
os.environ["LANGSMITH_API_KEY"] = LANGSMITH_API_KEY
os.environ["LANGSMITH_PROJECT"] = LANGSMITH_PROJECT
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles # Import StaticFiles
# from api.recommend_image import router as recommend_image_router
from api.recommend_text import router as recommend_text_router
from common.middleware import ClerkAuthMiddleware
from config import PORT
# FastAPI app
app = FastAPI(
title="Contract AI Service",
description="API for Contract AI Service",
title="Canifa Product Recommendation API",
description="API for product recommendation using hybrid search",
version="1.0.0",
)
print("✅ Clerk Authentication middleware DISABLED (for testing)")
# Add CORS middleware
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
......@@ -59,51 +50,42 @@ app.add_middleware(
allow_headers=["*"],
)
# app.include_router(recommend_image_router)
# Routes
app.include_router(recommend_text_router)
try:
static_dir = os.path.join(os.path.dirname(__file__), "static")
if not os.path.exists(static_dir):
# Static files
static_dir = os.path.join(os.path.dirname(__file__), "static")
if not os.path.exists(static_dir):
os.makedirs(static_dir)
app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")
print(f"✅ Static files mounted at /static (Dir: {static_dir})")
except Exception as e:
print(f"⚠️ Failed to mount static files: {e}")
app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")
from fastapi.responses import RedirectResponse
@app.get("/")
async def root():
return RedirectResponse(url="/static/index.html")
if __name__ == "__main__":
print("=" * 60)
print("🚀 Contract AI Service Starting...")
print("=" * 60)
print(f"📡 REST API: http://localhost:{PORT}")
print(f"📡 SSE Streaming: http://localhost:{PORT}/api/ai-contract/chat")
print(f"📡 Test Chatbot: http://localhost:{PORT}/static/index.html")
print(f"📚 API Docs: http://localhost:{PORT}/docs")
print("=" * 60)
# ENABLE_RELOAD = os.getenv("ENABLE_RELOAD", "false").lower() in ("true", "1", "yes")
ENABLE_RELOAD = True
print("⚠️ Hot reload: FORCED ON (Dev Mode)")
reload_dirs = [
"ai_contract",
"conversation",
"common",
"api", # Watch api folder
"agent" # Watch agent folder
]
import uvicorn
is_dev = ENV_MODE != "prod"
logger.info(f"Starting server on port {PORT} (ENV_MODE={ENV_MODE})")
if is_dev:
uvicorn.run(
"server:app",
host="0.0.0.0",
port=PORT,
reload=True,
reload_dirs=["."],
log_level="info",
)
else:
uvicorn.run(
"server:app",
host="0.0.0.0",
port=PORT,
reload=ENABLE_RELOAD,
reload_dirs=reload_dirs,
workers=os.cpu_count() * 2 + 1,
log_level="info",
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment