1010from urllib .parse import quote
1111
1212from .engine .hybrid_search import BM25 , reciprocal_rank_fusion
13- from .engine .quality_gate import (
14- SearchQualityGate ,
15- SearchMetaLearner ,
16- compute_search_confidence ,
17- )
13+ from .engine .quality_gate import SearchQualityGate , SearchMetaLearner , compute_search_confidence
1814
1915logger = logging .getLogger (__name__ )
2016
@@ -104,13 +100,18 @@ def _resolve_fused_docs(
104100 def _run_hybrid_rerank (
105101 self , store_name : str , query : str , semantic_results : List
106102 ) -> Tuple [List [Dict [str , Any ]], float ]:
107- """BM25 + ChronosGrid semantic -> RRF -> resolved docs + confidence score."""
103+ """BM25 + ChronosGrid semantic -> RRF -> resolved docs + confidence score.
104+
105+ Returns:
106+ (resolved_docs, confidence) where confidence is rank-divergence-gated [0, 1].
107+ """
108108 if store_name not in self ._bm25_indices or store_name in self ._bm25_dirty :
109109 self ._rebuild_bm25 (store_name )
110110
111111 # alpha->0 (keyword-dominant): expand BM25 pool; alpha->1: contract it.
112112 alpha = (getattr (self , "_meta_alpha" , {})).get (store_name , 0.5 )
113- bm25_top_k = int (self .config .max_sources * 2 * max (0.5 , 1.5 - alpha ))
113+ bm25_multiplier = max (0.5 , 1.5 - alpha ) # alpha=0.2->1.3x, 0.5->1.0x, 0.8->0.7x
114+ bm25_top_k = max (1 , int (self .config .max_sources * 2 * bm25_multiplier ))
114115
115116 bm25_ranked = self ._collect_bm25_ranked (store_name , query , bm25_top_k )
116117 sem_ranked = self ._collect_sem_ranked (store_name , semantic_results )
@@ -119,10 +120,11 @@ def _run_hybrid_rerank(
119120 [sem_ranked , bm25_ranked ], k = 60 , top_k = self .config .max_sources
120121 )
121122
123+ bm25_uri_set = {r ["id" ] for r in bm25_ranked }
124+ sem_uri_set = {r ["id" ] for r in sem_ranked }
122125 raw_score = fused [0 ]["score" ] if fused else 0.0
123- confidence = compute_search_confidence (
124- raw_score , {r ["id" ] for r in bm25_ranked }, {r ["id" ] for r in sem_ranked }
125- )
126+ confidence = compute_search_confidence (raw_score , bm25_uri_set , sem_uri_set )
127+
126128 return self ._resolve_fused_docs (store_name , fused ), confidence
127129
128130 # ------------------------------------------------------------------
@@ -184,28 +186,22 @@ def _local_search(
184186 }
185187
186188 if not docs :
187- res = {
189+ result = {
188190 "status" : "success" ,
189191 "answer" : "No documents indexed yet." ,
190192 "sources" : [],
191193 ** base ,
192194 }
193195 if search_mode in ["semantic" , "hybrid" , "multimodal" ]:
194- res ["semantic_results" ] = semantic_results or []
195- return res
196+ result ["semantic_results" ] = semantic_results or []
197+ return result
196198
197- quality_gate : SearchQualityGate = getattr (
198- self , "_quality_gate" , SearchQualityGate ()
199- )
200- meta_learner : SearchMetaLearner = getattr (
201- self , "_meta_learner" , SearchMetaLearner ()
202- )
199+ quality_gate : SearchQualityGate = getattr (self , "_quality_gate" , SearchQualityGate ())
200+ meta_learner : SearchMetaLearner = getattr (self , "_meta_learner" , SearchMetaLearner ())
203201
204202 # Hybrid: BM25 + semantic RRF with quality gate
205203 if search_mode == "hybrid" and semantic_results :
206- fused_docs , confidence = self ._run_hybrid_rerank (
207- store_name , query , semantic_results
208- )
204+ fused_docs , confidence = self ._run_hybrid_rerank (store_name , query , semantic_results )
209205 verdict = quality_gate .evaluate (confidence )
210206
211207 if fused_docs :
@@ -274,9 +270,7 @@ def _local_search(
274270 result ["semantic_results" ] = semantic_results or []
275271 return result
276272
277- def _run_meta_adapt (
278- self , store_name : str , meta_learner : "SearchMetaLearner"
279- ) -> None :
273+ def _run_meta_adapt (self , store_name : str , meta_learner : "SearchMetaLearner" ) -> None :
280274 """Apply MetaLearner alpha recommendation when adaptation cycle triggers."""
281275 if not meta_learner .should_adapt ():
282276 return
@@ -288,10 +282,7 @@ def _run_meta_adapt(
288282 self ._meta_alpha [store_name ] = new_alpha
289283 logger .info (
290284 "[QualityGate] store=%s alpha %.3f -> %.3f trend=%s" ,
291- store_name ,
292- current ,
293- new_alpha ,
294- meta_learner .store_trend (store_name ),
285+ store_name , current , new_alpha , meta_learner .store_trend (store_name ),
295286 )
296287
297288 # ------------------------------------------------------------------
@@ -322,8 +313,7 @@ def _resolve_semantic_sources(
322313 if not resolved :
323314 return None
324315 snippets = [
325- self ._build_snippet (d .get ("content" , "" ), query )
326- or d .get ("content" , "" )[:200 ]
316+ self ._build_snippet (d .get ("content" , "" ), query ) or d .get ("content" , "" )[:200 ]
327317 for d in resolved [:5 ]
328318 ]
329319 answer = " " .join (s for s in snippets if s ) or (
0 commit comments