| from __future__ import annotations |
|
|
| import re |
|
|
| from sentence_transformers import SentenceTransformer |
| from qdrant_client import QdrantClient |
| from qdrant_client.models import ( |
| Filter, |
| FieldCondition, |
| MatchValue |
| ) |
|
|
|
|
| class ConstitutionSearch: |
|
|
| def __init__( |
| self, |
| collection_name="constitution", |
| qdrant_url="http://localhost:6333", |
| api_key=None |
| ): |
|
|
| self.collection_name = ( |
| collection_name |
| ) |
|
|
| self.client = QdrantClient( |
| url=qdrant_url, |
| api_key=api_key |
| ) |
|
|
| print( |
| f"Connected to: {qdrant_url}" |
| ) |
|
|
| collections = ( |
| self.client.get_collections() |
| ) |
|
|
| print("\nAvailable Collections:") |
|
|
| for c in collections.collections: |
| print("-", c.name) |
|
|
| self.embedding_model = ( |
| SentenceTransformer( |
| "BAAI/bge-large-en-v1.5" |
| ) |
| ) |
|
|
| |
| |
| |
|
|
| def extract_article_number( |
| self, |
| query: str |
| ): |
|
|
| match = re.search( |
| r"article\s+(\d+[A-Z]*)", |
| query, |
| re.I |
| ) |
|
|
| if match: |
| return match.group(1) |
|
|
| return None |
|
|
| |
| |
| |
|
|
| def search_article( |
| self, |
| article_no: str |
| ): |
|
|
| records, _ = self.client.scroll( |
| collection_name= |
| self.collection_name, |
|
|
| scroll_filter=Filter( |
| must=[ |
| FieldCondition( |
| key="article_no", |
| match=MatchValue( |
| value=article_no |
| ) |
| ), |
| FieldCondition( |
| key="chunk_type", |
| match=MatchValue( |
| value="article" |
| ) |
| ) |
| ] |
| ), |
|
|
| limit=1, |
|
|
| with_payload=True |
| ) |
|
|
| return records |
|
|
| |
| |
| |
|
|
| def semantic_search( |
| self, |
| query: str, |
| top_k: int = 5 |
| ): |
|
|
| query_embedding = ( |
| self.embedding_model.encode( |
| query, |
| normalize_embeddings=True |
| ) |
| ) |
|
|
| result = self.client.query_points( |
| collection_name= |
| self.collection_name, |
|
|
| query= |
| query_embedding.tolist(), |
|
|
| limit= |
| top_k |
| ) |
|
|
| return result.points |
|
|
| |
| |
| |
|
|
| def search( |
| self, |
| query: str, |
| top_k: int = 5 |
| ): |
|
|
| article_no = ( |
| self.extract_article_number( |
| query |
| ) |
| ) |
|
|
| if article_no: |
|
|
| print( |
| f"\nExact Article Lookup: " |
| f"{article_no}" |
| ) |
|
|
| return self.search_article( |
| article_no |
| ) |
|
|
| print( |
| "\nSemantic Search" |
| ) |
|
|
| return self.semantic_search( |
| query, |
| top_k |
| ) |
|
|
| |
| |
| |
|
|
| def print_results( |
| self, |
| results |
| ): |
|
|
| if not results: |
|
|
| print( |
| "No results found." |
| ) |
|
|
| return |
|
|
| for idx, result in enumerate( |
| results, |
| start=1 |
| ): |
|
|
| |
| if hasattr( |
| result, |
| "payload" |
| ): |
| payload = result.payload |
| score = getattr( |
| result, |
| "score", |
| None |
| ) |
| else: |
| payload = result |
| score = None |
|
|
| print( |
| "\n" + |
| "=" * 80 |
| ) |
|
|
| print( |
| f"Rank: {idx}" |
| ) |
|
|
| if score is not None: |
|
|
| print( |
| f"Score: " |
| f"{score:.4f}" |
| ) |
|
|
| print( |
| f"Chunk ID: " |
| f"{payload.get('chunk_id')}" |
| ) |
|
|
| print( |
| f"Type: " |
| f"{payload.get('chunk_type')}" |
| ) |
|
|
| print( |
| f"Article: " |
| f"{payload.get('article_no')}" |
| ) |
|
|
| print( |
| f"Title: " |
| f"{payload.get('article_title')}" |
| ) |
|
|
| print("\nTEXT:\n") |
|
|
| print( |
| payload.get( |
| "text", |
| "" |
| )[:2000] |
| ) |
|
|
| |
| |
| |
|
|
| def retrieve_context( |
| self, |
| query: str, |
| top_k: int = 5 |
| ): |
|
|
| results = self.search( |
| query, |
| top_k |
| ) |
|
|
| context = [] |
|
|
| for result in results: |
|
|
| payload = result.payload |
|
|
| context.append( |
| payload.get( |
| "text", |
| "" |
| ) |
| ) |
|
|
| return "\n\n".join( |
| context |
| ) |
|
|
|
|
| |
| |
| |
|
|
| if __name__ == "__main__": |
|
|
| search_engine = ConstitutionSearch( |
| collection_name= |
| "constitution", |
|
|
| qdrant_url= |
| "http://localhost:6333" |
| ) |
|
|
| query = ( |
| "What is the right to equality?" |
| ) |
|
|
| results = search_engine.search( |
| query, |
| top_k=5 |
| ) |
|
|
| search_engine.print_results( |
| results |
| ) |