swiftide_integrations/fastembed/
rerank.rs1use anyhow::{Context as _, Result};
2use itertools::Itertools;
3use std::sync::Arc;
4
5use async_trait::async_trait;
6use derive_builder::Builder;
7use fastembed::{RerankInitOptions, TextRerank};
8use swiftide_core::{
9 TransformResponse,
10 querying::{Query, states},
11};
12
13const TOP_K: usize = 10;
14
15#[derive(Clone, Builder)]
29pub struct Rerank {
30 #[builder(
32 default = "Arc::new(tokio::sync::Mutex::new(TextRerank::try_new(RerankInitOptions::default()).expect(\"Failed to build default rerank from Fastembed.rs\")))",
33 setter(into)
34 )]
35 model: Arc<tokio::sync::Mutex<TextRerank>>,
36
37 #[builder(default = TOP_K)]
39 top_k: usize,
40
41 #[builder(default = None)]
48 document_template: Option<String>,
49
50 #[builder(default = None)]
52 model_batch_size: Option<usize>,
53}
54
55impl std::fmt::Debug for Rerank {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("Rerank").finish()
58 }
59}
60
61impl Rerank {
62 pub fn builder() -> RerankBuilder {
63 RerankBuilder::default()
64 }
65}
66
67impl Default for Rerank {
68 fn default() -> Self {
69 Self {
70 model: Arc::new(tokio::sync::Mutex::new(
71 TextRerank::try_new(RerankInitOptions::default())
72 .expect("Failed to build default rerank from Fastembed.rs"),
73 )),
74 top_k: TOP_K,
75 document_template: None,
76 model_batch_size: None,
77 }
78 }
79}
80
81#[async_trait]
82impl TransformResponse for Rerank {
83 async fn transform_response(
84 &self,
85 query: Query<states::Retrieved>,
86 ) -> Result<Query<states::Retrieved>> {
87 let mut query = query;
88
89 let current_documents = std::mem::take(&mut query.documents);
90
91 let docs_for_rerank = if let Some(template) = &self.document_template {
92 current_documents
93 .iter()
94 .map(|doc| {
95 let context = tera::Context::from_serialize(doc)?;
96 tera::Tera::one_off(template, &context, false)
97 .context("Failed to render template")
98 })
99 .collect::<Result<Vec<_>>>()?
100 } else {
101 current_documents
102 .iter()
103 .map(|doc| doc.content().to_string())
104 .collect()
105 };
106
107 let mut model = self.model.lock().await;
108
109 let reranked_documents = model
110 .rerank(
111 query.original(),
112 docs_for_rerank
113 .iter()
114 .map(String::as_ref)
115 .collect::<Vec<&str>>(),
116 false,
117 self.model_batch_size,
118 )
119 .map_err(|e| anyhow::anyhow!("Failed to rerank documents: {e:?}"))?
120 .iter()
121 .take(self.top_k)
122 .map(|r| current_documents[r.index].clone())
123 .collect_vec();
124
125 query.documents = reranked_documents;
126
127 Ok(query)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use swiftide_core::{document::Document, indexing::Metadata};
134
135 use super::*;
136
137 #[ignore = "downloads a HuggingFace reranker model and can hit external rate limits"]
138 #[tokio::test]
139 async fn test_rerank_transform_response() {
140 let rerank = Rerank::builder().top_k(1).build().unwrap();
142
143 let documents = vec!["content1", "content2", "content3"]
144 .into_iter()
145 .map(Into::into)
146 .collect_vec();
147
148 let query = Query::builder()
149 .original("What is the capital of france?")
150 .state(states::Retrieved)
151 .documents(documents)
152 .build()
153 .unwrap();
154
155 let result = rerank.transform_response(query).await;
156
157 assert!(result.is_ok());
158 let transformed_query = result.unwrap();
159 assert_eq!(transformed_query.documents.len(), 1);
160
161 let rerank = Rerank::builder()
163 .top_k(1)
164 .document_template(Some("{{ metadata.title }}".to_string()))
165 .build()
166 .unwrap();
167
168 let metadata = Metadata::from([("title", "Title")]);
169
170 let documents = vec!["content1", "content2", "content3"]
171 .into_iter()
172 .map(|content| Document::new(content, Some(metadata.clone())))
173 .collect_vec();
174
175 let query = Query::builder()
176 .original("What is the capital of france?")
177 .state(states::Retrieved)
178 .documents(documents)
179 .build()
180 .unwrap();
181
182 let result = rerank.transform_response(query).await;
183
184 assert!(result.is_ok());
185 let transformed_query = result.unwrap();
186 assert_eq!(transformed_query.documents.len(), 1);
187 }
188}