Skip to main content

camel_component_llm/
bundle.rs

1use std::sync::Arc;
2
3use camel_component_api::{CamelError, ComponentBundle, ComponentRegistrar};
4
5use crate::LlmComponent;
6use crate::config::LlmGlobalConfig;
7use crate::provider_factory::{ProviderMap, build_provider_map};
8
9pub struct LlmBundle {
10    config: LlmGlobalConfig,
11    providers: ProviderMap,
12}
13
14impl ComponentBundle for LlmBundle {
15    fn config_key() -> &'static str {
16        "llm"
17    }
18
19    fn from_toml(value: toml::Value) -> Result<Self, CamelError> {
20        let config: LlmGlobalConfig = value
21            .try_into()
22            .map_err(|e: toml::de::Error| CamelError::Config(e.to_string()))?;
23        // Validate config semantics (reject zero timeouts/concurrency)
24        config.validate()?;
25        // Fail-fast: validate all providers can be constructed at startup
26        let providers = build_provider_map(&config).map_err(CamelError::from)?;
27        Ok(Self { config, providers })
28    }
29
30    fn register_all(self, ctx: &mut dyn ComponentRegistrar) {
31        let component = LlmComponent::from_parts(self.config, self.providers);
32        ctx.register_component_dyn(Arc::new(component));
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use camel_component_api::{ComponentBundle, ComponentRegistrar};
40    use std::sync::Arc;
41
42    struct TestRegistrar {
43        schemes: Vec<String>,
44    }
45
46    impl ComponentRegistrar for TestRegistrar {
47        fn register_component_dyn(&mut self, component: Arc<dyn camel_component_api::Component>) {
48            self.schemes.push(component.scheme().to_string());
49        }
50    }
51
52    #[test]
53    fn bundle_from_toml_registers_llm_scheme() {
54        let toml_str = r#"
55[providers.test]
56type = "mock"
57response = "echo"
58"#;
59        let value: toml::Value = toml::from_str(toml_str).expect("parse toml");
60        let bundle = LlmBundle::from_toml(value).expect("bundle");
61        let mut registrar = TestRegistrar { schemes: vec![] };
62        bundle.register_all(&mut registrar);
63        assert_eq!(registrar.schemes, vec!["llm"]);
64    }
65
66    #[test]
67    fn config_key_is_llm() {
68        assert_eq!(LlmBundle::config_key(), "llm");
69    }
70
71    #[test]
72    fn from_toml_fails_on_unsupported_provider() {
73        // A provider type that doesn't match any enum variant should fail
74        // at TOML deserialization, regardless of feature flags.
75        let toml_str = r#"
76[providers.unknown-test]
77type = "anthropic"
78api_key = "test-key"
79default_model = "claude-3"
80"#;
81        let value: toml::Value = toml::from_str(toml_str).expect("parse toml");
82        let result = LlmBundle::from_toml(value);
83        assert!(
84            result.is_err(),
85            "from_toml should fail for unsupported provider type"
86        );
87    }
88}