langchain_rust/chain/chain_trait.rs
1use std::{collections::HashMap, pin::Pin};
2
3use async_trait::async_trait;
4use futures::Stream;
5use serde_json::{json, Value};
6
7use crate::{
8 language_models::{result_events, GenerateResult, LLMEvent},
9 prompt::PromptArgs,
10 schemas::StreamData,
11};
12
13use super::ChainError;
14
15/// Stream returned by [`Chain::call_stream`].
16pub type ChainEventStream<'a> =
17 Pin<Box<dyn Stream<Item = Result<LLMEvent, ChainError>> + Send + 'a>>;
18
19/// Boxes a chain event stream; also pins down the error type for `try_stream!` blocks.
20pub(crate) fn box_chain_events<'a, S>(stream: S) -> ChainEventStream<'a>
21where
22 S: Stream<Item = Result<LLMEvent, ChainError>> + Send + 'a,
23{
24 Box::pin(stream)
25}
26
27pub(crate) const DEFAULT_OUTPUT_KEY: &str = "output";
28pub(crate) const DEFAULT_RESULT_KEY: &str = "generate_result";
29
30#[async_trait]
31pub trait Chain: Sync + Send {
32 /// Call the `Chain` and receive as output the result of the generation process along with
33 /// additional information like token consumption. The input is a set of variables passed
34 /// as a `PromptArgs` hashmap.
35 ///
36 /// # Example
37 ///
38 /// ```rust,ignore
39 /// # use crate::my_crate::{Chain, ConversationalChainBuilder, OpenAI, OpenAIModel, SimpleMemory, PromptArgs, prompt_args};
40 /// # async {
41 /// let llm = OpenAI::default().with_model(OpenAIModel::Gpt35);
42 /// let memory = SimpleMemory::new();
43 ///
44 /// let chain = ConversationalChainBuilder::new()
45 /// .llm(llm)
46 /// .memory(memory.into())
47 /// .build().expect("Error building ConversationalChain");
48 ///
49 /// let input_variables = prompt_args! {
50 /// "input" => "Im from Peru",
51 /// };
52 ///
53 /// match chain.call(input_variables).await {
54 /// Ok(result) => {
55 /// println!("Result: {:?}", result);
56 /// },
57 /// Err(e) => panic!("Error calling Chain: {:?}", e),
58 /// };
59 /// # };
60 /// ```
61 async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError>;
62
63 /// Invoke the `Chain` and receive just the generation result as a String.
64 /// The input is a set of variables passed as a `PromptArgs` hashmap.
65 ///
66 /// # Example
67 ///
68 /// ```rust,ignore
69 /// # use crate::my_crate::{Chain, ConversationalChainBuilder, OpenAI, OpenAIModel, SimpleMemory, PromptArgs, prompt_args};
70 /// # async {
71 /// let llm = OpenAI::default().with_model(OpenAIModel::Gpt35);
72 /// let memory = SimpleMemory::new();
73 ///
74 /// let chain = ConversationalChainBuilder::new()
75 /// .llm(llm)
76 /// .memory(memory.into())
77 /// .build().expect("Error building ConversationalChain");
78 ///
79 /// let input_variables = prompt_args! {
80 /// "input" => "Im from Peru",
81 /// };
82 ///
83 /// match chain.invoke(input_variables).await {
84 /// Ok(result) => {
85 /// println!("Result: {:?}", result);
86 /// },
87 /// Err(e) => panic!("Error invoking Chain: {:?}", e),
88 /// };
89 /// # };
90 /// ```
91 async fn invoke(&self, input_variables: PromptArgs) -> Result<String, ChainError> {
92 self.call(input_variables)
93 .await
94 .map(|result| result.generation)
95 }
96
97 /// Execute the `Chain` and return the result of the generation process
98 /// along with additional information like token consumption formatted as a `HashMap`.
99 /// The input is a set of variables passed as a `PromptArgs` hashmap.
100 /// The key for the generated output is specified by the `get_output_keys`
101 /// method (default key is `output`).
102 ///
103 /// # Example
104 ///
105 /// ```rust,ignore
106 /// # use crate::my_crate::{Chain, ConversationalChainBuilder, OpenAI, OpenAIModel, SimpleMemory, PromptArgs, prompt_args};
107 /// # async {
108 /// let llm = OpenAI::default().with_model(OpenAIModel::Gpt35);
109 /// let memory = SimpleMemory::new();
110 ///
111 /// let chain = ConversationalChainBuilder::new()
112 /// .llm(llm)
113 /// .memory(memory.into())
114 /// .output_key("name")
115 /// .build().expect("Error building ConversationalChain");
116 ///
117 /// let input_variables = prompt_args! {
118 /// "input" => "Im from Peru",
119 /// };
120 ///
121 /// match chain.execute(input_variables).await {
122 /// Ok(result) => {
123 /// println!("Result: {:?}", result);
124 /// },
125 /// Err(e) => panic!("Error executing Chain: {:?}", e),
126 /// };
127 /// # };
128 /// ```
129 async fn execute(
130 &self,
131 input_variables: PromptArgs,
132 ) -> Result<HashMap<String, Value>, ChainError> {
133 tracing::info!("Using default implementation");
134 let result = self.call(input_variables.clone()).await?;
135 let mut output = HashMap::new();
136 let output_key = self
137 .get_output_keys()
138 .first()
139 .unwrap_or(&DEFAULT_OUTPUT_KEY.to_string())
140 .clone();
141 output.insert(output_key, json!(result.generation));
142 output.insert(DEFAULT_RESULT_KEY.to_string(), json!(result));
143 Ok(output)
144 }
145 /// Stream the `Chain` and get an asynchronous stream of chain generations.
146 /// The input is a set of variables passed as a `PromptArgs` hashmap.
147 /// If the chain have memroy, the tream method will not be able to automaticaly
148 /// set the memroy, bocause it will not know if the how to extract the output message
149 /// out of the stram
150 /// # Example
151 ///
152 /// ```rust,ignore
153 /// # use futures::StreamExt;
154 /// # use crate::my_crate::{Chain, LLMChainBuilder, OpenAI, fmt_message, fmt_template,
155 /// # HumanMessagePromptTemplate, prompt_args, Message, template_fstring};
156 /// # async {
157 /// let open_ai = OpenAI::default();
158 ///
159 ///let prompt = message_formatter![
160 ///fmt_message!(Message::new_system_message(
161 ///"You are world class technical documentation writer."
162 ///)),
163 ///fmt_template!(HumanMessagePromptTemplate::new(template_fstring!(
164 /// "{input}", "input"
165 ///)))
166 ///];
167 ///
168 /// let chain = LLMChainBuilder::new()
169 /// .prompt(prompt)
170 /// .llm(open_ai.clone())
171 /// .build()
172 /// .unwrap();
173 ///
174 /// let mut stream = chain.stream(
175 /// prompt_args! {
176 /// "input" => "Who is the writer of 20,000 Leagues Under the Sea?"
177 /// }).await.unwrap();
178 ///
179 /// while let Some(result) = stream.next().await {
180 /// match result {
181 /// Ok(value) => {
182 /// println!("Content: {}", value.content);
183 /// },
184 /// Err(e) => panic!("Error invoking LLMChain: {:?}", e),
185 /// }
186 /// };
187 /// # };
188 /// ```
189 ///
190 async fn stream(
191 &self,
192 _input_variables: PromptArgs,
193 ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamData, ChainError>> + Send>>, ChainError>
194 {
195 Err(ChainError::OtherError(
196 "stream is not implemented for this chain".into(),
197 ))
198 }
199
200 /// Like [`call`](Chain::call), but streams the LLM output as [`LLMEvent`]s: `Text`
201 /// chunks, then one `Done` with the complete result.
202 ///
203 /// The default implementation runs `call` and emits the result in one chunk, so every
204 /// chain supports it. [`LLMChain`](crate::chain::LLMChain) streams for real, which is
205 /// what agents use to stream their answers.
206 fn call_stream<'a>(&'a self, input_variables: PromptArgs) -> ChainEventStream<'a> {
207 box_chain_events(async_stream::try_stream! {
208 let result = self.call(input_variables).await?;
209 for event in result_events(result) {
210 yield event;
211 }
212 })
213 }
214
215 // Get the input keys of the prompt
216 fn get_input_keys(&self) -> Vec<String> {
217 tracing::info!("Using default implementation");
218 vec![]
219 }
220
221 fn get_output_keys(&self) -> Vec<String> {
222 tracing::info!("Using default implementation");
223 vec![
224 String::from(DEFAULT_OUTPUT_KEY),
225 String::from(DEFAULT_RESULT_KEY),
226 ]
227 }
228}
229
230impl<C> From<C> for Box<dyn Chain>
231where
232 C: Chain + 'static,
233{
234 fn from(chain: C) -> Self {
235 Box::new(chain)
236 }
237}