litellm_rs/core/integrations/
manager.rs1use 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#[derive(Debug, Clone)]
17pub struct IntegrationManagerConfig {
18 pub fail_fast: bool,
20 pub parallel: bool,
22 pub timeout_ms: u64,
24 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
65pub struct IntegrationManager {
67 integrations: RwLock<Vec<BoxedIntegration>>,
69 config: IntegrationManagerConfig,
71}
72
73impl IntegrationManager {
74 pub fn new(config: IntegrationManagerConfig) -> Self {
76 Self {
77 integrations: RwLock::new(Vec::new()),
78 config,
79 }
80 }
81
82 pub fn with_defaults() -> Self {
84 Self::new(IntegrationManagerConfig::default())
85 }
86
87 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 pub async fn register_all(&self, integrations: Vec<BoxedIntegration>) {
100 for integration in integrations {
101 self.register(integration).await;
102 }
103 }
104
105 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 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 pub async fn count(&self) -> usize {
125 self.integrations.read().await.len()
126 }
127
128 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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;