lc_chains/document_chains/
refine.rs1use async_trait::async_trait;
5use futures_util::StreamExt;
6use lc_core::language_models::LLMResult;
7use lc_core::{BaseChatModel, Runnable};
8use lc_schema::Message;
9use lc_shared::document::Document;
10use serde_json::Value;
11use std::collections::HashMap;
12
13use crate::base::{BaseChain, ChainError, ChainResult, ChainStream, StreamToken};
14
15pub(crate) const DEFAULT_REFINE_INITIAL_PROMPT: &str =
17 "Answer the question based on the following reference information.
18
19Reference information:
20{context}
21
22Question: {input}
23
24Answer:";
25
26pub(crate) const DEFAULT_REFINE_PROMPT: &str = "You have provided an answer based on partial information. Here is additional reference information.
28
29Existing answer:
30{existing_answer}
31
32New reference information:
33{context}
34
35Please refine or modify your answer based on the new information. If the new information does not conflict with the existing answer, merge them. If the new information conflicts with the existing answer, prioritize the new information.
36
37Question: {input}
38
39Refined answer:";
40
41pub struct RefineDocumentsChain<M: BaseChatModel> {
46 llm: M,
47 initial_prompt_template: String,
48 refine_prompt_template: String,
49 document_variable_name: String,
50 input_key: String,
51 output_key: String,
52 name: String,
53 verbose: bool,
54}
55
56impl<M: BaseChatModel> RefineDocumentsChain<M> {
57 pub fn new(llm: M) -> Self {
58 Self {
59 llm,
60 initial_prompt_template: DEFAULT_REFINE_INITIAL_PROMPT.to_string(),
61 refine_prompt_template: DEFAULT_REFINE_PROMPT.to_string(),
62 document_variable_name: "context".to_string(),
63 input_key: "input".to_string(),
64 output_key: "output".to_string(),
65 name: "refine_documents".to_string(),
66 verbose: false,
67 }
68 }
69
70 pub fn with_initial_prompt(mut self, template: impl Into<String>) -> Self {
71 self.initial_prompt_template = template.into();
72 self
73 }
74
75 pub fn with_refine_prompt(mut self, template: impl Into<String>) -> Self {
76 self.refine_prompt_template = template.into();
77 self
78 }
79
80 pub fn with_document_variable(mut self, name: impl Into<String>) -> Self {
81 self.document_variable_name = name.into();
82 self
83 }
84
85 pub fn with_input_key(mut self, key: impl Into<String>) -> Self {
86 self.input_key = key.into();
87 self
88 }
89
90 pub fn with_output_key(mut self, key: impl Into<String>) -> Self {
91 self.output_key = key.into();
92 self
93 }
94
95 pub fn with_name(mut self, name: impl Into<String>) -> Self {
96 self.name = name.into();
97 self
98 }
99
100 pub fn with_verbose(mut self, verbose: bool) -> Self {
101 self.verbose = verbose;
102 self
103 }
104
105 pub fn build_initial_prompt(&self, context: &str, input: &str) -> String {
106 self.initial_prompt_template
107 .replace(&format!("{{{}}}", self.document_variable_name), context)
108 .replace("{input}", input)
109 }
110
111 pub fn build_refine_prompt(&self, context: &str, input: &str, existing_answer: &str) -> String {
112 self.refine_prompt_template
113 .replace(&format!("{{{}}}", self.document_variable_name), context)
114 .replace("{input}", input)
115 .replace("{existing_answer}", existing_answer)
116 }
117
118 pub async fn invoke_with_documents(
120 &self,
121 documents: Vec<Document>,
122 input: &str,
123 ) -> Result<String, ChainError>
124 where
125 <M as Runnable<Vec<Message>, LLMResult>>::Error: std::fmt::Display,
126 {
127 if documents.is_empty() {
128 return Err(ChainError::ExecutionError(
129 "Document list is empty".to_string(),
130 ));
131 }
132
133 if self.verbose {
134 println!("\n=== RefineDocumentsChain ===");
135 println!("Document count: {}", documents.len());
136 println!("Input: {}", input);
137 }
138
139 let first_context = &documents[0].content;
141 let initial_prompt = self.build_initial_prompt(first_context, input);
142
143 if self.verbose {
144 println!("\n--- Initial processing (document 1) ---");
145 }
146
147 let messages = vec![Message::human(&initial_prompt)];
148 let response =
149 self.llm.invoke(messages, None).await.map_err(|e| {
150 ChainError::ExecutionError(format!("LLM initial call failed: {}", e))
151 })?;
152 let mut answer = response.content;
153
154 if self.verbose {
155 println!("Initial answer: {}", answer);
156 }
157
158 for (i, doc) in documents[1..].iter().enumerate() {
160 if self.verbose {
161 println!("\n--- Refinement step {} (document {}) ---", i + 1, i + 2);
162 }
163
164 let refine_prompt = self.build_refine_prompt(&doc.content, input, &answer);
165
166 let messages = vec![Message::human(&refine_prompt)];
167 let response = self.llm.invoke(messages, None).await.map_err(|e| {
168 ChainError::ExecutionError(format!("LLM refinement call failed: {}", e))
169 })?;
170 answer = response.content;
171
172 if self.verbose {
173 println!("Refined answer: {}", answer);
174 }
175 }
176
177 if self.verbose {
178 println!("=== RefineDocumentsChain complete ===\n");
179 }
180
181 Ok(answer)
182 }
183}
184
185#[async_trait]
186impl<M: BaseChatModel + Send + Sync + 'static> BaseChain for RefineDocumentsChain<M>
187where
188 <M as Runnable<Vec<Message>, LLMResult>>::Error: std::fmt::Display,
189{
190 fn input_keys(&self) -> Vec<&str> {
191 vec![&self.input_key, "documents"]
192 }
193
194 fn output_keys(&self) -> Vec<&str> {
195 vec![&self.output_key]
196 }
197
198 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
199 let input = inputs
200 .get(&self.input_key)
201 .and_then(|v| v.as_str())
202 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
203
204 let documents: Vec<Document> = inputs
205 .get("documents")
206 .and_then(|v| v.as_array())
207 .map(|arr| {
208 arr.iter()
209 .filter_map(|v| serde_json::from_value(v.clone()).ok())
210 .collect()
211 })
212 .ok_or_else(|| ChainError::MissingInput("documents".to_string()))?;
213
214 let output = self.invoke_with_documents(documents, input).await?;
215
216 let mut result = HashMap::new();
217 result.insert(self.output_key.clone(), Value::String(output));
218 Ok(result)
219 }
220
221 async fn stream(&self, inputs: HashMap<String, Value>) -> Result<ChainStream, ChainError> {
227 self.validate_inputs(&inputs)?;
228
229 let input = inputs
230 .get(&self.input_key)
231 .and_then(|v| v.as_str())
232 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
233
234 let documents: Vec<Document> = inputs
235 .get("documents")
236 .and_then(|v| v.as_array())
237 .map(|arr| {
238 arr.iter()
239 .filter_map(|v| serde_json::from_value(v.clone()).ok())
240 .collect()
241 })
242 .ok_or_else(|| ChainError::MissingInput("documents".to_string()))?;
243
244 if documents.is_empty() {
245 return Err(ChainError::ExecutionError(
246 "Document list is empty".to_string(),
247 ));
248 }
249
250 let first_context = &documents[0].content;
252 let initial_prompt = self.build_initial_prompt(first_context, input);
253 let messages = vec![Message::human(&initial_prompt)];
254 let response = self.llm.invoke(messages, None).await.map_err(|e| {
255 ChainError::ExecutionError(format!("LLM initial call failed: {}", e))
256 })?;
257 let mut answer = response.content;
258
259 let last_idx = documents.len() - 1;
261 for (i, doc) in documents[1..last_idx].iter().enumerate() {
262 let refine_prompt = self.build_refine_prompt(&doc.content, input, &answer);
263 let messages = vec![Message::human(&refine_prompt)];
264 let response = self.llm.invoke(messages, None).await.map_err(|e| {
265 ChainError::ExecutionError(format!("LLM refinement call failed: {}", e))
266 })?;
267 answer = response.content;
268
269 if self.verbose {
270 println!("Refine step {} completed", i + 1);
271 }
272 }
273
274 let final_prompt = if last_idx == 0 {
276 self.build_initial_prompt(&documents[0].content, input)
278 } else {
279 self.build_refine_prompt(&documents[last_idx].content, input, &answer)
280 };
281
282 let messages = vec![Message::human(&final_prompt)];
283 let llm_stream = self
284 .llm
285 .stream_chat(messages, None)
286 .await
287 .map_err(|e| ChainError::StreamError(format!("LLM stream failed: {}", e)))?;
288
289 let stream = llm_stream.map(|result| match result {
290 Ok(token) => Ok(StreamToken {
291 token,
292 is_final: false,
293 }),
294 Err(e) => Err(ChainError::StreamError(format!(
295 "Stream token error: {}",
296 e
297 ))),
298 });
299
300 let final_stream =
301 stream.chain(futures_util::stream::once(async move {
302 Ok(StreamToken {
303 token: String::new(),
304 is_final: true,
305 })
306 }));
307
308 Ok(Box::pin(final_stream))
309 }
310
311 fn name(&self) -> &str {
312 &self.name
313 }
314}