Skip to main content

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}