1818"""
1919
2020import asyncio
21- import os
2221import sys
2322from pathlib import Path
2423
3332from fetchcraft .node_parser import HierarchicalNodeParser , SimpleNodeParser
3433from fetchcraft .vector_store import QdrantVectorStore
3534
36- # Configuration
37- QDRANT_HOST = "localhost"
38- QDRANT_PORT = 6333
39- COLLECTION_NAME = "fetchcraft_chatbot" # Different collection for hybrid search
40- DOCUMENTS_PATH = Path (os .getenv ("DOCUMENTS_PATH" , "Documents" ))
35+ from fetchcraft .demos .hybrid .settings import Settings
4136
42- # Embeddings configuration (adjust based on your setup)
43- EMBEDDING_MODEL = os .getenv ("EMBEDDING_MODEL" , "bge-m3" )
44- EMBEDDING_API_KEY = os .getenv ("OPENAI_API_KEY" , "sk-321" )
45- EMBEDDING_BASE_URL = os .getenv ("EMBEDDING_BASE_URL" , None ) # None = use OpenAI default
46- INDEX_ID = "docs-index"
47-
48- # LLM configuration for the agent
49- LLM_MODEL = os .getenv ("LLM_MODEL" , "gpt-4-turbo" )
50- LLM_API_KEY = os .getenv ("OPENAI_API_KEY" , "sk-123" )
51-
52- # Chunking configuration
53- CHUNK_SIZE = int (os .getenv ("CHUNK_SIZE" , "8192" ))
54- CHILD_SIZES = [4096 , 1024 ]
55- CHUNK_OVERLAP = int (os .getenv ("CHUNK_OVERLAP" , "200" ))
56- USE_HIERARCHICAL_CHUNKING = os .getenv ("USE_HIERARCHICAL_CHUNKING" , "true" ).lower () == "true"
57-
58- # 🔥 HYBRID SEARCH CONFIGURATION
59- ENABLE_HYBRID = os .getenv ("ENABLE_HYBRID" , "true" ).lower () == "true"
60- FUSION_METHOD = os .getenv ("FUSION_METHOD" , "rrf" ) # "rrf" or "dbsf"
37+ # Initialize settings
38+ settings = Settings ()
6139
6240
6341def collection_exists (client : QdrantClient , collection_name : str ) -> bool :
@@ -157,7 +135,7 @@ async def load_and_index_documents(
157135 print (f" 🔍 Sparse vector (keyword matching)" )
158136
159137 # Index all chunks (embeddings will be generated automatically)
160- await vector_index .add_nodes (all_chunks , show_progress = True )
138+ await vector_index .add_nodes (DocumentNode , all_chunks , show_progress = True )
161139
162140 print (f"✅ Successfully indexed { len (all_chunks )} chunks with hybrid search!" )
163141 return len (all_chunks )
@@ -180,64 +158,52 @@ async def setup_rag_system():
180158 # Initialize embeddings
181159 print ("\n 1️⃣ Initializing embeddings..." )
182160 embeddings = OpenAIEmbeddings (
183- model = EMBEDDING_MODEL ,
184- api_key = EMBEDDING_API_KEY ,
185- base_url = EMBEDDING_BASE_URL
161+ model = settings . embedding_model ,
162+ api_key = settings . openai_api_key ,
163+ base_url = settings . embedding_base_url
186164 )
187165
188166
189167 # Connect to Qdrant
190- print (f"\n 2️⃣ Connecting to Qdrant at { QDRANT_HOST } :{ QDRANT_PORT } ..." )
191- client = QdrantClient (host = QDRANT_HOST , port = QDRANT_PORT )
168+ print (f"\n 2️⃣ Connecting to Qdrant at { settings . qdrant_host } :{ settings . qdrant_port } ..." )
169+ client = QdrantClient (host = settings . qdrant_host , port = settings . qdrant_port )
192170 client .get_collections () # Test connection
193171 print (f" ✓ Connected to Qdrant" )
194172
195173 # Check if collection exists
196- print (f"\n 3️⃣ Checking collection '{ COLLECTION_NAME } '..." )
197- needs_indexing = not collection_exists (client , COLLECTION_NAME )
198-
199- if needs_indexing :
200- print (f" ⚠️ Collection '{ COLLECTION_NAME } ' does not exist - will create and index" )
201- else :
202- print (f" ✓ Collection '{ COLLECTION_NAME } ' already exists - skipping indexing" )
174+ print (f"\n 3️⃣ Checking collection '{ settings .collection_name } '..." )
175+ needs_indexing = not collection_exists (client , settings .collection_name )
203176
204177 # Create vector store with HYBRID SEARCH enabled
205178 print (f"\n 🔥 Creating vector store with HYBRID SEARCH..." )
206- print (f" • Enable Hybrid: { ENABLE_HYBRID } " )
207- print (f" • Fusion Method: { FUSION_METHOD .upper ()} " )
208-
209- try :
210- vector_store = QdrantVectorStore (
211- client = client ,
212- collection_name = COLLECTION_NAME ,
213- embeddings = embeddings ,
214- distance = "Cosine" ,
215- enable_hybrid = ENABLE_HYBRID , # 🔥 Enable hybrid search
216- fusion_method = FUSION_METHOD # Choose RRF or DBSF
217- )
218- print (f" ✓ Vector store created with hybrid search enabled!" )
219- except ImportError as e :
220- print (f"\n ❌ Error: { e } " )
221- print ("\n 💡 Hybrid search requires fastembed:" )
222- print (" pip install fastembed" )
223- sys .exit (1 )
224-
179+ print (f" • Enable Hybrid: { settings .enable_hybrid } " )
180+ print (f" • Fusion Method: { settings .fusion_method .upper ()} " )
181+
182+ vector_store = QdrantVectorStore (
183+ client = client ,
184+ collection_name = settings .collection_name ,
185+ embeddings = embeddings ,
186+ distance = "Cosine" ,
187+ enable_hybrid = settings .enable_hybrid , # 🔥 Enable hybrid search
188+ fusion_method = settings .fusion_method # Choose RRF or DBSF
189+ )
190+
225191 # Create vector index with a consistent index_id
226192 vector_index = VectorIndex (
227193 vector_store = vector_store ,
228- index_id = INDEX_ID
194+ index_id = settings . index_id
229195 )
230196 needs_indexing = False
231197 # Index documents if needed
232198 if needs_indexing :
233199 print (f"\n 4️⃣ Indexing documents with hybrid search..." )
234200 num_chunks = await load_and_index_documents (
235201 vector_index = vector_index ,
236- documents_path = DOCUMENTS_PATH ,
237- chunk_size = CHUNK_SIZE ,
238- child_sizes = CHILD_SIZES ,
239- overlap = CHUNK_OVERLAP ,
240- use_hierarchical = USE_HIERARCHICAL_CHUNKING
202+ documents_path = Path ( settings . documents_path ) ,
203+ chunk_size = settings . chunk_size ,
204+ child_sizes = settings . child_sizes ,
205+ overlap = settings . chunk_overlap ,
206+ use_hierarchical = settings . use_hierarchical_chunking
241207 )
242208 if num_chunks == 0 :
243209 print ("\n ⚠️ Warning: No documents were indexed!" )
@@ -256,7 +222,7 @@ async def setup_rag_system():
256222 tools = [Tool (tool_func , takes_ctx = True , max_retries = 3 )]
257223
258224 agent = PydanticAgent .create (
259- model = LLM_MODEL ,
225+ model = settings . llm_model ,
260226 tools = tools ,
261227 retries = 3
262228 )
@@ -331,33 +297,6 @@ async def repl_loop(agent: PydanticAgent):
331297 print ("─" * 70 )
332298
333299
334- def print_error_hints (error : Exception ):
335- """Print helpful hints based on the error type."""
336- error_msg = str (error ).lower ()
337-
338- if "fastembed" in error_msg :
339- print ("\n 💡 FastEmbed Missing:" )
340- print (" - Hybrid search requires fastembed" )
341- print (" - Install with: pip install fastembed" )
342- elif "api key" in error_msg or "authentication" in error_msg :
343- print ("\n 💡 API Key Issue:" )
344- print (" - Set OPENAI_API_KEY environment variable" )
345- print (" - Or configure EMBEDDING_BASE_URL for a custom endpoint" )
346- elif "connection" in error_msg or "refused" in error_msg or "qdrant" in error_msg :
347- print ("\n 💡 Connection Issue:" )
348- print (" - Make sure Qdrant is running on localhost:6333" )
349- print (" - Start with: docker run -p 6333:6333 qdrant/qdrant" )
350- elif "not found" in error_msg or "no such file" in error_msg :
351- print ("\n 💡 File Path Issue:" )
352- print (f" - Check that { DOCUMENTS_PATH } exists" )
353- print (" - Make sure it contains .txt files" )
354- elif "pydantic" in error_msg or "import" in error_msg :
355- print ("\n 💡 Dependency Issue:" )
356- print (" - Install required packages: pip install pydantic-ai qdrant-client openai fastembed" )
357- else :
358- print ("\n 💡 For more help, check the README.md file" )
359-
360-
361300async def main ():
362301 """Main entry point for the hybrid search demo."""
363302 try :
@@ -371,7 +310,6 @@ async def main():
371310 print ("\n \n 👋 Demo interrupted. Goodbye!" )
372311 except Exception as e :
373312 print (f"\n ❌ Error: { e } " )
374- print_error_hints (e )
375313 import traceback
376314 traceback .print_exc ()
377315 sys .exit (1 )
0 commit comments