1use lc_core::language_models::BaseChatModel;
8use lc_prompts::PromptTemplate;
9use lc_providers::ProviderError;
10use lc_schema::Message;
11use lc_vector_stores::{Document, SearchResult};
12
13use crate::retriever::RetrieverTrait;
14use std::collections::HashSet;
15use std::sync::Arc;
16
17#[derive(Debug)]
19#[non_exhaustive]
20pub enum HyDEError {
21 LLMError(String),
23 EmbeddingError(String),
25 RetrieverError(String),
27}
28
29impl std::fmt::Display for HyDEError {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 match self {
32 HyDEError::LLMError(msg) => write!(f, "LLM error: {}", msg),
33 HyDEError::EmbeddingError(msg) => write!(f, "embedding error: {}", msg),
34 HyDEError::RetrieverError(msg) => write!(f, "retrieval error: {}", msg),
35 }
36 }
37}
38
39impl std::error::Error for HyDEError {}
40
41pub struct HyDEConfig {
43 pub prompt_template: String,
45
46 pub k: usize,
48
49 pub include_original_query: bool,
51}
52
53impl Default for HyDEConfig {
54 fn default() -> Self {
55 Self {
56 prompt_template: DEFAULT_HYDE_PROMPT.to_string(),
57 k: 5,
58 include_original_query: true,
59 }
60 }
61}
62
63impl HyDEConfig {
64 pub fn new() -> Self {
66 Self::default()
67 }
68
69 pub fn with_k(mut self, k: usize) -> Self {
71 self.k = k;
72 self
73 }
74
75 pub fn with_prompt(mut self, prompt: impl Into<String>) -> Self {
77 self.prompt_template = prompt.into();
78 self
79 }
80
81 pub fn with_include_original_query(mut self, include: bool) -> Self {
83 self.include_original_query = include;
84 self
85 }
86}
87
88const DEFAULT_HYDE_PROMPT: &str = r#"Please write a passage to answer the question.
89
90Question: {question}
91
92Passage:"#;
93
94pub struct HyDERetriever {
103 llm: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>,
107 base_retriever: Arc<dyn RetrieverTrait>,
108 config: HyDEConfig,
109}
110
111impl HyDERetriever {
112 pub fn new<L>(llm: L, base_retriever: Arc<dyn RetrieverTrait>) -> Self
117 where
118 L: BaseChatModel + Send + Sync + 'static,
119 L::Error: Into<ProviderError>,
120 {
121 Self {
122 llm: lc_providers::wrap_chat_model(llm),
123 base_retriever,
124 config: HyDEConfig::default(),
125 }
126 }
127
128 pub fn new_arc(
130 llm: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>,
131 base_retriever: Arc<dyn RetrieverTrait>,
132 ) -> Self {
133 Self {
134 llm,
135 base_retriever,
136 config: HyDEConfig::default(),
137 }
138 }
139
140 pub fn with_config(mut self, config: HyDEConfig) -> Self {
142 self.config = config;
143 self
144 }
145
146 pub fn with_k(mut self, k: usize) -> Self {
148 self.config.k = k;
149 self
150 }
151
152 pub fn with_include_original_query(mut self, include: bool) -> Self {
154 self.config.include_original_query = include;
155 self
156 }
157
158 async fn generate_hypothetical_document(&self, query: &str) -> Result<String, HyDEError> {
159 let template = PromptTemplate::new(&self.config.prompt_template);
160 let mut vars = std::collections::HashMap::new();
161 vars.insert("question", query);
162 let prompt = template
163 .format(&vars)
164 .unwrap_or_else(|_| self.config.prompt_template.clone());
165
166 let messages = vec![Message::human(prompt)];
167
168 let response = self
169 .llm
170 .invoke(messages, None)
171 .await
172 .map_err(|e| HyDEError::LLMError(e.to_string()))?;
173
174 Ok(response.content)
175 }
176
177 pub async fn retrieve(&self, query: &str) -> Result<Vec<Document>, HyDEError> {
179 let hyde_doc = self.generate_hypothetical_document(query).await?;
180
181 let mut all_docs = Vec::new();
182 let mut seen_content: HashSet<String> = HashSet::new();
183
184 let hyde_results = self
185 .base_retriever
186 .retrieve(&hyde_doc, self.config.k)
187 .await
188 .map_err(|e| HyDEError::RetrieverError(e.to_string()))?;
189
190 for doc in &hyde_results {
191 seen_content.insert(doc.content.clone());
192 }
193 all_docs.extend(hyde_results);
194
195 if self.config.include_original_query {
196 let query_results = self
197 .base_retriever
198 .retrieve(query, self.config.k)
199 .await
200 .map_err(|e| HyDEError::RetrieverError(e.to_string()))?;
201
202 for doc in query_results {
203 if seen_content.insert(doc.content.clone()) {
204 all_docs.push(doc);
205 }
206 }
207 }
208
209 Ok(all_docs)
210 }
211
212 pub async fn retrieve_with_scores(&self, query: &str) -> Result<Vec<SearchResult>, HyDEError> {
214 let hyde_doc = self.generate_hypothetical_document(query).await?;
215
216 let mut all_results: Vec<SearchResult> = Vec::new();
217 let mut seen_content: HashSet<String> = HashSet::new();
218
219 let hyde_results = self
220 .base_retriever
221 .retrieve_with_scores(&hyde_doc, self.config.k)
222 .await
223 .map_err(|e| HyDEError::RetrieverError(e.to_string()))?;
224
225 for r in &hyde_results {
226 seen_content.insert(r.document.content.clone());
227 }
228 all_results.extend(hyde_results);
229
230 if self.config.include_original_query {
231 let query_results = self
232 .base_retriever
233 .retrieve_with_scores(query, self.config.k)
234 .await
235 .map_err(|e| HyDEError::RetrieverError(e.to_string()))?;
236
237 for result in query_results {
238 if seen_content.insert(result.document.content.clone()) {
239 all_results.push(result);
240 }
241 }
242 }
243
244 all_results.sort_by(|a, b| {
245 b.score
246 .partial_cmp(&a.score)
247 .unwrap_or(std::cmp::Ordering::Equal)
248 });
249
250 Ok(all_results)
251 }
252
253 pub async fn get_hypothetical_document(&self, query: &str) -> Result<String, HyDEError> {
255 self.generate_hypothetical_document(query).await
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_hyde_config_default() {
265 let config = HyDEConfig::default();
266
267 assert_eq!(config.k, 5);
268 assert!(config.include_original_query);
269 assert!(config.prompt_template.contains("{question}"));
270 }
271
272 #[test]
273 fn test_hyde_config_custom() {
274 let config = HyDEConfig::new()
275 .with_k(10)
276 .with_include_original_query(false);
277
278 assert_eq!(config.k, 10);
279 assert!(!config.include_original_query);
280 }
281
282 #[test]
283 fn test_hyde_config_prompt() {
284 let custom_prompt = "Answer this: {question}".to_string();
285 let config = HyDEConfig::new().with_prompt(custom_prompt.clone());
286
287 assert!(config.prompt_template.contains("{question}"));
288 }
289}