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::PromptArgs,
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
60#[async_trait]
61impl Chain for ConversationalChain {
62    async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError> {
63        let input_variable = &input_variables
64            .get(&self.input_key)
65            .ok_or(ChainError::MissingInputVariable(self.input_key.clone()))?;
66        let human_message =
67            Message::new_human_message(crate::schemas::messages::value_to_text(input_variable));
68
69        let history = {
70            let memory = self.memory.lock().await;
71            memory.to_string()
72        };
73        let mut input_variables = input_variables;
74        input_variables.insert("history".to_string(), history.into());
75        let result = self.llm.call(input_variables.clone()).await?;
76
77        let mut memory = self.memory.lock().await;
78        memory.add_message(human_message);
79        memory.add_message(Message::new_ai_message(&result.generation));
80        Ok(result)
81    }
82
83    async fn stream(
84        &self,
85        input_variables: PromptArgs,
86    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamData, ChainError>> + Send>>, ChainError>
87    {
88        let input_variable = &input_variables
89            .get(&self.input_key)
90            .ok_or(ChainError::MissingInputVariable(self.input_key.clone()))?;
91        let human_message =
92            Message::new_human_message(crate::schemas::messages::value_to_text(input_variable));
93
94        let history = {
95            let memory = self.memory.lock().await;
96            memory.to_string()
97        };
98
99        let mut input_variables = input_variables;
100        input_variables.insert("history".to_string(), history.into());
101
102        let complete_ai_message = Arc::new(Mutex::new(String::new()));
103        let complete_ai_message_clone = complete_ai_message.clone();
104
105        let memory = self.memory.clone();
106
107        let stream = self.llm.stream(input_variables).await?;
108        let output_stream = stream! {
109            pin_mut!(stream);
110            while let Some(result) = stream.next().await {
111                match result {
112                    Ok(data) => {
113                        let mut complete_ai_message_clone =
114                            complete_ai_message_clone.lock().await;
115                        complete_ai_message_clone.push_str(&data.content);
116
117                        yield Ok(data);
118                    },
119                    Err(e) => {
120                        yield Err(e);
121                    }
122                }
123            }
124
125            let mut memory = memory.lock().await;
126            memory.add_message(human_message);
127            memory.add_message(Message::new_ai_message(&complete_ai_message.lock().await));
128        };
129
130        Ok(Box::pin(output_stream))
131    }
132
133    fn get_input_keys(&self) -> Vec<String> {
134        vec![self.input_key.clone()]
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use crate::{
141        chain::conversational::builder::ConversationalChainBuilder,
142        llm::openai::{OpenAI, OpenAIModel},
143        prompt_args,
144    };
145
146    use super::*;
147
148    #[tokio::test]
149    #[ignore]
150    async fn test_invoke_conversational() {
151        let llm = OpenAI::default().with_model(OpenAIModel::Gpt35.to_string());
152        let chain = ConversationalChainBuilder::new()
153            .llm(llm)
154            .build()
155            .expect("Error building ConversationalChain");
156
157        let input_variables_first = prompt_args! {
158            "input" => "Soy de peru",
159        };
160        // Execute the first `chain.invoke` and assert that it should succeed
161        let result_first = chain.invoke(input_variables_first).await;
162        assert!(
163            result_first.is_ok(),
164            "Error invoking LLMChain: {:?}",
165            result_first.err()
166        );
167
168        // Optionally, if you want to print the successful result, you can do so like this:
169        if let Ok(result) = result_first {
170            println!("Result: {:?}", result);
171        }
172
173        let input_variables_second = prompt_args! {
174            "input" => "Cuales son platos tipicos de mi pais",
175        };
176        // Execute the second `chain.invoke` and assert that it should succeed
177        let result_second = chain.invoke(input_variables_second).await;
178        assert!(
179            result_second.is_ok(),
180            "Error invoking LLMChain: {:?}",
181            result_second.err()
182        );
183
184        // Optionally, if you want to print the successful result, you can do so like this:
185        if let Ok(result) = result_second {
186            println!("Result: {:?}", result);
187        }
188    }
189}