sqlite_graphrag/commands/hybrid_search/
args.rs1use crate::cli::MemoryType;
4use crate::errors::AppError;
5use crate::output::JsonOutputFormat;
6
7#[derive(clap::Args)]
14#[command(after_long_help = "EXAMPLES:\n \
15 # Basic hybrid search combining FTS5 + vector via RRF\n \
16 sqlite-graphrag hybrid-search \"postgres migration deadlock\" --k 10\n\n \
17 # Tune RRF weights to favor keyword matches over semantic similarity\n \
18 sqlite-graphrag hybrid-search \"jwt auth\" --weight-fts 1.5 --weight-vec 0.5 --k 5\n\n \
19 # Add graph traversal matches (entities connected to top results)\n \
20 sqlite-graphrag hybrid-search \"frontend architecture\" --with-graph --k 10\n\n \
21 # Graph traversal with custom depth and minimum edge weight\n \
22 sqlite-graphrag hybrid-search \"auth design\" --with-graph --max-hops 3 --min-weight 0.5 --k 10\n\n \
23NOTES:\n \
24 --with-graph enables entity graph traversal seeded by the top RRF results.\n \
25 Graph matches appear in the `graph_matches` array (separate from `results`).\n \
26 Without --with-graph, `graph_matches` is always empty.")]
27pub struct HybridSearchArgs {
28 #[arg(
29 allow_hyphen_values = true,
30 help = "Hybrid search query (vector KNN + FTS5 BM25 fused via RRF)"
31 )]
32 pub query: String,
34 #[arg(short = 'k', long, aliases = ["limit", "top-k"], default_value = "10", value_parser = crate::parsers::parse_k_range)]
39 pub k: usize,
40 #[arg(long, default_value = "60")]
42 pub rrf_k: u32,
43 #[arg(long, default_value = "1.0")]
45 pub weight_vec: f32,
46 #[arg(long, default_value = "1.0")]
48 pub weight_fts: f32,
49 #[arg(long, value_enum)]
53 pub r#type: Option<MemoryType>,
54 #[arg(long)]
56 pub namespace: Option<String>,
57 #[arg(long)]
59 pub with_graph: bool,
60 #[arg(long, value_name = "N")]
69 pub max_graph_results: Option<usize>,
70 #[arg(long, help = "Skip live query embedding; serve FTS5 BM25 only")]
73 pub fallback_fts_only: bool,
74 #[arg(long, value_parser = crate::parsers::parse_hops_range_u32)]
76 pub max_hops: Option<u32>,
77 #[arg(long)]
79 pub min_weight: Option<f64>,
80 #[arg(long, value_enum, default_value_t = JsonOutputFormat::Json)]
82 pub format: JsonOutputFormat,
83 #[arg(long)]
85 pub db: Option<String>,
86 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
88 pub json: bool,
89}
90
91impl HybridSearchArgs {
92 pub(super) fn validate_graph_flags(&self) -> Result<(), AppError> {
97 if !self.with_graph {
98 if self.max_hops.is_some() {
99 return Err(AppError::Validation(
100 "--max-hops requires --with-graph to be active".to_string(),
101 ));
102 }
103 if self.min_weight.is_some() {
104 return Err(AppError::Validation(
105 "--min-weight requires --with-graph to be active".to_string(),
106 ));
107 }
108 }
109 Ok(())
110 }
111}