Skip to main content

langchain_rust/chain/conversational/
mod.rs

1use std::{pin::Pin, sync::Arc};
2
3use async_stream::stream;
4use async_trait::async_trait;
5use futures::Stream;
6use futures_util::{pin_mut, StreamExt};
7use tokio::sync::Mutex;
8
9use crate::{
10    language_models::GenerateResult,
11    prompt::{human_message_from_inputs, PromptArgs, PromptError, DEFAULT_IMAGES_KEY},
12    prompt_args,
13    schemas::{memory::BaseMemory, messages::Message, StreamData},
14};
15
16const DEFAULT_INPUT_VARIABLE: &str = "input";
17
18use super::{chain_trait::Chain, llm_chain::LLMChain, ChainError};
19
20pub mod builder;
21mod prompt;
22
23///This is only usefull when you dont modify the original prompt
24pub struct ConversationalChainPromptBuilder {
25    input: String,
26}
27
28impl ConversationalChainPromptBuilder {
29    pub fn new() -> Self {
30        Self {
31            input: "".to_string(),
32        }
33    }
34
35    pub fn input<S: Into<String>>(mut self, input: S) -> Self {
36        self.input = input.into();
37        self
38    }
39
40    pub fn build(self) -> PromptArgs {
41        prompt_args! {
42            DEFAULT_INPUT_VARIABLE => self.input,
43        }
44    }
45}
46
47pub struct ConversationalChain {
48    llm: LLMChain,
49    input_key: String,
50    pub memory: Arc<Mutex<dyn BaseMemory>>,
51}
52
53//Conversational Chain is a simple chain to interact with ai as a string of messages
54impl ConversationalChain {
55    pub fn prompt_builder(&self) -> ConversationalChainPromptBuilder {
56        ConversationalChainPromptBuilder::new()
57    }
58
59    /// The user's turn as saved to memory, with the images from the `images` input.
60    fn human_message(&self, inputs: &PromptArgs) -> Result<Message, ChainError> {
61        human_message_from_inputs(inputs, &self.input_key, DEFAULT_IMAGES_KEY).map_err(
62            |e| match e {
63                PromptError::MissingVariable(key) => ChainError::MissingInputVariable(key),
64                other => other.into(),
65            },
66        )
67    }
68}
69
70#[async_trait]
71impl Chain for ConversationalChain {
72    async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError> {
73        let human_message = self.human_message(&input_variables)?;
74
75        let history = self.memory.lock().await.to_string().await?;
76        let mut input_variables = input_variables;
77        input_variables.insert("history".to_string(), history.into());
78        let result = self.llm.call(input_variables.clone()).await?;
79
80        let mut memory = self.memory.lock().await;
81        memory.add_message(human_message).await?;
82        memory
83            .add_message(Message::new_ai_message(&result.generation))
84            .await?;
85        Ok(result)
86    }
87
88    async fn stream(
89        &self,
90        input_variables: PromptArgs,
91    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamData, ChainError>> + Send>>, ChainError>
92    {
93        let human_message = self.human_message(&input_variables)?;
94
95        let history = self.memory.lock().await.to_string().await?;
96
97        let mut input_variables = input_variables;
98        input_variables.insert("history".to_string(), history.into());
99
100        let complete_ai_message = Arc::new(Mutex::new(String::new()));
101        let complete_ai_message_clone = complete_ai_message.clone();
102
103        let memory = self.memory.clone();
104
105        let stream = self.llm.stream(input_variables).await?;
106        let output_stream = stream! {
107            pin_mut!(stream);
108            while let Some(result) = stream.next().await {
109                match result {
110                    Ok(data) => {
111                        let mut complete_ai_message_clone =
112                            complete_ai_message_clone.lock().await;
113                        complete_ai_message_clone.push_str(&data.content);
114
115                        yield Ok(data);
116                    },
117                    Err(e) => {
118                        yield Err(e);
119                    }
120                }
121            }
122
123            let mut memory = memory.lock().await;
124            let ai_message = Message::new_ai_message(&complete_ai_message.lock().await);
125            for message in [human_message, ai_message] {
126                if let Err(e) = memory.add_message(message).await {
127                    yield Err(e.into());
128                    break;
129                }
130            }
131        };
132
133        Ok(Box::pin(output_stream))
134    }
135
136    fn get_input_keys(&self) -> Vec<String> {
137        vec![self.input_key.clone()]
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::{
144        chain::conversational::builder::ConversationalChainBuilder,
145        llm::openai::{OpenAI, OpenAIModel},
146        prompt_args,
147    };
148
149    use super::*;
150
151    #[tokio::test]
152    #[ignore]
153    async fn test_invoke_conversational() {
154        let llm = OpenAI::default().with_model(OpenAIModel::Gpt35.to_string());
155        let chain = ConversationalChainBuilder::new()
156            .llm(llm)
157            .build()
158            .expect("Error building ConversationalChain");
159
160        let input_variables_first = prompt_args! {
161            "input" => "Soy de peru",
162        };
163        // Execute the first `chain.invoke` and assert that it should succeed
164        let result_first = chain.invoke(input_variables_first).await;
165        assert!(
166            result_first.is_ok(),
167            "Error invoking LLMChain: {:?}",
168            result_first.err()
169        );
170
171        // Optionally, if you want to print the successful result, you can do so like this:
172        if let Ok(result) = result_first {
173            println!("Result: {:?}", result);
174        }
175
176        let input_variables_second = prompt_args! {
177            "input" => "Cuales son platos tipicos de mi pais",
178        };
179        // Execute the second `chain.invoke` and assert that it should succeed
180        let result_second = chain.invoke(input_variables_second).await;
181        assert!(
182            result_second.is_ok(),
183            "Error invoking LLMChain: {:?}",
184            result_second.err()
185        );
186
187        // Optionally, if you want to print the successful result, you can do so like this:
188        if let Ok(result) = result_second {
189            println!("Result: {:?}", result);
190        }
191    }
192}