agent_chain_core/tracers/
stdout.rs1use std::collections::HashMap;
7
8use serde_json::Value;
9use uuid::Uuid;
10
11use crate::tracers::base::BaseTracer;
12use crate::tracers::core::{TracerCore, TracerCoreConfig};
13use crate::tracers::schemas::Run;
14use crate::utils::input::{get_bolded_text, get_colored_text};
15
16const MILLISECONDS_IN_SECOND: f64 = 1000.0;
18
19pub fn try_json_stringify(obj: &Value, fallback: &str) -> String {
30 serde_json::to_string_pretty(obj).unwrap_or_else(|_| fallback.to_string())
31}
32
33pub fn elapsed(run: &Run) -> String {
43 if let Some(end_time) = run.end_time {
44 let duration = end_time.signed_duration_since(run.start_time);
45 let seconds = duration.num_milliseconds() as f64 / MILLISECONDS_IN_SECOND;
46 if seconds < 1.0 {
47 format!("{:.0}ms", seconds * MILLISECONDS_IN_SECOND)
48 } else {
49 format!("{:.2}s", seconds)
50 }
51 } else {
52 "N/A".to_string()
53 }
54}
55
56#[derive(Debug)]
58pub struct FunctionCallbackHandler<F>
59where
60 F: Fn(&str) + Send + Sync,
61{
62 function_callback: F,
64 config: TracerCoreConfig,
66 run_map: HashMap<String, Run>,
68 order_map: HashMap<Uuid, (Uuid, String)>,
70}
71
72impl<F> FunctionCallbackHandler<F>
73where
74 F: Fn(&str) + Send + Sync,
75{
76 pub fn new(function: F) -> Self {
82 Self {
83 function_callback: function,
84 config: TracerCoreConfig::default(),
85 run_map: HashMap::new(),
86 order_map: HashMap::new(),
87 }
88 }
89
90 pub fn get_parents(&self, run: &Run) -> Vec<Run> {
100 let mut parents = Vec::new();
101 let mut current_run = run.clone();
102
103 while let Some(parent_run_id) = current_run.parent_run_id {
104 if let Some(parent) = self.run_map.get(&parent_run_id.to_string()) {
105 parents.push(parent.clone());
106 current_run = parent.clone();
107 } else {
108 break;
109 }
110 }
111
112 parents
113 }
114
115 pub fn get_breadcrumbs(&self, run: &Run) -> String {
125 let parents: Vec<Run> = self.get_parents(run).into_iter().rev().collect();
126 let mut all_runs = parents;
127 all_runs.push(run.clone());
128
129 all_runs
130 .iter()
131 .map(|r| format!("{}:{}", r.run_type, r.name))
132 .collect::<Vec<_>>()
133 .join(" > ")
134 }
135}
136
137impl<F> TracerCore for FunctionCallbackHandler<F>
138where
139 F: Fn(&str) + Send + Sync + std::fmt::Debug,
140{
141 fn config(&self) -> &TracerCoreConfig {
142 &self.config
143 }
144
145 fn config_mut(&mut self) -> &mut TracerCoreConfig {
146 &mut self.config
147 }
148
149 fn run_map(&self) -> &HashMap<String, Run> {
150 &self.run_map
151 }
152
153 fn run_map_mut(&mut self) -> &mut HashMap<String, Run> {
154 &mut self.run_map
155 }
156
157 fn order_map(&self) -> &HashMap<Uuid, (Uuid, String)> {
158 &self.order_map
159 }
160
161 fn order_map_mut(&mut self) -> &mut HashMap<Uuid, (Uuid, String)> {
162 &mut self.order_map
163 }
164
165 fn persist_run(&mut self, _run: &Run) {}
166
167 fn on_chain_start(&mut self, run: &Run) {
168 let crumbs = self.get_breadcrumbs(run);
169 let run_type = capitalize_first(&run.run_type);
170 let inputs = serde_json::to_value(&run.inputs).unwrap_or_default();
171 (self.function_callback)(&format!(
172 "{} {}{}",
173 get_colored_text("[chain/start]", "green"),
174 get_bolded_text(&format!(
175 "[{}] Entering {} run with input:\n",
176 crumbs, run_type
177 )),
178 try_json_stringify(&inputs, "[inputs]")
179 ));
180 }
181
182 fn on_chain_end(&mut self, run: &Run) {
183 let crumbs = self.get_breadcrumbs(run);
184 let run_type = capitalize_first(&run.run_type);
185 let outputs = run
186 .outputs
187 .as_ref()
188 .map(|o| serde_json::to_value(o).unwrap_or_default())
189 .unwrap_or_default();
190 (self.function_callback)(&format!(
191 "{} {}{}",
192 get_colored_text("[chain/end]", "blue"),
193 get_bolded_text(&format!(
194 "[{}] [{}] Exiting {} run with output:\n",
195 crumbs,
196 elapsed(run),
197 run_type
198 )),
199 try_json_stringify(&outputs, "[outputs]")
200 ));
201 }
202
203 fn on_chain_error(&mut self, run: &Run) {
204 let crumbs = self.get_breadcrumbs(run);
205 let run_type = capitalize_first(&run.run_type);
206 let error = run
207 .error
208 .as_ref()
209 .map(|e| Value::String(e.clone()))
210 .unwrap_or_default();
211 (self.function_callback)(&format!(
212 "{} {}{}",
213 get_colored_text("[chain/error]", "red"),
214 get_bolded_text(&format!(
215 "[{}] [{}] {} run errored with error:\n",
216 crumbs,
217 elapsed(run),
218 run_type
219 )),
220 try_json_stringify(&error, "[error]")
221 ));
222 }
223
224 fn on_llm_start(&mut self, run: &Run) {
225 let crumbs = self.get_breadcrumbs(run);
226 let inputs = if let Some(Value::Array(arr)) = run.inputs.get("prompts") {
227 let trimmed: Vec<Value> = arr
228 .iter()
229 .map(|p| {
230 if let Value::String(s) = p {
231 Value::String(s.trim().to_string())
232 } else {
233 p.clone()
234 }
235 })
236 .collect();
237 serde_json::json!({ "prompts": trimmed })
238 } else {
239 serde_json::to_value(&run.inputs).unwrap_or_default()
240 };
241
242 (self.function_callback)(&format!(
243 "{} {}{}",
244 get_colored_text("[llm/start]", "green"),
245 get_bolded_text(&format!("[{}] Entering LLM run with input:\n", crumbs)),
246 try_json_stringify(&inputs, "[inputs]")
247 ));
248 }
249
250 fn on_llm_end(&mut self, run: &Run) {
251 let crumbs = self.get_breadcrumbs(run);
252 let outputs = run
253 .outputs
254 .as_ref()
255 .map(|o| serde_json::to_value(o).unwrap_or_default())
256 .unwrap_or_default();
257 (self.function_callback)(&format!(
258 "{} {}{}",
259 get_colored_text("[llm/end]", "blue"),
260 get_bolded_text(&format!(
261 "[{}] [{}] Exiting LLM run with output:\n",
262 crumbs,
263 elapsed(run)
264 )),
265 try_json_stringify(&outputs, "[response]")
266 ));
267 }
268
269 fn on_llm_error(&mut self, run: &Run) {
270 let crumbs = self.get_breadcrumbs(run);
271 let error = run
272 .error
273 .as_ref()
274 .map(|e| Value::String(e.clone()))
275 .unwrap_or_default();
276 (self.function_callback)(&format!(
277 "{} {}{}",
278 get_colored_text("[llm/error]", "red"),
279 get_bolded_text(&format!(
280 "[{}] [{}] LLM run errored with error:\n",
281 crumbs,
282 elapsed(run)
283 )),
284 try_json_stringify(&error, "[error]")
285 ));
286 }
287
288 fn on_tool_start(&mut self, run: &Run) {
289 let crumbs = self.get_breadcrumbs(run);
290 let input = run
291 .inputs
292 .get("input")
293 .and_then(|v| v.as_str())
294 .unwrap_or("")
295 .trim();
296 (self.function_callback)(&format!(
297 "{} {}\"{}\"",
298 get_colored_text("[tool/start]", "green"),
299 get_bolded_text(&format!("[{}] Entering Tool run with input:\n", crumbs)),
300 input
301 ));
302 }
303
304 fn on_tool_end(&mut self, run: &Run) {
305 let crumbs = self.get_breadcrumbs(run);
306 if let Some(outputs) = &run.outputs
307 && let Some(output) = outputs.get("output")
308 {
309 let output_str = match output {
310 Value::String(s) => s.trim().to_string(),
311 _ => output.to_string(),
312 };
313 (self.function_callback)(&format!(
314 "{} {}\"{}\"",
315 get_colored_text("[tool/end]", "blue"),
316 get_bolded_text(&format!(
317 "[{}] [{}] Exiting Tool run with output:\n",
318 crumbs,
319 elapsed(run)
320 )),
321 output_str
322 ));
323 }
324 }
325
326 fn on_tool_error(&mut self, run: &Run) {
327 let crumbs = self.get_breadcrumbs(run);
328 let error = run.error.as_deref().unwrap_or("");
329 (self.function_callback)(&format!(
330 "{} {}Tool run errored with error:\n{}",
331 get_colored_text("[tool/error]", "red"),
332 get_bolded_text(&format!("[{}] [{}] ", crumbs, elapsed(run))),
333 error
334 ));
335 }
336}
337
338impl<F> BaseTracer for FunctionCallbackHandler<F>
339where
340 F: Fn(&str) + Send + Sync + std::fmt::Debug,
341{
342 fn persist_run_impl(&mut self, _run: &Run) {}
343}
344
345fn capitalize_first(s: &str) -> String {
347 let mut chars = s.chars();
348 match chars.next() {
349 None => String::new(),
350 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
351 }
352}
353
354#[derive(Debug)]
356pub struct ConsoleCallbackHandler {
357 inner: FunctionCallbackHandler<fn(&str)>,
359}
360
361impl ConsoleCallbackHandler {
362 pub fn new() -> Self {
364 fn print_fn(s: &str) {
365 println!("{}", s);
366 }
367 Self {
368 inner: FunctionCallbackHandler::new(print_fn as fn(&str)),
369 }
370 }
371}
372
373impl Default for ConsoleCallbackHandler {
374 fn default() -> Self {
375 Self::new()
376 }
377}
378
379impl TracerCore for ConsoleCallbackHandler {
380 fn config(&self) -> &TracerCoreConfig {
381 self.inner.config()
382 }
383
384 fn config_mut(&mut self) -> &mut TracerCoreConfig {
385 self.inner.config_mut()
386 }
387
388 fn run_map(&self) -> &HashMap<String, Run> {
389 self.inner.run_map()
390 }
391
392 fn run_map_mut(&mut self) -> &mut HashMap<String, Run> {
393 self.inner.run_map_mut()
394 }
395
396 fn order_map(&self) -> &HashMap<Uuid, (Uuid, String)> {
397 self.inner.order_map()
398 }
399
400 fn order_map_mut(&mut self) -> &mut HashMap<Uuid, (Uuid, String)> {
401 self.inner.order_map_mut()
402 }
403
404 fn persist_run(&mut self, run: &Run) {
405 self.inner.persist_run(run)
406 }
407
408 fn on_chain_start(&mut self, run: &Run) {
409 self.inner.on_chain_start(run)
410 }
411
412 fn on_chain_end(&mut self, run: &Run) {
413 self.inner.on_chain_end(run)
414 }
415
416 fn on_chain_error(&mut self, run: &Run) {
417 self.inner.on_chain_error(run)
418 }
419
420 fn on_llm_start(&mut self, run: &Run) {
421 self.inner.on_llm_start(run)
422 }
423
424 fn on_llm_end(&mut self, run: &Run) {
425 self.inner.on_llm_end(run)
426 }
427
428 fn on_llm_error(&mut self, run: &Run) {
429 self.inner.on_llm_error(run)
430 }
431
432 fn on_tool_start(&mut self, run: &Run) {
433 self.inner.on_tool_start(run)
434 }
435
436 fn on_tool_end(&mut self, run: &Run) {
437 self.inner.on_tool_end(run)
438 }
439
440 fn on_tool_error(&mut self, run: &Run) {
441 self.inner.on_tool_error(run)
442 }
443}
444
445impl BaseTracer for ConsoleCallbackHandler {
446 fn persist_run_impl(&mut self, run: &Run) {
447 self.inner.persist_run_impl(run)
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn test_try_json_stringify() {
457 let obj = serde_json::json!({"key": "value"});
458 let result = try_json_stringify(&obj, "fallback");
459 assert!(result.contains("key"));
460 assert!(result.contains("value"));
461 }
462
463 #[test]
464 fn test_try_json_stringify_fallback() {
465 let obj = serde_json::json!(null);
466 let result = try_json_stringify(&obj, "fallback");
467 assert_eq!(result, "null");
468 }
469
470 #[test]
471 fn test_capitalize_first() {
472 assert_eq!(capitalize_first("hello"), "Hello");
473 assert_eq!(capitalize_first("HELLO"), "HELLO");
474 assert_eq!(capitalize_first(""), "");
475 assert_eq!(capitalize_first("chain"), "Chain");
476 }
477
478 #[test]
479 fn test_elapsed() {
480 let mut run = Run::default();
481 assert_eq!(elapsed(&run), "N/A");
482
483 run.end_time = Some(run.start_time + chrono::Duration::milliseconds(500));
484 assert!(elapsed(&run).contains("ms"));
485
486 run.end_time = Some(run.start_time + chrono::Duration::seconds(2));
487 assert!(elapsed(&run).contains("s"));
488 }
489
490 #[test]
491 fn test_console_callback_handler_creation() {
492 let handler = ConsoleCallbackHandler::new();
493 assert!(handler.run_map().is_empty());
494 }
495
496 #[test]
497 fn test_console_callback_handler_get_breadcrumbs() {
498 let handler = ConsoleCallbackHandler::new();
499
500 let run = Run::new(
501 Uuid::new_v4(),
502 "test_run",
503 "chain",
504 HashMap::new(),
505 HashMap::new(),
506 );
507
508 let breadcrumbs = handler.inner.get_breadcrumbs(&run);
509 assert_eq!(breadcrumbs, "chain:test_run");
510 }
511}