use colored::Colorize;
use velesdb_core::Database;
use crate::repl_commands::{parse_flag, CommandResult};
fn print_search_results(results: &[velesdb_core::SearchResult], title: &str, extra: &str) {
if results.is_empty() {
println!("No results found.\n");
} else {
println!(
"\n{} ({} results{})\n",
title.bold().underline(),
results.len(),
extra
);
for r in results {
println!(
" id={} score={:.6}",
r.point.id.to_string().cyan(),
r.score
);
}
println!();
}
}
fn parse_sparse_json(json: &str) -> Result<velesdb_core::sparse_index::SparseVector, String> {
let pairs: Vec<(u32, f32)> = serde_json::from_str(json).map_err(|e| {
format!("Invalid sparse vector JSON: {e}\nExpected format: [[index, weight], ...]")
})?;
Ok(velesdb_core::sparse_index::SparseVector::new(pairs))
}
pub(crate) fn cmd_sparse_search(db: &Database, parts: &[&str]) -> CommandResult {
if parts.len() < 4 {
println!("Usage: .sparse-search <collection> <index_name> <json_sparse_vector> [k]\n");
println!(
" JSON sparse vector format: {}",
"[[index, weight], ...]".italic().white()
);
println!(
" Example: {} my_col \"\" [[0,1.5],[3,0.8]] 10\n",
".sparse-search".yellow()
);
return CommandResult::Continue;
}
let name = parts[1];
let index_name = parts[2];
let sparse_json = parts[3];
let k: usize = parts.get(4).and_then(|s| s.parse().ok()).unwrap_or(10);
let sparse_vec = match parse_sparse_json(sparse_json) {
Ok(v) => v,
Err(e) => return CommandResult::Error(e),
};
match db.get_vector_collection(name) {
Some(col) => match col.sparse_search(&sparse_vec, k, index_name) {
Ok(results) => print_search_results(&results, "Sparse Search Results", ""),
Err(e) => return CommandResult::Error(format!("Sparse search error: {e}")),
},
None => {
return CommandResult::Error(format!("Collection '{name}' not found"));
}
}
CommandResult::Continue
}
#[allow(clippy::too_many_lines)]
pub(crate) fn cmd_hybrid_sparse(db: &Database, parts: &[&str]) -> CommandResult {
if parts.len() < 4 {
println!(
"Usage: .hybrid-sparse <collection> <dense_json> <sparse_json> [k] \
[--strategy rrf|average|max|weighted|relative_score] [--index <name>]\n"
);
println!(" Dense vector: {}", "[0.1, 0.2, ...]".italic().white());
println!(
" Sparse vector: {}",
"[[index, weight], ...]".italic().white()
);
println!(
" Example: {} docs [0.1,0.2,0.3,0.4] [[0,1.5],[3,0.8]] 10 --strategy rrf\n",
".hybrid-sparse".yellow()
);
return CommandResult::Continue;
}
let name = parts[1];
let dense_json = parts[2];
let sparse_json = parts[3];
let k: usize = parts.get(4).and_then(|s| s.parse().ok()).unwrap_or(10);
let strategy_str = parse_flag(parts, "--strategy").unwrap_or_else(|| "rrf".to_string());
let index_name = parse_flag(parts, "--index").unwrap_or_default();
let dense_vector: Vec<f32> = match serde_json::from_str(dense_json) {
Ok(v) => v,
Err(e) => {
return CommandResult::Error(format!(
"Invalid dense vector JSON: {e}\nExpected format: [0.1, 0.2, ...]"
));
}
};
let sparse_vec = match parse_sparse_json(sparse_json) {
Ok(v) => v,
Err(e) => return CommandResult::Error(e),
};
let strategy = match strategy_str.as_str() {
"rrf" => velesdb_core::FusionStrategy::rrf_default(),
"average" => velesdb_core::FusionStrategy::Average,
"max" | "maximum" => velesdb_core::FusionStrategy::Maximum,
"weighted" => velesdb_core::FusionStrategy::Weighted {
avg_weight: 0.5,
max_weight: 0.3,
hit_weight: 0.2,
},
"relative_score" | "rsf" => velesdb_core::FusionStrategy::RelativeScore {
dense_weight: 0.5,
sparse_weight: 0.5,
},
other => {
return CommandResult::Error(format!(
"Unknown fusion strategy: '{other}'. \
Use rrf, average, max, weighted, or relative_score."
));
}
};
match db.get_vector_collection(name) {
Some(col) => {
match col.hybrid_sparse_search(&dense_vector, &sparse_vec, k, &index_name, &strategy) {
Ok(results) => {
let extra = format!(", strategy={}", strategy_str.cyan());
print_search_results(&results, "Hybrid Sparse Search Results", &extra);
}
Err(e) => return CommandResult::Error(format!("Hybrid search error: {e}")),
}
}
None => {
return CommandResult::Error(format!("Collection '{name}' not found"));
}
}
CommandResult::Continue
}
pub(crate) fn cmd_agent(_parts: &[&str]) -> CommandResult {
println!(
"\n{}\n\n Agent memory management is available via the {} or {}.\n \
See docs/guides/ for usage examples.\n",
"Agent Memory".bold().underline(),
"Python/TypeScript SDK".cyan(),
"REST API".cyan(),
);
CommandResult::Continue
}
pub(crate) fn cmd_guardrails() -> CommandResult {
let limits = velesdb_core::guardrails::QueryLimits::default();
println!("\n{}", "Query Guard-Rails Configuration".bold().underline());
println!();
println!(" {} {} ms", "Timeout:".cyan(), limits.timeout_ms);
println!(" {} {}", "Max Depth:".cyan(), limits.max_depth);
println!(" {} {}", "Max Cardinality:".cyan(), limits.max_cardinality);
println!(
" {} {} bytes ({:.0} MB)",
"Memory Limit:".cyan(),
limits.memory_limit_bytes,
limits.memory_limit_bytes as f64 / (1024.0 * 1024.0)
);
println!(" {} {} qps", "Rate Limit:".cyan(), limits.rate_limit_qps);
println!(
" {} {} failures",
"Circuit Breaker Threshold:".cyan(),
limits.circuit_failure_threshold
);
println!(
" {} {} s",
"Circuit Recovery:".cyan(),
limits.circuit_recovery_seconds
);
println!();
println!(
" {}",
"These are the built-in defaults (read-only in the REPL). A running \
velesdb-server can adjust them at runtime via its PUT /guardrails \
endpoint; the REPL has no '.guardrails set' command."
.dimmed()
);
println!();
CommandResult::Continue
}