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::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
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
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 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 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 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 if let Ok(result) = result_second {
186 println!("Result: {:?}", result);
187 }
188 }
189}