Skip to main content

litellm_rs/core/integrations/
manager.rs

1//! Integration Manager
2//!
3//! Manages registration and invocation of all integrations.
4
5use std::sync::Arc;
6use tokio::sync::RwLock;
7use tracing::{debug, error, warn};
8
9use crate::core::traits::integration::{
10    BoxedIntegration, CacheHitEvent, EmbeddingEndEvent, EmbeddingErrorEvent, EmbeddingStartEvent,
11    GuardrailDecisionEvent, IntegrationError, IntegrationResult, LlmEndEvent, LlmErrorEvent,
12    LlmStartEvent, LlmStreamEvent,
13};
14
15/// Configuration for the integration manager
16#[derive(Debug, Clone)]
17pub struct IntegrationManagerConfig {
18    /// Whether to fail fast on integration errors (default: false)
19    pub fail_fast: bool,
20    /// Whether to run integrations in parallel (default: true)
21    pub parallel: bool,
22    /// Timeout for each integration call in milliseconds (default: 5000)
23    pub timeout_ms: u64,
24    /// Whether to log integration errors (default: true)
25    pub log_errors: bool,
26}
27
28impl Default for IntegrationManagerConfig {
29    fn default() -> Self {
30        Self {
31            fail_fast: false,
32            parallel: true,
33            timeout_ms: 5000,
34            log_errors: true,
35        }
36    }
37}
38
39impl IntegrationManagerConfig {
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    pub fn fail_fast(mut self, fail_fast: bool) -> Self {
45        self.fail_fast = fail_fast;
46        self
47    }
48
49    pub fn parallel(mut self, parallel: bool) -> Self {
50        self.parallel = parallel;
51        self
52    }
53
54    pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
55        self.timeout_ms = timeout_ms;
56        self
57    }
58
59    pub fn log_errors(mut self, log_errors: bool) -> Self {
60        self.log_errors = log_errors;
61        self
62    }
63}
64
65/// Integration Manager - manages all registered integrations
66pub struct IntegrationManager {
67    /// Registered integrations
68    integrations: RwLock<Vec<BoxedIntegration>>,
69    /// Configuration
70    config: IntegrationManagerConfig,
71}
72
73impl IntegrationManager {
74    /// Create a new integration manager
75    pub fn new(config: IntegrationManagerConfig) -> Self {
76        Self {
77            integrations: RwLock::new(Vec::new()),
78            config,
79        }
80    }
81
82    /// Create with default configuration
83    pub fn with_defaults() -> Self {
84        Self::new(IntegrationManagerConfig::default())
85    }
86
87    /// Register an integration
88    pub async fn register(&self, integration: BoxedIntegration) {
89        if integration.is_enabled() {
90            debug!("Registering integration: {}", integration.name());
91            let mut integrations = self.integrations.write().await;
92            integrations.push(integration);
93        } else {
94            debug!("Skipping disabled integration: {}", integration.name());
95        }
96    }
97
98    /// Register multiple integrations
99    pub async fn register_all(&self, integrations: Vec<BoxedIntegration>) {
100        for integration in integrations {
101            self.register(integration).await;
102        }
103    }
104
105    /// Unregister an integration by name
106    pub async fn unregister(&self, name: &str) -> bool {
107        let mut integrations = self.integrations.write().await;
108        let initial_len = integrations.len();
109        integrations.retain(|i| i.name() != name);
110        let removed = integrations.len() < initial_len;
111        if removed {
112            debug!("Unregistered integration: {}", name);
113        }
114        removed
115    }
116
117    /// Get list of registered integration names
118    pub async fn list_integrations(&self) -> Vec<&'static str> {
119        let integrations = self.integrations.read().await;
120        integrations.iter().map(|i| i.name()).collect()
121    }
122
123    /// Get count of registered integrations
124    pub async fn count(&self) -> usize {
125        self.integrations.read().await.len()
126    }
127
128    /// Check if an integration is registered
129    pub async fn has_integration(&self, name: &str) -> bool {
130        let integrations = self.integrations.read().await;
131        integrations.iter().any(|i| i.name() == name)
132    }
133
134    /// Notify all integrations of LLM start
135    pub async fn on_llm_start(&self, event: &LlmStartEvent) -> IntegrationResult<()> {
136        let integrations = self.integrations.read().await;
137        if integrations.is_empty() {
138            return Ok(());
139        }
140
141        let event = event.clone();
142        if self.config.parallel {
143            self.dispatch_parallel_start(&integrations, event).await
144        } else {
145            self.dispatch_sequential_start(&integrations, event).await
146        }
147    }
148
149    /// Notify all integrations of LLM end
150    pub async fn on_llm_end(&self, event: &LlmEndEvent) -> IntegrationResult<()> {
151        let integrations = self.integrations.read().await;
152        if integrations.is_empty() {
153            return Ok(());
154        }
155
156        let event = event.clone();
157        if self.config.parallel {
158            self.dispatch_parallel_end(&integrations, event).await
159        } else {
160            self.dispatch_sequential_end(&integrations, event).await
161        }
162    }
163
164    /// Notify all integrations of LLM error
165    pub async fn on_llm_error(&self, event: &LlmErrorEvent) -> IntegrationResult<()> {
166        let integrations = self.integrations.read().await;
167        if integrations.is_empty() {
168            return Ok(());
169        }
170
171        let event = event.clone();
172        if self.config.parallel {
173            self.dispatch_parallel_error(&integrations, event).await
174        } else {
175            self.dispatch_sequential_error(&integrations, event).await
176        }
177    }
178
179    /// Notify all integrations of a sanitized guardrail decision.
180    pub async fn on_guardrail_decision(
181        &self,
182        event: &GuardrailDecisionEvent,
183    ) -> IntegrationResult<()> {
184        let integrations = self.integrations.read().await;
185        for integration in integrations.iter() {
186            if integration.is_enabled()
187                && let Err(e) = integration.on_guardrail_decision(event).await
188            {
189                if self.config.log_errors {
190                    warn!(
191                        "Integration {} guardrail decision error: {}",
192                        integration.name(),
193                        e
194                    );
195                }
196                if self.config.fail_fast {
197                    return Err(e);
198                }
199            }
200        }
201        Ok(())
202    }
203
204    /// Notify all integrations of LLM stream chunk
205    pub async fn on_llm_stream(&self, event: &LlmStreamEvent) -> IntegrationResult<()> {
206        let integrations = self.integrations.read().await;
207        for integration in integrations.iter() {
208            if integration.is_enabled()
209                && let Err(e) = integration.on_llm_stream(event).await
210            {
211                if self.config.log_errors {
212                    warn!("Integration {} stream error: {}", integration.name(), e);
213                }
214                if self.config.fail_fast {
215                    return Err(e);
216                }
217            }
218        }
219        Ok(())
220    }
221
222    /// Notify all integrations of embedding start
223    pub async fn on_embedding_start(&self, event: &EmbeddingStartEvent) -> IntegrationResult<()> {
224        let integrations = self.integrations.read().await;
225        for integration in integrations.iter() {
226            if integration.is_enabled()
227                && let Err(e) = integration.on_embedding_start(event).await
228            {
229                if self.config.log_errors {
230                    warn!(
231                        "Integration {} embedding start error: {}",
232                        integration.name(),
233                        e
234                    );
235                }
236                if self.config.fail_fast {
237                    return Err(e);
238                }
239            }
240        }
241        Ok(())
242    }
243
244    /// Notify all integrations of embedding end
245    pub async fn on_embedding_end(&self, event: &EmbeddingEndEvent) -> IntegrationResult<()> {
246        let integrations = self.integrations.read().await;
247        for integration in integrations.iter() {
248            if integration.is_enabled()
249                && let Err(e) = integration.on_embedding_end(event).await
250            {
251                if self.config.log_errors {
252                    warn!(
253                        "Integration {} embedding end error: {}",
254                        integration.name(),
255                        e
256                    );
257                }
258                if self.config.fail_fast {
259                    return Err(e);
260                }
261            }
262        }
263        Ok(())
264    }
265
266    /// Notify all integrations of an embedding error
267    pub async fn on_embedding_error(&self, event: &EmbeddingErrorEvent) -> IntegrationResult<()> {
268        let integrations = self.integrations.read().await;
269        if integrations.is_empty() {
270            return Ok(());
271        }
272
273        let event = event.clone();
274        if self.config.parallel {
275            self.dispatch_parallel_embedding_error(&integrations, event)
276                .await
277        } else {
278            self.dispatch_sequential_embedding_error(&integrations, event)
279                .await
280        }
281    }
282
283    /// Notify all integrations of cache hit
284    pub async fn on_cache_hit(&self, event: &CacheHitEvent) -> IntegrationResult<()> {
285        let integrations = self.integrations.read().await;
286        for integration in integrations.iter() {
287            if integration.is_enabled()
288                && let Err(e) = integration.on_cache_hit(event).await
289            {
290                if self.config.log_errors {
291                    warn!("Integration {} cache hit error: {}", integration.name(), e);
292                }
293                if self.config.fail_fast {
294                    return Err(e);
295                }
296            }
297        }
298        Ok(())
299    }
300
301    /// Flush all integrations
302    pub async fn flush(&self) -> IntegrationResult<()> {
303        let integrations = self.integrations.read().await;
304        for integration in integrations.iter() {
305            if integration.is_enabled()
306                && let Err(e) = integration.flush().await
307            {
308                if self.config.log_errors {
309                    warn!("Integration {} flush error: {}", integration.name(), e);
310                }
311                if self.config.fail_fast {
312                    return Err(e);
313                }
314            }
315        }
316        Ok(())
317    }
318
319    /// Shutdown all integrations
320    pub async fn shutdown(&self) -> IntegrationResult<()> {
321        debug!("Shutting down all integrations");
322        let integrations = self.integrations.read().await;
323
324        let mut errors = Vec::new();
325        for integration in integrations.iter() {
326            if let Err(e) = integration.shutdown().await {
327                if self.config.log_errors {
328                    error!(
329                        "Error shutting down integration {}: {}",
330                        integration.name(),
331                        e
332                    );
333                }
334                errors.push((integration.name(), e));
335            }
336        }
337
338        if self.config.fail_fast
339            && let Some((name, err)) = errors.into_iter().next()
340        {
341            return Err(IntegrationError::Other(format!(
342                "Integration {} shutdown failed: {}",
343                name, err
344            )));
345        }
346
347        Ok(())
348    }
349
350    /// Dispatch LLM start events in parallel
351    async fn dispatch_parallel_start(
352        &self,
353        integrations: &[BoxedIntegration],
354        event: LlmStartEvent,
355    ) -> IntegrationResult<()> {
356        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
357        let mut handles = Vec::with_capacity(integrations.len());
358
359        for integration in integrations.iter() {
360            if !integration.is_enabled() {
361                continue;
362            }
363
364            let integration = Arc::clone(integration);
365            let event = event.clone();
366            let name = integration.name();
367
368            handles.push(tokio::spawn(async move {
369                match tokio::time::timeout(timeout, integration.on_llm_start(&event)).await {
370                    Ok(result) => (name, result),
371                    Err(_) => (
372                        name,
373                        Err(IntegrationError::Timeout {
374                            timeout_ms: timeout.as_millis() as u64,
375                        }),
376                    ),
377                }
378            }));
379        }
380
381        self.collect_results(handles).await
382    }
383
384    /// Dispatch LLM start events sequentially
385    async fn dispatch_sequential_start(
386        &self,
387        integrations: &[BoxedIntegration],
388        event: LlmStartEvent,
389    ) -> IntegrationResult<()> {
390        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
391
392        for integration in integrations.iter() {
393            if !integration.is_enabled() {
394                continue;
395            }
396
397            let result = tokio::time::timeout(timeout, integration.on_llm_start(&event)).await;
398            self.handle_sequential_result(integration.name(), result)?;
399        }
400
401        Ok(())
402    }
403
404    /// Dispatch LLM end events in parallel
405    async fn dispatch_parallel_end(
406        &self,
407        integrations: &[BoxedIntegration],
408        event: LlmEndEvent,
409    ) -> IntegrationResult<()> {
410        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
411        let mut handles = Vec::with_capacity(integrations.len());
412
413        for integration in integrations.iter() {
414            if !integration.is_enabled() {
415                continue;
416            }
417
418            let integration = Arc::clone(integration);
419            let event = event.clone();
420            let name = integration.name();
421
422            handles.push(tokio::spawn(async move {
423                match tokio::time::timeout(timeout, integration.on_llm_end(&event)).await {
424                    Ok(result) => (name, result),
425                    Err(_) => (
426                        name,
427                        Err(IntegrationError::Timeout {
428                            timeout_ms: timeout.as_millis() as u64,
429                        }),
430                    ),
431                }
432            }));
433        }
434
435        self.collect_results(handles).await
436    }
437
438    /// Dispatch LLM end events sequentially
439    async fn dispatch_sequential_end(
440        &self,
441        integrations: &[BoxedIntegration],
442        event: LlmEndEvent,
443    ) -> IntegrationResult<()> {
444        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
445
446        for integration in integrations.iter() {
447            if !integration.is_enabled() {
448                continue;
449            }
450
451            let result = tokio::time::timeout(timeout, integration.on_llm_end(&event)).await;
452            self.handle_sequential_result(integration.name(), result)?;
453        }
454
455        Ok(())
456    }
457
458    /// Dispatch LLM error events in parallel
459    async fn dispatch_parallel_error(
460        &self,
461        integrations: &[BoxedIntegration],
462        event: LlmErrorEvent,
463    ) -> IntegrationResult<()> {
464        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
465        let mut handles = Vec::with_capacity(integrations.len());
466
467        for integration in integrations.iter() {
468            if !integration.is_enabled() {
469                continue;
470            }
471
472            let integration = Arc::clone(integration);
473            let event = event.clone();
474            let name = integration.name();
475
476            handles.push(tokio::spawn(async move {
477                match tokio::time::timeout(timeout, integration.on_llm_error(&event)).await {
478                    Ok(result) => (name, result),
479                    Err(_) => (
480                        name,
481                        Err(IntegrationError::Timeout {
482                            timeout_ms: timeout.as_millis() as u64,
483                        }),
484                    ),
485                }
486            }));
487        }
488
489        self.collect_results(handles).await
490    }
491
492    /// Dispatch LLM error events sequentially
493    async fn dispatch_sequential_error(
494        &self,
495        integrations: &[BoxedIntegration],
496        event: LlmErrorEvent,
497    ) -> IntegrationResult<()> {
498        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
499
500        for integration in integrations.iter() {
501            if !integration.is_enabled() {
502                continue;
503            }
504
505            let result = tokio::time::timeout(timeout, integration.on_llm_error(&event)).await;
506            self.handle_sequential_result(integration.name(), result)?;
507        }
508
509        Ok(())
510    }
511
512    /// Dispatch embedding error events in parallel
513    async fn dispatch_parallel_embedding_error(
514        &self,
515        integrations: &[BoxedIntegration],
516        event: EmbeddingErrorEvent,
517    ) -> IntegrationResult<()> {
518        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
519        let mut handles = Vec::with_capacity(integrations.len());
520
521        for integration in integrations.iter() {
522            if !integration.is_enabled() {
523                continue;
524            }
525            let integration = Arc::clone(integration);
526            let event = event.clone();
527            let name = integration.name();
528            handles.push(tokio::spawn(async move {
529                match tokio::time::timeout(timeout, integration.on_embedding_error(&event)).await {
530                    Ok(result) => (name, result),
531                    Err(_) => (
532                        name,
533                        Err(IntegrationError::Timeout {
534                            timeout_ms: timeout.as_millis() as u64,
535                        }),
536                    ),
537                }
538            }));
539        }
540
541        self.collect_results(handles).await
542    }
543
544    /// Dispatch embedding error events sequentially
545    async fn dispatch_sequential_embedding_error(
546        &self,
547        integrations: &[BoxedIntegration],
548        event: EmbeddingErrorEvent,
549    ) -> IntegrationResult<()> {
550        let timeout = std::time::Duration::from_millis(self.config.timeout_ms);
551        for integration in integrations.iter() {
552            if !integration.is_enabled() {
553                continue;
554            }
555            let result =
556                tokio::time::timeout(timeout, integration.on_embedding_error(&event)).await;
557            self.handle_sequential_result(integration.name(), result)?;
558        }
559        Ok(())
560    }
561
562    /// Collect results from parallel dispatch
563    async fn collect_results(
564        &self,
565        handles: Vec<tokio::task::JoinHandle<(&'static str, IntegrationResult<()>)>>,
566    ) -> IntegrationResult<()> {
567        let mut errors = Vec::new();
568
569        for handle in handles {
570            match handle.await {
571                Ok((name, Err(e))) => {
572                    if self.config.log_errors {
573                        warn!("Integration {} error: {}", name, e);
574                    }
575                    errors.push((name, e));
576                }
577                Err(e) => {
578                    if self.config.log_errors {
579                        error!("Integration task panicked: {}", e);
580                    }
581                }
582                Ok((_, Ok(()))) => {}
583            }
584        }
585
586        if self.config.fail_fast
587            && let Some((name, err)) = errors.into_iter().next()
588        {
589            return Err(IntegrationError::Other(format!(
590                "Integration {} failed: {}",
591                name, err
592            )));
593        }
594
595        Ok(())
596    }
597
598    /// Handle result from sequential dispatch
599    fn handle_sequential_result(
600        &self,
601        name: &'static str,
602        result: Result<IntegrationResult<()>, tokio::time::error::Elapsed>,
603    ) -> IntegrationResult<()> {
604        match result {
605            Ok(Ok(())) => Ok(()),
606            Ok(Err(e)) => {
607                if self.config.log_errors {
608                    warn!("Integration {} error: {}", name, e);
609                }
610                if self.config.fail_fast {
611                    Err(e)
612                } else {
613                    Ok(())
614                }
615            }
616            Err(_) => {
617                let err = IntegrationError::Timeout {
618                    timeout_ms: self.config.timeout_ms,
619                };
620                if self.config.log_errors {
621                    warn!("Integration {} timeout", name);
622                }
623                if self.config.fail_fast {
624                    Err(err)
625                } else {
626                    Ok(())
627                }
628            }
629        }
630    }
631}
632
633impl Default for IntegrationManager {
634    fn default() -> Self {
635        Self::with_defaults()
636    }
637}
638
639#[cfg(test)]
640#[path = "manager_tests.rs"]
641mod tests;