mojentic/examples/react/
events.rs

1//! Event definitions for the ReAct pattern.
2//!
3//! This module defines all event types used to coordinate the ReAct loop,
4//! including thinking, decisioning, tool calls, completion, and failure events.
5
6use crate::event::Event;
7use crate::llm::tools::LlmTool;
8use serde::{Deserialize, Serialize};
9use std::any::Any;
10use std::sync::Arc;
11
12use super::models::{CurrentContext, NextAction};
13
14/// Event to trigger the thinking/planning phase.
15///
16/// This event initiates the planning process where the agent creates
17/// or refines a plan for answering the user's query.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct InvokeThinking {
20    pub source: String,
21    pub correlation_id: Option<String>,
22    /// The current context as we work through our response.
23    pub context: CurrentContext,
24}
25
26impl Event for InvokeThinking {
27    fn source(&self) -> &str {
28        &self.source
29    }
30
31    fn correlation_id(&self) -> Option<&str> {
32        self.correlation_id.as_deref()
33    }
34
35    fn set_correlation_id(&mut self, id: String) {
36        self.correlation_id = Some(id);
37    }
38
39    fn as_any(&self) -> &dyn Any {
40        self
41    }
42
43    fn clone_box(&self) -> Box<dyn Event> {
44        Box::new(self.clone())
45    }
46}
47
48/// Event to trigger the decision-making phase.
49///
50/// This event initiates the decision process where the agent evaluates
51/// the current plan and history to decide on the next action.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct InvokeDecisioning {
54    pub source: String,
55    pub correlation_id: Option<String>,
56    /// The current context as we work through our response.
57    pub context: CurrentContext,
58}
59
60impl Event for InvokeDecisioning {
61    fn source(&self) -> &str {
62        &self.source
63    }
64
65    fn correlation_id(&self) -> Option<&str> {
66        self.correlation_id.as_deref()
67    }
68
69    fn set_correlation_id(&mut self, id: String) {
70        self.correlation_id = Some(id);
71    }
72
73    fn as_any(&self) -> &dyn Any {
74        self
75    }
76
77    fn clone_box(&self) -> Box<dyn Event> {
78        Box::new(self.clone())
79    }
80}
81
82/// Event to trigger a tool invocation.
83///
84/// This event carries the information needed to execute a specific tool
85/// with given arguments, along with the reasoning behind the decision.
86/// Note: Cannot be cloned/serialized due to Arc<dyn LlmTool>.
87pub struct InvokeToolCall {
88    pub source: String,
89    pub correlation_id: Option<String>,
90    /// The current context as we work through our response.
91    pub context: CurrentContext,
92    /// The reasoning behind the decision.
93    pub thought: String,
94    /// The next action type.
95    pub action: NextAction,
96    /// The tool instance to invoke.
97    pub tool: Arc<dyn LlmTool>,
98    /// Arguments to pass to the tool.
99    pub tool_arguments: std::collections::HashMap<String, serde_json::Value>,
100}
101
102impl std::fmt::Debug for InvokeToolCall {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        f.debug_struct("InvokeToolCall")
105            .field("source", &self.source)
106            .field("correlation_id", &self.correlation_id)
107            .field("context", &self.context)
108            .field("thought", &self.thought)
109            .field("action", &self.action)
110            .field("tool", &self.tool.descriptor().function.name)
111            .field("tool_arguments", &self.tool_arguments)
112            .finish()
113    }
114}
115
116impl Event for InvokeToolCall {
117    fn source(&self) -> &str {
118        &self.source
119    }
120
121    fn correlation_id(&self) -> Option<&str> {
122        self.correlation_id.as_deref()
123    }
124
125    fn set_correlation_id(&mut self, id: String) {
126        self.correlation_id = Some(id);
127    }
128
129    fn as_any(&self) -> &dyn Any {
130        self
131    }
132
133    fn clone_box(&self) -> Box<dyn Event> {
134        // We can't truly clone the tool, but we can clone the Arc
135        Box::new(InvokeToolCall {
136            source: self.source.clone(),
137            correlation_id: self.correlation_id.clone(),
138            context: self.context.clone(),
139            thought: self.thought.clone(),
140            action: self.action,
141            tool: self.tool.clone(),
142            tool_arguments: self.tool_arguments.clone(),
143        })
144    }
145}
146
147/// Event to trigger the completion and summarization phase.
148///
149/// This event indicates that the agent has gathered sufficient information
150/// to answer the user's query and should generate a final response.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct FinishAndSummarize {
153    pub source: String,
154    pub correlation_id: Option<String>,
155    /// The current context as we work through our response.
156    pub context: CurrentContext,
157    /// The reasoning behind the decision.
158    pub thought: String,
159}
160
161impl Event for FinishAndSummarize {
162    fn source(&self) -> &str {
163        &self.source
164    }
165
166    fn correlation_id(&self) -> Option<&str> {
167        self.correlation_id.as_deref()
168    }
169
170    fn set_correlation_id(&mut self, id: String) {
171        self.correlation_id = Some(id);
172    }
173
174    fn as_any(&self) -> &dyn Any {
175        self
176    }
177
178    fn clone_box(&self) -> Box<dyn Event> {
179        Box::new(self.clone())
180    }
181}
182
183/// Event to signal a failure in the ReAct loop.
184///
185/// This event captures errors or unrecoverable situations that prevent
186/// the agent from continuing to process the user's query.
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct FailureOccurred {
189    pub source: String,
190    pub correlation_id: Option<String>,
191    /// The current context as we work through our response.
192    pub context: CurrentContext,
193    /// The reason for the failure.
194    pub reason: String,
195}
196
197impl Event for FailureOccurred {
198    fn source(&self) -> &str {
199        &self.source
200    }
201
202    fn correlation_id(&self) -> Option<&str> {
203        self.correlation_id.as_deref()
204    }
205
206    fn set_correlation_id(&mut self, id: String) {
207        self.correlation_id = Some(id);
208    }
209
210    fn as_any(&self) -> &dyn Any {
211        self
212    }
213
214    fn clone_box(&self) -> Box<dyn Event> {
215        Box::new(self.clone())
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_invoke_thinking_event() {
225        let mut event = InvokeThinking {
226            source: "TestAgent".to_string(),
227            correlation_id: None,
228            context: CurrentContext::new("Test query"),
229        };
230
231        assert_eq!(event.source(), "TestAgent");
232        assert_eq!(event.correlation_id(), None);
233
234        event.set_correlation_id("test-123".to_string());
235        assert_eq!(event.correlation_id(), Some("test-123"));
236    }
237
238    #[test]
239    fn test_invoke_decisioning_event() {
240        let event = InvokeDecisioning {
241            source: "DecisionAgent".to_string(),
242            correlation_id: Some("corr-456".to_string()),
243            context: CurrentContext::new("Test query"),
244        };
245
246        assert_eq!(event.source(), "DecisionAgent");
247        assert_eq!(event.correlation_id(), Some("corr-456"));
248    }
249
250    #[test]
251    fn test_finish_and_summarize_event() {
252        let event = FinishAndSummarize {
253            source: "SummaryAgent".to_string(),
254            correlation_id: Some("finish-789".to_string()),
255            context: CurrentContext::new("Test query"),
256            thought: "I have enough information".to_string(),
257        };
258
259        assert_eq!(event.source(), "SummaryAgent");
260        assert_eq!(event.thought, "I have enough information");
261    }
262
263    #[test]
264    fn test_failure_occurred_event() {
265        let event = FailureOccurred {
266            source: "ToolAgent".to_string(),
267            correlation_id: Some("fail-101".to_string()),
268            context: CurrentContext::new("Test query"),
269            reason: "Tool execution failed".to_string(),
270        };
271
272        assert_eq!(event.source(), "ToolAgent");
273        assert_eq!(event.reason, "Tool execution failed");
274    }
275
276    #[test]
277    fn test_event_clone_box() {
278        let event = InvokeThinking {
279            source: "CloneTest".to_string(),
280            correlation_id: Some("clone-123".to_string()),
281            context: CurrentContext::new("Clone test query"),
282        };
283
284        let cloned = event.clone_box();
285        assert_eq!(cloned.source(), "CloneTest");
286        assert_eq!(cloned.correlation_id(), Some("clone-123"));
287    }
288}