Ship floating launcher, automatic discovery and bounded media search improvements

This commit is contained in:
karl committed 2026-10-14 11:44:15 +02:00
1 parent 01279a6b25
commit 521966a2ac
43 files changed
+3165 -267

No files matched your search

+83 -2
View File
@@ -11,6 +11,7 @@ pub use speech::{speech_policy_key, BudgetedSpeechProvider, SpeechExcerpt, Speec
mod prepared;
pub use prepared::PreparedGraphs;
mod pipeline;
mod relevance;
mod routing;
pub use routing::SemanticOrigin;
mod store;
@@ -280,6 +281,12 @@ pub struct HybridSearch {
}
impl HybridSearch {
/// Prepare storage before background discovery starts. No model is loaded.
pub fn prepare_database(db: impl AsRef<Path>) -> Result<()> {
let _ = SearchIndex::open(&db)?;
let _ = Store::open(db.as_ref(), GEMMA_DIMENSIONS)?;
Ok(())
}
/// Attach shared admission without replacing the warmed model or query LRU.
pub fn with_budget(
mut self,
@@ -485,7 +492,11 @@ impl HybridSearch {
/// Execute real query inference and load the active ANN generation before
/// foreground use. Call on a background worker after assets are verified.
pub fn warmup(&mut self) -> Result<()> {
self.lexical.prewarm()?;
if let Err(error) = self.lexical.prewarm() {
if !is_busy_sqlite_error(&error) {
return Err(error);
}
}
let vector = self.embedder.embed_query("warm up local file search")?;
let _ = self.store.search(
&vector,
@@ -580,8 +591,14 @@ impl HybridSearch {
promote_identifiers(query.trim(), &mut lexical)
});
let vector = self.query_vector(query, options.code_only, trace, parent)?;
let semantic =
let mut semantic =
self.routed_semantic_traced(query, &vector, candidates, options, trace, parent)?;
trace.timed("hybrid.semantic_shortlist", parent, |trace, span| {
trace.counter(span, "candidates_before", semantic.len() as u64);
let removed = relevance::prune_semantic_tail(&mut semantic);
trace.counter(span, "tail_candidates_pruned", removed as u64);
trace.counter(span, "candidates_after", semantic.len() as u64);
});
let results = trace.timed("hybrid.rank_fusion", parent, |trace, span| {
trace.counter(span, "lexical_files", lexical.len() as u64);
trace.counter(span, "semantic_files", semantic.len() as u64);
@@ -684,6 +701,11 @@ impl HybridSearch {
}
}
fn is_busy_sqlite_error(error: &anyhow::Error) -> bool {
matches!(error.downcast_ref::<rusqlite::Error>(), Some(rusqlite::Error::SqliteFailure(error, _))
if matches!(error.code, rusqlite::ErrorCode::DatabaseBusy | rusqlite::ErrorCode::DatabaseLocked))
}
fn bounded_title(title: &str) -> &str {
let mut end = title.len().min(512);
while !title.is_char_boundary(end) {
@@ -995,3 +1017,62 @@ mod tests {
#[cfg(test)]
mod coverage_tests;
#[cfg(test)]
mod startup_lock_tests {
use super::*;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
struct QueryProbe(Arc<AtomicUsize>);
impl EmbeddingProvider for QueryProbe {
fn fingerprint(&self) -> &str {
"query-path-probe"
}
fn dimensions(&self) -> usize {
GEMMA_DIMENSIONS
}
fn split_windows(&mut self, _: &str, _: &str) -> Result<Vec<TextWindow>> {
anyhow::bail!("unused")
}
fn embed_documents(&mut self, _: &[&str], _: &str) -> Result<Vec<Vec<f32>>> {
anyhow::bail!("unused")
}
fn embed_query(&mut self, _: &str) -> Result<Vec<f32>> {
self.0.fetch_add(1, Ordering::SeqCst);
anyhow::bail!("Reached query inference; no fabricated vector")
}
}
#[test]
fn optional_lexical_cache_busy_does_not_abort_query_preparation() -> Result<()> {
let temporary = tempfile::tempdir()?;
let db = temporary.path().join("index.db");
HybridSearch::prepare_database(&db)?;
let mut writer = rusqlite::Connection::open(&db)?;
let transaction =
writer.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
let calls = Arc::new(AtomicUsize::new(0));
let mut service = HybridSearch::with_provider(&db, Box::new(QueryProbe(calls.clone())))?;
assert!(service
.warmup()
.unwrap_err()
.to_string()
.contains("Reached query inference"));
assert_eq!(calls.load(Ordering::SeqCst), 1);
transaction.rollback()?;
Ok(())
}
#[test]
fn non_busy_sqlite_cache_errors_remain_visible() -> Result<()> {
let temporary = tempfile::tempdir()?;
let db = temporary.path().join("index.db");
HybridSearch::prepare_database(&db)?;
let calls = Arc::new(AtomicUsize::new(0));
let mut service = HybridSearch::with_provider(&db, Box::new(QueryProbe(calls.clone())))?;
rusqlite::Connection::open(&db)?.execute("DROP TABLE name_statistics_state", [])?;
assert!(service.warmup().is_err());
assert_eq!(calls.load(Ordering::SeqCst), 0);
Ok(())
}
}