Spaces:
Sleeping
Sleeping
compile regex and multi-threaded
Browse files- core/qa_pipeline.py +11 -8
- core/rerank.py +8 -3
- core/text_utils.py +17 -7
core/qa_pipeline.py
CHANGED
|
@@ -12,6 +12,7 @@ from .prompting import create_advanced_prompt
|
|
| 12 |
from .retriever import HybridRetriever
|
| 13 |
from .analyze_and_expand import analyze_and_expand_query
|
| 14 |
from .llm_utils import safe_invoke, safe_stream
|
|
|
|
| 15 |
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
|
|
@@ -315,22 +316,24 @@ def ask_ai_stream_delta(message: str, history: List, hybrid_retriever) -> Genera
|
|
| 315 |
def fetch_docs(year_hint):
|
| 316 |
docs_temp = []
|
| 317 |
seen_temp = set()
|
| 318 |
-
|
|
|
|
| 319 |
current_alpha = 0.4 if "CNTT" in query.upper() else 0.5
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
| 326 |
for doc in retrieved:
|
|
|
|
| 327 |
content_hash = hashlib.sha256(doc.page_content.encode("utf-8")).hexdigest()
|
| 328 |
if content_hash not in seen_temp:
|
| 329 |
docs_temp.append(doc)
|
| 330 |
seen_temp.add(content_hash)
|
| 331 |
return docs_temp
|
| 332 |
# Tìm tài liệu
|
| 333 |
-
|
| 334 |
# Cố gắng tìm tài liệu khớp chính xác với năm học người dùng nhắc đến
|
| 335 |
all_docs = fetch_docs(year_scope_hint)
|
| 336 |
|
|
|
|
| 12 |
from .retriever import HybridRetriever
|
| 13 |
from .analyze_and_expand import analyze_and_expand_query
|
| 14 |
from .llm_utils import safe_invoke, safe_stream
|
| 15 |
+
import concurrent.futures
|
| 16 |
|
| 17 |
logger = logging.getLogger(__name__)
|
| 18 |
|
|
|
|
| 316 |
def fetch_docs(year_hint):
|
| 317 |
docs_temp = []
|
| 318 |
seen_temp = set()
|
| 319 |
+
|
| 320 |
+
def single_search(query):
|
| 321 |
current_alpha = 0.4 if "CNTT" in query.upper() else 0.5
|
| 322 |
+
return hybrid_retriever.search(query, k=TOP_K_RESULTS, alpha=current_alpha, year_scope=year_hint)
|
| 323 |
+
|
| 324 |
+
# Bắn đồng loạt các truy vấn cùng 1 lúc
|
| 325 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
|
| 326 |
+
results = executor.map(single_search, queries)
|
| 327 |
+
|
| 328 |
+
for retrieved in results:
|
| 329 |
for doc in retrieved:
|
| 330 |
+
# Tối ưu: Dùng id của Qdrant (nếu có) hoặc hash nội dung
|
| 331 |
content_hash = hashlib.sha256(doc.page_content.encode("utf-8")).hexdigest()
|
| 332 |
if content_hash not in seen_temp:
|
| 333 |
docs_temp.append(doc)
|
| 334 |
seen_temp.add(content_hash)
|
| 335 |
return docs_temp
|
| 336 |
# Tìm tài liệu
|
|
|
|
| 337 |
# Cố gắng tìm tài liệu khớp chính xác với năm học người dùng nhắc đến
|
| 338 |
all_docs = fetch_docs(year_scope_hint)
|
| 339 |
|
core/rerank.py
CHANGED
|
@@ -8,10 +8,15 @@ logger = logging.getLogger(__name__)
|
|
| 8 |
def advanced_rerank(question: str, docs: List, top_k: int = 5) -> List:
|
| 9 |
if not docs:
|
| 10 |
return []
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
scores = cross_encoder.predict(pairs, show_progress_bar=False)
|
| 14 |
-
ranked = sorted(zip(scores,
|
|
|
|
| 15 |
logger.info("Top 3 điểm: %s", [f"{s:.3f}" for s, _ in ranked[:3]])
|
| 16 |
return [doc for score, doc in ranked[:top_k]]
|
| 17 |
|
|
|
|
| 8 |
def advanced_rerank(question: str, docs: List, top_k: int = 5) -> List:
|
| 9 |
if not docs:
|
| 10 |
return []
|
| 11 |
+
MAX_DOCS_TO_RERANK = 15
|
| 12 |
+
pruned_docs = docs[:MAX_DOCS_TO_RERANK]
|
| 13 |
+
|
| 14 |
+
logger.info("Đang rerank %s tài liệu với Cross-Encoder...", len(pruned_docs))
|
| 15 |
+
pairs = [(question, (doc.page_content or "")[:MAX_RERANK_CHARS]) for doc in pruned_docs]
|
| 16 |
+
|
| 17 |
scores = cross_encoder.predict(pairs, show_progress_bar=False)
|
| 18 |
+
ranked = sorted(zip(scores, pruned_docs), key=lambda x: x[0], reverse=True)
|
| 19 |
+
|
| 20 |
logger.info("Top 3 điểm: %s", [f"{s:.3f}" for s, _ in ranked[:3]])
|
| 21 |
return [doc for score, doc in ranked[:top_k]]
|
| 22 |
|
core/text_utils.py
CHANGED
|
@@ -1,24 +1,34 @@
|
|
| 1 |
import re
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
def clean_text(text: str) -> str:
|
| 4 |
if not text or not text.strip():
|
| 5 |
return ""
|
| 6 |
|
| 7 |
# Nối các từ bị gãy ngang do xuống dòng
|
| 8 |
-
text =
|
| 9 |
|
| 10 |
# \| và < > vào để bảo vệ khung Bảng Markdown và các Placeholder
|
| 11 |
-
text =
|
| 12 |
|
| 13 |
# Chuẩn hóa khoảng trắng
|
| 14 |
-
text =
|
| 15 |
-
text =
|
| 16 |
-
text =
|
| 17 |
|
| 18 |
# Giới hạn tối đa 2 dòng trống liên tiếp
|
| 19 |
-
text =
|
| 20 |
|
| 21 |
# Sửa lỗi dư khoảng trắng trước dấu câu
|
| 22 |
-
text =
|
| 23 |
|
| 24 |
return text.strip()
|
|
|
|
| 1 |
import re
|
| 2 |
|
| 3 |
+
#Compile regex patterns một lần toàn cục - tránh recompile mỗi lần gọi
|
| 4 |
+
_HYPHENATED_WORD_PATTERN = re.compile(r'(\w+)-\s*\n\s*(\w+)')
|
| 5 |
+
_INVALID_CHARS_PATTERN = re.compile(r'[^\w\s\.,;:!?\-$$\"\'\À-ỹ\n\|<>]')
|
| 6 |
+
_MULTIPLE_SPACES_PATTERN = re.compile(r'[ \t]+')
|
| 7 |
+
_SPACE_BEFORE_NEWLINE_PATTERN = re.compile(r' +\n')
|
| 8 |
+
_SPACE_AFTER_NEWLINE_PATTERN = re.compile(r'\n +')
|
| 9 |
+
_MULTIPLE_NEWLINES_PATTERN = re.compile(r'\n{3,}')
|
| 10 |
+
_SPACE_BEFORE_PUNCTUATION_PATTERN = re.compile(r'\s+([.,;:!?])')
|
| 11 |
+
|
| 12 |
+
|
| 13 |
def clean_text(text: str) -> str:
|
| 14 |
if not text or not text.strip():
|
| 15 |
return ""
|
| 16 |
|
| 17 |
# Nối các từ bị gãy ngang do xuống dòng
|
| 18 |
+
text = _HYPHENATED_WORD_PATTERN.sub(r'\1\2', text)
|
| 19 |
|
| 20 |
# \| và < > vào để bảo vệ khung Bảng Markdown và các Placeholder
|
| 21 |
+
text = _INVALID_CHARS_PATTERN.sub(' ', text)
|
| 22 |
|
| 23 |
# Chuẩn hóa khoảng trắng
|
| 24 |
+
text = _MULTIPLE_SPACES_PATTERN.sub(' ', text)
|
| 25 |
+
text = _SPACE_BEFORE_NEWLINE_PATTERN.sub('\n', text)
|
| 26 |
+
text = _SPACE_AFTER_NEWLINE_PATTERN.sub('\n', text)
|
| 27 |
|
| 28 |
# Giới hạn tối đa 2 dòng trống liên tiếp
|
| 29 |
+
text = _MULTIPLE_NEWLINES_PATTERN.sub('\n\n', text)
|
| 30 |
|
| 31 |
# Sửa lỗi dư khoảng trắng trước dấu câu
|
| 32 |
+
text = _SPACE_BEFORE_PUNCTUATION_PATTERN.sub(r'\1', text)
|
| 33 |
|
| 34 |
return text.strip()
|