1use super::event_store::EventStore;
8use super::tracer_events::*;
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::Arc;
12use std::time::{SystemTime, UNIX_EPOCH};
13
14pub struct TracerSystem {
20 event_store: Arc<EventStore>,
21 enabled: Arc<AtomicBool>,
22}
23
24impl TracerSystem {
25 pub fn new(event_store: Option<Arc<EventStore>>, enabled: bool) -> Self {
32 Self {
33 event_store: event_store.unwrap_or_else(|| Arc::new(EventStore::default())),
34 enabled: Arc::new(AtomicBool::new(enabled)),
35 }
36 }
37
38 pub fn is_enabled(&self) -> bool {
40 self.enabled.load(Ordering::SeqCst)
41 }
42
43 pub fn enable(&self) {
45 self.enabled.store(true, Ordering::SeqCst);
46 }
47
48 pub fn disable(&self) {
50 self.enabled.store(false, Ordering::SeqCst);
51 }
52
53 pub fn record_event(&self, event: Box<dyn TracerEvent>) {
59 if !self.is_enabled() {
60 return;
61 }
62 self.event_store.store(event);
63 }
64
65 pub fn record_llm_call(
76 &self,
77 model: impl Into<String>,
78 messages: Vec<HashMap<String, serde_json::Value>>,
79 temperature: f64,
80 tools: Option<Vec<HashMap<String, serde_json::Value>>>,
81 source: impl Into<String>,
82 correlation_id: impl Into<String>,
83 ) {
84 if !self.is_enabled() {
85 return;
86 }
87
88 let event = Box::new(LlmCallTracerEvent {
89 timestamp: current_timestamp(),
90 correlation_id: correlation_id.into(),
91 source: source.into(),
92 model: model.into(),
93 messages,
94 temperature,
95 tools,
96 });
97
98 self.event_store.store(event);
99 }
100
101 pub fn record_llm_response(
112 &self,
113 model: impl Into<String>,
114 content: impl Into<String>,
115 tool_calls: Option<Vec<HashMap<String, serde_json::Value>>>,
116 call_duration_ms: Option<f64>,
117 source: impl Into<String>,
118 correlation_id: impl Into<String>,
119 ) {
120 if !self.is_enabled() {
121 return;
122 }
123
124 let event = Box::new(LlmResponseTracerEvent {
125 timestamp: current_timestamp(),
126 correlation_id: correlation_id.into(),
127 source: source.into(),
128 model: model.into(),
129 content: content.into(),
130 tool_calls,
131 call_duration_ms,
132 });
133
134 self.event_store.store(event);
135 }
136
137 #[allow(clippy::too_many_arguments)]
149 pub fn record_tool_call(
150 &self,
151 tool_name: impl Into<String>,
152 arguments: HashMap<String, serde_json::Value>,
153 result: serde_json::Value,
154 caller: Option<String>,
155 call_duration_ms: Option<f64>,
156 source: impl Into<String>,
157 correlation_id: impl Into<String>,
158 ) {
159 if !self.is_enabled() {
160 return;
161 }
162
163 let event = Box::new(ToolCallTracerEvent {
164 timestamp: current_timestamp(),
165 correlation_id: correlation_id.into(),
166 source: source.into(),
167 tool_name: tool_name.into(),
168 arguments,
169 result,
170 caller,
171 call_duration_ms,
172 });
173
174 self.event_store.store(event);
175 }
176
177 pub fn record_agent_interaction(
188 &self,
189 from_agent: impl Into<String>,
190 to_agent: impl Into<String>,
191 event_type: impl Into<String>,
192 event_id: Option<String>,
193 source: impl Into<String>,
194 correlation_id: impl Into<String>,
195 ) {
196 if !self.is_enabled() {
197 return;
198 }
199
200 let event = Box::new(AgentInteractionTracerEvent {
201 timestamp: current_timestamp(),
202 correlation_id: correlation_id.into(),
203 source: source.into(),
204 from_agent: from_agent.into(),
205 to_agent: to_agent.into(),
206 event_type: event_type.into(),
207 event_id,
208 });
209
210 self.event_store.store(event);
211 }
212
213 pub fn get_event_summaries(
225 &self,
226 start_time: Option<f64>,
227 end_time: Option<f64>,
228 filter_func: Option<&dyn super::EventFilterFn>,
229 ) -> Vec<String> {
230 self.event_store.get_event_summaries(start_time, end_time, filter_func)
231 }
232
233 pub fn get_last_n_summaries(
244 &self,
245 n: usize,
246 filter_func: Option<&dyn super::EventFilterFn>,
247 ) -> Vec<String> {
248 self.event_store.get_last_n_summaries(n, filter_func)
249 }
250
251 pub fn count_events(
263 &self,
264 start_time: Option<f64>,
265 end_time: Option<f64>,
266 filter_func: Option<&dyn super::EventFilterFn>,
267 ) -> usize {
268 self.event_store.count_events(start_time, end_time, filter_func)
269 }
270
271 pub fn clear(&self) {
273 self.event_store.clear();
274 }
275
276 pub fn len(&self) -> usize {
278 self.event_store.len()
279 }
280
281 pub fn is_empty(&self) -> bool {
283 self.event_store.is_empty()
284 }
285}
286
287impl Default for TracerSystem {
288 fn default() -> Self {
289 Self::new(None, true)
290 }
291}
292
293fn current_timestamp() -> f64 {
295 SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64()
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_new_tracer_system() {
304 let tracer = TracerSystem::default();
305 assert!(tracer.is_enabled());
306 assert_eq!(tracer.len(), 0);
307 }
308
309 #[test]
310 fn test_enable_disable() {
311 let tracer = TracerSystem::default();
312 assert!(tracer.is_enabled());
313
314 tracer.disable();
315 assert!(!tracer.is_enabled());
316
317 tracer.enable();
318 assert!(tracer.is_enabled());
319 }
320
321 #[test]
322 fn test_record_llm_call() {
323 let tracer = TracerSystem::default();
324
325 tracer.record_llm_call("llama3.2", vec![], 0.7, None, "test", "corr-123");
326
327 assert_eq!(tracer.len(), 1);
328 }
329
330 #[test]
331 fn test_record_llm_response() {
332 let tracer = TracerSystem::default();
333
334 tracer.record_llm_response(
335 "llama3.2",
336 "Hello, world!",
337 None,
338 Some(150.5),
339 "test",
340 "corr-456",
341 );
342
343 assert_eq!(tracer.len(), 1);
344 }
345
346 #[test]
347 fn test_record_tool_call() {
348 let tracer = TracerSystem::default();
349 let mut args = HashMap::new();
350 args.insert("input".to_string(), serde_json::json!("test"));
351
352 tracer.record_tool_call(
353 "example_tool",
354 args,
355 serde_json::json!({"output": "result"}),
356 Some("agent1".to_string()),
357 Some(25.0),
358 "test",
359 "corr-789",
360 );
361
362 assert_eq!(tracer.len(), 1);
363 }
364
365 #[test]
366 fn test_record_agent_interaction() {
367 let tracer = TracerSystem::default();
368
369 tracer.record_agent_interaction(
370 "agent1",
371 "agent2",
372 "message",
373 Some("evt-123".to_string()),
374 "test",
375 "corr-abc",
376 );
377
378 assert_eq!(tracer.len(), 1);
379 }
380
381 #[test]
382 fn test_disabled_tracer_doesnt_record() {
383 let tracer = TracerSystem::new(None, false);
384 assert!(!tracer.is_enabled());
385
386 tracer.record_llm_call("llama3.2", vec![], 1.0, None, "test", "corr-123");
387
388 assert_eq!(tracer.len(), 0);
389 }
390
391 #[test]
392 fn test_clear() {
393 let tracer = TracerSystem::default();
394
395 tracer.record_llm_call("llama3.2", vec![], 1.0, None, "test", "corr-123");
396
397 assert_eq!(tracer.len(), 1);
398
399 tracer.clear();
400 assert_eq!(tracer.len(), 0);
401 assert!(tracer.is_empty());
402 }
403
404 #[test]
405 fn test_multiple_events() {
406 let tracer = TracerSystem::default();
407
408 for i in 0..5 {
409 tracer.record_llm_call("llama3.2", vec![], 1.0, None, "test", format!("corr-{}", i));
410 }
411
412 assert_eq!(tracer.len(), 5);
413 }
414}