Coverage for src/qdrant_loader_mcp_server/search/hybrid/engine.py: 91%
45 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-08 06:06 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-08 06:06 +0000
1"""
2Hybrid Search Engine Implementation.
4This module implements the main HybridSearchEngine class that orchestrates
5vector search, keyword search, intent classification, knowledge graph integration,
6cross-document intelligence, and faceted search capabilities.
7"""
9from __future__ import annotations
11from typing import Any
13from ...config import SearchConfig
14from ...utils.logging import LoggingConfig
15from .api import HybridEngineAPI
16from .models import (
17 DEFAULT_KEYWORD_WEIGHT,
18 DEFAULT_METADATA_WEIGHT,
19 DEFAULT_MIN_SCORE,
20 DEFAULT_VECTOR_WEIGHT,
21 HybridProcessingConfig,
22)
24logger = LoggingConfig.get_logger(__name__)
27class HybridSearchEngine(HybridEngineAPI):
28 """Refactored hybrid search service using modular components."""
30 def __init__(
31 self,
32 qdrant_client: Any,
33 openai_client: Any,
34 collection_name: str,
35 vector_weight: float = DEFAULT_VECTOR_WEIGHT,
36 keyword_weight: float = DEFAULT_KEYWORD_WEIGHT,
37 metadata_weight: float = DEFAULT_METADATA_WEIGHT,
38 min_score: float = DEFAULT_MIN_SCORE,
39 # Enhanced search parameters
40 knowledge_graph: Any = None,
41 enable_intent_adaptation: bool = True,
42 search_config: SearchConfig | None = None,
43 processing_config: HybridProcessingConfig | None = None,
44 ):
45 """Initialize the hybrid search service.
47 Args:
48 qdrant_client: Qdrant client instance
49 openai_client: OpenAI client instance
50 collection_name: Name of the Qdrant collection
51 vector_weight: Weight for vector search scores (0-1)
52 keyword_weight: Weight for keyword search scores (0-1)
53 metadata_weight: Weight for metadata-based scoring (0-1)
54 min_score: Minimum combined score threshold
55 knowledge_graph: Optional knowledge graph for integration
56 enable_intent_adaptation: Enable intent-aware adaptive search
57 search_config: Optional search configuration for performance optimization
58 processing_config: Optional processing configuration controlling hybrid processing behaviors
59 """
60 self.qdrant_client = qdrant_client
61 # Use a property-backed attribute so test fixtures can inject a client after init
62 self._openai_client = None
63 # Assign via setter to allow future propagation when pipeline is ready
64 self.openai_client = openai_client
65 self.collection_name = collection_name
66 self.vector_weight = vector_weight
67 self.keyword_weight = keyword_weight
68 self.metadata_weight = metadata_weight
69 self.min_score = min_score
70 self.logger = LoggingConfig.get_logger(__name__)
72 # Centralized initialization via builder
73 from .components.builder import initialize_engine_components
74 from .models import HybridProcessingConfig as _HPC
76 effective_processing_config = processing_config or _HPC()
77 initialize_engine_components(
78 self,
79 qdrant_client=qdrant_client,
80 openai_client=openai_client,
81 collection_name=collection_name,
82 vector_weight=vector_weight,
83 keyword_weight=keyword_weight,
84 metadata_weight=metadata_weight,
85 min_score=min_score,
86 knowledge_graph=knowledge_graph,
87 enable_intent_adaptation=enable_intent_adaptation,
88 search_config=search_config,
89 processing_config=effective_processing_config,
90 )
92 @property
93 def openai_client(self) -> Any:
94 return self._openai_client
96 @openai_client.setter
97 def openai_client(self, client: Any) -> None:
98 # Store locally
99 self._openai_client = client
100 # Best-effort propagate to vector search service and prefer explicit client over provider
101 try:
102 vss = getattr(self, "vector_search_service", None)
103 if vss is not None:
104 try:
105 vss.openai_client = client
106 if client is not None:
107 # Prefer explicit OpenAI client; disable provider to avoid NotImplemented stubs
108 vss.embeddings_provider = None
109 except Exception:
110 pass
111 except Exception:
112 pass
114 async def search(self, *args, **kwargs): # type: ignore[override]
115 try:
116 return await super().search(*args, **kwargs)
117 except Exception as e:
118 self.logger.error(
119 "Error in hybrid search", error=str(e), query=kwargs.get("query")
120 )
121 raise
123 # All other public and internal methods are provided by HybridEngineAPI