langchain_rust/chain/combine_documents/
refine.rs1use futures::StreamExt;
2use serde_json::{json, Value};
3
4use crate::{
5 chain::{
6 stuff_documents::{
7 input_documents, COMBINE_DOCUMENTS_DEFAULT_DOCUMENT_VARIABLE_NAME,
8 COMBINE_DOCUMENTS_DEFAULT_INPUT_KEY,
9 },
10 Chain, ChainError, ChainEventStream,
11 },
12 language_models::{GenerateResult, LLMEvent, TokenUsage},
13 prompt::PromptArgs,
14 schemas::Document,
15};
16
17pub(super) fn add_usage(total: &mut Option<TokenUsage>, usage: &Option<TokenUsage>) {
19 if let Some(usage) = usage {
20 total.get_or_insert_with(TokenUsage::default).add(usage);
21 }
22}
23
24pub(super) fn shared_inputs(input_variables: &PromptArgs, input_key: &str) -> PromptArgs {
27 let mut rest = input_variables.clone();
28 rest.remove(input_key);
29 rest
30}
31
32pub(super) fn stream_last_step<'a>(
34 chain: &'a dyn Chain,
35 inputs: PromptArgs,
36 usage: Option<TokenUsage>,
37) -> ChainEventStream<'a> {
38 Box::pin(chain.call_stream(inputs).map(move |event| match event {
39 Ok(LLMEvent::Done(mut result)) => {
40 let mut total = usage.clone();
41 add_usage(&mut total, &result.tokens);
42 result.tokens = total;
43 Ok(LLMEvent::Done(result))
44 }
45 other => other,
46 }))
47}
48
49pub struct RefineDocuments {
67 initial_chain: Box<dyn Chain>,
68 refine_chain: Box<dyn Chain>,
69 input_key: String,
70 document_variable_name: String,
71 initial_response_name: String,
72}
73
74impl RefineDocuments {
75 pub fn new<C1: Into<Box<dyn Chain>>, C2: Into<Box<dyn Chain>>>(
78 initial_chain: C1,
79 refine_chain: C2,
80 ) -> Self {
81 Self {
82 initial_chain: initial_chain.into(),
83 refine_chain: refine_chain.into(),
84 input_key: COMBINE_DOCUMENTS_DEFAULT_INPUT_KEY.into(),
85 document_variable_name: COMBINE_DOCUMENTS_DEFAULT_DOCUMENT_VARIABLE_NAME.into(),
86 initial_response_name: "existing_answer".into(),
87 }
88 }
89
90 pub fn with_input_key<S: Into<String>>(mut self, key: S) -> Self {
92 self.input_key = key.into();
93 self
94 }
95
96 pub fn with_document_variable_name<S: Into<String>>(mut self, name: S) -> Self {
98 self.document_variable_name = name.into();
99 self
100 }
101
102 pub fn with_initial_response_name<S: Into<String>>(mut self, name: S) -> Self {
104 self.initial_response_name = name.into();
105 self
106 }
107
108 fn step_inputs(&self, rest: &PromptArgs, doc: &Document, answer: Option<&str>) -> PromptArgs {
109 let mut inputs = rest.clone();
110 inputs.insert(
111 self.document_variable_name.clone(),
112 Value::String(doc.page_content.clone()),
113 );
114 if let Some(answer) = answer {
115 inputs.insert(self.initial_response_name.clone(), json!(answer));
116 }
117 inputs
118 }
119
120 async fn run_until_last(
123 &self,
124 input_variables: &PromptArgs,
125 ) -> Result<(&dyn Chain, PromptArgs, Option<TokenUsage>), ChainError> {
126 let docs = input_documents(input_variables, &self.input_key)?;
127 let Some((last, init)) = docs.split_last() else {
128 return Err(ChainError::OtherError(format!(
129 "{} must contain at least one document",
130 self.input_key
131 )));
132 };
133 let rest = shared_inputs(input_variables, &self.input_key);
134
135 let mut usage = None;
136 let mut answer: Option<String> = None;
137 for doc in init {
138 let (chain, inputs) = self.step(&rest, doc, answer.as_deref());
139 let result = chain.call(inputs).await?;
140 add_usage(&mut usage, &result.tokens);
141 answer = Some(result.generation);
142 }
143 let (chain, inputs) = self.step(&rest, last, answer.as_deref());
144 Ok((chain, inputs, usage))
145 }
146
147 fn step(
149 &self,
150 rest: &PromptArgs,
151 doc: &Document,
152 answer: Option<&str>,
153 ) -> (&dyn Chain, PromptArgs) {
154 let chain = match answer {
155 None => self.initial_chain.as_ref(),
156 Some(_) => self.refine_chain.as_ref(),
157 };
158 (chain, self.step_inputs(rest, doc, answer))
159 }
160}
161
162#[async_trait::async_trait]
163impl Chain for RefineDocuments {
164 async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError> {
165 let (chain, inputs, mut usage) = self.run_until_last(&input_variables).await?;
166 let mut result = chain.call(inputs).await?;
167 add_usage(&mut usage, &result.tokens);
168 result.tokens = usage;
169 Ok(result)
170 }
171
172 fn call_stream<'a>(&'a self, input_variables: PromptArgs) -> ChainEventStream<'a> {
174 Box::pin(
175 futures::stream::once(async move {
176 match self.run_until_last(&input_variables).await {
177 Ok((chain, inputs, usage)) => stream_last_step(chain, inputs, usage),
178 Err(e) => Box::pin(futures::stream::iter([Err(e)])) as ChainEventStream<'a>,
179 }
180 })
181 .flatten(),
182 )
183 }
184
185 fn get_input_keys(&self) -> Vec<String> {
186 let mut keys = vec![self.input_key.clone()];
187 keys.extend(
188 self.initial_chain
189 .get_input_keys()
190 .into_iter()
191 .filter(|k| *k != self.document_variable_name),
192 );
193 keys
194 }
195}