graph_flow/context.rs
1//! Context and state management for workflows.
2//!
3//! This module provides thread-safe state management across workflow tasks,
4//! including regular data storage and specialized chat history management.
5//!
6//! # Examples
7//!
8//! ## Basic Context Usage
9//!
10//! ```rust
11//! use graph_flow::Context;
12//!
13//! # #[tokio::main]
14//! # async fn main() {
15//! let context = Context::new();
16//!
17//! // Store different types of data
18//! context.set("user_id", 12345).await;
19//! context.set("name", "Alice".to_string()).await;
20//! context.set("active", true).await;
21//!
22//! // Retrieve data with type safety
23//! let user_id: Option<i32> = context.get("user_id").await;
24//! let name: Option<String> = context.get("name").await;
25//! let active: Option<bool> = context.get("active").await;
26//!
27//! // Synchronous access (useful in edge conditions)
28//! let name_sync: Option<String> = context.get_sync("name");
29//! # }
30//! ```
31//!
32//! ## Chat History Management
33//!
34//! ```rust
35//! use graph_flow::Context;
36//!
37//! # #[tokio::main]
38//! # async fn main() {
39//! let context = Context::new();
40//!
41//! // Add messages to chat history
42//! context.add_user_message("Hello, assistant!".to_string()).await;
43//! context.add_assistant_message("Hello! How can I help you?".to_string()).await;
44//! context.add_system_message("User session started".to_string()).await;
45//!
46//! // Get chat history
47//! let history = context.get_chat_history().await;
48//! let all_messages = context.get_all_messages().await;
49//! let last_5 = context.get_last_messages(5).await;
50//!
51//! // Check history status
52//! let count = context.chat_history_len().await;
53//! let is_empty = context.is_chat_history_empty().await;
54//! # }
55//! ```
56//!
57//! ## Context with Message Limits
58//!
59//! ```rust
60//! use graph_flow::Context;
61//!
62//! # #[tokio::main]
63//! # async fn main() {
64//! // Create context with maximum 100 messages
65//! let context = Context::with_max_chat_messages(100);
66//!
67//! // Messages will be automatically pruned when limit is exceeded
68//! for i in 0..150 {
69//! context.add_user_message(format!("Message {}", i)).await;
70//! }
71//!
72//! // Only the last 100 messages are kept
73//! assert_eq!(context.chat_history_len().await, 100);
74//! # }
75//! ```
76//!
77//! ## LLM Integration (with `rig` feature)
78//!
79//! ```rust
80//! # #[cfg(feature = "rig")]
81//! # {
82//! use graph_flow::Context;
83//!
84//! # #[tokio::main]
85//! # async fn main() {
86//! let context = Context::new();
87//!
88//! context.add_user_message("What is the capital of France?".to_string()).await;
89//! context.add_assistant_message("The capital of France is Paris.".to_string()).await;
90//!
91//! // Get messages in rig format for LLM calls
92//! let rig_messages = context.get_rig_messages().await;
93//! let recent_messages = context.get_last_rig_messages(10).await;
94//!
95//! // Use with rig's completion API
96//! // let response = agent.completion(&rig_messages).await?;
97//! # }
98//! # }
99//! ```
100
101use chrono::{DateTime, Utc};
102use dashmap::DashMap;
103use serde::{Deserialize, Serialize};
104use serde_json::Value;
105use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
106
107use crate::error::GraphError;
108
109#[cfg(feature = "rig")]
110use rig::completion::Message;
111
112/// Represents the role of a message in a conversation.
113///
114/// Used in chat history to distinguish between different types of messages.
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
116pub enum MessageRole {
117 /// Message from a user/human
118 User,
119 /// Message from an assistant/AI
120 Assistant,
121 /// System message (instructions, status updates, etc.)
122 System,
123}
124
125/// A serializable message that can be converted to/from rig::completion::Message.
126///
127/// This struct provides a unified message format that can be stored, serialized,
128/// and optionally converted to other formats like rig's Message type.
129///
130/// # Examples
131///
132/// ```rust
133/// use graph_flow::{SerializableMessage, MessageRole};
134///
135/// // Create different types of messages
136/// let user_msg = SerializableMessage::user("Hello!".to_string());
137/// let assistant_msg = SerializableMessage::assistant("Hi there!".to_string());
138/// let system_msg = SerializableMessage::system("Session started".to_string());
139///
140/// // Access message properties
141/// assert_eq!(user_msg.role, MessageRole::User);
142/// assert_eq!(user_msg.content, "Hello!");
143/// ```
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct SerializableMessage {
146 /// The role of the message sender
147 pub role: MessageRole,
148 /// The content of the message
149 pub content: String,
150 /// When the message was created
151 pub timestamp: DateTime<Utc>,
152}
153
154impl SerializableMessage {
155 /// Create a new message with the specified role and content.
156 ///
157 /// The timestamp is automatically set to the current UTC time.
158 pub fn new(role: MessageRole, content: String) -> Self {
159 Self {
160 role,
161 content,
162 timestamp: Utc::now(),
163 }
164 }
165
166 /// Create a new user message.
167 ///
168 /// # Examples
169 ///
170 /// ```rust
171 /// use graph_flow::SerializableMessage;
172 ///
173 /// let msg = SerializableMessage::user("Hello, world!".to_string());
174 /// ```
175 pub fn user(content: String) -> Self {
176 Self::new(MessageRole::User, content)
177 }
178
179 /// Create a new assistant message.
180 ///
181 /// # Examples
182 ///
183 /// ```rust
184 /// use graph_flow::SerializableMessage;
185 ///
186 /// let msg = SerializableMessage::assistant("Hello! How can I help?".to_string());
187 /// ```
188 pub fn assistant(content: String) -> Self {
189 Self::new(MessageRole::Assistant, content)
190 }
191
192 /// Create a new system message.
193 ///
194 /// # Examples
195 ///
196 /// ```rust
197 /// use graph_flow::SerializableMessage;
198 ///
199 /// let msg = SerializableMessage::system("User logged in".to_string());
200 /// ```
201 pub fn system(content: String) -> Self {
202 Self::new(MessageRole::System, content)
203 }
204}
205
206/// Container for managing chat history with serialization support.
207///
208/// Provides automatic message limit management and convenient methods
209/// for adding and retrieving messages.
210///
211/// # Examples
212///
213/// ```rust
214/// use graph_flow::ChatHistory;
215///
216/// let mut history = ChatHistory::new();
217/// history.add_user_message("Hello".to_string());
218/// history.add_assistant_message("Hi there!".to_string());
219///
220/// assert_eq!(history.len(), 2);
221/// assert!(!history.is_empty());
222/// ```
223#[derive(Debug, Clone, Serialize, Deserialize, Default)]
224pub struct ChatHistory {
225 messages: Vec<SerializableMessage>,
226 max_messages: Option<usize>,
227}
228
229impl ChatHistory {
230 /// Create a new empty chat history with a default limit of 1000 messages.
231 pub fn new() -> Self {
232 Self {
233 messages: Vec::new(),
234 max_messages: Some(1000), // Default limit to prevent unbounded growth
235 }
236 }
237
238 /// Create a new chat history with a maximum message limit.
239 ///
240 /// When the limit is exceeded, older messages are automatically removed.
241 ///
242 /// # Examples
243 ///
244 /// ```rust
245 /// use graph_flow::ChatHistory;
246 ///
247 /// let mut history = ChatHistory::with_max_messages(10);
248 ///
249 /// // Add 15 messages
250 /// for i in 0..15 {
251 /// history.add_user_message(format!("Message {}", i));
252 /// }
253 ///
254 /// // Only the last 10 are kept
255 /// assert_eq!(history.len(), 10);
256 /// ```
257 pub fn with_max_messages(max: usize) -> Self {
258 Self {
259 messages: Vec::new(),
260 max_messages: Some(max),
261 }
262 }
263
264 /// Add a user message to the chat history.
265 pub fn add_user_message(&mut self, content: String) {
266 self.add_message(SerializableMessage::user(content));
267 }
268
269 /// Add an assistant message to the chat history.
270 pub fn add_assistant_message(&mut self, content: String) {
271 self.add_message(SerializableMessage::assistant(content));
272 }
273
274 /// Add a system message to the chat history.
275 pub fn add_system_message(&mut self, content: String) {
276 self.add_message(SerializableMessage::system(content));
277 }
278
279 /// Add a message to the chat history, respecting max_messages limit.
280 fn add_message(&mut self, message: SerializableMessage) {
281 self.messages.push(message);
282
283 if let Some(max) = self.max_messages
284 && self.messages.len() > max
285 {
286 self.messages.drain(0..(self.messages.len() - max));
287 }
288 }
289
290 /// Clear all messages from the chat history.
291 pub fn clear(&mut self) {
292 self.messages.clear();
293 }
294
295 /// Get the number of messages in the chat history.
296 pub fn len(&self) -> usize {
297 self.messages.len()
298 }
299
300 /// Check if the chat history is empty.
301 pub fn is_empty(&self) -> bool {
302 self.messages.is_empty()
303 }
304
305 /// Get a reference to all messages.
306 pub fn messages(&self) -> &[SerializableMessage] {
307 &self.messages
308 }
309
310 /// Get the last N messages.
311 ///
312 /// If N is greater than the total number of messages, all messages are returned.
313 ///
314 /// # Examples
315 ///
316 /// ```rust
317 /// use graph_flow::ChatHistory;
318 ///
319 /// let mut history = ChatHistory::new();
320 /// history.add_user_message("Message 1".to_string());
321 /// history.add_user_message("Message 2".to_string());
322 /// history.add_user_message("Message 3".to_string());
323 ///
324 /// let last_two = history.last_messages(2);
325 /// assert_eq!(last_two.len(), 2);
326 /// assert_eq!(last_two[0].content, "Message 2");
327 /// assert_eq!(last_two[1].content, "Message 3");
328 /// ```
329 pub fn last_messages(&self, n: usize) -> &[SerializableMessage] {
330 let start = if self.messages.len() > n {
331 self.messages.len() - n
332 } else {
333 0
334 };
335 &self.messages[start..]
336 }
337}
338
339/// Helper struct for serializing/deserializing Context
340#[derive(Serialize, Deserialize)]
341struct ContextData {
342 data: std::collections::HashMap<String, Value>,
343 chat_history: ChatHistory,
344}
345
346/// Context for sharing data between tasks in a graph execution.
347///
348/// Provides thread-safe storage for workflow state and dedicated chat history
349/// management. The context is shared across all tasks in a workflow execution.
350///
351/// # Examples
352///
353/// ## Basic Usage
354///
355/// ```rust
356/// use graph_flow::Context;
357///
358/// # #[tokio::main]
359/// # async fn main() {
360/// let context = Context::new();
361///
362/// // Store different types of data
363/// context.set("user_id", 12345).await;
364/// context.set("name", "Alice".to_string()).await;
365/// context.set("settings", vec!["opt1", "opt2"]).await;
366///
367/// // Retrieve data
368/// let user_id: Option<i32> = context.get("user_id").await;
369/// let name: Option<String> = context.get("name").await;
370/// let settings: Option<Vec<String>> = context.get("settings").await;
371/// # }
372/// ```
373///
374/// ## Chat History
375///
376/// ```rust
377/// use graph_flow::Context;
378///
379/// # #[tokio::main]
380/// # async fn main() {
381/// let context = Context::new();
382///
383/// // Add messages
384/// context.add_user_message("Hello".to_string()).await;
385/// context.add_assistant_message("Hi there!".to_string()).await;
386///
387/// // Get message history
388/// let history = context.get_chat_history().await;
389/// let last_5 = context.get_last_messages(5).await;
390/// # }
391/// ```
392#[derive(Clone, Debug)]
393pub struct Context {
394 data: Arc<DashMap<String, Value>>,
395 chat_history: Arc<RwLock<ChatHistory>>,
396}
397
398impl Context {
399 /// Create a new empty context.
400 pub fn new() -> Self {
401 Self {
402 data: Arc::new(DashMap::new()),
403 chat_history: Arc::new(RwLock::new(ChatHistory::new())),
404 }
405 }
406
407 /// Create a new context with a maximum chat history size.
408 ///
409 /// When the chat history exceeds this size, older messages are automatically removed.
410 ///
411 /// # Examples
412 ///
413 /// ```rust
414 /// use graph_flow::Context;
415 ///
416 /// # #[tokio::main]
417 /// # async fn main() {
418 /// let context = Context::with_max_chat_messages(50);
419 ///
420 /// // Chat history will be limited to 50 messages
421 /// for i in 0..100 {
422 /// context.add_user_message(format!("Message {}", i)).await;
423 /// }
424 ///
425 /// assert_eq!(context.chat_history_len().await, 50);
426 /// # }
427 /// ```
428 pub fn with_max_chat_messages(max: usize) -> Self {
429 Self {
430 data: Arc::new(DashMap::new()),
431 chat_history: Arc::new(RwLock::new(ChatHistory::with_max_messages(max))),
432 }
433 }
434
435 /// Acquire the chat history read lock, recovering from poisoning.
436 ///
437 /// A poisoned lock means a task panicked while holding it; the history
438 /// itself is still valid (messages are appended atomically), so we recover
439 /// the guard rather than silently returning empty data.
440 fn history_read(&self) -> RwLockReadGuard<'_, ChatHistory> {
441 self.chat_history
442 .read()
443 .unwrap_or_else(|poisoned| poisoned.into_inner())
444 }
445
446 /// Acquire the chat history write lock, recovering from poisoning (see
447 /// [`Context::history_read`]).
448 fn history_write(&self) -> RwLockWriteGuard<'_, ChatHistory> {
449 self.chat_history
450 .write()
451 .unwrap_or_else(|poisoned| poisoned.into_inner())
452 }
453
454 // Regular context methods (unchanged API)
455
456 /// Set a value in the context.
457 ///
458 /// The value must be serializable. Most common Rust types are supported.
459 ///
460 /// # Examples
461 ///
462 /// ```rust
463 /// use graph_flow::Context;
464 /// use serde::{Serialize, Deserialize};
465 ///
466 /// #[derive(Serialize, Deserialize)]
467 /// struct UserData {
468 /// id: u32,
469 /// name: String,
470 /// }
471 ///
472 /// # #[tokio::main]
473 /// # async fn main() {
474 /// let context = Context::new();
475 ///
476 /// // Store primitive types
477 /// context.set("count", 42).await;
478 /// context.set("name", "Alice".to_string()).await;
479 /// context.set("active", true).await;
480 ///
481 /// // Store complex types
482 /// let user = UserData { id: 1, name: "Bob".to_string() };
483 /// context.set("user", user).await;
484 /// # }
485 /// ```
486 ///
487 /// # Panics
488 ///
489 /// Panics if the value cannot be serialized to JSON (e.g. a map with
490 /// non-string keys). Use [`Context::try_set`] to handle this as an error.
491 pub async fn set(&self, key: impl Into<String>, value: impl serde::Serialize) {
492 self.set_sync(key, value);
493 }
494
495 /// Fallible version of [`Context::set`].
496 ///
497 /// Returns `Err(GraphError::ContextError)` instead of panicking when the
498 /// value cannot be serialized to JSON.
499 pub async fn try_set(
500 &self,
501 key: impl Into<String>,
502 value: impl serde::Serialize,
503 ) -> crate::error::Result<()> {
504 self.try_set_sync(key, value)
505 }
506
507 /// Synchronous, fallible version of [`Context::set`] (see [`Context::try_set`]).
508 pub fn try_set_sync(
509 &self,
510 key: impl Into<String>,
511 value: impl serde::Serialize,
512 ) -> crate::error::Result<()> {
513 let key = key.into();
514 let value = serde_json::to_value(value).map_err(|e| {
515 GraphError::ContextError(format!("Failed to serialize value for key '{key}': {e}"))
516 })?;
517 self.data.insert(key, value);
518 Ok(())
519 }
520
521 /// Get a value from the context.
522 ///
523 /// Returns `None` if the key doesn't exist or if deserialization fails.
524 ///
525 /// # Examples
526 ///
527 /// ```rust
528 /// use graph_flow::Context;
529 ///
530 /// # #[tokio::main]
531 /// # async fn main() {
532 /// let context = Context::new();
533 /// context.set("count", 42).await;
534 ///
535 /// let count: Option<i32> = context.get("count").await;
536 /// assert_eq!(count, Some(42));
537 ///
538 /// let missing: Option<String> = context.get("missing").await;
539 /// assert_eq!(missing, None);
540 /// # }
541 /// ```
542 pub async fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
543 self.get_sync(key)
544 }
545
546 /// Remove a value from the context.
547 ///
548 /// Returns the removed value if it existed.
549 ///
550 /// # Examples
551 ///
552 /// ```rust
553 /// use graph_flow::Context;
554 ///
555 /// # #[tokio::main]
556 /// # async fn main() {
557 /// let context = Context::new();
558 /// context.set("temp", "value".to_string()).await;
559 ///
560 /// let removed = context.remove("temp").await;
561 /// assert!(removed.is_some());
562 ///
563 /// let value: Option<String> = context.get("temp").await;
564 /// assert_eq!(value, None);
565 /// # }
566 /// ```
567 pub async fn remove(&self, key: &str) -> Option<Value> {
568 self.data.remove(key).map(|(_, v)| v)
569 }
570
571 /// Clear all regular context data (does not affect chat history).
572 ///
573 /// # Examples
574 ///
575 /// ```rust
576 /// use graph_flow::Context;
577 ///
578 /// # #[tokio::main]
579 /// # async fn main() {
580 /// let context = Context::new();
581 /// context.set("key1", "value1".to_string()).await;
582 /// context.set("key2", "value2".to_string()).await;
583 /// context.add_user_message("Hello".to_string()).await;
584 ///
585 /// context.clear().await;
586 ///
587 /// // Regular data is cleared
588 /// let value: Option<String> = context.get("key1").await;
589 /// assert_eq!(value, None);
590 ///
591 /// // Chat history is preserved
592 /// assert_eq!(context.chat_history_len().await, 1);
593 /// # }
594 /// ```
595 pub async fn clear(&self) {
596 self.data.clear();
597 }
598
599 /// Synchronous version of get for use in edge conditions.
600 ///
601 /// This method should only be used when you're certain the data exists
602 /// and when async is not available (e.g., in edge condition closures).
603 ///
604 /// # Examples
605 ///
606 /// ```rust
607 /// use graph_flow::{Context, GraphBuilder};
608 ///
609 /// # #[tokio::main]
610 /// # async fn main() {
611 /// let context = Context::new();
612 /// context.set("condition", true).await;
613 ///
614 /// // Used in edge conditions
615 /// let graph = GraphBuilder::new("test")
616 /// .add_conditional_edge(
617 /// "task1",
618 /// |ctx| ctx.get_sync::<bool>("condition").unwrap_or(false),
619 /// "task2",
620 /// "task3"
621 /// );
622 /// # }
623 /// ```
624 pub fn get_sync<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
625 let entry = self.data.get(key)?;
626 // Deserialize straight from a reference to avoid cloning the stored
627 // Value (which can be large, e.g. documents or chat transcripts).
628 match T::deserialize(entry.value()) {
629 Ok(value) => Some(value),
630 Err(e) => {
631 tracing::warn!(
632 key = %key,
633 error = %e,
634 "Context value exists but failed to deserialize to the requested type"
635 );
636 None
637 }
638 }
639 }
640
641 /// Synchronous version of set for use when async is not available.
642 ///
643 /// # Examples
644 ///
645 /// ```rust
646 /// use graph_flow::Context;
647 ///
648 /// let context = Context::new();
649 /// context.set_sync("key", "value".to_string());
650 ///
651 /// let value: Option<String> = context.get_sync("key");
652 /// assert_eq!(value, Some("value".to_string()));
653 /// ```
654 ///
655 /// # Panics
656 ///
657 /// Panics if the value cannot be serialized to JSON. Use
658 /// [`Context::try_set_sync`] to handle this as an error.
659 pub fn set_sync(&self, key: impl Into<String>, value: impl serde::Serialize) {
660 let value = serde_json::to_value(value).expect("Failed to serialize value");
661 self.data.insert(key.into(), value);
662 }
663
664 // Chat history methods
665
666 /// Add a user message to the chat history.
667 ///
668 /// # Examples
669 ///
670 /// ```rust
671 /// use graph_flow::Context;
672 ///
673 /// # #[tokio::main]
674 /// # async fn main() {
675 /// let context = Context::new();
676 /// context.add_user_message("Hello, assistant!".to_string()).await;
677 /// # }
678 /// ```
679 pub async fn add_user_message(&self, content: String) {
680 self.history_write().add_user_message(content);
681 }
682
683 /// Add an assistant message to the chat history.
684 ///
685 /// # Examples
686 ///
687 /// ```rust
688 /// use graph_flow::Context;
689 ///
690 /// # #[tokio::main]
691 /// # async fn main() {
692 /// let context = Context::new();
693 /// context.add_assistant_message("Hello! How can I help you?".to_string()).await;
694 /// # }
695 /// ```
696 pub async fn add_assistant_message(&self, content: String) {
697 self.history_write().add_assistant_message(content);
698 }
699
700 /// Add a system message to the chat history.
701 ///
702 /// # Examples
703 ///
704 /// ```rust
705 /// use graph_flow::Context;
706 ///
707 /// # #[tokio::main]
708 /// # async fn main() {
709 /// let context = Context::new();
710 /// context.add_system_message("Session started".to_string()).await;
711 /// # }
712 /// ```
713 pub async fn add_system_message(&self, content: String) {
714 self.history_write().add_system_message(content);
715 }
716
717 /// Get a clone of the current chat history.
718 ///
719 /// # Examples
720 ///
721 /// ```rust
722 /// use graph_flow::Context;
723 ///
724 /// # #[tokio::main]
725 /// # async fn main() {
726 /// let context = Context::new();
727 /// context.add_user_message("Hello".to_string()).await;
728 ///
729 /// let history = context.get_chat_history().await;
730 /// assert_eq!(history.len(), 1);
731 /// # }
732 /// ```
733 pub async fn get_chat_history(&self) -> ChatHistory {
734 self.history_read().clone()
735 }
736
737 /// Clear the chat history.
738 ///
739 /// # Examples
740 ///
741 /// ```rust
742 /// use graph_flow::Context;
743 ///
744 /// # #[tokio::main]
745 /// # async fn main() {
746 /// let context = Context::new();
747 /// context.add_user_message("Hello".to_string()).await;
748 /// assert_eq!(context.chat_history_len().await, 1);
749 ///
750 /// context.clear_chat_history().await;
751 /// assert_eq!(context.chat_history_len().await, 0);
752 /// # }
753 /// ```
754 pub async fn clear_chat_history(&self) {
755 self.history_write().clear();
756 }
757
758 /// Get the number of messages in the chat history.
759 pub async fn chat_history_len(&self) -> usize {
760 self.history_read().len()
761 }
762
763 /// Check if the chat history is empty.
764 pub async fn is_chat_history_empty(&self) -> bool {
765 self.history_read().is_empty()
766 }
767
768 /// Get the last N messages from chat history.
769 ///
770 /// # Examples
771 ///
772 /// ```rust
773 /// use graph_flow::Context;
774 ///
775 /// # #[tokio::main]
776 /// # async fn main() {
777 /// let context = Context::new();
778 /// context.add_user_message("Message 1".to_string()).await;
779 /// context.add_user_message("Message 2".to_string()).await;
780 /// context.add_user_message("Message 3".to_string()).await;
781 ///
782 /// let last_two = context.get_last_messages(2).await;
783 /// assert_eq!(last_two.len(), 2);
784 /// assert_eq!(last_two[0].content, "Message 2");
785 /// assert_eq!(last_two[1].content, "Message 3");
786 /// # }
787 /// ```
788 pub async fn get_last_messages(&self, n: usize) -> Vec<SerializableMessage> {
789 self.history_read().last_messages(n).to_vec()
790 }
791
792 /// Get all messages from chat history as SerializableMessage.
793 ///
794 /// # Examples
795 ///
796 /// ```rust
797 /// use graph_flow::Context;
798 ///
799 /// # #[tokio::main]
800 /// # async fn main() {
801 /// let context = Context::new();
802 /// context.add_user_message("Hello".to_string()).await;
803 /// context.add_assistant_message("Hi there!".to_string()).await;
804 ///
805 /// let all_messages = context.get_all_messages().await;
806 /// assert_eq!(all_messages.len(), 2);
807 /// # }
808 /// ```
809 pub async fn get_all_messages(&self) -> Vec<SerializableMessage> {
810 self.history_read().messages().to_vec()
811 }
812
813 // Rig integration methods (only available when rig feature is enabled)
814
815 #[cfg(feature = "rig")]
816 /// Get all chat history messages converted to rig::completion::Message format.
817 ///
818 /// This method is only available when the "rig" feature is enabled.
819 ///
820 /// # Examples
821 ///
822 /// ```rust
823 /// # #[cfg(feature = "rig")]
824 /// # {
825 /// use graph_flow::Context;
826 ///
827 /// # #[tokio::main]
828 /// # async fn main() {
829 /// let context = Context::new();
830 /// context.add_user_message("Hello".to_string()).await;
831 /// context.add_assistant_message("Hi there!".to_string()).await;
832 ///
833 /// let rig_messages = context.get_rig_messages().await;
834 /// assert_eq!(rig_messages.len(), 2);
835 /// # }
836 /// # }
837 /// ```
838 pub async fn get_rig_messages(&self) -> Vec<Message> {
839 self.get_all_messages()
840 .await
841 .into_iter()
842 .map(Message::from)
843 .collect()
844 }
845
846 #[cfg(feature = "rig")]
847 /// Get the last N messages converted to rig::completion::Message format.
848 ///
849 /// This method is only available when the "rig" feature is enabled.
850 ///
851 /// # Examples
852 ///
853 /// ```rust
854 /// # #[cfg(feature = "rig")]
855 /// # {
856 /// use graph_flow::Context;
857 ///
858 /// # #[tokio::main]
859 /// # async fn main() {
860 /// let context = Context::new();
861 /// for i in 0..10 {
862 /// context.add_user_message(format!("Message {}", i)).await;
863 /// }
864 ///
865 /// let last_5 = context.get_last_rig_messages(5).await;
866 /// assert_eq!(last_5.len(), 5);
867 /// # }
868 /// # }
869 /// ```
870 pub async fn get_last_rig_messages(&self, n: usize) -> Vec<Message> {
871 self.get_last_messages(n)
872 .await
873 .into_iter()
874 .map(Message::from)
875 .collect()
876 }
877}
878
879#[cfg(feature = "rig")]
880impl From<SerializableMessage> for Message {
881 /// Convert a [`SerializableMessage`] into a `rig::completion::Message`,
882 /// moving the content instead of cloning it.
883 ///
884 /// Only available when the "rig" feature is enabled.
885 fn from(msg: SerializableMessage) -> Self {
886 match msg.role {
887 MessageRole::User => Message::user(msg.content),
888 MessageRole::Assistant => Message::assistant(msg.content),
889 MessageRole::System => Message::system(msg.content),
890 }
891 }
892}
893
894impl Default for Context {
895 fn default() -> Self {
896 Self::new()
897 }
898}
899
900// Serialization support for Context
901impl Serialize for Context {
902 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
903 where
904 S: serde::Serializer,
905 {
906 // Convert DashMap to HashMap for serialization
907 let data: std::collections::HashMap<String, Value> = self
908 .data
909 .iter()
910 .map(|entry| (entry.key().clone(), entry.value().clone()))
911 .collect();
912
913 let chat_history = self.history_read().clone();
914
915 let context_data = ContextData { data, chat_history };
916 context_data.serialize(serializer)
917 }
918}
919
920impl<'de> Deserialize<'de> for Context {
921 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
922 where
923 D: serde::Deserializer<'de>,
924 {
925 let context_data = ContextData::deserialize(deserializer)?;
926
927 let data = Arc::new(DashMap::new());
928 for (key, value) in context_data.data {
929 data.insert(key, value);
930 }
931
932 let chat_history = Arc::new(RwLock::new(context_data.chat_history));
933
934 Ok(Context { data, chat_history })
935 }
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941
942 #[tokio::test]
943 async fn test_basic_context_operations() {
944 let context = Context::new();
945
946 context.set("key", "value").await;
947 let value: Option<String> = context.get("key").await;
948 assert_eq!(value, Some("value".to_string()));
949 }
950
951 #[tokio::test]
952 async fn test_chat_history_operations() {
953 let context = Context::new();
954
955 assert!(context.is_chat_history_empty().await);
956 assert_eq!(context.chat_history_len().await, 0);
957
958 context.add_user_message("Hello".to_string()).await;
959 context.add_assistant_message("Hi there!".to_string()).await;
960
961 assert!(!context.is_chat_history_empty().await);
962 assert_eq!(context.chat_history_len().await, 2);
963
964 let history = context.get_chat_history().await;
965 assert_eq!(history.len(), 2);
966 assert_eq!(history.messages()[0].content, "Hello");
967 assert_eq!(history.messages()[0].role, MessageRole::User);
968 assert_eq!(history.messages()[1].content, "Hi there!");
969 assert_eq!(history.messages()[1].role, MessageRole::Assistant);
970 }
971
972 #[tokio::test]
973 async fn test_chat_history_max_messages() {
974 let context = Context::with_max_chat_messages(2);
975
976 context.add_user_message("Message 1".to_string()).await;
977 context
978 .add_assistant_message("Response 1".to_string())
979 .await;
980 context.add_user_message("Message 2".to_string()).await;
981
982 let history = context.get_chat_history().await;
983 assert_eq!(history.len(), 2);
984 assert_eq!(history.messages()[0].content, "Response 1");
985 assert_eq!(history.messages()[1].content, "Message 2");
986 }
987
988 #[tokio::test]
989 async fn test_last_messages() {
990 let context = Context::new();
991
992 context.add_user_message("Message 1".to_string()).await;
993 context
994 .add_assistant_message("Response 1".to_string())
995 .await;
996 context.add_user_message("Message 2".to_string()).await;
997 context
998 .add_assistant_message("Response 2".to_string())
999 .await;
1000
1001 let last_two = context.get_last_messages(2).await;
1002 assert_eq!(last_two.len(), 2);
1003 assert_eq!(last_two[0].content, "Message 2");
1004 assert_eq!(last_two[1].content, "Response 2");
1005 }
1006
1007 #[tokio::test]
1008 async fn test_context_serialization() {
1009 let context = Context::new();
1010 context.set("key", "value").await;
1011 context.add_user_message("test message".to_string()).await;
1012
1013 let serialized = serde_json::to_string(&context).unwrap();
1014 let deserialized: Context = serde_json::from_str(&serialized).unwrap();
1015
1016 let value: Option<String> = deserialized.get("key").await;
1017 assert_eq!(value, Some("value".to_string()));
1018
1019 assert_eq!(deserialized.chat_history_len().await, 1);
1020 let history = deserialized.get_chat_history().await;
1021 assert_eq!(history.messages()[0].content, "test message");
1022 assert_eq!(history.messages()[0].role, MessageRole::User);
1023 }
1024
1025 #[test]
1026 fn test_serializable_message() {
1027 let msg = SerializableMessage::user("test content".to_string());
1028 assert_eq!(msg.role, MessageRole::User);
1029 assert_eq!(msg.content, "test content");
1030
1031 let serialized = serde_json::to_string(&msg).unwrap();
1032 let deserialized: SerializableMessage = serde_json::from_str(&serialized).unwrap();
1033
1034 assert_eq!(msg.role, deserialized.role);
1035 assert_eq!(msg.content, deserialized.content);
1036 }
1037
1038 #[test]
1039 fn test_chat_history_serialization() {
1040 let mut history = ChatHistory::new();
1041 history.add_user_message("Hello".to_string());
1042 history.add_assistant_message("Hi!".to_string());
1043
1044 let serialized = serde_json::to_string(&history).unwrap();
1045 let deserialized: ChatHistory = serde_json::from_str(&serialized).unwrap();
1046
1047 assert_eq!(deserialized.len(), 2);
1048 assert_eq!(deserialized.messages()[0].content, "Hello");
1049 assert_eq!(deserialized.messages()[1].content, "Hi!");
1050 }
1051
1052 #[cfg(feature = "rig")]
1053 #[tokio::test]
1054 async fn test_rig_integration() {
1055 let context = Context::new();
1056
1057 context.add_user_message("Hello".to_string()).await;
1058 context.add_assistant_message("Hi there!".to_string()).await;
1059 context
1060 .add_system_message("System message".to_string())
1061 .await;
1062
1063 let rig_messages = context.get_rig_messages().await;
1064 assert_eq!(rig_messages.len(), 3);
1065
1066 // Verify each message maps to the correct rig variant.
1067 assert!(
1068 matches!(&rig_messages[0], Message::User { .. }),
1069 "Expected Message::User, got: {:?}",
1070 rig_messages[0]
1071 );
1072 assert!(
1073 matches!(&rig_messages[1], Message::Assistant { .. }),
1074 "Expected Message::Assistant, got: {:?}",
1075 rig_messages[1]
1076 );
1077 assert!(
1078 matches!(&rig_messages[2], Message::System { content } if content == "System message"),
1079 "Expected Message::System with correct content, got: {:?}",
1080 rig_messages[2]
1081 );
1082
1083 // Verify get_last_rig_messages tail ordering when the tail includes a system message.
1084 let last_two = context.get_last_rig_messages(2).await;
1085 assert_eq!(last_two.len(), 2);
1086 assert!(
1087 matches!(&last_two[0], Message::Assistant { .. }),
1088 "Expected Message::Assistant as second-to-last, got: {:?}",
1089 last_two[0]
1090 );
1091 assert!(
1092 matches!(&last_two[1], Message::System { .. }),
1093 "Expected Message::System as last, got: {:?}",
1094 last_two[1]
1095 );
1096 }
1097}