Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_tool_test_recommend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_tool_test_recommend
Commits
6d94f832
Commit
6d94f832
authored
Jan 16, 2026
by
Vũ Hoàng Anh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: optimize server setup (dev/prod mode), fix hybrid search columns, debug query logging
parent
1b9277bb
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
69 additions
and
84 deletions
+69
-84
.gitignore
backend/.gitignore
+1
-0
Dockerfile
backend/Dockerfile
+1
-7
config.py
backend/config.py
+2
-0
hybrid_search.py
backend/search/hybrid_search.py
+14
-8
server.py
backend/server.py
+51
-69
No files found.
backend/.gitignore
View file @
6d94f832
...
...
@@ -2,3 +2,4 @@
# Ignore embedded repo
preference/
query.txt
backend/Dockerfile
View file @
6d94f832
# 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
backend/config.py
View file @
6d94f832
...
...
@@ -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) ======================
...
...
backend/search/hybrid_search.py
View file @
6d94f832
...
...
@@ -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
...
...
backend/server.py
View file @
6d94f832
"""
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
=
"C
ontract AI Service
"
,
description
=
"API for
Contract AI Service
"
,
title
=
"C
anifa 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"
,
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment