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