use crate::cli::MemoryType;
use crate::errors::AppError;
use crate::output::JsonOutputFormat;
#[derive(clap::Args)]
#[command(after_long_help = "EXAMPLES:\n \
# Basic hybrid search combining FTS5 + vector via RRF\n \
sqlite-graphrag hybrid-search \"postgres migration deadlock\" --k 10\n\n \
# Tune RRF weights to favor keyword matches over semantic similarity\n \
sqlite-graphrag hybrid-search \"jwt auth\" --weight-fts 1.5 --weight-vec 0.5 --k 5\n\n \
# Add graph traversal matches (entities connected to top results)\n \
sqlite-graphrag hybrid-search \"frontend architecture\" --with-graph --k 10\n\n \
# Graph traversal with custom depth and minimum edge weight\n \
sqlite-graphrag hybrid-search \"auth design\" --with-graph --max-hops 3 --min-weight 0.5 --k 10\n\n \
NOTES:\n \
--with-graph enables entity graph traversal seeded by the top RRF results.\n \
Graph matches appear in the `graph_matches` array (separate from `results`).\n \
Without --with-graph, `graph_matches` is always empty.")]
pub struct HybridSearchArgs {
#[arg(
allow_hyphen_values = true,
help = "Hybrid search query (vector KNN + FTS5 BM25 fused via RRF)"
)]
pub query: String,
#[arg(short = 'k', long, aliases = ["limit", "top-k"], default_value = "10", value_parser = crate::parsers::parse_k_range)]
pub k: usize,
#[arg(long, default_value = "60")]
pub rrf_k: u32,
#[arg(long, default_value = "1.0")]
pub weight_vec: f32,
#[arg(long, default_value = "1.0")]
pub weight_fts: f32,
#[arg(long, value_enum)]
pub r#type: Option<MemoryType>,
#[arg(long)]
pub namespace: Option<String>,
#[arg(long)]
pub with_graph: bool,
#[arg(long, value_name = "N")]
pub max_graph_results: Option<usize>,
#[arg(long, help = "Skip live query embedding; serve FTS5 BM25 only")]
pub fallback_fts_only: bool,
#[arg(long, value_parser = crate::parsers::parse_hops_range_u32)]
pub max_hops: Option<u32>,
#[arg(long)]
pub min_weight: Option<f64>,
#[arg(long, value_enum, default_value_t = JsonOutputFormat::Json)]
pub format: JsonOutputFormat,
#[arg(long)]
pub db: Option<String>,
#[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
pub json: bool,
}
impl HybridSearchArgs {
pub(super) fn validate_graph_flags(&self) -> Result<(), AppError> {
if !self.with_graph {
if self.max_hops.is_some() {
return Err(AppError::Validation(
"--max-hops requires --with-graph to be active".to_string(),
));
}
if self.min_weight.is_some() {
return Err(AppError::Validation(
"--min-weight requires --with-graph to be active".to_string(),
));
}
}
Ok(())
}
}