1use async_trait::async_trait;
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::sync::Mutex;
8
9use super::run_tree::RunTree;
10use crate::tracing::{SpanGuard, SpanKind, Tracer};
11use lc_schema::Message;
12
13#[async_trait]
39pub trait CallbackHandler: Send + Sync {
40 async fn on_run_start(&self, run: &RunTree);
44
45 async fn on_run_end(&self, run: &RunTree);
47
48 async fn on_run_error(&self, run: &RunTree, error: &str);
50
51 async fn on_llm_start(&self, run: &RunTree, _messages: &[Message]) {
55 self.on_run_start(run).await;
56 }
57
58 async fn on_llm_end(&self, run: &RunTree, _response: &str) {
60 self.on_run_end(run).await;
61 }
62
63 async fn on_llm_new_token(&self, _run: &RunTree, _token: &str) {
65 }
67
68 async fn on_llm_thinking(&self, _run: &RunTree, _thinking: &str) {
75 }
77
78 async fn on_llm_error(&self, run: &RunTree, error: &str) {
80 self.on_run_error(run, error).await;
81 }
82
83 async fn on_chain_start(&self, run: &RunTree, _inputs: &serde_json::Value) {
87 self.on_run_start(run).await;
88 }
89
90 async fn on_chain_end(&self, run: &RunTree, _outputs: &serde_json::Value) {
92 self.on_run_end(run).await;
93 }
94
95 async fn on_chain_error(&self, run: &RunTree, error: &str) {
97 self.on_run_error(run, error).await;
98 }
99
100 async fn on_tool_start(&self, run: &RunTree, _tool_name: &str, _input: &str) {
104 self.on_run_start(run).await;
105 }
106
107 async fn on_tool_end(&self, run: &RunTree, _output: &str) {
109 self.on_run_end(run).await;
110 }
111
112 async fn on_tool_error(&self, run: &RunTree, error: &str) {
114 self.on_run_error(run, error).await;
115 }
116
117 async fn on_retriever_start(&self, run: &RunTree, _query: &str) {
121 self.on_run_start(run).await;
122 }
123
124 async fn on_retriever_end(&self, run: &RunTree, _documents: &[serde_json::Value]) {
126 self.on_run_end(run).await;
127 }
128
129 async fn on_retriever_error(&self, run: &RunTree, error: &str) {
131 self.on_run_error(run, error).await;
132 }
133}
134
135pub struct CallbackManager {
143 inner: Arc<CallbackManagerInner>,
144 tracer: Option<Arc<Tracer>>,
145 active_trace_spans: Arc<Mutex<HashMap<String, SpanGuard>>>,
146}
147
148struct CallbackManagerInner {
149 handlers: Vec<Arc<dyn CallbackHandler>>,
150}
151
152impl std::fmt::Debug for CallbackManager {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 f.debug_struct("CallbackManager")
155 .field("handlers_count", &self.inner.handlers.len())
156 .field("tracer_attached", &self.tracer.is_some())
157 .finish()
158 }
159}
160
161impl CallbackManager {
162 pub fn new() -> Self {
164 Self {
165 inner: Arc::new(CallbackManagerInner {
166 handlers: Vec::new(),
167 }),
168 tracer: None,
169 active_trace_spans: Arc::new(Mutex::new(HashMap::new())),
170 }
171 }
172
173 pub fn add_handler(self, handler: Arc<dyn CallbackHandler>) -> Self {
175 let mut handlers = self.inner.handlers.clone();
176 handlers.push(handler);
177 Self {
178 inner: Arc::new(CallbackManagerInner { handlers }),
179 tracer: self.tracer,
180 active_trace_spans: self.active_trace_spans,
181 }
182 }
183
184 pub fn with_tracer(self, tracer: Arc<Tracer>) -> Self {
191 Self {
192 inner: self.inner,
193 tracer: Some(tracer),
194 active_trace_spans: self.active_trace_spans,
195 }
196 }
197
198 pub fn handlers(&self) -> &[Arc<dyn CallbackHandler>] {
200 &self.inner.handlers
201 }
202
203 pub fn is_empty(&self) -> bool {
205 self.inner.handlers.is_empty()
206 }
207
208 pub fn merge_with(&self, other: &CallbackManager) -> CallbackManager {
215 let mut handlers = self.inner.handlers.clone();
216 handlers.extend(other.inner.handlers.iter().cloned());
217 let tracer = self.tracer.clone().or_else(|| other.tracer.clone());
218 CallbackManager {
219 inner: Arc::new(CallbackManagerInner { handlers }),
220 tracer,
221 active_trace_spans: Arc::new(Mutex::new(HashMap::new())),
222 }
223 }
224
225 async fn begin_trace_span(&self, run: &RunTree) {
229 if let Some(tracer) = &self.tracer {
230 let kind = SpanKind::from(run.run_type);
231 let guard = tracer.start_child(&run.name, kind);
233 self.active_trace_spans
234 .lock()
235 .await
236 .insert(run.id.to_string(), guard);
237 }
238 }
239
240 async fn end_trace_span(&self, run: &RunTree, error: Option<&str>) {
242 if let Some(mut guard) = self
243 .active_trace_spans
244 .lock()
245 .await
246 .remove(&run.id.to_string())
247 {
248 if let Some(msg) = error {
249 guard.set_error(msg);
250 }
251 guard.end();
252 }
253 }
254}
255
256impl Default for CallbackManager {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262impl Clone for CallbackManager {
263 fn clone(&self) -> Self {
264 Self {
265 inner: Arc::clone(&self.inner),
266 tracer: self.tracer.clone(),
267 active_trace_spans: Arc::clone(&self.active_trace_spans),
268 }
269 }
270}
271
272impl CallbackManager {
275 pub async fn dispatch_run_start(&self, run: &RunTree) {
277 self.begin_trace_span(run).await;
278 for handler in &self.inner.handlers {
279 handler.on_run_start(run).await;
280 }
281 }
282
283 pub async fn dispatch_run_end(&self, run: &RunTree) {
285 self.end_trace_span(run, None).await;
286 for handler in &self.inner.handlers {
287 handler.on_run_end(run).await;
288 }
289 }
290
291 pub async fn dispatch_run_error(&self, run: &RunTree, error: &str) {
293 self.end_trace_span(run, Some(error)).await;
294 for handler in &self.inner.handlers {
295 handler.on_run_error(run, error).await;
296 }
297 }
298
299 pub async fn dispatch_chain_start(&self, run: &RunTree, inputs: &serde_json::Value) {
301 self.begin_trace_span(run).await;
302 for handler in &self.inner.handlers {
303 handler.on_chain_start(run, inputs).await;
304 }
305 }
306
307 pub async fn dispatch_chain_end(&self, run: &RunTree, outputs: &serde_json::Value) {
309 self.end_trace_span(run, None).await;
310 for handler in &self.inner.handlers {
311 handler.on_chain_end(run, outputs).await;
312 }
313 }
314
315 pub async fn dispatch_chain_error(&self, run: &RunTree, error: &str) {
317 self.end_trace_span(run, Some(error)).await;
318 for handler in &self.inner.handlers {
319 handler.on_chain_error(run, error).await;
320 }
321 }
322
323 pub async fn dispatch_llm_start(&self, run: &RunTree, messages: &[lc_schema::Message]) {
325 self.begin_trace_span(run).await;
326 for handler in &self.inner.handlers {
327 handler.on_llm_start(run, messages).await;
328 }
329 }
330
331 pub async fn dispatch_llm_end(&self, run: &RunTree, response: &str) {
333 self.end_trace_span(run, None).await;
334 for handler in &self.inner.handlers {
335 handler.on_llm_end(run, response).await;
336 }
337 }
338
339 pub async fn dispatch_llm_error(&self, run: &RunTree, error: &str) {
341 self.end_trace_span(run, Some(error)).await;
342 for handler in &self.inner.handlers {
343 handler.on_llm_error(run, error).await;
344 }
345 }
346
347 pub async fn dispatch_llm_new_token(&self, run: &RunTree, token: &str) {
349 for handler in &self.inner.handlers {
350 handler.on_llm_new_token(run, token).await;
351 }
352 }
353
354 pub async fn dispatch_llm_thinking(&self, run: &RunTree, thinking: &str) {
356 for handler in &self.inner.handlers {
357 handler.on_llm_thinking(run, thinking).await;
358 }
359 }
360
361 pub async fn dispatch_tool_start(&self, run: &RunTree, tool_name: &str, input: &str) {
363 self.begin_trace_span(run).await;
364 for handler in &self.inner.handlers {
365 handler.on_tool_start(run, tool_name, input).await;
366 }
367 }
368
369 pub async fn dispatch_tool_end(&self, run: &RunTree, output: &str) {
371 self.end_trace_span(run, None).await;
372 for handler in &self.inner.handlers {
373 handler.on_tool_end(run, output).await;
374 }
375 }
376
377 pub async fn dispatch_tool_error(&self, run: &RunTree, error: &str) {
379 self.end_trace_span(run, Some(error)).await;
380 for handler in &self.inner.handlers {
381 handler.on_tool_error(run, error).await;
382 }
383 }
384
385 pub async fn dispatch_retriever_start(&self, run: &RunTree, query: &str) {
387 self.begin_trace_span(run).await;
388 for handler in &self.inner.handlers {
389 handler.on_retriever_start(run, query).await;
390 }
391 }
392
393 pub async fn dispatch_retriever_end(&self, run: &RunTree, documents: &[serde_json::Value]) {
395 self.end_trace_span(run, None).await;
396 for handler in &self.inner.handlers {
397 handler.on_retriever_end(run, documents).await;
398 }
399 }
400
401 pub async fn dispatch_retriever_error(&self, run: &RunTree, error: &str) {
403 self.end_trace_span(run, Some(error)).await;
404 for handler in &self.inner.handlers {
405 handler.on_retriever_error(run, error).await;
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use crate::tracing::InMemoryTracingBackend;
414
415 struct RecordingHandler {
416 events: Mutex<Vec<String>>,
417 }
418
419 #[async_trait]
420 impl CallbackHandler for RecordingHandler {
421 async fn on_run_start(&self, run: &RunTree) {
422 self.events.lock().await.push(format!("start:{}", run.name));
423 }
424 async fn on_run_end(&self, run: &RunTree) {
425 self.events.lock().await.push(format!("end:{}", run.name));
426 }
427 async fn on_run_error(&self, _run: &RunTree, error: &str) {
428 self.events.lock().await.push(format!("error:{}", error));
429 }
430 }
431
432 #[tokio::test]
433 async fn test_dispatch_run_start_end() {
434 let handler = Arc::new(RecordingHandler {
435 events: Mutex::new(Vec::new()),
436 });
437 let cm = CallbackManager::new().add_handler(handler.clone());
438
439 let run = RunTree::new("r1", crate::RunType::Chain, serde_json::json!({}));
440 cm.dispatch_run_start(&run).await;
441 cm.dispatch_run_end(&run).await;
442
443 let events = handler.events.lock().await.clone();
444 assert_eq!(events, vec!["start:r1".to_string(), "end:r1".to_string()]);
445 }
446
447 #[tokio::test]
448 async fn test_dispatch_run_error_forwards_message() {
449 let handler = Arc::new(RecordingHandler {
450 events: Mutex::new(Vec::new()),
451 });
452 let cm = CallbackManager::new().add_handler(handler.clone());
453
454 let run = RunTree::new("r1", crate::RunType::Tool, serde_json::json!({}));
455 cm.dispatch_run_start(&run).await;
456 cm.dispatch_run_error(&run, "boom").await;
457
458 let events = handler.events.lock().await.clone();
459 assert!(events.contains(&"error:boom".to_string()));
460 }
461
462 #[tokio::test]
463 async fn test_dispatch_llm_thinking_forwards() {
464 struct ThinkingHandler {
465 received: Mutex<Vec<String>>,
466 }
467 #[async_trait]
468 impl CallbackHandler for ThinkingHandler {
469 async fn on_run_start(&self, _run: &RunTree) {}
470 async fn on_run_end(&self, _run: &RunTree) {}
471 async fn on_run_error(&self, _run: &RunTree, _e: &str) {}
472 async fn on_llm_thinking(&self, _run: &RunTree, thinking: &str) {
473 self.received.lock().await.push(thinking.to_string());
474 }
475 }
476 let handler = Arc::new(ThinkingHandler {
477 received: Mutex::new(Vec::new()),
478 });
479 let cm = CallbackManager::new().add_handler(handler.clone());
480
481 let run = RunTree::new("llm1", crate::RunType::Llm, serde_json::json!({}));
482 cm.dispatch_llm_start(&run, &[]).await;
483 cm.dispatch_llm_thinking(&run, "let me think").await;
484 cm.dispatch_llm_end(&run, "answer").await;
485
486 let received = handler.received.lock().await.clone();
487 assert_eq!(received, vec!["let me think".to_string()]);
488 }
489
490 #[tokio::test]
491 async fn test_with_tracer_publishes_run_spans() {
492 let backend = Arc::new(InMemoryTracingBackend::new());
493 let tracer = Arc::new(Tracer::new(backend.clone()));
494
495 let cm = CallbackManager::new().with_tracer(tracer);
496 let run = RunTree::new("span_run", crate::RunType::Chain, serde_json::json!({}));
497
498 cm.dispatch_run_start(&run).await;
499 cm.dispatch_run_end(&run).await;
500
501 let spans = backend.spans();
502 assert_eq!(spans.len(), 1);
503 assert_eq!(spans[0].name, "span_run");
504 assert_eq!(spans[0].kind, SpanKind::Chain);
505 }
506
507 #[tokio::test]
508 async fn test_with_tracer_publishes_error_status() {
509 let backend = Arc::new(InMemoryTracingBackend::new());
510 let tracer = Arc::new(Tracer::new(backend.clone()));
511
512 let cm = CallbackManager::new().with_tracer(tracer);
513 let run = RunTree::new("fail_run", crate::RunType::Llm, serde_json::json!({}));
514
515 cm.dispatch_llm_start(&run, &[]).await;
516 cm.dispatch_llm_error(&run, "model timeout").await;
517
518 let spans = backend.spans();
519 assert_eq!(spans.len(), 1);
520 assert!(matches!(
521 &spans[0].status,
522 crate::tracing::SpanStatus::Error(e) if e == "model timeout"
523 ));
524 }
525
526 #[tokio::test]
527 async fn test_nested_runs_become_parent_child_spans() {
528 let backend = Arc::new(InMemoryTracingBackend::new());
529 let tracer = Arc::new(Tracer::new(backend.clone()));
530
531 let cm = CallbackManager::new().with_tracer(tracer);
532
533 let chain = RunTree::new("outer_chain", crate::RunType::Chain, serde_json::json!({}));
534 cm.dispatch_chain_start(&chain, &serde_json::json!({}))
535 .await;
536
537 let llm = RunTree::new("inner_llm", crate::RunType::Llm, serde_json::json!({}));
538 cm.dispatch_llm_start(&llm, &[]).await;
539 cm.dispatch_llm_end(&llm, "answer").await;
540
541 cm.dispatch_chain_end(&chain, &serde_json::json!({})).await;
542
543 let spans = backend.spans();
544 assert_eq!(spans.len(), 2);
545 let outer = spans.iter().find(|s| s.name == "outer_chain").unwrap();
546 let inner = spans.iter().find(|s| s.name == "inner_llm").unwrap();
547 assert!(outer.parent_id.is_none());
548 assert_eq!(inner.parent_id.as_deref(), Some(outer.id.as_str()));
549 }
550}