Codebase hợp nhất các ý tưởng tốt nhất trong các thử nghiệm search/RAG cùng repository, nhưng tách phần điều phối khỏi vendor SDK để dễ test và thay thế hạ tầng.
| Nguồn | Điểm giữ lại | Cách triển khai trong codebase mới |
|---|---|---|
| GeneralRAG | Query augmentation, semantic + text search | QueryAnalyzer và pipeline hybrid |
| DeepRAG | Multi-query, tìm chunk phân cấp, carry kết quả tầng trước | DeepSearch và seed_vectors |
| LegalRAG | Phân lớp semantic/syntax, bộ lọc domain/thời gian | interfaces.py, SearchFilters, typed models |
| ChaosSearchEngine | Package layout theo trách nhiệm | Dùng modules/core/pipelines, public API ở __init__.py |
| LegalSearchEngine | BaseSearch/DeepSearch, reference search, rerank cuối |
Hai pipeline dùng chung orchestration, sửa luồng reference |
Các lỗi phổ biến của bản cũ cũng được loại bỏ: không dùng mutable default, không set()
trên dict, không ghép sai query với embedding, không mutate kết quả provider, không hard-code
credential/endpoint và không gọi embedding khi query đã được phân loại là không liên quan.
src/hybrid_search/
├── connectors/ # DatabaseConnector: Elasticsearch, FAISS, Milvus, Qdrant
├── core/ # Dedupe, merge và score normalization thuần Python
├── modules/
│ ├── decompose/ # Query classification, rewrite, decomposition
│ ├── retrieval/ # Dense/Elasticsearch/metadata retrieval + fusion
│ └── rerank/ # Per-query threshold/top-K + optional Base score
├── utils/ # Helper đơn giản, không phải module nghiệp vụ
├── pipelines/ # Ghép decompose -> retrieval -> rerank
├── config.py # Cấu hình hành vi pipeline
├── models.py # Pydantic domain models có validation, immutable
└── interfaces.py # Protocol cho LLM/embed/retrieve/rerank providers
Phiên bản 0.2 có thêm EvidenceSearch dành cho chunk giàu feature:
- dense vector của nội dung: Milvus, FAISS, Qdrant hoặc connector khác;
- dense vector của summary;
- sparse/full-text: Elasticsearch multi-match/BM25;
- domain, entity, relation, số hiệu và reference;
- vị trí gốc: văn bản, phần, chương, mục, điều, đoạn và trang.
Mô hình dữ liệu và luồng retrieval được mô tả chi tiết tại docs/RICH_RETRIEVAL.md.
Ranh giới giữa database connector và chiến lược pipeline được mô tả tại docs/DATABASE_CONNECTOR.md.
Phần giải thích rõ từng score, per-query RRF, Evidence, union/dedup và Base score optional nằm tại docs/FUSION_AND_EVIDENCE_SCORING.md.
Phần đang nghiên cứu về calibration, Answerability, Constraint Match và EvidenceScore nhiều aspect nằm tại docs/research.md.
Luồng EvidenceSearch hiện tại:
Q -> decompose q_i
-> mỗi q_i search dense_content + dense_summary + sparse
-> RRF riêng cho q_i
-> cross-encoder(q_i, chunk)
-> threshold + top-K riêng cho q_i
-> Evidence -> union/dedup chunk
-> Base score optional để sắp xếp
Luồng baseline:
query -> analyze/rewrite -> multi-query embeddings -> document retrieval
-> semantic chunks + full-text + references -> dedupe -> rerank -> normalize
DeepSearch duyệt các lớp tài liệu theo thứ tự. Kết quả tốt của lớp trước được giữ làm
seed_vectors cho lớp sau, sau đó toàn bộ kết quả mới được rerank lần cuối.
Các data/config object đều kế thừa Pydantic BaseModel, cấm field lạ và immutable sau khi
khởi tạo. Vì vậy payload từ API có thể đi qua model_validate(...), còn output có thể dùng
model_dump() mà vẫn giữ type rõ ràng giữa các module.
Không cần database hay model service bên ngoài; cài package (bao gồm Pydantic) rồi chạy:
cd HybridSearchEngine
PYTHONPATH=src python example/example.py
PYTHONPATH=src python example/evidence_search.py
PYTHONPATH=src python -m unittest discover -s tests -vfrom hybrid_search import ChunkLocation, DecomposeModule, KnowledgeChunk
from hybrid_search.connectors import InMemoryDatabaseConnector
from hybrid_search.modules import RerankModule, RetrievalModule
from hybrid_search.pipelines import EvidenceSearch
from hybrid_search.utils import HashEmbedder, SimpleChunkScorer
chunks = (
KnowledgeChunk(
id="doc-1:20",
text="Điều 20 quy định thời hạn hợp đồng.",
location=ChunkLocation(document_id="doc-1", article="Điều 20"),
),
)
database = InMemoryDatabaseConnector(chunks)
await database.connect()
retrieval_module = RetrievalModule(
embedder=HashEmbedder(),
dense_database=database,
chunks=chunks,
)
rerank_module = RerankModule(scorer=SimpleChunkScorer())
searcher = EvidenceSearch(
decompose_module=DecomposeModule(),
retrieval_module=retrieval_module,
rerank_module=rerank_module,
)
response = await searcher.search("thời hạn hợp đồng")
print(response.to_dict())Ví dụ nhanh không truyền sparse_database, vì vậy sparse channel được tắt; không còn lexical
sparse fallback trong Python. Khi chạy thật, truyền ElasticsearchDatabaseConnector như dưới đây.
Tạo module cho từng contract trong interfaces.py:
QueryAnalyzer: OpenAI-compatible structured output.Embedder: Triton hoặc embedding service.DatabaseConnector: Elasticsearch, FAISS, Milvus hoặc Qdrant.FullTextRetriever: Elasticsearch.ChunkScorer: Vietnamese reranker, Cohere hoặc Triton rerank endpoint.
Sau đó inject module/connector vào pipeline. Endpoint, credential và collection name nằm trong connector config/environment; pipeline chỉ biết interface chung.
pip install -e '.[faiss]'
pip install -e '.[elasticsearch]'from hybrid_search.connectors import FaissDatabaseConnector
from hybrid_search.connectors import ElasticsearchConfig, ElasticsearchDatabaseConnector
from hybrid_search.modules import RerankModule, RetrievalModule
from hybrid_search.pipelines import EvidenceSearch
database = FaissDatabaseConnector(chunks)
elasticsearch = ElasticsearchDatabaseConnector(
ElasticsearchConfig(
hosts=("http://localhost:9200",),
index_name="chunks",
)
)
await database.connect()
await elasticsearch.connect()
retrieval_module = RetrievalModule(
dense_database=database,
sparse_database=elasticsearch,
chunks=chunks,
embedder=production_embedder,
)
rerank_module = RerankModule(scorer=cross_encoder)
searcher = EvidenceSearch(
decompose_module=decomposing_analyzer,
retrieval_module=retrieval_module,
rerank_module=rerank_module,
)FaissDatabaseConnector hỗ trợ index content và summary riêng, cosine similarity, metadata
post-filter và index factory (Flat, HNSW,Flat, IVF...). Với dữ liệu lớn, nên shard
theo domain/document type thay vì quét toàn index khi áp dụng filter.
ElasticsearchDatabaseConnector dùng multi_match trên text, summary, entity và số hiệu. BM25
raw score được giữ trong provenance; fusion chỉ dùng rank nên không cộng trực tiếp BM25 với cosine.