@@ -27,6 +27,10 @@ pub struct PilotConfig {
2727 pub guide_at_backtrack : bool ,
2828 /// Optional path to custom prompt templates.
2929 pub prompt_template_path : Option < String > ,
30+ /// Pre-filtering configuration for reducing candidates before Pilot.
31+ pub prefilter : PrefilterConfig ,
32+ /// Binary pruning configuration for quick relevance filtering.
33+ pub prune : PruneConfig ,
3034}
3135
3236impl Default for PilotConfig {
@@ -38,6 +42,8 @@ impl Default for PilotConfig {
3842 guide_at_start : true ,
3943 guide_at_backtrack : true ,
4044 prompt_template_path : None ,
45+ prefilter : PrefilterConfig :: default ( ) ,
46+ prune : PruneConfig :: default ( ) ,
4147 }
4248 }
4349}
@@ -51,7 +57,7 @@ impl PilotConfig {
5157 }
5258 }
5359
54- /// Create a high-quality config (more LLM calls).
60+ /// Create a high-quality config (more LLM calls, generous pre-filter ).
5561 pub fn high_quality ( ) -> Self {
5662 Self {
5763 mode : PilotMode :: Aggressive ,
@@ -71,10 +77,20 @@ impl PilotConfig {
7177 guide_at_start : true ,
7278 guide_at_backtrack : true ,
7379 prompt_template_path : None ,
80+ prefilter : PrefilterConfig {
81+ threshold : 20 ,
82+ max_to_pilot : 20 ,
83+ enabled : true ,
84+ } ,
85+ prune : PruneConfig {
86+ enabled : true ,
87+ threshold : 25 ,
88+ min_keep : 5 ,
89+ } ,
7490 }
7591 }
7692
77- /// Create a low-cost config (fewer LLM calls).
93+ /// Create a low-cost config (fewer LLM calls, aggressive pre-filter ).
7894 pub fn low_cost ( ) -> Self {
7995 Self {
8096 mode : PilotMode :: Conservative ,
@@ -94,13 +110,33 @@ impl PilotConfig {
94110 guide_at_start : false ,
95111 guide_at_backtrack : true ,
96112 prompt_template_path : None ,
113+ prefilter : PrefilterConfig {
114+ threshold : 8 ,
115+ max_to_pilot : 8 ,
116+ enabled : true ,
117+ } ,
118+ prune : PruneConfig {
119+ enabled : true ,
120+ threshold : 12 ,
121+ min_keep : 2 ,
122+ } ,
97123 }
98124 }
99125
100126 /// Create a pure algorithm config (no LLM calls).
101127 pub fn algorithm_only ( ) -> Self {
102128 Self {
103129 mode : PilotMode :: AlgorithmOnly ,
130+ prefilter : PrefilterConfig {
131+ threshold : 15 ,
132+ max_to_pilot : 15 ,
133+ enabled : false ,
134+ } ,
135+ prune : PruneConfig {
136+ enabled : false ,
137+ threshold : 20 ,
138+ min_keep : 3 ,
139+ } ,
104140 ..Default :: default ( )
105141 }
106142 }
@@ -228,6 +264,88 @@ impl InterventionConfig {
228264 }
229265}
230266
267+ /// Configuration for NodeScorer-based pre-filtering before Pilot scoring.
268+ ///
269+ /// When a node has many children, sending all to the LLM is wasteful.
270+ /// Pre-filtering uses cheap NodeScorer (keyword/BM25) to narrow the
271+ /// candidate set before expensive Pilot (LLM) scoring.
272+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
273+ pub struct PrefilterConfig {
274+ /// Minimum number of candidates to trigger pre-filtering.
275+ ///
276+ /// When `candidates.len()` exceeds this threshold, NodeScorer
277+ /// pre-filters before sending to Pilot.
278+ /// Default: 15.
279+ pub threshold : usize ,
280+
281+ /// Maximum number of candidates passed to Pilot after pre-filtering.
282+ ///
283+ /// NodeScorer's top-N are kept; the rest get NodeScorer-only scores.
284+ /// Default: 15.
285+ pub max_to_pilot : usize ,
286+
287+ /// Whether pre-filtering is enabled.
288+ /// Default: true.
289+ pub enabled : bool ,
290+ }
291+
292+ impl Default for PrefilterConfig {
293+ fn default ( ) -> Self {
294+ Self {
295+ threshold : 15 ,
296+ max_to_pilot : 15 ,
297+ enabled : true ,
298+ }
299+ }
300+ }
301+
302+ impl PrefilterConfig {
303+ /// Check if pre-filtering should be applied given the candidate count.
304+ pub fn should_prefilter ( & self , candidate_count : usize ) -> bool {
305+ self . enabled && candidate_count > self . threshold
306+ }
307+ }
308+
309+ /// Configuration for binary pruning before full Pilot scoring.
310+ ///
311+ /// After P2 pre-filtering, if candidates still exceed this threshold,
312+ /// a lightweight LLM call asks "which are relevant?" before the full
313+ /// scoring call. This reduces the number of candidates that receive
314+ /// expensive detailed scoring.
315+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
316+ pub struct PruneConfig {
317+ /// Whether binary pruning is enabled.
318+ /// Default: true.
319+ pub enabled : bool ,
320+
321+ /// Trigger threshold — binary prune activates when the candidate
322+ /// count (after P2 pre-filtering) exceeds this value.
323+ /// Default: 20.
324+ pub threshold : usize ,
325+
326+ /// Minimum candidates to keep after pruning, even if LLM says
327+ /// fewer are relevant. Prevents over-aggressive pruning.
328+ /// Default: 3.
329+ pub min_keep : usize ,
330+ }
331+
332+ impl Default for PruneConfig {
333+ fn default ( ) -> Self {
334+ Self {
335+ enabled : true ,
336+ threshold : 20 ,
337+ min_keep : 3 ,
338+ }
339+ }
340+ }
341+
342+ impl PruneConfig {
343+ /// Check if binary pruning should be applied given the candidate count.
344+ pub fn should_prune ( & self , candidate_count : usize ) -> bool {
345+ self . enabled && candidate_count > self . threshold
346+ }
347+ }
348+
231349#[ cfg( test) ]
232350mod tests {
233351 use super :: * ;
@@ -269,11 +387,68 @@ mod tests {
269387 fn test_pilot_config_presets ( ) {
270388 let high = PilotConfig :: high_quality ( ) ;
271389 assert_eq ! ( high. mode, PilotMode :: Aggressive ) ;
390+ assert ! ( high. prefilter. enabled) ;
391+ assert_eq ! ( high. prefilter. threshold, 20 ) ;
272392
273393 let low = PilotConfig :: low_cost ( ) ;
274394 assert_eq ! ( low. mode, PilotMode :: Conservative ) ;
395+ assert ! ( low. prefilter. enabled) ;
396+ assert_eq ! ( low. prefilter. threshold, 8 ) ;
275397
276398 let algo = PilotConfig :: algorithm_only ( ) ;
277399 assert_eq ! ( algo. mode, PilotMode :: AlgorithmOnly ) ;
400+ assert ! ( !algo. prefilter. enabled) ;
401+ }
402+
403+ #[ test]
404+ fn test_prefilter_config_default ( ) {
405+ let cfg = PrefilterConfig :: default ( ) ;
406+ assert ! ( cfg. enabled) ;
407+ assert_eq ! ( cfg. threshold, 15 ) ;
408+ assert_eq ! ( cfg. max_to_pilot, 15 ) ;
409+ }
410+
411+ #[ test]
412+ fn test_prefilter_should_prefilter ( ) {
413+ let cfg = PrefilterConfig :: default ( ) ;
414+ assert ! ( !cfg. should_prefilter( 15 ) ) ; // at threshold
415+ assert ! ( !cfg. should_prefilter( 10 ) ) ; // below
416+ assert ! ( cfg. should_prefilter( 16 ) ) ; // above
417+
418+ let disabled = PrefilterConfig { enabled : false , ..Default :: default ( ) } ;
419+ assert ! ( !disabled. should_prefilter( 100 ) ) ;
420+ }
421+
422+ #[ test]
423+ fn test_prune_config_default ( ) {
424+ let cfg = PruneConfig :: default ( ) ;
425+ assert ! ( cfg. enabled) ;
426+ assert_eq ! ( cfg. threshold, 20 ) ;
427+ assert_eq ! ( cfg. min_keep, 3 ) ;
428+ }
429+
430+ #[ test]
431+ fn test_prune_should_prune ( ) {
432+ let cfg = PruneConfig :: default ( ) ;
433+ assert ! ( !cfg. should_prune( 20 ) ) ; // at threshold
434+ assert ! ( !cfg. should_prune( 15 ) ) ; // below
435+ assert ! ( cfg. should_prune( 21 ) ) ; // above
436+
437+ let disabled = PruneConfig { enabled : false , ..Default :: default ( ) } ;
438+ assert ! ( !disabled. should_prune( 100 ) ) ;
439+ }
440+
441+ #[ test]
442+ fn test_pilot_config_presets_prune ( ) {
443+ let high = PilotConfig :: high_quality ( ) ;
444+ assert ! ( high. prune. enabled) ;
445+ assert_eq ! ( high. prune. threshold, 25 ) ;
446+
447+ let low = PilotConfig :: low_cost ( ) ;
448+ assert ! ( low. prune. enabled) ;
449+ assert_eq ! ( low. prune. threshold, 12 ) ;
450+
451+ let algo = PilotConfig :: algorithm_only ( ) ;
452+ assert ! ( !algo. prune. enabled) ;
278453 }
279454}
0 commit comments