1use async_trait::async_trait;
7use futures_util::StreamExt;
8use lc_core::language_models::LLMResult;
9use lc_core::{BaseChatModel, Runnable};
10use lc_memory::{BaseMemory, ConversationBufferMemory};
11use lc_schema::Message;
12use serde_json::Value;
13use std::collections::HashMap;
14use std::sync::Arc;
15use tokio::sync::Mutex;
16
17use crate::base::{BaseChain, ChainError, ChainResult, ChainStream, StreamToken};
18
19pub struct ConversationChain<M: BaseChatModel> {
23 llm: M,
24 memory: Arc<Mutex<dyn BaseMemory>>,
25 system_prompt: Option<String>,
26 input_key: String,
27 output_key: String,
28 name: String,
29 verbose: bool,
30}
31
32impl<M: BaseChatModel + 'static> ConversationChain<M> {
33 pub fn new(llm: M, memory: ConversationBufferMemory) -> Self {
39 Self::from_memory(llm, Arc::new(Mutex::new(memory.with_return_messages(true))))
40 }
41
42 pub fn from_memory(llm: M, memory: Arc<Mutex<dyn BaseMemory>>) -> Self {
49 Self {
50 llm,
51 memory,
52 system_prompt: None,
53 input_key: "input".to_string(),
54 output_key: "output".to_string(),
55 name: "conversation_chain".to_string(),
56 verbose: false,
57 }
58 }
59
60 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
62 self.system_prompt = Some(prompt.into());
63 self
64 }
65
66 pub fn with_input_key(mut self, key: impl Into<String>) -> Self {
68 self.input_key = key.into();
69 self
70 }
71
72 pub fn with_output_key(mut self, key: impl Into<String>) -> Self {
74 self.output_key = key.into();
75 self
76 }
77
78 pub fn with_name(mut self, name: impl Into<String>) -> Self {
80 self.name = name.into();
81 self
82 }
83
84 pub fn with_verbose(mut self, verbose: bool) -> Self {
86 self.verbose = verbose;
87 self
88 }
89
90 pub fn memory(&self) -> &Arc<Mutex<dyn BaseMemory>> {
92 &self.memory
93 }
94
95 pub fn builder(llm: M) -> ConversationChainBuilder<M> {
96 ConversationChainBuilder::new(llm)
97 }
98
99 pub async fn clear_memory(&self) -> Result<(), ChainError> {
101 let mut memory = self.memory.lock().await;
102 memory
103 .clear()
104 .await
105 .map_err(|e| ChainError::ExecutionError(format!("Failed to clear memory: {}", e)))?;
106 Ok(())
107 }
108
109 pub async fn predict(&self, input: impl Into<String>) -> Result<String, ChainError> {
113 let inputs = HashMap::from([(self.input_key.clone(), Value::String(input.into()))]);
114
115 let result = self.invoke(inputs).await?;
116
117 result
118 .get(&self.output_key)
119 .and_then(|v| v.as_str())
120 .map(|s| s.to_string())
121 .ok_or_else(|| ChainError::OutputError("Missing output".to_string()))
122 }
123
124 pub fn prepare_messages(&self, input: &str, history_messages: &[Message]) -> Vec<Message> {
128 let mut messages = Vec::new();
129
130 if let Some(system_prompt) = &self.system_prompt {
131 messages.push(Message::system(system_prompt));
132 }
133
134 for msg in history_messages {
135 messages.push(msg.clone());
136 }
137
138 messages.push(Message::human(input));
139
140 messages
141 }
142
143 async fn load_history(&self, input: &str) -> Result<Vec<Message>, ChainError> {
149 let memory = self.memory.lock().await;
150 let inputs = HashMap::from([(self.input_key.clone(), input.to_string())]);
151 let vars = memory
152 .load_memory_variables(&inputs)
153 .await
154 .map_err(|e| ChainError::ExecutionError(format!("Failed to load memory: {}", e)))?;
155 Ok(crate::base::variables_to_messages(&vars))
156 }
157
158 async fn save_context(&self, input: &str, output: &str) -> Result<(), ChainError> {
160 let mut memory = self.memory.lock().await;
161
162 let inputs = HashMap::from([(self.input_key.clone(), input.to_string())]);
163 let outputs = HashMap::from([(self.output_key.clone(), output.to_string())]);
164
165 memory
166 .save_context(&inputs, &outputs)
167 .await
168 .map_err(|e| ChainError::ExecutionError(format!("Failed to save context: {}", e)))?;
169
170 Ok(())
171 }
172}
173
174#[async_trait]
175impl<M: BaseChatModel + Send + Sync + 'static> BaseChain for ConversationChain<M>
176where
177 <M as Runnable<Vec<Message>, LLMResult>>::Error: std::fmt::Display,
178{
179 fn input_keys(&self) -> Vec<&str> {
180 vec![&self.input_key]
181 }
182
183 fn output_keys(&self) -> Vec<&str> {
184 vec![&self.output_key]
185 }
186
187 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
188 self.validate_inputs(&inputs)?;
189
190 let input = inputs
191 .get(&self.input_key)
192 .and_then(|v| v.as_str())
193 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
194
195 if self.verbose {
196 println!("\n=== ConversationChain execution ===");
197 println!("User input: {}", input);
198 }
199
200 let history_messages = self.load_history(input).await?;
201
202 if self.verbose && !history_messages.is_empty() {
203 println!("History message count: {}", history_messages.len());
204 }
205
206 let messages = self.prepare_messages(input, &history_messages);
207
208 if self.verbose {
209 println!("Total message count: {}", messages.len());
210 }
211
212 let result = self
213 .llm
214 .invoke(messages, None)
215 .await
216 .map_err(|e| ChainError::ExecutionError(format!("LLM call failed: {}", e)))?;
217
218 let output = result.content;
219
220 if self.verbose {
221 println!("AI response: {}", output);
222 }
223
224 self.save_context(input, &output).await?;
225
226 if self.verbose {
227 println!("=== ConversationChain complete ===\n");
228 }
229
230 let mut result = HashMap::new();
231 result.insert(self.output_key.clone(), Value::String(output));
232
233 Ok(result)
234 }
235
236 async fn stream(&self, inputs: HashMap<String, Value>) -> Result<ChainStream, ChainError> {
238 self.validate_inputs(&inputs)?;
239
240 let input = inputs
241 .get(&self.input_key)
242 .and_then(|v| v.as_str())
243 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
244
245 let history_messages = self.load_history(input).await?;
246
247 let messages = self.prepare_messages(input, &history_messages);
248
249 let llm_stream = self
250 .llm
251 .stream_chat(messages, None)
252 .await
253 .map_err(|e| ChainError::StreamError(format!("LLM stream failed: {}", e)))?;
254
255 let memory = self.memory.clone();
256 let input_key = self.input_key.clone();
257 let output_key = self.output_key.clone();
258 let input_str = input.to_string();
259
260 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<String>();
264
265 let stream = llm_stream.map(move |result| match result {
266 Ok(token) => {
267 let _ = tx.send(token.clone());
268 Ok(StreamToken {
269 token,
270 is_final: false,
271 })
272 }
273 Err(e) => Err(ChainError::StreamError(format!(
274 "Stream token error: {}",
275 e
276 ))),
277 });
278
279 let finalizer_stream = async move {
280 let mut output = String::new();
283 let mut rx = rx;
284 while let Some(token) = rx.recv().await {
285 output.push_str(&token);
286 }
287
288 if !output.is_empty() {
289 let mut mem = memory.lock().await;
290 let ctx_inputs = HashMap::from([(input_key.clone(), input_str.clone())]);
291 let ctx_outputs = HashMap::from([(output_key.clone(), output)]);
292 if let Err(e) = mem.save_context(&ctx_inputs, &ctx_outputs).await {
293 log::error!("[ConversationChain] failed to save context: {}", e);
294 }
295 }
296 };
297
298 let final_stream = stream.chain(futures_util::stream::once(async move {
299 finalizer_stream.await;
300 Ok(StreamToken {
301 token: String::new(),
302 is_final: true,
303 })
304 }));
305
306 Ok(Box::pin(final_stream))
307 }
308
309 fn name(&self) -> &str {
310 &self.name
311 }
312}
313
314pub struct ConversationChainBuilder<M: BaseChatModel> {
318 llm: M,
319 memory: Option<Arc<Mutex<dyn BaseMemory>>>,
320 system_prompt: Option<String>,
321 input_key: Option<String>,
322 output_key: Option<String>,
323 name: Option<String>,
324 verbose: Option<bool>,
325}
326
327impl<M: BaseChatModel + 'static> ConversationChainBuilder<M> {
328 pub fn new(llm: M) -> Self {
329 Self {
330 llm,
331 memory: None,
332 system_prompt: None,
333 input_key: None,
334 output_key: None,
335 name: None,
336 verbose: None,
337 }
338 }
339
340 pub fn memory<Mem: BaseMemory + 'static>(mut self, memory: Mem) -> Self {
341 self.memory = Some(Arc::new(Mutex::new(memory)));
342 self
343 }
344
345 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
346 self.system_prompt = Some(prompt.into());
347 self
348 }
349
350 pub fn input_key(mut self, key: impl Into<String>) -> Self {
351 self.input_key = Some(key.into());
352 self
353 }
354
355 pub fn output_key(mut self, key: impl Into<String>) -> Self {
356 self.output_key = Some(key.into());
357 self
358 }
359
360 pub fn name(mut self, name: impl Into<String>) -> Self {
361 self.name = Some(name.into());
362 self
363 }
364
365 pub fn verbose(mut self, verbose: bool) -> Self {
366 self.verbose = Some(verbose);
367 self
368 }
369
370 pub fn build(self) -> ConversationChain<M> {
371 let mut chain = match self.memory {
372 Some(memory) => ConversationChain::from_memory(self.llm, memory),
373 None => ConversationChain::new(self.llm, ConversationBufferMemory::new()),
374 };
375
376 if let Some(prompt) = self.system_prompt {
377 chain = chain.with_system_prompt(prompt);
378 }
379
380 if let Some(key) = self.input_key {
381 chain = chain.with_input_key(key);
382 }
383
384 if let Some(key) = self.output_key {
385 chain = chain.with_output_key(key);
386 }
387
388 if let Some(name) = self.name {
389 chain = chain.with_name(name);
390 }
391
392 if let Some(verbose) = self.verbose {
393 chain = chain.with_verbose(verbose);
394 }
395
396 chain
397 }
398}