diff options
Diffstat (limited to 'src/rag/mod.rs')
| -rw-r--r-- | src/rag/mod.rs | 126 |
1 files changed, 71 insertions, 55 deletions
diff --git a/src/rag/mod.rs b/src/rag/mod.rs index e84ece3..4cee391 100644 --- a/src/rag/mod.rs +++ b/src/rag/mod.rs @@ -1,4 +1,3 @@ -use self::bm25::*; use self::loader::*; use self::splitter::*; @@ -6,12 +5,12 @@ use crate::client::*; use crate::config::*; use crate::utils::*; -mod bm25; mod loader; mod serde_vectors; mod splitter; use anyhow::{anyhow, bail, Context, Result}; +use bm25::{LanguageMode, SearchEngine, SearchEngineBuilder}; use hnsw_rs::prelude::*; use indexmap::{IndexMap, IndexSet}; use inquire::{required, validator::Validation, Confirm, Select, Text}; @@ -19,7 +18,7 @@ use parking_lot::RwLock; use path_absolutize::Absolutize; use serde::{Deserialize, Serialize}; use serde_json::json; -use std::{collections::HashMap, env, fmt::Debug, fs, path::Path, time::Duration}; +use std::{collections::HashMap, env, fmt::Debug, fs, hash::Hash, path::Path, time::Duration}; use tokio::time::sleep; pub struct Rag { @@ -28,7 +27,7 @@ pub struct Rag { path: String, embedding_model: Model, hnsw: Hnsw<'static, f32, DistCosine>, - bm25: BM25<DocumentId>, + bm25: SearchEngine<DocumentId>, data: RagData, last_sources: RwLock<Option<String>>, } @@ -52,7 +51,7 @@ impl Clone for Rag { path: self.path.clone(), embedding_model: self.embedding_model.clone(), hnsw: self.data.build_hnsw(), - bm25: self.bm25.clone(), + bm25: self.data.build_bm25(), data: self.data.clone(), last_sources: RwLock::new(None), } @@ -230,7 +229,7 @@ impl Rag { let sources: IndexSet<_> = ids .iter() .filter_map(|id| { - let (file_index, _) = split_document_id(*id); + let (file_index, _) = id.split(); let file = self.data.files.get(&file_index)?; Some(file.path.clone()) }) @@ -394,14 +393,7 @@ impl Rag { &separator, ); - let metadata = metadata - .iter() - .map(|(k, v)| format!("{k}: {v}\n")) - .collect::<Vec<String>>() - .join(""); - let split_options = SplitterChunkHeaderOptions::default().with_chunk_header(&format!( - "<document_metadata>\npath: {path}\n{metadata}</document_metadata>\n\n" - )); + let split_options = SplitterChunkHeaderOptions::default(); let document = RagDocument::new(contents); let split_documents = splitter.split_documents(&[document], &split_options); rag_files.push(RagFile { @@ -420,7 +412,7 @@ impl Rag { let mut texts = vec![]; for file in rag_files.into_iter() { for (document_index, document) in file.documents.iter().enumerate() { - document_ids.push(combine_document_id(next_file_id, document_index)); + document_ids.push(DocumentId::new(next_file_id, document_index)); texts.push(document.page_content.clone()) } files.push((next_file_id, file)); @@ -456,17 +448,21 @@ impl Rag { min_score_keyword_search: f32, rerank_model: Option<&str>, ) -> Result<Vec<(DocumentId, String)>> { - let (vector_search_result, text_search_result) = tokio::join!( + let (vector_search_results, keyword_search_results) = tokio::join!( self.vector_search(query, top_k, min_score_vector_search), self.keyword_search(query, top_k, min_score_keyword_search) ); - let vector_search_ids = vector_search_result?; - let keyword_search_ids = text_search_result?; - debug!( - "vector_search_ids: {:?}, keyword_search_ids: {:?}", - pretty_document_ids(&vector_search_ids), - pretty_document_ids(&keyword_search_ids) - ); + + let vector_search_results = vector_search_results?; + debug!("vector_search_results: {vector_search_results:?}",); + let vector_search_ids: Vec<DocumentId> = + vector_search_results.into_iter().map(|(v, _)| v).collect(); + + let keyword_search_results = keyword_search_results?; + debug!("keyword_search_results: {keyword_search_results:?}",); + let keyword_search_ids: Vec<DocumentId> = + keyword_search_results.into_iter().map(|(v, _)| v).collect(); + let ids = match rerank_model { Some(model_id) => { let model = Model::retrieve_reranker(&self.config.read(), model_id)?; @@ -490,7 +486,7 @@ impl Rag { .take(top_k) .filter_map(|item| documents_ids.get(item.index).cloned()) .collect(); - debug!("rerank_ids: {:?}", pretty_document_ids(&ids)); + debug!("rerank_ids: {ids:?}"); ids } None => { @@ -499,7 +495,7 @@ impl Rag { vec![1.0, 1.0], top_k, ); - debug!("rrf_ids: {:?}", pretty_document_ids(&ids)); + debug!("rrf_ids: {ids:?}"); ids } }; @@ -518,7 +514,7 @@ impl Rag { query: &str, top_k: usize, min_score: f32, - ) -> Result<Vec<DocumentId>> { + ) -> Result<Vec<(DocumentId, f32)>> { let splitter = RecursiveCharacterTextSplitter::new( self.data.chunk_size, self.data.chunk_overlap, @@ -534,10 +530,12 @@ impl Rag { .flat_map(|list| { list.into_iter() .filter_map(|v| { - if v.distance < min_score { - return None; + let score = 1.0 - v.distance; + if score > min_score { + Some((DocumentId(v.d_id), score)) + } else { + None } - Some(v.d_id) }) .collect::<Vec<_>>() }) @@ -550,8 +548,19 @@ impl Rag { query: &str, top_k: usize, min_score: f32, - ) -> Result<Vec<DocumentId>> { - let output = self.bm25.search(query, top_k, Some(min_score as f64)); + ) -> Result<Vec<(DocumentId, f32)>> { + let results = self.bm25.search(query, top_k); + let output: Vec<(DocumentId, f32)> = results + .into_iter() + .filter_map(|v| { + let score = v.score; + if score > min_score { + Some((v.document.id, score)) + } else { + None + } + }) + .collect(); Ok(output) } @@ -670,7 +679,7 @@ impl RagData { } pub fn get(&self, id: DocumentId) -> Option<&RagDocument> { - let (file_index, document_index) = split_document_id(id); + let (file_index, document_index) = id.split(); let file = self.files.get(&file_index)?; let document = file.documents.get(document_index)?; Some(document) @@ -680,7 +689,7 @@ impl RagData { for file_id in file_ids { if let Some(file) = self.files.swap_remove(&file_id) { for (document_index, _) in file.documents.iter().enumerate() { - let document_id = combine_document_id(file_id, document_index); + let document_id = DocumentId::new(file_id, document_index); self.vectors.swap_remove(&document_id); } } @@ -702,20 +711,23 @@ impl RagData { pub fn build_hnsw(&self) -> Hnsw<'static, f32, DistCosine> { let hnsw = Hnsw::new(32, self.vectors.len(), 16, 200, DistCosine {}); - let list: Vec<_> = self.vectors.iter().map(|(k, v)| (v, *k)).collect(); + let list: Vec<_> = self.vectors.iter().map(|(k, v)| (v, k.0)).collect(); hnsw.parallel_insert(&list); hnsw } - pub fn build_bm25(&self) -> BM25<DocumentId> { - let mut corpus = vec![]; + pub fn build_bm25(&self) -> SearchEngine<DocumentId> { + let mut documents = vec![]; for (file_index, file) in self.files.iter() { for (document_index, document) in file.documents.iter().enumerate() { - let id = combine_document_id(*file_index, document_index); - corpus.push((id, document.page_content.clone())); + let id = DocumentId::new(*file_index, document_index); + documents.push(bm25::Document::new(id, &document.page_content)) } } - BM25::new(corpus, BM25Options::default()) + SearchEngineBuilder::<DocumentId>::with_documents(LanguageMode::Detect, documents) + .k1(1.5) + .b(0.75) + .build() } } @@ -753,26 +765,30 @@ impl Default for RagDocument { pub type RagMetadata = IndexMap<String, String>; pub type FileId = usize; -pub type DocumentId = usize; -pub fn combine_document_id(file_index: usize, document_index: usize) -> DocumentId { - file_index << (usize::BITS / 2) | document_index -} +#[derive(Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct DocumentId(usize); -pub fn split_document_id(value: DocumentId) -> (usize, usize) { - let low_mask = (1 << (usize::BITS / 2)) - 1; - let low = value & low_mask; - let high = value >> (usize::BITS / 2); - (high, low) +impl Debug for DocumentId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (file_index, document_index) = self.split(); + f.write_fmt(format_args!("{file_index}-{document_index}")) + } } -fn pretty_document_ids(ids: &[DocumentId]) -> Vec<String> { - ids.iter() - .map(|v| { - let (h, l) = split_document_id(*v); - format!("{h}-{l}") - }) - .collect() +impl DocumentId { + pub fn new(file_index: usize, document_index: usize) -> Self { + let value = file_index << (usize::BITS / 2) | document_index; + Self(value) + } + + pub fn split(self) -> (usize, usize) { + let value = self.0; + let low_mask = (1 << (usize::BITS / 2)) - 1; + let low = value & low_mask; + let high = value >> (usize::BITS / 2); + (high, low) + } } fn select_embedding_model(models: &[&Model]) -> Result<String> { |
