tiny_agent_rs/core/
memory.rs1use super::steps::AgentStep;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use tracing::info;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct AgentMemory {
10 steps: Vec<AgentStep>,
11 system_prompt: Option<String>,
12}
13
14impl AgentMemory {
15 pub fn new(system_prompt: Option<String>) -> Self {
17 Self {
18 steps: Vec::new(),
19 system_prompt,
20 }
21 }
22
23 pub fn with_default_system() -> Self {
25 Self::new(Some(
26 "You are a helpful assistant with access to tools. Use tools when necessary to provide accurate information. Be concise and helpful. When you are ready to give the final response, you MUST call the `final_answer` tool with an `answer` string instead of replying directly.".to_string()
27 ))
28 }
29
30 pub fn add_step(&mut self, step: AgentStep) {
32 let description = step.describe();
33 info!(target: "tinyagent::steps", "{}", description);
34 self.steps.push(step);
35 }
36
37 pub fn steps(&self) -> &[AgentStep] {
39 &self.steps
40 }
41
42 pub fn last_step(&self) -> Option<&AgentStep> {
44 self.steps.last()
45 }
46
47 pub fn as_messages(&self) -> Vec<Value> {
49 let mut messages = Vec::new();
50
51 if let Some(system_prompt) = &self.system_prompt {
52 messages.push(serde_json::json!({
53 "role": "system",
54 "content": system_prompt
55 }));
56 }
57
58 for step in &self.steps {
59 messages.push(step.to_message());
60 }
61
62 messages
63 }
64
65 pub fn clear_steps(&mut self) {
67 self.steps.clear();
68 }
69
70 pub fn step_count(&self) -> usize {
72 self.steps.len()
73 }
74
75 pub fn is_empty(&self) -> bool {
77 self.steps.is_empty()
78 }
79
80 pub fn filter_steps<F>(&self, predicate: F) -> Vec<&AgentStep>
82 where
83 F: Fn(&AgentStep) -> bool,
84 {
85 self.steps.iter().filter(|step| predicate(step)).collect()
86 }
87
88 pub fn count_actions(&self) -> usize {
90 self.steps
91 .iter()
92 .filter(|s| matches!(s, AgentStep::Action { .. }))
93 .count()
94 }
95
96 pub fn count_observations(&self) -> usize {
97 self.steps
98 .iter()
99 .filter(|s| matches!(s, AgentStep::Observation { .. }))
100 .count()
101 }
102
103 pub fn set_final_answer_structured(&mut self, structured: Value) {
104 if let Some(AgentStep::FinalAnswer {
105 structured: slot, ..
106 }) = self
107 .steps
108 .iter_mut()
109 .rev()
110 .find(|step| matches!(step, AgentStep::FinalAnswer { .. }))
111 {
112 *slot = Some(structured);
113 }
114 }
115}
116
117impl Default for AgentMemory {
118 fn default() -> Self {
119 Self::with_default_system()
120 }
121}
122
123impl From<Vec<Value>> for AgentMemory {
124 fn from(messages: Vec<Value>) -> Self {
125 let mut memory = AgentMemory::new(None);
126
127 for msg in messages {
128 if let Some(role) = msg.get("role").and_then(|r| r.as_str()) {
129 match role {
130 "system" => {
131 if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
132 memory.system_prompt = Some(content.to_string());
133 }
134 }
135 "user" => {
136 if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
137 memory.add_step(AgentStep::Task {
138 content: content.to_string(),
139 });
140 }
141 }
142 "assistant" => {
143 if let Some(tool_calls) = msg.get("tool_calls") {
144 if let Some(calls_array) = tool_calls.as_array() {
145 for call in calls_array {
146 if let (Some(id), Some(function)) = (
147 call.get("id").and_then(|i| i.as_str()),
148 call.get("function"),
149 ) {
150 let name = function
151 .get("name")
152 .and_then(|n| n.as_str())
153 .unwrap_or("unknown");
154 let args_str = function
155 .get("arguments")
156 .and_then(|a| a.as_str())
157 .unwrap_or("{}");
158 let args: Value =
159 serde_json::from_str(args_str).unwrap_or(Value::Null);
160
161 memory.add_step(AgentStep::Action {
162 tool_name: name.to_string(),
163 tool_call_id: id.to_string(),
164 arguments: args,
165 });
166 }
167 }
168 }
169 } else if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
170 if !content.is_empty() {
171 memory.add_step(AgentStep::FinalAnswer {
172 answer: content.to_string(),
173 structured: None,
174 });
175 }
176 }
177 }
178 "tool" => {
179 if let (Some(id), Some(content)) = (
180 msg.get("tool_call_id").and_then(|i| i.as_str()),
181 msg.get("content").and_then(|c| c.as_str()),
182 ) {
183 let is_error = detect_tool_error(content);
184 memory.add_step(AgentStep::Observation {
185 tool_call_id: id.to_string(),
186 result: content.to_string(),
187 is_error,
188 });
189 }
190 }
191 _ => {}
192 }
193 }
194 }
195
196 memory
197 }
198}
199
200fn detect_tool_error(content: &str) -> bool {
201 match serde_json::from_str::<Value>(content) {
202 Ok(Value::Object(map)) => map.get("error").map(|err| !err.is_null()).unwrap_or(false),
203 _ => false,
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_memory_creation() {
213 let memory = AgentMemory::new(Some("System".to_string()));
214 assert_eq!(memory.step_count(), 0);
215 assert!(memory.is_empty());
216 }
217
218 #[test]
219 fn test_add_steps() {
220 let mut memory = AgentMemory::default();
221 memory.add_step(AgentStep::Task {
222 content: "Test task".to_string(),
223 });
224 assert_eq!(memory.step_count(), 1);
225 assert!(!memory.is_empty());
226 }
227
228 #[test]
229 fn test_as_messages() {
230 let mut memory = AgentMemory::with_default_system();
231 memory.add_step(AgentStep::Task {
232 content: "Hello".to_string(),
233 });
234
235 let messages = memory.as_messages();
236 assert_eq!(messages.len(), 2); assert_eq!(messages[0]["role"], "system");
238 assert_eq!(messages[1]["role"], "user");
239 }
240
241 #[test]
242 fn test_count_actions() {
243 let mut memory = AgentMemory::default();
244 memory.add_step(AgentStep::Action {
245 tool_name: "test".to_string(),
246 tool_call_id: "1".to_string(),
247 arguments: Value::Null,
248 });
249 memory.add_step(AgentStep::Action {
250 tool_name: "test2".to_string(),
251 tool_call_id: "2".to_string(),
252 arguments: Value::Null,
253 });
254 assert_eq!(memory.count_actions(), 2);
255 }
256}