mojentic/examples/react/
models.rs

1//! Data models for the ReAct pattern.
2//!
3//! This module defines the core data structures used throughout the ReAct
4//! implementation, including actions, plans, observations, and context.
5
6use serde::{Deserialize, Serialize};
7
8/// Enumeration of possible next actions in the ReAct loop.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
10#[serde(rename_all = "UPPERCASE")]
11pub enum NextAction {
12    /// Create or refine a plan
13    Plan,
14    /// Execute a tool action
15    Act,
16    /// Complete and summarize the results
17    Finish,
18}
19
20/// A single step in the ReAct loop capturing thought, action, and observation.
21///
22/// This model represents one iteration of the ReAct pattern where the agent:
23/// 1. Thinks about what to do
24/// 2. Takes an action
25/// 3. Observes the result
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ThoughtActionObservation {
28    /// The thought process behind the action taken in the current context.
29    pub thought: String,
30    /// The action taken in the current context.
31    pub action: String,
32    /// The observation made after the action taken in the current context.
33    pub observation: String,
34}
35
36/// A structured plan for solving a user query.
37///
38/// Contains a list of steps that outline how to approach answering the query.
39#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema, Default)]
40pub struct Plan {
41    /// How to answer the query, step by step, each step outlining an action to take.
42    #[serde(default)]
43    pub steps: Vec<String>,
44}
45
46/// The complete context for a ReAct session.
47///
48/// This model tracks everything needed to maintain state throughout the
49/// reasoning and acting loop, including the user's query, the plan,
50/// the history of actions, and the iteration count.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct CurrentContext {
53    /// The user query to which we are responding.
54    pub user_query: String,
55    /// The current plan of action for the current context.
56    #[serde(default)]
57    pub plan: Plan,
58    /// The history of actions taken and observations made in the current context.
59    #[serde(default)]
60    pub history: Vec<ThoughtActionObservation>,
61    /// The number of iterations taken in the current context.
62    #[serde(default)]
63    pub iteration: usize,
64}
65
66impl CurrentContext {
67    /// Create a new context with the given user query.
68    pub fn new(user_query: impl Into<String>) -> Self {
69        Self {
70            user_query: user_query.into(),
71            plan: Plan::default(),
72            history: Vec::new(),
73            iteration: 0,
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_next_action_serialization() {
84        assert_eq!(serde_json::to_string(&NextAction::Plan).unwrap(), "\"PLAN\"");
85        assert_eq!(serde_json::to_string(&NextAction::Act).unwrap(), "\"ACT\"");
86        assert_eq!(serde_json::to_string(&NextAction::Finish).unwrap(), "\"FINISH\"");
87    }
88
89    #[test]
90    fn test_thought_action_observation() {
91        let tao = ThoughtActionObservation {
92            thought: "I need to get the date".to_string(),
93            action: "Called resolve_date".to_string(),
94            observation: "2025-11-29".to_string(),
95        };
96
97        assert_eq!(tao.thought, "I need to get the date");
98        assert_eq!(tao.action, "Called resolve_date");
99        assert_eq!(tao.observation, "2025-11-29");
100    }
101
102    #[test]
103    fn test_plan_default() {
104        let plan = Plan::default();
105        assert!(plan.steps.is_empty());
106    }
107
108    #[test]
109    fn test_plan_serialization() {
110        let plan = Plan {
111            steps: vec!["Step 1".to_string(), "Step 2".to_string()],
112        };
113
114        let json = serde_json::to_string(&plan).unwrap();
115        assert!(json.contains("Step 1"));
116        assert!(json.contains("Step 2"));
117
118        let deserialized: Plan = serde_json::from_str(&json).unwrap();
119        assert_eq!(deserialized.steps.len(), 2);
120    }
121
122    #[test]
123    fn test_current_context_new() {
124        let ctx = CurrentContext::new("What is the date?");
125        assert_eq!(ctx.user_query, "What is the date?");
126        assert_eq!(ctx.iteration, 0);
127        assert!(ctx.history.is_empty());
128        assert!(ctx.plan.steps.is_empty());
129    }
130
131    #[test]
132    fn test_current_context_with_history() {
133        let mut ctx = CurrentContext::new("Test query");
134        ctx.history.push(ThoughtActionObservation {
135            thought: "Thinking".to_string(),
136            action: "Acting".to_string(),
137            observation: "Observing".to_string(),
138        });
139        ctx.iteration = 1;
140
141        assert_eq!(ctx.history.len(), 1);
142        assert_eq!(ctx.iteration, 1);
143    }
144}