langchain_rust/chain/conversational/
mod.rs1use 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
23pub 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
53impl ConversationalChain {
55 pub fn prompt_builder(&self) -> ConversationalChainPromptBuilder {
56 ConversationalChainPromptBuilder::new()
57 }
58
59 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 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 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 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 if let Ok(result) = result_second {
189 println!("Result: {:?}", result);
190 }
191 }
192}