autoagents_core/agent/
context.rs1#[cfg(not(target_arch = "wasm32"))]
2use crate::actor::{ActorMessage, Topic};
3use crate::agent::AgentConfig;
4use crate::agent::memory::MemoryProvider;
5use crate::agent::state::AgentState;
6use crate::protocol::Event;
7use crate::tool::ToolT;
8use autoagents_llm::LLMProvider;
9use autoagents_llm::chat::ChatMessage;
10use std::any::Any;
11use std::sync::Arc;
12#[cfg(not(target_arch = "wasm32"))]
13use tokio::sync::{Mutex, mpsc};
14
15#[cfg(target_arch = "wasm32")]
16use futures::channel::mpsc;
17#[cfg(target_arch = "wasm32")]
18use futures::lock::Mutex;
19
20pub struct Context {
27 llm: Arc<dyn LLMProvider>,
28 messages: Vec<ChatMessage>,
29 memory: Option<Arc<Mutex<Box<dyn MemoryProvider>>>>,
30 tools: Vec<Box<dyn ToolT>>,
31 config: AgentConfig,
32 state: Arc<Mutex<AgentState>>,
33 tx: Option<mpsc::Sender<Event>>,
34 stream: bool,
35}
36
37#[derive(Clone, Debug, thiserror::Error)]
38pub enum ContextError {
39 #[error("Tx value is None, Tx is only set for Actor agents")]
40 EmptyTx,
41 #[error("Failed to send event: {0}")]
43 EventSendError(String),
44}
45
46impl Context {
47 pub fn new(llm: Arc<dyn LLMProvider>, tx: Option<mpsc::Sender<Event>>) -> Self {
48 Self {
49 llm,
50 messages: vec![],
51 memory: None,
52 tools: vec![],
53 config: AgentConfig::default(),
54 state: Arc::new(Mutex::new(AgentState::new())),
55 stream: false,
56 tx,
57 }
58 }
59
60 #[cfg(not(target_arch = "wasm32"))]
61 pub async fn publish<M: ActorMessage>(
62 &self,
63 topic: Topic<M>,
64 message: M,
65 ) -> Result<(), ContextError> {
66 self.tx
67 .as_ref()
68 .ok_or(ContextError::EmptyTx)?
69 .send(Event::PublishMessage {
70 topic_name: topic.name().to_string(),
71 message: Arc::new(message) as Arc<dyn Any + Send + Sync>,
72 topic_type: topic.type_id(),
73 })
74 .await
75 .map_err(|e| ContextError::EventSendError(e.to_string()))
76 }
77
78 pub fn with_memory(mut self, memory: Option<Arc<Mutex<Box<dyn MemoryProvider>>>>) -> Self {
79 self.memory = memory;
80 self
81 }
82
83 pub fn with_tools(mut self, tools: Vec<Box<dyn ToolT>>) -> Self {
84 self.tools = tools;
85 self
86 }
87
88 pub fn with_config(mut self, config: AgentConfig) -> Self {
89 self.config = config;
90 self
91 }
92
93 pub fn with_messages(mut self, messages: Vec<ChatMessage>) -> Self {
94 self.messages = messages;
95 self
96 }
97
98 pub fn with_stream(mut self, stream: bool) -> Self {
99 self.stream = stream;
100 self
101 }
102
103 pub fn llm(&self) -> &Arc<dyn LLMProvider> {
105 &self.llm
106 }
107
108 pub fn messages(&self) -> &[ChatMessage] {
109 &self.messages
110 }
111
112 pub fn memory(&self) -> Option<Arc<Mutex<Box<dyn MemoryProvider>>>> {
113 self.memory.clone()
114 }
115
116 pub fn tools(&self) -> &[Box<dyn ToolT>] {
117 &self.tools
118 }
119
120 pub fn config(&self) -> &AgentConfig {
121 &self.config
122 }
123
124 pub fn state(&self) -> Arc<Mutex<AgentState>> {
125 self.state.clone()
126 }
127
128 pub fn tx(&self) -> Result<mpsc::Sender<Event>, ContextError> {
130 Ok(self.tx.as_ref().ok_or(ContextError::EmptyTx)?.clone())
131 }
132
133 pub fn stream(&self) -> bool {
134 self.stream
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::agent::memory::SlidingWindowMemory;
142 use autoagents_llm::chat::{ChatMessage, ChatMessageBuilder, ChatRole};
143 use autoagents_test_utils::llm::MockLLMProvider;
144 use std::sync::Arc;
145
146 #[test]
147 fn test_context_creation() {
148 let llm = Arc::new(MockLLMProvider);
149 let context = Context::new(llm, None);
150
151 assert!(context.messages.is_empty());
152 assert!(context.memory.is_none());
153 assert!(context.tools.is_empty());
154 assert!(!context.stream);
155 }
156
157 #[test]
158 fn test_context_with_llm_provider() {
159 let llm = Arc::new(MockLLMProvider);
160 let context = Context::new(llm.clone(), None);
161
162 let context_llm = context.llm();
164 assert!(Arc::strong_count(context_llm) > 0);
165 }
166
167 #[test]
168 fn test_context_with_memory() {
169 let llm = Arc::new(MockLLMProvider);
170 let memory = Box::new(SlidingWindowMemory::new(5));
171 let context = Context::new(llm, None).with_memory(Some(Arc::new(Mutex::new(memory))));
172
173 assert!(context.memory().is_some());
174 }
175
176 #[test]
177 fn test_context_with_messages() {
178 let llm = Arc::new(MockLLMProvider);
179 let message = ChatMessage::user().content("Hello".to_string()).build();
180 let context = Context::new(llm, None).with_messages(vec![message]);
181
182 assert_eq!(context.messages().len(), 1);
183 assert_eq!(context.messages()[0].role, ChatRole::User);
184 assert_eq!(context.messages()[0].content, "Hello");
185 }
186
187 #[test]
188 fn test_context_streaming_flag() {
189 let llm = Arc::new(MockLLMProvider);
190 let context = Context::new(llm, None).with_stream(true);
191 assert!(context.stream());
192 }
193
194 #[test]
195 fn test_context_fluent_interface() {
196 let llm = Arc::new(MockLLMProvider);
197 let memory = Box::new(SlidingWindowMemory::new(3));
198 let message = ChatMessageBuilder::new(ChatRole::System)
199 .content("System prompt".to_string())
200 .build();
201
202 let context = Context::new(llm, None)
203 .with_memory(Some(Arc::new(Mutex::new(memory))))
204 .with_messages(vec![message])
205 .with_stream(true);
206
207 assert!(context.memory().is_some());
208 assert_eq!(context.messages().len(), 1);
209 assert!(context.stream());
210 }
211}