perf: optimize store batch serialization, zero-clone semantic search, and lock contention
This commit is contained in:
1 parent
3b08f45618
commit
35c802c1b8
6 files changed
+210
-314
No files matched your search
+1
-140
@@ -469,143 +469,4 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::embedding::{cosine_similarity, generate_embedding_async, generate_embeddings_async};
|
||||
|
||||
pub struct UnifiedSearchResult {
|
||||
pub id: String,
|
||||
pub doc_type: String,
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
pub score: f32,
|
||||
}
|
||||
|
||||
pub struct SearchService {
|
||||
state: Arc<MemoryState>,
|
||||
}
|
||||
|
||||
impl SearchService {
|
||||
pub fn new(state: Arc<MemoryState>) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
pub async fn semantic_search(
|
||||
&self,
|
||||
query: &str,
|
||||
filter_namespace: Option<&str>,
|
||||
limit: usize,
|
||||
) -> crate::error::Result<Vec<UnifiedSearchResult>> {
|
||||
let query_emb = generate_embedding_async(query.to_string())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let mut results = Vec::new();
|
||||
let mut cached_items = Vec::new();
|
||||
let mut uncached_texts = Vec::new();
|
||||
let mut uncached_meta = Vec::new();
|
||||
|
||||
self.state.code.snippets.read_with(|snips| {
|
||||
for snippet in snips.iter() {
|
||||
let title = snippet.name.clone();
|
||||
let desc = snippet.description.clone();
|
||||
if let Some(ref emb) = snippet.embedding {
|
||||
cached_items.push((title, "snippet".to_string(), desc, emb.clone()));
|
||||
} else if uncached_texts.len() < 50 {
|
||||
uncached_texts.push(format!(
|
||||
"{} {} {}",
|
||||
snippet.name, snippet.description, snippet.code
|
||||
));
|
||||
uncached_meta.push((title, "snippet".to_string(), desc));
|
||||
}
|
||||
}
|
||||
});
|
||||
self.state.read_graph(|graph| {
|
||||
for entity in graph.entities.values() {
|
||||
if let Some(ns) = filter_namespace {
|
||||
if entity.namespace != ns {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let title = entity.name.clone();
|
||||
let obs = entity.observations.join("; ");
|
||||
let desc = format!("{}: {}", entity.entity_type, obs);
|
||||
if let Some(ref emb) = entity.embedding {
|
||||
cached_items.push((title, "entity".to_string(), desc, emb.clone()));
|
||||
} else if uncached_texts.len() < 50 {
|
||||
uncached_texts.push(format!("{} {} {}", entity.name, entity.entity_type, obs));
|
||||
uncached_meta.push((title, "entity".to_string(), desc));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.state.code.error_fixes.read_with(|fixes| {
|
||||
for fix in fixes.iter() {
|
||||
let title = fix.signature.clone();
|
||||
let desc = fix.solution.clone();
|
||||
if let Some(ref emb) = fix.embedding {
|
||||
cached_items.push((title, "error_fix".to_string(), desc, emb.clone()));
|
||||
} else if uncached_texts.len() < 50 {
|
||||
uncached_texts.push(format!("{} {}", fix.signature, fix.solution));
|
||||
uncached_meta.push((title, "error_fix".to_string(), desc));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (title, doc_type, body, emb) in cached_items {
|
||||
let sim = cosine_similarity(&query_emb, &emb);
|
||||
results.push(UnifiedSearchResult {
|
||||
id: title.clone(),
|
||||
doc_type,
|
||||
title,
|
||||
body,
|
||||
score: sim,
|
||||
});
|
||||
}
|
||||
|
||||
if !uncached_texts.is_empty()
|
||||
&& let Ok(embeddings) = generate_embeddings_async(uncached_texts).await
|
||||
{
|
||||
for (emb, meta) in embeddings.into_iter().zip(uncached_meta) {
|
||||
let sim = cosine_similarity(&query_emb, &emb);
|
||||
results.push(UnifiedSearchResult {
|
||||
id: meta.0.clone(),
|
||||
doc_type: meta.1.clone(),
|
||||
title: meta.0,
|
||||
body: meta.2,
|
||||
score: sim,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
results.sort_by(|a, b| {
|
||||
b.score
|
||||
.partial_cmp(&a.score)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
results.truncate(limit);
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn keyword_search(
|
||||
&self,
|
||||
query: &str,
|
||||
filter_namespace: Option<&str>,
|
||||
limit: usize,
|
||||
) -> crate::error::Result<Vec<UnifiedSearchResult>> {
|
||||
let idx = self.state.get_search_index().await;
|
||||
let matches = idx
|
||||
.search(query, filter_namespace)
|
||||
.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for (id, doc_type, title, body, score) in matches.into_iter().take(limit) {
|
||||
results.push(UnifiedSearchResult {
|
||||
id,
|
||||
doc_type,
|
||||
title,
|
||||
body,
|
||||
score,
|
||||
});
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
pub use crate::search::{SearchResult as UnifiedSearchResult, SearchService};
|
||||
Reference in new issue
Block a user