1use async_trait::async_trait;
7use futures_util::StreamExt;
8use lc_core::BaseChatModel;
9use lc_memory::{BaseMemory, ConversationBufferMemory};
10use lc_providers::{wrap_chat_model, ProviderError};
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};
18use crate::BoxedChatModel;
19
20pub struct ConversationChain {
24 llm: BoxedChatModel,
25 memory: Arc<Mutex<dyn BaseMemory>>,
26 system_prompt: Option<String>,
27 input_key: String,
28 output_key: String,
29 name: String,
30 verbose: bool,
31}
32
33impl ConversationChain {
34 pub fn new<L>(llm: L, memory: ConversationBufferMemory) -> Self
40 where
41 L: BaseChatModel + Send + Sync + 'static,
42 L::Error: Into<ProviderError>,
43 {
44 Self::from_memory(llm, Arc::new(Mutex::new(memory.with_return_messages(true))))
45 }
46
47 pub fn from_memory<L>(llm: L, memory: Arc<Mutex<dyn BaseMemory>>) -> Self
54 where
55 L: BaseChatModel + Send + Sync + 'static,
56 L::Error: Into<ProviderError>,
57 {
58 Self::from_wrapped_memory(wrap_chat_model(llm), memory)
59 }
60
61 pub(crate) fn from_wrapped_memory(
63 llm: BoxedChatModel,
64 memory: Arc<Mutex<dyn BaseMemory>>,
65 ) -> Self {
66 Self {
67 llm,
68 memory,
69 system_prompt: None,
70 input_key: "input".to_string(),
71 output_key: "output".to_string(),
72 name: "conversation_chain".to_string(),
73 verbose: false,
74 }
75 }
76
77 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
79 self.system_prompt = Some(prompt.into());
80 self
81 }
82
83 pub fn with_input_key(mut self, key: impl Into<String>) -> Self {
85 self.input_key = key.into();
86 self
87 }
88
89 pub fn with_output_key(mut self, key: impl Into<String>) -> Self {
91 self.output_key = key.into();
92 self
93 }
94
95 pub fn with_name(mut self, name: impl Into<String>) -> Self {
97 self.name = name.into();
98 self
99 }
100
101 pub fn with_verbose(mut self, verbose: bool) -> Self {
103 self.verbose = verbose;
104 self
105 }
106
107 pub fn memory(&self) -> &Arc<Mutex<dyn BaseMemory>> {
109 &self.memory
110 }
111
112 pub fn builder<L>(llm: L) -> ConversationChainBuilder
114 where
115 L: BaseChatModel + Send + Sync + 'static,
116 L::Error: Into<ProviderError>,
117 {
118 ConversationChainBuilder::new(llm)
119 }
120
121 pub async fn clear_memory(&self) -> Result<(), ChainError> {
123 let mut memory = self.memory.lock().await;
124 memory
125 .clear()
126 .await
127 .map_err(|e| ChainError::ExecutionError(format!("Failed to clear memory: {}", e)))?;
128 Ok(())
129 }
130
131 pub async fn predict(&self, input: impl Into<String>) -> Result<String, ChainError> {
135 let inputs = HashMap::from([(self.input_key.clone(), Value::String(input.into()))]);
136
137 let result = self.invoke(inputs).await?;
138
139 result
140 .get(&self.output_key)
141 .and_then(|v| v.as_str())
142 .map(|s| s.to_string())
143 .ok_or_else(|| ChainError::OutputError("Missing output".to_string()))
144 }
145
146 pub fn prepare_messages(&self, input: &str, history_messages: &[Message]) -> Vec<Message> {
150 let mut messages = Vec::new();
151
152 if let Some(system_prompt) = &self.system_prompt {
153 messages.push(Message::system(system_prompt));
154 }
155
156 for msg in history_messages {
157 messages.push(msg.clone());
158 }
159
160 messages.push(Message::human(input));
161
162 messages
163 }
164
165 async fn load_history(&self, input: &str) -> Result<Vec<Message>, ChainError> {
171 let memory = self.memory.lock().await;
172 let inputs = HashMap::from([(self.input_key.clone(), input.to_string())]);
173 let vars = memory
174 .load_memory_variables(&inputs)
175 .await
176 .map_err(|e| ChainError::ExecutionError(format!("Failed to load memory: {}", e)))?;
177 Ok(crate::base::variables_to_messages(&vars))
178 }
179
180 async fn save_context(&self, input: &str, output: &str) -> Result<(), ChainError> {
182 let mut memory = self.memory.lock().await;
183
184 let inputs = HashMap::from([(self.input_key.clone(), input.to_string())]);
185 let outputs = HashMap::from([(self.output_key.clone(), output.to_string())]);
186
187 memory
188 .save_context(&inputs, &outputs)
189 .await
190 .map_err(|e| ChainError::ExecutionError(format!("Failed to save context: {}", e)))?;
191
192 Ok(())
193 }
194}
195
196#[async_trait]
197impl BaseChain for ConversationChain {
198 fn input_keys(&self) -> Vec<&str> {
199 vec![&self.input_key]
200 }
201
202 fn output_keys(&self) -> Vec<&str> {
203 vec![&self.output_key]
204 }
205
206 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
207 self.validate_inputs(&inputs)?;
208
209 let input = inputs
210 .get(&self.input_key)
211 .and_then(|v| v.as_str())
212 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
213
214 if self.verbose {
215 println!("\n=== ConversationChain execution ===");
216 println!("User input: {}", input);
217 }
218
219 let history_messages = self.load_history(input).await?;
220
221 if self.verbose && !history_messages.is_empty() {
222 println!("History message count: {}", history_messages.len());
223 }
224
225 let messages = self.prepare_messages(input, &history_messages);
226
227 if self.verbose {
228 println!("Total message count: {}", messages.len());
229 }
230
231 let result = self
232 .llm
233 .invoke(messages, None)
234 .await
235 .map_err(|e| ChainError::ExecutionError(format!("LLM call failed: {}", e)))?;
236
237 let output = result.content;
238
239 if self.verbose {
240 println!("AI response: {}", output);
241 }
242
243 self.save_context(input, &output).await?;
244
245 if self.verbose {
246 println!("=== ConversationChain complete ===\n");
247 }
248
249 let mut result = HashMap::new();
250 result.insert(self.output_key.clone(), Value::String(output));
251
252 Ok(result)
253 }
254
255 async fn stream(&self, inputs: HashMap<String, Value>) -> Result<ChainStream, ChainError> {
257 self.validate_inputs(&inputs)?;
258
259 let input = inputs
260 .get(&self.input_key)
261 .and_then(|v| v.as_str())
262 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
263
264 let history_messages = self.load_history(input).await?;
265
266 let messages = self.prepare_messages(input, &history_messages);
267
268 let llm_stream = self
269 .llm
270 .stream_chat(messages, None)
271 .await
272 .map_err(|e| ChainError::StreamError(format!("LLM stream failed: {}", e)))?;
273
274 let memory = self.memory.clone();
275 let input_key = self.input_key.clone();
276 let output_key = self.output_key.clone();
277 let input_str = input.to_string();
278
279 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<String>();
283
284 let stream = llm_stream.map(move |result| match result {
285 Ok(token) => {
286 let _ = tx.send(token.clone());
287 Ok(StreamToken {
288 token,
289 is_final: false,
290 })
291 }
292 Err(e) => Err(ChainError::StreamError(format!(
293 "Stream token error: {}",
294 e
295 ))),
296 });
297
298 let finalizer_stream = async move {
299 let mut output = String::new();
302 let mut rx = rx;
303 while let Some(token) = rx.recv().await {
304 output.push_str(&token);
305 }
306
307 if !output.is_empty() {
308 let mut mem = memory.lock().await;
309 let ctx_inputs = HashMap::from([(input_key.clone(), input_str.clone())]);
310 let ctx_outputs = HashMap::from([(output_key.clone(), output)]);
311 if let Err(e) = mem.save_context(&ctx_inputs, &ctx_outputs).await {
312 log::error!("[ConversationChain] failed to save context: {}", e);
313 }
314 }
315 };
316
317 let final_stream = stream.chain(futures_util::stream::once(async move {
318 finalizer_stream.await;
319 Ok(StreamToken {
320 token: String::new(),
321 is_final: true,
322 })
323 }));
324
325 Ok(Box::pin(final_stream))
326 }
327
328 fn name(&self) -> &str {
329 &self.name
330 }
331}
332
333pub struct ConversationChainBuilder {
337 llm: BoxedChatModel,
338 memory: Option<Arc<Mutex<dyn BaseMemory>>>,
339 system_prompt: Option<String>,
340 input_key: Option<String>,
341 output_key: Option<String>,
342 name: Option<String>,
343 verbose: Option<bool>,
344}
345
346impl ConversationChainBuilder {
347 pub fn new<L>(llm: L) -> Self
349 where
350 L: BaseChatModel + Send + Sync + 'static,
351 L::Error: Into<ProviderError>,
352 {
353 Self {
354 llm: wrap_chat_model(llm),
355 memory: None,
356 system_prompt: None,
357 input_key: None,
358 output_key: None,
359 name: None,
360 verbose: None,
361 }
362 }
363
364 pub fn memory<Mem: BaseMemory + 'static>(mut self, memory: Mem) -> Self {
366 self.memory = Some(Arc::new(Mutex::new(memory)));
367 self
368 }
369
370 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
372 self.system_prompt = Some(prompt.into());
373 self
374 }
375
376 pub fn input_key(mut self, key: impl Into<String>) -> Self {
378 self.input_key = Some(key.into());
379 self
380 }
381
382 pub fn output_key(mut self, key: impl Into<String>) -> Self {
384 self.output_key = Some(key.into());
385 self
386 }
387
388 pub fn name(mut self, name: impl Into<String>) -> Self {
390 self.name = Some(name.into());
391 self
392 }
393
394 pub fn verbose(mut self, verbose: bool) -> Self {
396 self.verbose = Some(verbose);
397 self
398 }
399
400 pub fn build(self) -> ConversationChain {
402 let mut chain = match self.memory {
403 Some(memory) => ConversationChain::from_wrapped_memory(self.llm, memory),
404 None => ConversationChain::from_wrapped_memory(
405 self.llm,
406 Arc::new(Mutex::new(
407 ConversationBufferMemory::new().with_return_messages(true),
408 )),
409 ),
410 };
411
412 if let Some(prompt) = self.system_prompt {
413 chain = chain.with_system_prompt(prompt);
414 }
415
416 if let Some(key) = self.input_key {
417 chain = chain.with_input_key(key);
418 }
419
420 if let Some(key) = self.output_key {
421 chain = chain.with_output_key(key);
422 }
423
424 if let Some(name) = self.name {
425 chain = chain.with_name(name);
426 }
427
428 if let Some(verbose) = self.verbose {
429 chain = chain.with_verbose(verbose);
430 }
431
432 chain
433 }
434}