refactor: Implement unified search abstraction, MemoryState refactoring, error handling, and unit test expansion
This commit is contained in:
1 parent
a34554b7ff
commit
462f65f66d
21 files changed
+425
-535
No files matched your search
+100
-1
@@ -164,6 +164,10 @@ impl MemoryState {
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn search(self: &Arc<Self>) -> SearchService {
|
||||
SearchService::new(self.clone())
|
||||
}
|
||||
|
||||
pub async fn rebuild_index(self: &Arc<Self>) {
|
||||
let idx = self.search_index.read().unwrap().clone();
|
||||
idx.delete_all();
|
||||
@@ -258,7 +262,7 @@ mod tests {
|
||||
// idx.reader.searcher().num_docs()
|
||||
// );
|
||||
|
||||
let all_docs = idx.search("Test", None).expect("Search failed");
|
||||
let _all_docs = idx.search("Test", None).expect("Search failed");
|
||||
// tracing::info!("All docs for 'Test': {:?}", all_docs);
|
||||
|
||||
// Verify the task added synchronously is actually searchable
|
||||
@@ -271,3 +275,98 @@ mod tests {
|
||||
assert_eq!(results[0].1, "task", "Expected document type to be task");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
use crate::embedding::{generate_embedding_async, generate_embeddings_async, cosine_similarity};
|
||||
|
||||
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 vdb_search = false;
|
||||
if let Some(vdb) = &*self.state.vector_db.read().await {
|
||||
vdb_search = true;
|
||||
if let Ok(search_results) = vdb.search(query_emb.clone(), limit as u64).await {
|
||||
for res in search_results {
|
||||
results.push(UnifiedSearchResult {
|
||||
id: res.id.clone(),
|
||||
doc_type: res.doc_type.clone(),
|
||||
title: res.id,
|
||||
body: res.text,
|
||||
score: res.score,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !vdb_search {
|
||||
let mut texts_to_embed = Vec::new();
|
||||
let mut metadata = Vec::new();
|
||||
|
||||
let snippets = self.state.code.snippets.read_with(|snips| snips.clone());
|
||||
for snippet in snippets {
|
||||
let combined = format!("{} {} {}", snippet.name, snippet.description, snippet.code);
|
||||
texts_to_embed.push(combined);
|
||||
metadata.push((snippet.name, "snippet".to_string(), snippet.description));
|
||||
}
|
||||
|
||||
let sticky = self.state.code.sticky.read_with(|s| s.clone());
|
||||
for note in sticky {
|
||||
texts_to_embed.push(note.content.clone());
|
||||
metadata.push(("StickyNote".to_string(), "sticky".to_string(), note.content.chars().take(200).collect::<String>()));
|
||||
}
|
||||
|
||||
if let Ok(embeddings) = generate_embeddings_async(texts_to_embed).await {
|
||||
for (emb, meta) in embeddings.into_iter().zip(metadata) {
|
||||
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 fn keyword_search(&self, query: &str, filter_namespace: Option<&str>, limit: usize) -> crate::error::Result<Vec<UnifiedSearchResult>> {
|
||||
let idx = self.state.get_search_index();
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user