Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
chatbot canifa
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
1
Merge Requests
1
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
chatbot canifa
Commits
b4329aaa
Commit
b4329aaa
authored
Jan 21, 2026
by
Vũ Hoàng Anh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update server config, docker-compose and add test scripts
parent
79dfe7d5
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
187 additions
and
19 deletions
+187
-19
system_prompt.txt
backend/agent/system_prompt.txt
+5
-14
prompt_route.py
backend/api/prompt_route.py
+42
-2
middleware.py
backend/common/middleware.py
+21
-2
docker-compose.yml
backend/docker-compose.yml
+11
-1
manual_test_chat.py
backend/manual_test_chat.py
+38
-0
test_auth_verify.py
backend/test_auth_verify.py
+70
-0
No files found.
backend/agent/system_prompt.txt
View file @
b4329aaa
...
...
@@ -7,22 +7,13 @@ Bạn là CiCi - Chuyên viên tư vấn thời trang CANIFA.
---
# QUY TẮC TRUNG THỰC - BẮT BUỘC
KHÔNG BAO GIỜ BỊA ĐẶT - CHỈ NÓI THEO DỮ LIỆU
ĐÚNG:
**ĐÚNG:**
- Tool trả về áo thun → Giới thiệu áo thun
- Tool trả về 0 sản phẩm → Nói "Shop chưa có sản phẩm này"
- Tool trả về quần nỉ mà khách hỏi bikini → Nói "Shop chưa có bikini"
**CẤM:**
- Tool trả về quần nỉ → Gọi là "đồ bơi"
- Tool trả về 0 kết quả → Nói "shop có sản phẩm X"
- Tự bịa mã sản phẩm, giá tiền, chính sách
Không có trong data = Không nói = Không tư vấn láo
Tool trả về áo thun → Giới thiệu áo thun
Tool trả về 0 sản phẩm → Nói "Shop chưa có sản phẩm này"
Tool trả về quần nỉ mà khách hỏi bikini → Nói "Shop chưa có bikini"
Khách hỏi giá online vs offline mà không có data → "Mình không rõ chi tiết so sánh giá, bạn có thể xem trực tiếp trên web hoặc liên hệ hotline nhé"
---
# NGÔN NGỮ & XƯNG HÔ
...
...
backend/api/prompt_route.py
View file @
b4329aaa
from
fastapi
import
APIRouter
,
HTTPException
from
pydantic
import
BaseModel
import
os
import
re
from
agent.graph
import
reset_graph
router
=
APIRouter
()
PROMPT_FILE_PATH
=
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
"../agent/system_prompt.txt"
)
# Allowed variables in prompt (single braces OK for these)
ALLOWED_VARIABLES
=
{
"date_str"
}
class
PromptUpdateRequest
(
BaseModel
):
content
:
str
def
validate_prompt_braces
(
content
:
str
)
->
tuple
[
bool
,
list
[
str
]]:
"""
Validate that all braces in prompt are properly escaped.
Returns (is_valid, list of problematic patterns)
"""
# Find all {word} patterns
single_brace_pattern
=
re
.
findall
(
r'\{([^{}]+)\}'
,
content
)
# Filter out allowed variables
problematic
=
[
var
for
var
in
single_brace_pattern
if
var
.
strip
()
not
in
ALLOWED_VARIABLES
and
not
var
.
startswith
(
'{'
)
]
return
len
(
problematic
)
==
0
,
problematic
@
router
.
get
(
"/api/agent/system-prompt"
)
async
def
get_system_prompt_content
():
"""Get current system prompt content"""
# ... existing code ...
try
:
if
os
.
path
.
exists
(
PROMPT_FILE_PATH
):
with
open
(
PROMPT_FILE_PATH
,
"r"
,
encoding
=
"utf-8"
)
as
f
:
...
...
@@ -28,6 +47,19 @@ async def get_system_prompt_content():
async
def
update_system_prompt_content
(
request
:
PromptUpdateRequest
):
"""Update system prompt content"""
try
:
# Validate braces
is_valid
,
problematic
=
validate_prompt_braces
(
request
.
content
)
if
not
is_valid
:
# Return warning but still allow save
warning
=
(
f
"⚠️ Phát hiện {{...}} chưa escape: {problematic[:3]}... "
f
"Nếu đây là JSON, hãy dùng {{{{ }}}} thay vì {{ }}. "
f
"Prompt vẫn được lưu nhưng có thể gây lỗi khi chat."
)
else
:
warning
=
None
# 1. Update file
with
open
(
PROMPT_FILE_PATH
,
"w"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
request
.
content
)
...
...
@@ -35,6 +67,14 @@ async def update_system_prompt_content(request: PromptUpdateRequest):
# 2. Reset Graph Singleton to force reload prompt
reset_graph
()
return
{
"status"
:
"success"
,
"message"
:
"System prompt updated successfully. Graph reloaded."
}
response
=
{
"status"
:
"success"
,
"message"
:
"System prompt updated successfully. Graph reloaded."
}
if
warning
:
response
[
"warning"
]
=
warning
return
response
except
Exception
as
e
:
raise
HTTPException
(
status_code
=
500
,
detail
=
str
(
e
))
backend/common/middleware.py
View file @
b4329aaa
...
...
@@ -4,6 +4,7 @@ Singleton Pattern cho cả 2 services
"""
from
__future__
import
annotations
import
json
import
logging
from
collections.abc
import
Callable
from
typing
import
TYPE_CHECKING
...
...
@@ -79,7 +80,25 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware):
# =====================================================================
try
:
auth_header
=
request
.
headers
.
get
(
"Authorization"
)
device_id
=
request
.
headers
.
get
(
"device_id"
,
""
)
# --- Device ID from Body ---
device_id
=
""
if
method
in
[
"POST"
,
"PUT"
,
"PATCH"
]:
try
:
body_bytes
=
await
request
.
body
()
async
def
receive_wrapper
():
return
{
"type"
:
"http.request"
,
"body"
:
body_bytes
}
request
.
_receive
=
receive_wrapper
if
body_bytes
:
try
:
body_json
=
json
.
loads
(
body_bytes
)
device_id
=
body_json
.
get
(
"device_id"
,
""
)
except
json
.
JSONDecodeError
:
pass
except
Exception
as
e
:
logger
.
warning
(
f
"Error reading device_id from body: {e}"
)
# ========== DEV MODE: Bypass auth ==========
dev_user_id
=
request
.
headers
.
get
(
"X-Dev-User-Id"
)
...
...
@@ -133,7 +152,7 @@ class CanifaAuthMiddleware(BaseHTTPMiddleware):
request
.
state
.
user
=
None
request
.
state
.
user_id
=
None
request
.
state
.
is_authenticated
=
False
request
.
state
.
device_id
=
request
.
headers
.
get
(
"device_id"
,
""
)
request
.
state
.
device_id
=
""
# =====================================================================
# STEP 2: RATE LIMIT CHECK (Chỉ cho các path cần limit)
...
...
backend/docker-compose.yml
View file @
b4329aaa
...
...
@@ -15,8 +15,18 @@ services:
resources
:
limits
:
memory
:
8g
networks
:
-
backend_network
logging
:
driver
:
"
json-file"
options
:
tag
:
"
{{.Name}}"
networks
:
backend_network
:
driver
:
bridge
ipam
:
driver
:
default
config
:
-
subnet
:
"
172.24.0.0/16"
gateway
:
"
172.24.0.1"
backend/manual_test_chat.py
0 → 100644
View file @
b4329aaa
import
requests
import
json
import
time
url
=
"http://localhost:5000/api/agent/chat"
token
=
"071w198x23ict4hs1i6bl889fit5p3f7"
headers
=
{
"Content-Type"
:
"application/json"
,
"Authorization"
:
f
"Bearer {token}"
}
payload
=
{
"user_query"
:
"tư vấn cho mình áo hoodie"
}
print
(
f
"Sending AUTHENTICATED POST request to {url}..."
)
print
(
f
"Token: {token}"
)
start
=
time
.
time
()
try
:
response
=
requests
.
post
(
url
,
json
=
payload
,
headers
=
headers
,
timeout
=
120
)
print
(
f
"Status Code: {response.status_code}"
)
print
(
f
"Time taken: {time.time() - start:.2f}s"
)
if
response
.
status_code
==
200
:
data
=
response
.
json
()
print
(
"Response JSON:"
)
# Print limit info specifically to check if limit increased to USER level (100)
if
"limit_info"
in
data
:
print
(
"Limit Info:"
,
json
.
dumps
(
data
[
"limit_info"
],
indent
=
2
))
else
:
print
(
json
.
dumps
(
data
,
indent
=
2
,
ensure_ascii
=
False
))
else
:
print
(
"Error Response:"
)
print
(
response
.
text
)
except
Exception
as
e
:
print
(
f
"Error: {e}"
)
backend/test_auth_verify.py
0 → 100644
View file @
b4329aaa
import
httpx
import
asyncio
import
logging
# Configure logging
logging
.
basicConfig
(
level
=
logging
.
INFO
)
logger
=
logging
.
getLogger
(
__name__
)
async
def
test_auth
():
url
=
"http://localhost:5000/api/agent/chat"
# 1. Test GUEST Mode (No Token)
logger
.
info
(
"--- TEST 1: GUEST MODE (No Token) ---"
)
payload_guest
=
{
"device_id"
:
"device_guest_123"
,
"user_query"
:
"hello guest"
}
try
:
async
with
httpx
.
AsyncClient
(
timeout
=
10.0
)
as
client
:
resp
=
await
client
.
post
(
url
,
json
=
payload_guest
)
if
resp
.
status_code
==
200
:
data
=
resp
.
json
()
limit
=
data
.
get
(
"limit_info"
,
{})
.
get
(
"limit"
)
used
=
data
.
get
(
"limit_info"
,
{})
.
get
(
"used"
)
logger
.
info
(
f
"✅ Guest Response Limit: {limit} (Expected 10)"
)
if
limit
==
10
:
logger
.
info
(
"=> Logic Guest OK"
)
else
:
logger
.
error
(
f
"=> Logic Guest FAILED (Limit is {limit})"
)
else
:
logger
.
error
(
f
"Request failed: {resp.text}"
)
except
Exception
as
e
:
logger
.
error
(
f
"Error Test 1: {e}"
)
# 2. Test USER Mode (With Token)
logger
.
info
(
"
\n
--- TEST 2: USER MODE (With Access Token) ---"
)
token
=
"071w198x23ict4hs1i6bl889fit5p3f7"
payload_user
=
{
"device_id"
:
"device_user_123"
,
# device_id này sẽ bị ignore nếu token valid
"user_query"
:
"hello user"
}
headers
=
{
"Authorization"
:
f
"Bearer {token}"
}
try
:
async
with
httpx
.
AsyncClient
(
timeout
=
20.0
)
as
client
:
resp
=
await
client
.
post
(
url
,
json
=
payload_user
,
headers
=
headers
)
if
resp
.
status_code
==
200
:
data
=
resp
.
json
()
limit
=
data
.
get
(
"limit_info"
,
{})
.
get
(
"limit"
)
used
=
data
.
get
(
"limit_info"
,
{})
.
get
(
"used"
)
logger
.
info
(
f
"✅ User Response Limit: {limit} (Expected 100)"
)
if
limit
==
100
:
logger
.
info
(
"=> Logic User OK (Prioritized Token over DeviceID)"
)
elif
limit
==
10
:
logger
.
warning
(
"=> Logic User FAILED (Still Guest Mode). Token might be invalid or Canifa API unreachable."
)
else
:
logger
.
info
(
f
"=> Unexpected Limit: {limit}"
)
elif
resp
.
status_code
==
429
:
logger
.
warning
(
"Rate limit exceeded for this user/device."
)
else
:
logger
.
error
(
f
"Request failed: {resp.status_code} - {resp.text}"
)
except
Exception
as
e
:
logger
.
error
(
f
"Error Test 2: {e}"
)
if
__name__
==
"__main__"
:
asyncio
.
run
(
test_auth
())
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