Skip to main content

camel_component_llm/
lib.rs

1//! camel-component-llm — LLM integration component for rust-camel.
2//!
3//! Provides chat (streaming + materialized), embeddings, tool calling, and
4//! multi-turn conversations through multiple LLM providers (OpenAI, Ollama,
5//! Mock). Features include response caching with single-flight, cost
6//! observability (config-driven pricing tables), retry with per-attempt delay
7//! override (ADR-0021), and concurrency control via producer semaphore.
8//!
9//! Uses a project-owned `LlmProvider` trait with strict adapter boundary
10//! isolating the siumai SDK to exactly two production files (ADR-0020).
11
12pub mod bundle;
13pub mod config;
14pub mod cost;
15pub mod endpoint;
16pub mod error;
17pub mod headers;
18pub mod producer;
19pub mod producer_cache;
20pub mod provider;
21pub mod provider_factory;
22
23pub use bundle::LlmBundle;
24pub use config::{
25    LlmEndpointConfig, LlmGlobalConfig, LlmOperation, LlmProviderConfig, MockProviderConfig,
26    OllamaProviderConfig, OpenaiProviderConfig,
27};
28pub use cost::PricingTable;
29pub use error::LlmError;
30pub use provider::{
31    ChatEvent, ChatMessage, ChatRequest, ChatRole, EmbedRequest, EmbedResponse, EmittedToolCall,
32    FinishReason, LlmProvider, LlmUsage, ToolChoice, ToolDefinition,
33};
34
35use std::sync::Arc;
36
37use camel_component_api::{CamelError, Component, ComponentContext, Endpoint};
38use provider_factory::ProviderMap;
39
40pub struct LlmComponent {
41    providers: Arc<ProviderMap>,
42    config: Arc<LlmGlobalConfig>,
43}
44
45impl LlmComponent {
46    pub fn new(config: LlmGlobalConfig) -> Result<Self, CamelError> {
47        let providers = provider_factory::build_provider_map(&config).map_err(CamelError::from)?;
48        Ok(Self {
49            providers: Arc::new(providers),
50            config: Arc::new(config),
51        })
52    }
53
54    pub fn from_parts(config: LlmGlobalConfig, providers: ProviderMap) -> Self {
55        Self {
56            providers: Arc::new(providers),
57            config: Arc::new(config),
58        }
59    }
60
61    pub fn providers(&self) -> &ProviderMap {
62        &self.providers
63    }
64
65    pub fn config(&self) -> &LlmGlobalConfig {
66        &self.config
67    }
68}
69
70impl Component for LlmComponent {
71    fn scheme(&self) -> &str {
72        "llm"
73    }
74
75    fn create_endpoint(
76        &self,
77        uri: &str,
78        _ctx: &dyn ComponentContext,
79    ) -> Result<Box<dyn Endpoint>, CamelError> {
80        let endpoint_config = LlmEndpointConfig::from_uri(uri)?;
81
82        // Fail-fast: validate provider exists and supports the operation
83        let provider_name = endpoint::resolve_provider_name(&endpoint_config, &self.config)?;
84
85        let provider = self.providers.get(provider_name).ok_or_else(|| {
86            CamelError::InvalidUri(format!("provider '{}' not found in config", provider_name))
87        })?;
88
89        if endpoint_config.operation == LlmOperation::Embed && !provider.supports_embed() {
90            return Err(CamelError::InvalidUri(format!(
91                "provider '{}' does not support embed",
92                provider_name
93            )));
94        }
95
96        Ok(Box::new(endpoint::LlmEndpoint::new(
97            uri.to_string(),
98            endpoint_config,
99            Arc::clone(&self.providers),
100            Arc::clone(&self.config),
101        )))
102    }
103}