@@ -337,6 +337,163 @@ pub struct StrategyConfig {
337337 /// Low similarity threshold for "explore" decision.
338338 #[ serde( default = "default_low_similarity_threshold" ) ]
339339 pub low_similarity_threshold : f32 ,
340+
341+ /// Hybrid strategy configuration (BM25 + LLM refinement).
342+ #[ serde( default ) ]
343+ pub hybrid : HybridStrategyConfig ,
344+
345+ /// Cross-document strategy configuration.
346+ #[ serde( default ) ]
347+ pub cross_document : CrossDocumentStrategyConfig ,
348+
349+ /// Page-range strategy configuration.
350+ #[ serde( default ) ]
351+ pub page_range : PageRangeStrategyConfig ,
352+ }
353+
354+ /// Hybrid strategy configuration (BM25 pre-filter + LLM refinement).
355+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
356+ pub struct HybridStrategyConfig {
357+ /// Enable hybrid strategy.
358+ #[ serde( default = "default_true" ) ]
359+ pub enabled : bool ,
360+
361+ /// BM25 pre-filter: keep top N% of candidates.
362+ #[ serde( default = "default_pre_filter_ratio" ) ]
363+ pub pre_filter_ratio : f32 ,
364+
365+ /// Minimum candidates to pass to LLM.
366+ #[ serde( default = "default_min_candidates" ) ]
367+ pub min_candidates : usize ,
368+
369+ /// Maximum candidates for LLM refinement.
370+ #[ serde( default = "default_max_candidates" ) ]
371+ pub max_candidates : usize ,
372+
373+ /// BM25 score for auto-accept (skip LLM).
374+ #[ serde( default = "default_auto_accept_threshold" ) ]
375+ pub auto_accept_threshold : f32 ,
376+
377+ /// BM25 score for auto-reject (skip LLM).
378+ #[ serde( default = "default_auto_reject_threshold" ) ]
379+ pub auto_reject_threshold : f32 ,
380+
381+ /// Weight for BM25 score in final scoring.
382+ #[ serde( default = "default_bm25_weight" ) ]
383+ pub bm25_weight : f32 ,
384+
385+ /// Weight for LLM score in final scoring.
386+ #[ serde( default = "default_llm_weight" ) ]
387+ pub llm_weight : f32 ,
388+ }
389+
390+ fn default_true ( ) -> bool { true }
391+ fn default_pre_filter_ratio ( ) -> f32 { 0.3 }
392+ fn default_min_candidates ( ) -> usize { 2 }
393+ fn default_max_candidates ( ) -> usize { 5 }
394+ fn default_auto_accept_threshold ( ) -> f32 { 0.85 }
395+ fn default_auto_reject_threshold ( ) -> f32 { 0.15 }
396+ fn default_bm25_weight ( ) -> f32 { 0.4 }
397+ fn default_llm_weight ( ) -> f32 { 0.6 }
398+
399+ impl Default for HybridStrategyConfig {
400+ fn default ( ) -> Self {
401+ Self {
402+ enabled : true ,
403+ pre_filter_ratio : default_pre_filter_ratio ( ) ,
404+ min_candidates : default_min_candidates ( ) ,
405+ max_candidates : default_max_candidates ( ) ,
406+ auto_accept_threshold : default_auto_accept_threshold ( ) ,
407+ auto_reject_threshold : default_auto_reject_threshold ( ) ,
408+ bm25_weight : default_bm25_weight ( ) ,
409+ llm_weight : default_llm_weight ( ) ,
410+ }
411+ }
412+ }
413+
414+ /// Cross-document strategy configuration.
415+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
416+ pub struct CrossDocumentStrategyConfig {
417+ /// Enable cross-document strategy.
418+ #[ serde( default = "default_true" ) ]
419+ pub enabled : bool ,
420+
421+ /// Maximum documents to search.
422+ #[ serde( default = "default_max_documents" ) ]
423+ pub max_documents : usize ,
424+
425+ /// Maximum results per document.
426+ #[ serde( default = "default_max_results_per_doc" ) ]
427+ pub max_results_per_doc : usize ,
428+
429+ /// Maximum total results.
430+ #[ serde( default = "default_max_total_results" ) ]
431+ pub max_total_results : usize ,
432+
433+ /// Minimum score threshold.
434+ #[ serde( default = "default_min_score" ) ]
435+ pub min_score : f32 ,
436+
437+ /// Merge strategy: TopK, BestPerDocument, WeightedByRelevance.
438+ #[ serde( default = "default_merge_strategy" ) ]
439+ pub merge_strategy : String ,
440+
441+ /// Search documents in parallel.
442+ #[ serde( default = "default_true" ) ]
443+ pub parallel_search : bool ,
444+ }
445+
446+ fn default_max_documents ( ) -> usize { 10 }
447+ fn default_max_results_per_doc ( ) -> usize { 3 }
448+ fn default_max_total_results ( ) -> usize { 10 }
449+ fn default_min_score ( ) -> f32 { 0.3 }
450+ fn default_merge_strategy ( ) -> String { "TopK" . to_string ( ) }
451+
452+ impl Default for CrossDocumentStrategyConfig {
453+ fn default ( ) -> Self {
454+ Self {
455+ enabled : true ,
456+ max_documents : default_max_documents ( ) ,
457+ max_results_per_doc : default_max_results_per_doc ( ) ,
458+ max_total_results : default_max_total_results ( ) ,
459+ min_score : default_min_score ( ) ,
460+ merge_strategy : default_merge_strategy ( ) ,
461+ parallel_search : true ,
462+ }
463+ }
464+ }
465+
466+ /// Page-range strategy configuration.
467+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
468+ pub struct PageRangeStrategyConfig {
469+ /// Enable page-range strategy.
470+ #[ serde( default = "default_true" ) ]
471+ pub enabled : bool ,
472+
473+ /// Include nodes that span across the boundary.
474+ #[ serde( default = "default_true" ) ]
475+ pub include_boundary_nodes : bool ,
476+
477+ /// Expand range by N pages for context.
478+ #[ serde( default ) ]
479+ pub expand_context_pages : usize ,
480+
481+ /// Minimum overlap ratio for node inclusion.
482+ #[ serde( default = "default_min_overlap_ratio" ) ]
483+ pub min_overlap_ratio : f32 ,
484+ }
485+
486+ fn default_min_overlap_ratio ( ) -> f32 { 0.1 }
487+
488+ impl Default for PageRangeStrategyConfig {
489+ fn default ( ) -> Self {
490+ Self {
491+ enabled : true ,
492+ include_boundary_nodes : true ,
493+ expand_context_pages : 0 ,
494+ min_overlap_ratio : default_min_overlap_ratio ( ) ,
495+ }
496+ }
340497}
341498
342499fn default_exploration_weight ( ) -> f32 {
@@ -362,6 +519,9 @@ impl Default for StrategyConfig {
362519 similarity_threshold : default_similarity_threshold ( ) ,
363520 high_similarity_threshold : default_high_similarity_threshold ( ) ,
364521 low_similarity_threshold : default_low_similarity_threshold ( ) ,
522+ hybrid : HybridStrategyConfig :: default ( ) ,
523+ cross_document : CrossDocumentStrategyConfig :: default ( ) ,
524+ page_range : PageRangeStrategyConfig :: default ( ) ,
365525 }
366526 }
367527}
@@ -453,5 +613,37 @@ mod tests {
453613 let config = StrategyConfig :: default ( ) ;
454614 assert ! ( ( config. exploration_weight - 1.414 ) . abs( ) < 0.001 ) ;
455615 assert_eq ! ( config. similarity_threshold, 0.5 ) ;
616+ assert ! ( config. hybrid. enabled) ;
617+ assert ! ( config. cross_document. enabled) ;
618+ assert ! ( config. page_range. enabled) ;
619+ }
620+
621+ #[ test]
622+ fn test_hybrid_strategy_config_defaults ( ) {
623+ let config = HybridStrategyConfig :: default ( ) ;
624+ assert ! ( config. enabled) ;
625+ assert ! ( ( config. pre_filter_ratio - 0.3 ) . abs( ) < f32 :: EPSILON ) ;
626+ assert_eq ! ( config. min_candidates, 2 ) ;
627+ assert_eq ! ( config. max_candidates, 5 ) ;
628+ assert ! ( ( config. auto_accept_threshold - 0.85 ) . abs( ) < f32 :: EPSILON ) ;
629+ }
630+
631+ #[ test]
632+ fn test_cross_document_strategy_config_defaults ( ) {
633+ let config = CrossDocumentStrategyConfig :: default ( ) ;
634+ assert ! ( config. enabled) ;
635+ assert_eq ! ( config. max_documents, 10 ) ;
636+ assert_eq ! ( config. max_results_per_doc, 3 ) ;
637+ assert_eq ! ( config. merge_strategy, "TopK" ) ;
638+ assert ! ( config. parallel_search) ;
639+ }
640+
641+ #[ test]
642+ fn test_page_range_strategy_config_defaults ( ) {
643+ let config = PageRangeStrategyConfig :: default ( ) ;
644+ assert ! ( config. enabled) ;
645+ assert ! ( config. include_boundary_nodes) ;
646+ assert_eq ! ( config. expand_context_pages, 0 ) ;
647+ assert ! ( ( config. min_overlap_ratio - 0.1 ) . abs( ) < f32 :: EPSILON ) ;
456648 }
457649}
0 commit comments