langchain_rust/chain/combine_documents/
map_reduce.rs1use futures::{stream, StreamExt, TryStreamExt};
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, TokenUsage},
13 prompt::PromptArgs,
14 schemas::Document,
15};
16
17use super::refine::{add_usage, shared_inputs, stream_last_step};
18
19const DEFAULT_MAX_CONCURRENCY: usize = 5;
20
21pub struct MapReduceDocuments {
39 map_chain: Box<dyn Chain>,
40 reduce_chain: Box<dyn Chain>,
41 input_key: String,
42 document_variable_name: String,
43 reduce_input_key: String,
44 max_concurrency: usize,
45}
46
47impl MapReduceDocuments {
48 pub fn new<M: Into<Box<dyn Chain>>, R: Into<Box<dyn Chain>>>(
50 map_chain: M,
51 reduce_chain: R,
52 ) -> Self {
53 Self {
54 map_chain: map_chain.into(),
55 reduce_chain: reduce_chain.into(),
56 input_key: COMBINE_DOCUMENTS_DEFAULT_INPUT_KEY.into(),
57 document_variable_name: COMBINE_DOCUMENTS_DEFAULT_DOCUMENT_VARIABLE_NAME.into(),
58 reduce_input_key: COMBINE_DOCUMENTS_DEFAULT_INPUT_KEY.into(),
59 max_concurrency: DEFAULT_MAX_CONCURRENCY,
60 }
61 }
62
63 pub fn with_input_key<S: Into<String>>(mut self, key: S) -> Self {
65 self.input_key = key.into();
66 self
67 }
68
69 pub fn with_document_variable_name<S: Into<String>>(mut self, name: S) -> Self {
71 self.document_variable_name = name.into();
72 self
73 }
74
75 pub fn with_reduce_input_key<S: Into<String>>(mut self, key: S) -> Self {
78 self.reduce_input_key = key.into();
79 self
80 }
81
82 pub fn with_max_concurrency(mut self, max_concurrency: usize) -> Self {
84 self.max_concurrency = max_concurrency.max(1);
85 self
86 }
87
88 async fn map(
90 &self,
91 input_variables: &PromptArgs,
92 ) -> Result<(PromptArgs, Option<TokenUsage>), ChainError> {
93 let docs = input_documents(input_variables, &self.input_key)?;
94 let rest = shared_inputs(input_variables, &self.input_key);
95
96 let map_inputs: Vec<PromptArgs> = docs
98 .iter()
99 .map(|doc| {
100 let mut inputs = rest.clone();
101 inputs.insert(
102 self.document_variable_name.clone(),
103 Value::String(doc.page_content.clone()),
104 );
105 inputs
106 })
107 .collect();
108 let map_chain = self.map_chain.as_ref();
109 let results: Vec<GenerateResult> = stream::iter(map_inputs)
110 .map(|inputs| map_chain.call(inputs))
111 .buffered(self.max_concurrency)
112 .try_collect()
113 .await?;
114
115 let mut usage = None;
116 let mapped: Vec<Document> = docs
117 .into_iter()
118 .zip(results)
119 .map(|(doc, result)| {
120 add_usage(&mut usage, &result.tokens);
121 Document {
122 page_content: result.generation,
123 ..doc
124 }
125 })
126 .collect();
127
128 let mut reduce_inputs = rest;
129 reduce_inputs.insert(self.reduce_input_key.clone(), json!(mapped));
130 Ok((reduce_inputs, usage))
131 }
132}
133
134#[async_trait::async_trait]
135impl Chain for MapReduceDocuments {
136 async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError> {
137 let (reduce_inputs, mut usage) = self.map(&input_variables).await?;
138 let mut result = self.reduce_chain.call(reduce_inputs).await?;
139 add_usage(&mut usage, &result.tokens);
140 result.tokens = usage;
141 Ok(result)
142 }
143
144 fn call_stream<'a>(&'a self, input_variables: PromptArgs) -> ChainEventStream<'a> {
146 Box::pin(
147 stream::once(async move {
148 match self.map(&input_variables).await {
149 Ok((inputs, usage)) => {
150 stream_last_step(self.reduce_chain.as_ref(), inputs, usage)
151 }
152 Err(e) => Box::pin(stream::iter([Err(e)])) as ChainEventStream<'a>,
153 }
154 })
155 .flatten(),
156 )
157 }
158
159 fn get_input_keys(&self) -> Vec<String> {
160 let mut keys = vec![self.input_key.clone()];
161 keys.extend(
162 self.map_chain
163 .get_input_keys()
164 .into_iter()
165 .filter(|k| *k != self.document_variable_name),
166 );
167 keys
168 }
169}