Ship floating launcher, automatic discovery and bounded media search improvements
This commit is contained in:
1 parent
01279a6b25
commit
521966a2ac
43 files changed
+3165
-267
No files matched your search
@@ -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(())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user