Spaces:
Runtime error
Runtime error
Update chat with cohort_key
Browse files- core/config.py +7 -0
- core/qa_pipeline.py +9 -4
- main.py +34 -11
core/config.py
CHANGED
|
@@ -67,6 +67,13 @@ SUPABASE_SYNC_ALLOWED_IPS = [ip.strip() for ip in os.getenv('SUPABASE_SYNC_ALLOW
|
|
| 67 |
SUPABASE_SYNC_ALLOW_PRIVATE_NETWORK = os.getenv('SUPABASE_SYNC_ALLOW_PRIVATE_NETWORK', 'true').strip().lower() in {'1', 'true', 'yes', 'on'}
|
| 68 |
COLLECTION_ROUTER_TOP_N = _bounded_int_from_env('COLLECTION_ROUTER_TOP_N', 3, 1, 20)
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
# - Context and output limits
|
| 71 |
MAX_CONTEXT_CHARS = int(os.getenv('MAX_CONTEXT_CHARS', '12000'))
|
| 72 |
MAX_OUT_CHARS = int(os.getenv('MAX_OUT_CHARS', '3000'))
|
|
|
|
| 67 |
SUPABASE_SYNC_ALLOW_PRIVATE_NETWORK = os.getenv('SUPABASE_SYNC_ALLOW_PRIVATE_NETWORK', 'true').strip().lower() in {'1', 'true', 'yes', 'on'}
|
| 68 |
COLLECTION_ROUTER_TOP_N = _bounded_int_from_env('COLLECTION_ROUTER_TOP_N', 3, 1, 20)
|
| 69 |
|
| 70 |
+
# Cohort to academic year mapping
|
| 71 |
+
COHORT_TO_YEAR = {
|
| 72 |
+
'k65': '2023-2024',
|
| 73 |
+
'k64': '2022-2023',
|
| 74 |
+
'k63': '2021-2022',
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
# - Context and output limits
|
| 78 |
MAX_CONTEXT_CHARS = int(os.getenv('MAX_CONTEXT_CHARS', '12000'))
|
| 79 |
MAX_OUT_CHARS = int(os.getenv('MAX_OUT_CHARS', '3000'))
|
core/qa_pipeline.py
CHANGED
|
@@ -221,16 +221,16 @@ def generate_standalone_query(message: str, history: List) -> str:
|
|
| 221 |
|
| 222 |
return message
|
| 223 |
|
| 224 |
-
def ask_ai_improved(message: str, history: List, hybrid_retriever) -> Generator[str, None, None]:
|
| 225 |
full_response = ""
|
| 226 |
-
for delta in ask_ai_stream_delta(message, history, hybrid_retriever):
|
| 227 |
full_response += delta
|
| 228 |
if len(full_response) > MAX_OUT_CHARS:
|
| 229 |
yield full_response[:MAX_OUT_CHARS] + "\n\n[Đã cắt bớt nội dung dài]"
|
| 230 |
return
|
| 231 |
yield full_response
|
| 232 |
|
| 233 |
-
def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Generator[str, None, None]:
|
| 234 |
if not message.strip():
|
| 235 |
yield " Bạn chưa nhập câu hỏi."
|
| 236 |
return
|
|
@@ -261,7 +261,12 @@ def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Genera
|
|
| 261 |
|
| 262 |
all_docs: List = []
|
| 263 |
seen = set()
|
| 264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
for query in queries:
|
| 266 |
#Giữ nguyên logic alpha ngành CNTT của Minh
|
| 267 |
current_alpha = 0.4 if "CNTT" in query.upper() else 0.5
|
|
|
|
| 221 |
|
| 222 |
return message
|
| 223 |
|
| 224 |
+
def ask_ai_improved(message: str, history: List, hybrid_retriever, year_scope: str | None = None) -> Generator[str, None, None]:
|
| 225 |
full_response = ""
|
| 226 |
+
for delta in ask_ai_stream_delta(message, history, hybrid_retriever, year_scope=year_scope):
|
| 227 |
full_response += delta
|
| 228 |
if len(full_response) > MAX_OUT_CHARS:
|
| 229 |
yield full_response[:MAX_OUT_CHARS] + "\n\n[Đã cắt bớt nội dung dài]"
|
| 230 |
return
|
| 231 |
yield full_response
|
| 232 |
|
| 233 |
+
def ask_ai_stream_delta(message: str, history: List, hybrid_retriever, year_scope: str | None = None) -> Generator[str, None, None]:
|
| 234 |
if not message.strip():
|
| 235 |
yield " Bạn chưa nhập câu hỏi."
|
| 236 |
return
|
|
|
|
| 261 |
|
| 262 |
all_docs: List = []
|
| 263 |
seen = set()
|
| 264 |
+
# Prefer passed year_scope over detected year
|
| 265 |
+
if year_scope:
|
| 266 |
+
year_scope_hint = year_scope
|
| 267 |
+
logger.info(f"Sử dụng year_scope từ cohort: {year_scope_hint}")
|
| 268 |
+
else:
|
| 269 |
+
year_scope_hint = requested_year_range or (", ".join(sorted(mentioned_years)) if mentioned_years else None)
|
| 270 |
for query in queries:
|
| 271 |
#Giữ nguyên logic alpha ngành CNTT của Minh
|
| 272 |
current_alpha = 0.4 if "CNTT" in query.upper() else 0.5
|
main.py
CHANGED
|
@@ -14,6 +14,7 @@ from qdrant_client import QdrantClient
|
|
| 14 |
#Import các model và các hàm cần thiết từ core
|
| 15 |
from core.config import (
|
| 16 |
COLLECTION_ROUTER_TOP_N,
|
|
|
|
| 17 |
DATABASE_URL,
|
| 18 |
QDRANT_API_KEY,
|
| 19 |
QDRANT_URL,
|
|
@@ -62,6 +63,7 @@ async def init_db_asyncpg(pool: asyncpg.Pool):
|
|
| 62 |
# 2 lệnh ALTER TABLE để cập nhật bảng cũ nếu đã tồn tại
|
| 63 |
await conn.execute('ALTER TABLE history ADD COLUMN IF NOT EXISTS user_id TEXT')
|
| 64 |
await conn.execute('ALTER TABLE history ADD COLUMN IF NOT EXISTS title TEXT')
|
|
|
|
| 65 |
|
| 66 |
await conn.execute('''
|
| 67 |
CREATE INDEX IF NOT EXISTS idx_history_session_id_id
|
|
@@ -99,7 +101,7 @@ async def get_history_async(pool: asyncpg.Pool, session_id: str):
|
|
| 99 |
return []
|
| 100 |
|
| 101 |
# Hàm lưu lượt chat để hỗ trợ title và user_id
|
| 102 |
-
async def save_turn_async(pool: asyncpg.Pool, session_id: str, user_msg: str, assistant_msg: str, user_id: str = None):
|
| 103 |
try:
|
| 104 |
async with pool.acquire() as conn:
|
| 105 |
# Kiểm tra xem session này đã có tiêu đề chưa
|
|
@@ -110,12 +112,12 @@ async def save_turn_async(pool: asyncpg.Pool, session_id: str, user_msg: str, as
|
|
| 110 |
|
| 111 |
async with conn.transaction():
|
| 112 |
await conn.execute(
|
| 113 |
-
"INSERT INTO history (session_id, user_id, role, content, title) VALUES ($1, $2, $3, $4, $5)",
|
| 114 |
-
session_id, user_id, "user", user_msg, title
|
| 115 |
)
|
| 116 |
await conn.execute(
|
| 117 |
-
"INSERT INTO history (session_id, user_id, role, content, title) VALUES ($1, $2, $3, $4, $5)",
|
| 118 |
-
session_id, user_id, "assistant", assistant_msg, title
|
| 119 |
)
|
| 120 |
except Exception:
|
| 121 |
logger.exception("Lỗi khi lưu lượt hội thoại:", exc_info=True)
|
|
@@ -299,6 +301,7 @@ class ChatRequest(BaseModel):
|
|
| 299 |
session_id: str
|
| 300 |
user_id: str = None
|
| 301 |
message: str
|
|
|
|
| 302 |
|
| 303 |
class ChatResponse(BaseModel):
|
| 304 |
response: str
|
|
@@ -339,19 +342,29 @@ async def chat_endpoint(payload: ChatRequest, request: Request):
|
|
| 339 |
|
| 340 |
session_id = payload.session_id
|
| 341 |
user_id = payload.user_id # Lấy user_id từ request
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
history = await get_history_async(db_pool, session_id)
|
| 343 |
|
| 344 |
# Tập hợp toàn bộ response từ generator
|
| 345 |
full_response = ""
|
| 346 |
try:
|
| 347 |
-
async for chunk in iterate_in_threadpool(ask_ai_improved(user_msg, history, retriever)):
|
| 348 |
full_response = chunk
|
| 349 |
except Exception:
|
| 350 |
logger.exception("Lỗi khi xử lý phản hồi từ AI:", exc_info=True)
|
| 351 |
raise HTTPException(status_code=500, detail="Lỗi khi xử lý yêu cầu")
|
| 352 |
|
| 353 |
-
# Lưu lịch sử sau khi có response đầy đủ (Kèm theo user_id)
|
| 354 |
-
await save_turn_async(db_pool, session_id, user_msg, full_response, user_id)
|
| 355 |
|
| 356 |
return ChatResponse(response=full_response)
|
| 357 |
|
|
@@ -366,6 +379,16 @@ async def chat_stream_endpoint(payload: ChatRequest, request: Request):
|
|
| 366 |
|
| 367 |
session_id = payload.session_id
|
| 368 |
user_id = payload.user_id # Lấy user_id từ request
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
history = await get_history_async(db_pool, session_id)
|
| 370 |
|
| 371 |
async def event_stream_generator():
|
|
@@ -373,7 +396,7 @@ async def chat_stream_endpoint(payload: ChatRequest, request: Request):
|
|
| 373 |
full_response = ""
|
| 374 |
try:
|
| 375 |
# ask_ai_stream_delta yield từng delta chunk (không cumulative)
|
| 376 |
-
async for delta_chunk in iterate_in_threadpool(ask_ai_stream_delta(user_msg, history, retriever)):
|
| 377 |
full_response += delta_chunk
|
| 378 |
# Gửi SSE event với delta chunk
|
| 379 |
sse_data = json.dumps({"delta": delta_chunk, "done": False}, ensure_ascii=False)
|
|
@@ -382,8 +405,8 @@ async def chat_stream_endpoint(payload: ChatRequest, request: Request):
|
|
| 382 |
# Gửi tín hiệu kết thúc
|
| 383 |
yield 'data: {"delta": "", "done": true}\n\n'
|
| 384 |
|
| 385 |
-
# Lưu lịch sử sau khi stream xong (Kèm theo user_id)
|
| 386 |
-
await save_turn_async(db_pool, session_id, user_msg, full_response, user_id)
|
| 387 |
|
| 388 |
except Exception:
|
| 389 |
logger.exception("Lỗi khi stream phản hồi từ AI:", exc_info=True)
|
|
|
|
| 14 |
#Import các model và các hàm cần thiết từ core
|
| 15 |
from core.config import (
|
| 16 |
COLLECTION_ROUTER_TOP_N,
|
| 17 |
+
COHORT_TO_YEAR,
|
| 18 |
DATABASE_URL,
|
| 19 |
QDRANT_API_KEY,
|
| 20 |
QDRANT_URL,
|
|
|
|
| 63 |
# 2 lệnh ALTER TABLE để cập nhật bảng cũ nếu đã tồn tại
|
| 64 |
await conn.execute('ALTER TABLE history ADD COLUMN IF NOT EXISTS user_id TEXT')
|
| 65 |
await conn.execute('ALTER TABLE history ADD COLUMN IF NOT EXISTS title TEXT')
|
| 66 |
+
await conn.execute('ALTER TABLE history ADD COLUMN IF NOT EXISTS cohort_key TEXT')
|
| 67 |
|
| 68 |
await conn.execute('''
|
| 69 |
CREATE INDEX IF NOT EXISTS idx_history_session_id_id
|
|
|
|
| 101 |
return []
|
| 102 |
|
| 103 |
# Hàm lưu lượt chat để hỗ trợ title và user_id
|
| 104 |
+
async def save_turn_async(pool: asyncpg.Pool, session_id: str, user_msg: str, assistant_msg: str, user_id: str = None, cohort_key: str = None):
|
| 105 |
try:
|
| 106 |
async with pool.acquire() as conn:
|
| 107 |
# Kiểm tra xem session này đã có tiêu đề chưa
|
|
|
|
| 112 |
|
| 113 |
async with conn.transaction():
|
| 114 |
await conn.execute(
|
| 115 |
+
"INSERT INTO history (session_id, user_id, role, content, title, cohort_key) VALUES ($1, $2, $3, $4, $5, $6)",
|
| 116 |
+
session_id, user_id, "user", user_msg, title, cohort_key
|
| 117 |
)
|
| 118 |
await conn.execute(
|
| 119 |
+
"INSERT INTO history (session_id, user_id, role, content, title, cohort_key) VALUES ($1, $2, $3, $4, $5, $6)",
|
| 120 |
+
session_id, user_id, "assistant", assistant_msg, title, cohort_key
|
| 121 |
)
|
| 122 |
except Exception:
|
| 123 |
logger.exception("Lỗi khi lưu lượt hội thoại:", exc_info=True)
|
|
|
|
| 301 |
session_id: str
|
| 302 |
user_id: str = None
|
| 303 |
message: str
|
| 304 |
+
cohort_key: str = None
|
| 305 |
|
| 306 |
class ChatResponse(BaseModel):
|
| 307 |
response: str
|
|
|
|
| 342 |
|
| 343 |
session_id = payload.session_id
|
| 344 |
user_id = payload.user_id # Lấy user_id từ request
|
| 345 |
+
cohort_key = payload.cohort_key # Lấy cohort_key từ request
|
| 346 |
+
|
| 347 |
+
# Convert cohort_key to year_scope for collection routing
|
| 348 |
+
year_scope = None
|
| 349 |
+
if cohort_key and cohort_key in COHORT_TO_YEAR:
|
| 350 |
+
year_scope = COHORT_TO_YEAR[cohort_key]
|
| 351 |
+
logger.info(f"Sử dụng cohort: {cohort_key} -> năm học: {year_scope}")
|
| 352 |
+
elif cohort_key:
|
| 353 |
+
logger.warning(f"Cohort không hợp lệ: {cohort_key}")
|
| 354 |
+
|
| 355 |
history = await get_history_async(db_pool, session_id)
|
| 356 |
|
| 357 |
# Tập hợp toàn bộ response từ generator
|
| 358 |
full_response = ""
|
| 359 |
try:
|
| 360 |
+
async for chunk in iterate_in_threadpool(ask_ai_improved(user_msg, history, retriever, year_scope=year_scope)):
|
| 361 |
full_response = chunk
|
| 362 |
except Exception:
|
| 363 |
logger.exception("Lỗi khi xử lý phản hồi từ AI:", exc_info=True)
|
| 364 |
raise HTTPException(status_code=500, detail="Lỗi khi xử lý yêu cầu")
|
| 365 |
|
| 366 |
+
# Lưu lịch sử sau khi có response đầy đủ (Kèm theo user_id và cohort_key)
|
| 367 |
+
await save_turn_async(db_pool, session_id, user_msg, full_response, user_id, cohort_key)
|
| 368 |
|
| 369 |
return ChatResponse(response=full_response)
|
| 370 |
|
|
|
|
| 379 |
|
| 380 |
session_id = payload.session_id
|
| 381 |
user_id = payload.user_id # Lấy user_id từ request
|
| 382 |
+
cohort_key = payload.cohort_key # Lấy cohort_key từ request
|
| 383 |
+
|
| 384 |
+
# Convert cohort_key to year_scope for collection routing
|
| 385 |
+
year_scope = None
|
| 386 |
+
if cohort_key and cohort_key in COHORT_TO_YEAR:
|
| 387 |
+
year_scope = COHORT_TO_YEAR[cohort_key]
|
| 388 |
+
logger.info(f"Sử dụng cohort: {cohort_key} -> năm học: {year_scope}")
|
| 389 |
+
elif cohort_key:
|
| 390 |
+
logger.warning(f"Cohort không hợp lệ: {cohort_key}")
|
| 391 |
+
|
| 392 |
history = await get_history_async(db_pool, session_id)
|
| 393 |
|
| 394 |
async def event_stream_generator():
|
|
|
|
| 396 |
full_response = ""
|
| 397 |
try:
|
| 398 |
# ask_ai_stream_delta yield từng delta chunk (không cumulative)
|
| 399 |
+
async for delta_chunk in iterate_in_threadpool(ask_ai_stream_delta(user_msg, history, retriever, year_scope=year_scope)):
|
| 400 |
full_response += delta_chunk
|
| 401 |
# Gửi SSE event với delta chunk
|
| 402 |
sse_data = json.dumps({"delta": delta_chunk, "done": False}, ensure_ascii=False)
|
|
|
|
| 405 |
# Gửi tín hiệu kết thúc
|
| 406 |
yield 'data: {"delta": "", "done": true}\n\n'
|
| 407 |
|
| 408 |
+
# Lưu lịch sử sau khi stream xong (Kèm theo user_id và cohort_key)
|
| 409 |
+
await save_turn_async(db_pool, session_id, user_msg, full_response, user_id, cohort_key)
|
| 410 |
|
| 411 |
except Exception:
|
| 412 |
logger.exception("Lỗi khi stream phản hồi từ AI:", exc_info=True)
|