llmkit/
client.rs

1//! LLMKit client for unified LLM access.
2//!
3//! The `LLMKitClient` provides a unified interface to interact with multiple LLM providers.
4
5use std::collections::HashMap;
6use std::pin::Pin;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use futures::Stream;
11use tokio::time::sleep;
12
13use crate::audio::{
14    SpeechProvider, SpeechRequest, SpeechResponse, TranscriptionProvider, TranscriptionRequest,
15    TranscriptionResponse,
16};
17use crate::embedding::{EmbeddingProvider, EmbeddingRequest, EmbeddingResponse};
18use crate::error::{Error, Result};
19use crate::image::{ImageGenerationRequest, ImageGenerationResponse, ImageProvider};
20use crate::provider::{Provider, ProviderConfig};
21use crate::retry::RetryConfig;
22use crate::specialized::{
23    ClassificationProvider, ClassificationRequest, ClassificationResponse, ModerationProvider,
24    ModerationRequest, ModerationResponse, RankingProvider, RankingRequest, RankingResponse,
25};
26use crate::types::{
27    BatchJob, BatchRequest, BatchResult, CompletionRequest, CompletionResponse, StreamChunk,
28    TokenCountRequest, TokenCountResult,
29};
30use crate::video::{
31    VideoGenerationRequest, VideoGenerationResponse, VideoJobStatus, VideoProvider,
32};
33
34/// A dynamic retrying provider that wraps `Arc<dyn Provider>`.
35///
36/// This enables retry logic at the client level without requiring
37/// static typing of the underlying provider.
38struct DynamicRetryingProvider {
39    inner: Arc<dyn Provider>,
40    config: RetryConfig,
41}
42
43impl DynamicRetryingProvider {
44    /// Execute an operation with retry logic.
45    async fn execute_with_retry<T, F, Fut>(&self, operation_name: &str, mut f: F) -> Result<T>
46    where
47        F: FnMut() -> Fut,
48        Fut: std::future::Future<Output = Result<T>>,
49    {
50        let mut last_error: Option<Error> = None;
51
52        for attempt in 0..=self.config.max_retries {
53            match f().await {
54                Ok(result) => {
55                    if attempt > 0 {
56                        tracing::info!(
57                            provider = %self.inner.name(),
58                            operation = %operation_name,
59                            attempt = attempt + 1,
60                            "Operation succeeded after retry"
61                        );
62                    }
63                    return Ok(result);
64                }
65                Err(e) => {
66                    if !e.is_retryable() {
67                        tracing::debug!(
68                            provider = %self.inner.name(),
69                            operation = %operation_name,
70                            error = %e,
71                            "Non-retryable error, failing immediately"
72                        );
73                        return Err(e);
74                    }
75
76                    if attempt < self.config.max_retries {
77                        // Calculate delay, respecting retry-after header if present
78                        let delay = e
79                            .retry_after()
80                            .unwrap_or_else(|| self.config.delay_for_attempt(attempt));
81
82                        tracing::warn!(
83                            provider = %self.inner.name(),
84                            operation = %operation_name,
85                            attempt = attempt + 1,
86                            max_retries = self.config.max_retries,
87                            delay_ms = delay.as_millis(),
88                            error = %e,
89                            "Retryable error, will retry after delay"
90                        );
91
92                        sleep(delay).await;
93                    }
94
95                    last_error = Some(e);
96                }
97            }
98        }
99
100        tracing::error!(
101            provider = %self.inner.name(),
102            operation = %operation_name,
103            max_retries = self.config.max_retries,
104            "All retry attempts exhausted"
105        );
106
107        Err(last_error.unwrap_or_else(|| Error::other("Unknown retry failure")))
108    }
109}
110
111#[async_trait]
112impl Provider for DynamicRetryingProvider {
113    fn name(&self) -> &str {
114        self.inner.name()
115    }
116
117    async fn complete(&self, request: CompletionRequest) -> Result<CompletionResponse> {
118        let request = Arc::new(request);
119        self.execute_with_retry("complete", || {
120            let request = (*request).clone();
121            let inner = Arc::clone(&self.inner);
122            async move { inner.complete(request).await }
123        })
124        .await
125    }
126
127    async fn complete_stream(
128        &self,
129        request: CompletionRequest,
130    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>> {
131        let request = Arc::new(request);
132        self.execute_with_retry("complete_stream", || {
133            let request = (*request).clone();
134            let inner = Arc::clone(&self.inner);
135            async move { inner.complete_stream(request).await }
136        })
137        .await
138    }
139
140    fn supports_tools(&self) -> bool {
141        self.inner.supports_tools()
142    }
143
144    fn supports_vision(&self) -> bool {
145        self.inner.supports_vision()
146    }
147
148    fn supports_streaming(&self) -> bool {
149        self.inner.supports_streaming()
150    }
151
152    async fn count_tokens(&self, request: TokenCountRequest) -> Result<TokenCountResult> {
153        let request = Arc::new(request);
154        self.execute_with_retry("count_tokens", || {
155            let request = (*request).clone();
156            let inner = Arc::clone(&self.inner);
157            async move { inner.count_tokens(request).await }
158        })
159        .await
160    }
161
162    fn supports_token_counting(&self) -> bool {
163        self.inner.supports_token_counting()
164    }
165
166    async fn create_batch(&self, requests: Vec<BatchRequest>) -> Result<BatchJob> {
167        self.inner.create_batch(requests).await
168    }
169
170    async fn get_batch(&self, batch_id: &str) -> Result<BatchJob> {
171        self.inner.get_batch(batch_id).await
172    }
173
174    async fn get_batch_results(&self, batch_id: &str) -> Result<Vec<BatchResult>> {
175        self.inner.get_batch_results(batch_id).await
176    }
177
178    async fn cancel_batch(&self, batch_id: &str) -> Result<BatchJob> {
179        self.inner.cancel_batch(batch_id).await
180    }
181
182    async fn list_batches(&self, limit: Option<u32>) -> Result<Vec<BatchJob>> {
183        self.inner.list_batches(limit).await
184    }
185
186    fn supports_batch(&self) -> bool {
187        self.inner.supports_batch()
188    }
189}
190
191/// Parse a model identifier in the required "provider/model" format.
192///
193/// The format "provider/model" (e.g., "anthropic/claude-sonnet-4-20250514") is required.
194/// Returns Ok((provider, model_name)) if valid, or Err if the format is invalid.
195///
196/// # Examples
197///
198/// ```ignore
199/// let (provider, model) = parse_model_identifier("anthropic/claude-sonnet-4-20250514")?;
200/// assert_eq!(provider, "anthropic");
201/// assert_eq!(model, "claude-sonnet-4-20250514");
202///
203/// // This will return an error - provider is required
204/// let result = parse_model_identifier("gpt-4o");
205/// assert!(result.is_err());
206/// ```
207fn parse_model_identifier(model: &str) -> Result<(&str, &str)> {
208    if let Some(idx) = model.find('/') {
209        let provider = &model[..idx];
210        let model_name = &model[idx + 1..];
211        // Validate provider name (shouldn't contain special chars)
212        if !provider.is_empty()
213            && !provider.contains('-')
214            && !provider.contains('.')
215            && !provider.contains(':')
216        {
217            return Ok((provider, model_name));
218        }
219    }
220    Err(Error::InvalidRequest(format!(
221        "Model must be in 'provider/model' format (e.g., 'openai/gpt-4o'), got: {}",
222        model
223    )))
224}
225
226/// Main client for accessing LLM providers.
227///
228/// # Model Format
229///
230/// Models must be specified using the `"provider/model"` format (e.g., `"openai/gpt-4o"`).
231/// This ensures the client routes the request to the correct provider.
232///
233/// # Example
234///
235/// ```ignore
236/// use llmkit::LLMKitClient;
237///
238/// let client = LLMKitClient::builder()
239///     .with_anthropic_from_env()
240///     .with_openai_from_env()
241///     .with_baidu(api_key, secret_key)?
242///     .with_alibaba(api_key)?
243///     .build()?;
244///
245/// // Use explicit provider/model format for any provider
246/// let request = CompletionRequest::new("anthropic/claude-sonnet-4-20250514", messages);
247/// let response = client.complete(request).await?;
248///
249/// // Regional providers (Phase 2.3)
250/// let request = CompletionRequest::new("baidu/ERNIE-Bot-Ultra", messages);
251/// let response = client.complete(request).await?;
252///
253/// let request = CompletionRequest::new("alibaba/qwen-max", messages);
254/// let response = client.complete(request).await?;
255/// ```
256pub struct LLMKitClient {
257    providers: HashMap<String, Arc<dyn Provider>>,
258    embedding_providers: HashMap<String, Arc<dyn EmbeddingProvider>>,
259    speech_providers: HashMap<String, Arc<dyn SpeechProvider>>,
260    transcription_providers: HashMap<String, Arc<dyn TranscriptionProvider>>,
261    image_providers: HashMap<String, Arc<dyn ImageProvider>>,
262    video_providers: HashMap<String, Arc<dyn VideoProvider>>,
263    ranking_providers: HashMap<String, Arc<dyn RankingProvider>>,
264    moderation_providers: HashMap<String, Arc<dyn ModerationProvider>>,
265    classification_providers: HashMap<String, Arc<dyn ClassificationProvider>>,
266    default_provider: Option<String>,
267}
268
269impl LLMKitClient {
270    /// Create a new client builder.
271    pub fn builder() -> ClientBuilder {
272        ClientBuilder::new()
273    }
274
275    /// Get a provider by name.
276    pub fn provider(&self, name: &str) -> Option<Arc<dyn Provider>> {
277        self.providers.get(name).cloned()
278    }
279
280    /// Get the default provider.
281    pub fn default_provider(&self) -> Option<Arc<dyn Provider>> {
282        self.default_provider
283            .as_ref()
284            .and_then(|name| self.providers.get(name).cloned())
285    }
286
287    /// List all registered providers.
288    pub fn providers(&self) -> Vec<&str> {
289        self.providers.keys().map(|s| s.as_str()).collect()
290    }
291
292    /// Make a completion request.
293    ///
294    /// The provider is determined from:
295    /// 1. Explicit provider in model string (e.g., "anthropic/claude-sonnet-4-20250514")
296    /// 2. Model name prefix inference (e.g., "claude-" -> anthropic, "gpt-" -> openai)
297    /// 3. Default provider as fallback
298    pub async fn complete(&self, mut request: CompletionRequest) -> Result<CompletionResponse> {
299        let (provider, model_name) = self.resolve_provider(&request.model)?;
300        request.model = model_name;
301        provider.complete(request).await
302    }
303
304    /// Make a streaming completion request.
305    ///
306    /// The provider is determined using the same logic as `complete()`.
307    pub async fn complete_stream(
308        &self,
309        mut request: CompletionRequest,
310    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>> {
311        let (provider, model_name) = self.resolve_provider(&request.model)?;
312        request.model = model_name;
313        provider.complete_stream(request).await
314    }
315
316    /// Complete a request using a specific provider.
317    pub async fn complete_with_provider(
318        &self,
319        provider_name: &str,
320        request: CompletionRequest,
321    ) -> Result<CompletionResponse> {
322        let provider = self
323            .providers
324            .get(provider_name)
325            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
326        provider.complete(request).await
327    }
328
329    /// Stream a completion using a specific provider.
330    pub async fn complete_stream_with_provider(
331        &self,
332        provider_name: &str,
333        request: CompletionRequest,
334    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>> {
335        let provider = self
336            .providers
337            .get(provider_name)
338            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
339        provider.complete_stream(request).await
340    }
341
342    /// Count tokens in a request.
343    ///
344    /// This allows estimation of token counts before making a completion request,
345    /// useful for cost estimation and context window management.
346    ///
347    /// Note: Not all providers support token counting. Use `supports_token_counting`
348    /// on the provider to check support.
349    ///
350    /// The provider is determined using the same logic as `complete()`.
351    pub async fn count_tokens(&self, mut request: TokenCountRequest) -> Result<TokenCountResult> {
352        let (provider, model_name) = self.resolve_provider(&request.model)?;
353        request.model = model_name;
354        provider.count_tokens(request).await
355    }
356
357    /// Count tokens using a specific provider.
358    pub async fn count_tokens_with_provider(
359        &self,
360        provider_name: &str,
361        request: TokenCountRequest,
362    ) -> Result<TokenCountResult> {
363        let provider = self
364            .providers
365            .get(provider_name)
366            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
367        provider.count_tokens(request).await
368    }
369
370    // ========== Batch Processing ==========
371
372    /// Create a batch of requests for asynchronous processing.
373    ///
374    /// Batch processing can be significantly cheaper (up to 50% on some providers)
375    /// and is ideal for non-time-sensitive workloads.
376    ///
377    /// The provider is determined from the first request's model name using the
378    /// same logic as `complete()`.
379    pub async fn create_batch(&self, mut requests: Vec<BatchRequest>) -> Result<BatchJob> {
380        if requests.is_empty() {
381            return Err(Error::invalid_request(
382                "Batch must contain at least one request",
383            ));
384        }
385        let (provider, model_name) = self.resolve_provider(&requests[0].request.model)?;
386        // Update the model name in all requests to strip provider prefix
387        for req in &mut requests {
388            let (_, req_model) = parse_model_identifier(&req.request.model)?;
389            req.request.model = req_model.to_string();
390        }
391        // Also update the first request's model
392        requests[0].request.model = model_name;
393        provider.create_batch(requests).await
394    }
395
396    /// Create a batch using a specific provider.
397    pub async fn create_batch_with_provider(
398        &self,
399        provider_name: &str,
400        requests: Vec<BatchRequest>,
401    ) -> Result<BatchJob> {
402        let provider = self
403            .providers
404            .get(provider_name)
405            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
406        provider.create_batch(requests).await
407    }
408
409    /// Get the status of a batch job.
410    pub async fn get_batch(&self, provider_name: &str, batch_id: &str) -> Result<BatchJob> {
411        let provider = self
412            .providers
413            .get(provider_name)
414            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
415        provider.get_batch(batch_id).await
416    }
417
418    /// Get the results of a completed batch.
419    pub async fn get_batch_results(
420        &self,
421        provider_name: &str,
422        batch_id: &str,
423    ) -> Result<Vec<BatchResult>> {
424        let provider = self
425            .providers
426            .get(provider_name)
427            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
428        provider.get_batch_results(batch_id).await
429    }
430
431    /// Cancel a batch job.
432    pub async fn cancel_batch(&self, provider_name: &str, batch_id: &str) -> Result<BatchJob> {
433        let provider = self
434            .providers
435            .get(provider_name)
436            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
437        provider.cancel_batch(batch_id).await
438    }
439
440    /// List recent batch jobs for a provider.
441    pub async fn list_batches(
442        &self,
443        provider_name: &str,
444        limit: Option<u32>,
445    ) -> Result<Vec<BatchJob>> {
446        let provider = self
447            .providers
448            .get(provider_name)
449            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
450        provider.list_batches(limit).await
451    }
452
453    // ========== Embeddings ==========
454
455    /// Generate embeddings for text.
456    ///
457    /// The provider is determined from:
458    /// 1. Explicit provider in model string (e.g., "openai/text-embedding-3-small")
459    /// 2. Model name prefix inference (e.g., "text-embedding-" -> openai, "voyage-" -> voyage)
460    /// 3. First available embedding provider as fallback
461    ///
462    /// # Example
463    ///
464    /// ```ignore
465    /// use llmkit::{LLMKitClient, EmbeddingRequest};
466    ///
467    /// let client = LLMKitClient::builder()
468    ///     .with_openai_from_env()
469    ///     .build()?;
470    ///
471    /// // Explicit provider
472    /// let request = EmbeddingRequest::new("openai/text-embedding-3-small", "Hello, world!");
473    /// let response = client.embed(request).await?;
474    ///
475    /// // Or with inference (backward compatible)
476    /// let request = EmbeddingRequest::new("text-embedding-3-small", "Hello, world!");
477    /// let response = client.embed(request).await?;
478    /// println!("Embedding dimensions: {}", response.dimensions());
479    /// ```
480    pub async fn embed(&self, mut request: EmbeddingRequest) -> Result<EmbeddingResponse> {
481        let (provider, model_name) = self.resolve_embedding_provider(&request.model)?;
482        request.model = model_name;
483        provider.embed(request).await
484    }
485
486    /// Generate embeddings using a specific provider.
487    pub async fn embed_with_provider(
488        &self,
489        provider_name: &str,
490        request: EmbeddingRequest,
491    ) -> Result<EmbeddingResponse> {
492        let provider = self
493            .embedding_providers
494            .get(provider_name)
495            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
496        provider.embed(request).await
497    }
498
499    /// List all registered embedding providers.
500    pub fn embedding_providers(&self) -> Vec<&str> {
501        self.embedding_providers
502            .keys()
503            .map(|s| s.as_str())
504            .collect()
505    }
506
507    /// Check if a provider supports embeddings.
508    pub fn supports_embeddings(&self, provider_name: &str) -> bool {
509        self.embedding_providers.contains_key(provider_name)
510    }
511
512    // ========== Speech (Text-to-Speech) ==========
513
514    /// Generate speech from text.
515    ///
516    /// The provider is determined from the model string in "provider/model" format.
517    ///
518    /// # Example
519    ///
520    /// ```ignore
521    /// use llmkit::{LLMKitClient, SpeechRequest};
522    ///
523    /// let client = LLMKitClient::builder()
524    ///     .with_elevenlabs_from_env()
525    ///     .build()?;
526    ///
527    /// let request = SpeechRequest::new("elevenlabs/eleven_monolingual_v1", "Hello, world!", "voice_id");
528    /// let response = client.speech(request).await?;
529    /// response.save("output.mp3")?;
530    /// ```
531    pub async fn speech(&self, mut request: SpeechRequest) -> Result<SpeechResponse> {
532        let (provider, model_name) = self.resolve_speech_provider(&request.model)?;
533        request.model = model_name;
534        provider.speech(request).await
535    }
536
537    /// Generate speech using a specific provider.
538    pub async fn speech_with_provider(
539        &self,
540        provider_name: &str,
541        request: SpeechRequest,
542    ) -> Result<SpeechResponse> {
543        let provider = self
544            .speech_providers
545            .get(provider_name)
546            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
547        provider.speech(request).await
548    }
549
550    /// List all registered speech providers.
551    pub fn speech_providers(&self) -> Vec<&str> {
552        self.speech_providers.keys().map(|s| s.as_str()).collect()
553    }
554
555    // ========== Transcription (Speech-to-Text) ==========
556
557    /// Transcribe audio to text.
558    ///
559    /// The provider is determined from the model string in "provider/model" format.
560    ///
561    /// # Example
562    ///
563    /// ```ignore
564    /// use llmkit::{LLMKitClient, TranscriptionRequest, AudioInput};
565    ///
566    /// let client = LLMKitClient::builder()
567    ///     .with_deepgram_from_env()
568    ///     .build()?;
569    ///
570    /// let request = TranscriptionRequest::new(
571    ///     "deepgram/nova-2",
572    ///     AudioInput::file("audio.mp3"),
573    /// );
574    /// let response = client.transcribe(request).await?;
575    /// println!("Text: {}", response.text);
576    /// ```
577    pub async fn transcribe(
578        &self,
579        mut request: TranscriptionRequest,
580    ) -> Result<TranscriptionResponse> {
581        let (provider, model_name) = self.resolve_transcription_provider(&request.model)?;
582        request.model = model_name;
583        provider.transcribe(request).await
584    }
585
586    /// Transcribe audio using a specific provider.
587    pub async fn transcribe_with_provider(
588        &self,
589        provider_name: &str,
590        request: TranscriptionRequest,
591    ) -> Result<TranscriptionResponse> {
592        let provider = self
593            .transcription_providers
594            .get(provider_name)
595            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
596        provider.transcribe(request).await
597    }
598
599    /// List all registered transcription providers.
600    pub fn transcription_providers(&self) -> Vec<&str> {
601        self.transcription_providers
602            .keys()
603            .map(|s| s.as_str())
604            .collect()
605    }
606
607    // ========== Image Generation ==========
608
609    /// Generate images from a text prompt.
610    ///
611    /// The provider is determined from the model string in "provider/model" format.
612    ///
613    /// # Example
614    ///
615    /// ```ignore
616    /// use llmkit::{LLMKitClient, ImageGenerationRequest};
617    ///
618    /// let client = LLMKitClient::builder()
619    ///     .with_stability_from_env()
620    ///     .build()?;
621    ///
622    /// let request = ImageGenerationRequest::new(
623    ///     "stability/stable-diffusion-xl",
624    ///     "A serene mountain landscape at sunset",
625    /// );
626    /// let response = client.generate_image(request).await?;
627    /// println!("Generated {} images", response.images.len());
628    /// ```
629    pub async fn generate_image(
630        &self,
631        mut request: ImageGenerationRequest,
632    ) -> Result<ImageGenerationResponse> {
633        let (provider, model_name) = self.resolve_image_provider(&request.model)?;
634        request.model = model_name;
635        provider.generate_image(request).await
636    }
637
638    /// Generate images using a specific provider.
639    pub async fn generate_image_with_provider(
640        &self,
641        provider_name: &str,
642        request: ImageGenerationRequest,
643    ) -> Result<ImageGenerationResponse> {
644        let provider = self
645            .image_providers
646            .get(provider_name)
647            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
648        provider.generate_image(request).await
649    }
650
651    /// List all registered image providers.
652    pub fn image_providers(&self) -> Vec<&str> {
653        self.image_providers.keys().map(|s| s.as_str()).collect()
654    }
655
656    // ========== Video Generation ==========
657
658    /// Start a video generation job.
659    ///
660    /// Video generation is asynchronous - use `get_video_status` to poll for completion.
661    ///
662    /// # Example
663    ///
664    /// ```ignore
665    /// use llmkit::{LLMKitClient, VideoGenerationRequest};
666    ///
667    /// let client = LLMKitClient::builder()
668    ///     .with_runway_from_env()
669    ///     .build()?;
670    ///
671    /// let request = VideoGenerationRequest::new(
672    ///     "runway/gen-3",
673    ///     "A cat playing with a ball",
674    /// ).with_duration(6);
675    ///
676    /// let job = client.generate_video(request).await?;
677    /// println!("Job ID: {}", job.job_id);
678    /// ```
679    pub async fn generate_video(
680        &self,
681        mut request: VideoGenerationRequest,
682    ) -> Result<VideoGenerationResponse> {
683        let (provider, model_name) = self.resolve_video_provider(&request.model)?;
684        request.model = model_name;
685        provider.generate_video(request).await
686    }
687
688    /// Generate video using a specific provider.
689    pub async fn generate_video_with_provider(
690        &self,
691        provider_name: &str,
692        request: VideoGenerationRequest,
693    ) -> Result<VideoGenerationResponse> {
694        let provider = self
695            .video_providers
696            .get(provider_name)
697            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
698        provider.generate_video(request).await
699    }
700
701    /// Get the status of a video generation job.
702    pub async fn get_video_status(
703        &self,
704        provider_name: &str,
705        job_id: &str,
706    ) -> Result<VideoJobStatus> {
707        let provider = self
708            .video_providers
709            .get(provider_name)
710            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
711        provider.get_video_status(job_id).await
712    }
713
714    /// List all registered video providers.
715    pub fn video_providers(&self) -> Vec<&str> {
716        self.video_providers.keys().map(|s| s.as_str()).collect()
717    }
718
719    // ========== Ranking (Document Reranking) ==========
720
721    /// Rank documents by relevance to a query.
722    ///
723    /// # Example
724    ///
725    /// ```ignore
726    /// use llmkit::{LLMKitClient, RankingRequest};
727    ///
728    /// let client = LLMKitClient::builder()
729    ///     .with_cohere_from_env()
730    ///     .build()?;
731    ///
732    /// let request = RankingRequest::new(
733    ///     "cohere/rerank-english-v3.0",
734    ///     "What is the capital of France?",
735    ///     vec!["Paris is the capital", "Berlin is a city"],
736    /// );
737    /// let response = client.rank(request).await?;
738    /// println!("Top result: index {} with score {}", response.results[0].index, response.results[0].score);
739    /// ```
740    pub async fn rank(&self, mut request: RankingRequest) -> Result<RankingResponse> {
741        let (provider, model_name) = self.resolve_ranking_provider(&request.model)?;
742        request.model = model_name;
743        provider.rank(request).await
744    }
745
746    /// Rank documents using a specific provider.
747    pub async fn rank_with_provider(
748        &self,
749        provider_name: &str,
750        request: RankingRequest,
751    ) -> Result<RankingResponse> {
752        let provider = self
753            .ranking_providers
754            .get(provider_name)
755            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
756        provider.rank(request).await
757    }
758
759    /// List all registered ranking providers.
760    pub fn ranking_providers(&self) -> Vec<&str> {
761        self.ranking_providers.keys().map(|s| s.as_str()).collect()
762    }
763
764    // ========== Moderation ==========
765
766    /// Check content for policy violations.
767    ///
768    /// # Example
769    ///
770    /// ```ignore
771    /// use llmkit::{LLMKitClient, ModerationRequest};
772    ///
773    /// let client = LLMKitClient::builder()
774    ///     .with_openai_from_env()
775    ///     .build()?;
776    ///
777    /// let request = ModerationRequest::new(
778    ///     "openai/omni-moderation-latest",
779    ///     "Some user content to check",
780    /// );
781    /// let response = client.moderate(request).await?;
782    /// if response.flagged {
783    ///     println!("Content was flagged for: {:?}", response.flagged_categories());
784    /// }
785    /// ```
786    pub async fn moderate(&self, mut request: ModerationRequest) -> Result<ModerationResponse> {
787        let (provider, model_name) = self.resolve_moderation_provider(&request.model)?;
788        request.model = model_name;
789        provider.moderate(request).await
790    }
791
792    /// Moderate content using a specific provider.
793    pub async fn moderate_with_provider(
794        &self,
795        provider_name: &str,
796        request: ModerationRequest,
797    ) -> Result<ModerationResponse> {
798        let provider = self
799            .moderation_providers
800            .get(provider_name)
801            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
802        provider.moderate(request).await
803    }
804
805    /// List all registered moderation providers.
806    pub fn moderation_providers(&self) -> Vec<&str> {
807        self.moderation_providers
808            .keys()
809            .map(|s| s.as_str())
810            .collect()
811    }
812
813    // ========== Classification ==========
814
815    /// Classify text into one or more labels.
816    ///
817    /// # Example
818    ///
819    /// ```ignore
820    /// use llmkit::{LLMKitClient, ClassificationRequest};
821    ///
822    /// let client = LLMKitClient::builder()
823    ///     .with_cohere_from_env()
824    ///     .build()?;
825    ///
826    /// let request = ClassificationRequest::new(
827    ///     "cohere/embed-english-v3.0",
828    ///     "I love this product!",
829    ///     vec!["positive", "negative", "neutral"],
830    /// );
831    /// let response = client.classify(request).await?;
832    /// println!("Predicted: {} ({:.2}%)", response.label().unwrap(), response.top().unwrap().score * 100.0);
833    /// ```
834    pub async fn classify(
835        &self,
836        mut request: ClassificationRequest,
837    ) -> Result<ClassificationResponse> {
838        let (provider, model_name) = self.resolve_classification_provider(&request.model)?;
839        request.model = model_name;
840        provider.classify(request).await
841    }
842
843    /// Classify text using a specific provider.
844    pub async fn classify_with_provider(
845        &self,
846        provider_name: &str,
847        request: ClassificationRequest,
848    ) -> Result<ClassificationResponse> {
849        let provider = self
850            .classification_providers
851            .get(provider_name)
852            .ok_or_else(|| Error::ProviderNotFound(provider_name.to_string()))?;
853        provider.classify(request).await
854    }
855
856    /// List all registered classification providers.
857    pub fn classification_providers(&self) -> Vec<&str> {
858        self.classification_providers
859            .keys()
860            .map(|s| s.as_str())
861            .collect()
862    }
863
864    /// Resolve the embedding provider for a model in "provider/model" format.
865    ///
866    /// The model must be in "provider/model" format (e.g., "openai/text-embedding-3-small").
867    /// Returns the provider and the model name (without provider prefix).
868    fn resolve_embedding_provider(
869        &self,
870        model: &str,
871    ) -> Result<(Arc<dyn EmbeddingProvider>, String)> {
872        let (provider_name, model_name) = parse_model_identifier(model)?;
873
874        self.embedding_providers
875            .get(provider_name)
876            .cloned()
877            .map(|p| (p, model_name.to_string()))
878            .ok_or_else(|| {
879                Error::ProviderNotFound(format!(
880                    "Embedding provider '{}' not configured. Available providers: {:?}",
881                    provider_name,
882                    self.embedding_providers.keys().collect::<Vec<_>>()
883                ))
884            })
885    }
886
887    /// Resolve the provider for a model in "provider/model" format.
888    ///
889    /// The model must be in "provider/model" format (e.g., "anthropic/claude-sonnet-4-20250514").
890    /// Returns the provider and the model name (without provider prefix).
891    fn resolve_provider(&self, model: &str) -> Result<(Arc<dyn Provider>, String)> {
892        let (provider_name, model_name) = parse_model_identifier(model)?;
893
894        self.providers
895            .get(provider_name)
896            .cloned()
897            .map(|p| (p, model_name.to_string()))
898            .ok_or_else(|| {
899                Error::ProviderNotFound(format!(
900                    "Provider '{}' not configured. Available providers: {:?}",
901                    provider_name,
902                    self.providers.keys().collect::<Vec<_>>()
903                ))
904            })
905    }
906
907    /// Resolve a speech provider for a model in "provider/model" format.
908    fn resolve_speech_provider(&self, model: &str) -> Result<(Arc<dyn SpeechProvider>, String)> {
909        let (provider_name, model_name) = parse_model_identifier(model)?;
910
911        self.speech_providers
912            .get(provider_name)
913            .cloned()
914            .map(|p| (p, model_name.to_string()))
915            .ok_or_else(|| {
916                Error::ProviderNotFound(format!(
917                    "Speech provider '{}' not configured. Available providers: {:?}",
918                    provider_name,
919                    self.speech_providers.keys().collect::<Vec<_>>()
920                ))
921            })
922    }
923
924    /// Resolve a transcription provider for a model in "provider/model" format.
925    fn resolve_transcription_provider(
926        &self,
927        model: &str,
928    ) -> Result<(Arc<dyn TranscriptionProvider>, String)> {
929        let (provider_name, model_name) = parse_model_identifier(model)?;
930
931        self.transcription_providers
932            .get(provider_name)
933            .cloned()
934            .map(|p| (p, model_name.to_string()))
935            .ok_or_else(|| {
936                Error::ProviderNotFound(format!(
937                    "Transcription provider '{}' not configured. Available providers: {:?}",
938                    provider_name,
939                    self.transcription_providers.keys().collect::<Vec<_>>()
940                ))
941            })
942    }
943
944    /// Resolve an image provider for a model in "provider/model" format.
945    fn resolve_image_provider(&self, model: &str) -> Result<(Arc<dyn ImageProvider>, String)> {
946        let (provider_name, model_name) = parse_model_identifier(model)?;
947
948        self.image_providers
949            .get(provider_name)
950            .cloned()
951            .map(|p| (p, model_name.to_string()))
952            .ok_or_else(|| {
953                Error::ProviderNotFound(format!(
954                    "Image provider '{}' not configured. Available providers: {:?}",
955                    provider_name,
956                    self.image_providers.keys().collect::<Vec<_>>()
957                ))
958            })
959    }
960
961    /// Resolve a video provider for a model in "provider/model" format.
962    fn resolve_video_provider(&self, model: &str) -> Result<(Arc<dyn VideoProvider>, String)> {
963        let (provider_name, model_name) = parse_model_identifier(model)?;
964
965        self.video_providers
966            .get(provider_name)
967            .cloned()
968            .map(|p| (p, model_name.to_string()))
969            .ok_or_else(|| {
970                Error::ProviderNotFound(format!(
971                    "Video provider '{}' not configured. Available providers: {:?}",
972                    provider_name,
973                    self.video_providers.keys().collect::<Vec<_>>()
974                ))
975            })
976    }
977
978    /// Resolve a ranking provider for a model in "provider/model" format.
979    fn resolve_ranking_provider(&self, model: &str) -> Result<(Arc<dyn RankingProvider>, String)> {
980        let (provider_name, model_name) = parse_model_identifier(model)?;
981
982        self.ranking_providers
983            .get(provider_name)
984            .cloned()
985            .map(|p| (p, model_name.to_string()))
986            .ok_or_else(|| {
987                Error::ProviderNotFound(format!(
988                    "Ranking provider '{}' not configured. Available providers: {:?}",
989                    provider_name,
990                    self.ranking_providers.keys().collect::<Vec<_>>()
991                ))
992            })
993    }
994
995    /// Resolve a moderation provider for a model in "provider/model" format.
996    fn resolve_moderation_provider(
997        &self,
998        model: &str,
999    ) -> Result<(Arc<dyn ModerationProvider>, String)> {
1000        let (provider_name, model_name) = parse_model_identifier(model)?;
1001
1002        self.moderation_providers
1003            .get(provider_name)
1004            .cloned()
1005            .map(|p| (p, model_name.to_string()))
1006            .ok_or_else(|| {
1007                Error::ProviderNotFound(format!(
1008                    "Moderation provider '{}' not configured. Available providers: {:?}",
1009                    provider_name,
1010                    self.moderation_providers.keys().collect::<Vec<_>>()
1011                ))
1012            })
1013    }
1014
1015    /// Resolve a classification provider for a model in "provider/model" format.
1016    fn resolve_classification_provider(
1017        &self,
1018        model: &str,
1019    ) -> Result<(Arc<dyn ClassificationProvider>, String)> {
1020        let (provider_name, model_name) = parse_model_identifier(model)?;
1021
1022        self.classification_providers
1023            .get(provider_name)
1024            .cloned()
1025            .map(|p| (p, model_name.to_string()))
1026            .ok_or_else(|| {
1027                Error::ProviderNotFound(format!(
1028                    "Classification provider '{}' not configured. Available providers: {:?}",
1029                    provider_name,
1030                    self.classification_providers.keys().collect::<Vec<_>>()
1031                ))
1032            })
1033    }
1034}
1035
1036// ============================================================================
1037// Pending Provider Configurations (for deferred async initialization)
1038// ============================================================================
1039
1040/// Pending Vertex AI provider configuration.
1041/// Stored during builder chain, initialized in build().
1042#[cfg(feature = "vertex")]
1043#[derive(Clone)]
1044enum PendingVertexConfig {
1045    /// Initialize from environment using ADC
1046    FromEnv,
1047    /// Initialize from service account file
1048    ServiceAccount {
1049        path: std::path::PathBuf,
1050        project_id: String,
1051        location: String,
1052    },
1053    /// Initialize with specific publisher (partner models)
1054    WithPublisher { publisher: String },
1055}
1056
1057/// Pending Bedrock provider configuration.
1058/// Stored during builder chain, initialized in build().
1059#[cfg(feature = "bedrock")]
1060#[derive(Clone)]
1061enum PendingBedrockConfig {
1062    /// Initialize from environment with auto-detected region
1063    FromEnv,
1064    /// Initialize with specific region
1065    WithRegion { region: String },
1066}
1067
1068/// Builder for creating a `LLMKitClient`.
1069pub struct ClientBuilder {
1070    providers: HashMap<String, Arc<dyn Provider>>,
1071    embedding_providers: HashMap<String, Arc<dyn EmbeddingProvider>>,
1072    speech_providers: HashMap<String, Arc<dyn SpeechProvider>>,
1073    transcription_providers: HashMap<String, Arc<dyn TranscriptionProvider>>,
1074    image_providers: HashMap<String, Arc<dyn ImageProvider>>,
1075    video_providers: HashMap<String, Arc<dyn VideoProvider>>,
1076    ranking_providers: HashMap<String, Arc<dyn RankingProvider>>,
1077    moderation_providers: HashMap<String, Arc<dyn ModerationProvider>>,
1078    classification_providers: HashMap<String, Arc<dyn ClassificationProvider>>,
1079    default_provider: Option<String>,
1080    retry_config: Option<RetryConfig>,
1081
1082    // Pending async providers (initialized in build())
1083    #[cfg(feature = "vertex")]
1084    pending_vertex: Vec<(String, PendingVertexConfig)>,
1085    #[cfg(feature = "bedrock")]
1086    pending_bedrock: Vec<(String, PendingBedrockConfig)>,
1087}
1088
1089impl ClientBuilder {
1090    /// Create a new client builder.
1091    pub fn new() -> Self {
1092        Self {
1093            providers: HashMap::new(),
1094            embedding_providers: HashMap::new(),
1095            speech_providers: HashMap::new(),
1096            transcription_providers: HashMap::new(),
1097            image_providers: HashMap::new(),
1098            video_providers: HashMap::new(),
1099            ranking_providers: HashMap::new(),
1100            moderation_providers: HashMap::new(),
1101            classification_providers: HashMap::new(),
1102            default_provider: None,
1103            retry_config: None,
1104            #[cfg(feature = "vertex")]
1105            pending_vertex: Vec::new(),
1106            #[cfg(feature = "bedrock")]
1107            pending_bedrock: Vec::new(),
1108        }
1109    }
1110
1111    /// Add an embedding provider.
1112    pub fn with_embedding_provider(
1113        mut self,
1114        name: impl Into<String>,
1115        provider: Arc<dyn EmbeddingProvider>,
1116    ) -> Self {
1117        self.embedding_providers.insert(name.into(), provider);
1118        self
1119    }
1120
1121    /// Add a speech (TTS) provider.
1122    pub fn with_speech_provider(
1123        mut self,
1124        name: impl Into<String>,
1125        provider: Arc<dyn SpeechProvider>,
1126    ) -> Self {
1127        self.speech_providers.insert(name.into(), provider);
1128        self
1129    }
1130
1131    /// Add a transcription (STT) provider.
1132    pub fn with_transcription_provider(
1133        mut self,
1134        name: impl Into<String>,
1135        provider: Arc<dyn TranscriptionProvider>,
1136    ) -> Self {
1137        self.transcription_providers.insert(name.into(), provider);
1138        self
1139    }
1140
1141    /// Add an image generation provider.
1142    pub fn with_image_provider(
1143        mut self,
1144        name: impl Into<String>,
1145        provider: Arc<dyn ImageProvider>,
1146    ) -> Self {
1147        self.image_providers.insert(name.into(), provider);
1148        self
1149    }
1150
1151    /// Add a video generation provider.
1152    pub fn with_video_provider(
1153        mut self,
1154        name: impl Into<String>,
1155        provider: Arc<dyn VideoProvider>,
1156    ) -> Self {
1157        self.video_providers.insert(name.into(), provider);
1158        self
1159    }
1160
1161    /// Add a ranking/reranking provider.
1162    pub fn with_ranking_provider(
1163        mut self,
1164        name: impl Into<String>,
1165        provider: Arc<dyn RankingProvider>,
1166    ) -> Self {
1167        self.ranking_providers.insert(name.into(), provider);
1168        self
1169    }
1170
1171    /// Add a moderation provider.
1172    pub fn with_moderation_provider(
1173        mut self,
1174        name: impl Into<String>,
1175        provider: Arc<dyn ModerationProvider>,
1176    ) -> Self {
1177        self.moderation_providers.insert(name.into(), provider);
1178        self
1179    }
1180
1181    /// Add a classification provider.
1182    pub fn with_classification_provider(
1183        mut self,
1184        name: impl Into<String>,
1185        provider: Arc<dyn ClassificationProvider>,
1186    ) -> Self {
1187        self.classification_providers.insert(name.into(), provider);
1188        self
1189    }
1190
1191    /// Enable automatic retry with the specified configuration.
1192    ///
1193    /// All providers will be wrapped with retry logic using exponential backoff.
1194    ///
1195    /// # Example
1196    ///
1197    /// ```ignore
1198    /// let client = LLMKitClient::builder()
1199    ///     .with_anthropic_from_env()
1200    ///     .with_retry(RetryConfig::production())
1201    ///     .build()?;
1202    /// ```
1203    pub fn with_retry(mut self, config: RetryConfig) -> Self {
1204        self.retry_config = Some(config);
1205        self
1206    }
1207
1208    /// Enable automatic retry with default production configuration.
1209    ///
1210    /// Uses `RetryConfig::default()` which provides:
1211    /// - 10 retry attempts
1212    /// - Exponential backoff from 1s up to 5 minutes
1213    /// - Jitter enabled for better distributed retry timing
1214    pub fn with_default_retry(mut self) -> Self {
1215        self.retry_config = Some(RetryConfig::default());
1216        self
1217    }
1218
1219    /// Add a custom provider.
1220    pub fn with_provider(mut self, name: impl Into<String>, provider: Arc<dyn Provider>) -> Self {
1221        let name = name.into();
1222        if self.default_provider.is_none() {
1223            self.default_provider = Some(name.clone());
1224        }
1225        self.providers.insert(name, provider);
1226        self
1227    }
1228
1229    /// Set the default provider by name.
1230    pub fn with_default(mut self, name: impl Into<String>) -> Self {
1231        self.default_provider = Some(name.into());
1232        self
1233    }
1234
1235    /// Add Anthropic provider from environment.
1236    #[cfg(feature = "anthropic")]
1237    pub fn with_anthropic_from_env(self) -> Self {
1238        match crate::providers::chat::anthropic::AnthropicProvider::from_env() {
1239            Ok(provider) => self.with_provider("anthropic", Arc::new(provider)),
1240            Err(_) => self, // Skip if no API key
1241        }
1242    }
1243
1244    /// Add Anthropic provider with API key.
1245    #[cfg(feature = "anthropic")]
1246    pub fn with_anthropic(self, api_key: impl Into<String>) -> Result<Self> {
1247        let provider = crate::providers::chat::anthropic::AnthropicProvider::with_api_key(api_key)?;
1248        Ok(self.with_provider("anthropic", Arc::new(provider)))
1249    }
1250
1251    /// Add Anthropic provider with custom config.
1252    #[cfg(feature = "anthropic")]
1253    pub fn with_anthropic_config(self, config: ProviderConfig) -> Result<Self> {
1254        let provider = crate::providers::chat::anthropic::AnthropicProvider::new(config)?;
1255        Ok(self.with_provider("anthropic", Arc::new(provider)))
1256    }
1257
1258    /// Add OpenAI provider from environment.
1259    ///
1260    /// Also registers OpenAI as an embedding provider for text-embedding-* models.
1261    #[cfg(feature = "openai")]
1262    pub fn with_openai_from_env(mut self) -> Self {
1263        match crate::providers::chat::openai::OpenAIProvider::from_env() {
1264            Ok(provider) => {
1265                let provider = Arc::new(provider);
1266                self.embedding_providers.insert(
1267                    "openai".to_string(),
1268                    Arc::clone(&provider) as Arc<dyn EmbeddingProvider>,
1269                );
1270                self.with_provider("openai", provider)
1271            }
1272            Err(_) => self, // Skip if no API key
1273        }
1274    }
1275
1276    /// Add OpenAI provider with API key.
1277    ///
1278    /// Also registers OpenAI as an embedding provider for text-embedding-* models.
1279    #[cfg(feature = "openai")]
1280    pub fn with_openai(mut self, api_key: impl Into<String>) -> Result<Self> {
1281        let provider =
1282            Arc::new(crate::providers::chat::openai::OpenAIProvider::with_api_key(api_key)?);
1283        self.embedding_providers.insert(
1284            "openai".to_string(),
1285            Arc::clone(&provider) as Arc<dyn EmbeddingProvider>,
1286        );
1287        Ok(self.with_provider("openai", provider))
1288    }
1289
1290    /// Add OpenAI provider with custom config.
1291    ///
1292    /// Also registers OpenAI as an embedding provider for text-embedding-* models.
1293    #[cfg(feature = "openai")]
1294    pub fn with_openai_config(mut self, config: ProviderConfig) -> Result<Self> {
1295        let provider = Arc::new(crate::providers::chat::openai::OpenAIProvider::new(config)?);
1296        self.embedding_providers.insert(
1297            "openai".to_string(),
1298            Arc::clone(&provider) as Arc<dyn EmbeddingProvider>,
1299        );
1300        Ok(self.with_provider("openai", provider))
1301    }
1302
1303    /// Add Groq provider from environment.
1304    #[cfg(feature = "groq")]
1305    pub fn with_groq_from_env(self) -> Self {
1306        match crate::providers::chat::groq::GroqProvider::from_env() {
1307            Ok(provider) => self.with_provider("groq", Arc::new(provider)),
1308            Err(_) => self, // Skip if no API key
1309        }
1310    }
1311
1312    /// Add Groq provider with API key.
1313    #[cfg(feature = "groq")]
1314    pub fn with_groq(self, api_key: impl Into<String>) -> Result<Self> {
1315        let provider = crate::providers::chat::groq::GroqProvider::with_api_key(api_key)?;
1316        Ok(self.with_provider("groq", Arc::new(provider)))
1317    }
1318
1319    /// Add Groq provider with custom config.
1320    #[cfg(feature = "groq")]
1321    pub fn with_groq_config(self, config: ProviderConfig) -> Result<Self> {
1322        let provider = crate::providers::chat::groq::GroqProvider::new(config)?;
1323        Ok(self.with_provider("groq", Arc::new(provider)))
1324    }
1325
1326    /// Add Mistral provider from environment.
1327    #[cfg(feature = "mistral")]
1328    pub fn with_mistral_from_env(self) -> Self {
1329        match crate::providers::chat::mistral::MistralProvider::from_env() {
1330            Ok(provider) => self.with_provider("mistral", Arc::new(provider)),
1331            Err(_) => self, // Skip if no API key
1332        }
1333    }
1334
1335    /// Add Mistral provider with API key.
1336    #[cfg(feature = "mistral")]
1337    pub fn with_mistral(self, api_key: impl Into<String>) -> Result<Self> {
1338        let provider = crate::providers::chat::mistral::MistralProvider::with_api_key(api_key)?;
1339        Ok(self.with_provider("mistral", Arc::new(provider)))
1340    }
1341
1342    /// Add Mistral provider from environment.
1343    #[cfg(feature = "mistral")]
1344    pub fn with_mistral_config(self, _config: ProviderConfig) -> Result<Self> {
1345        let provider = crate::providers::chat::mistral::MistralProvider::from_env()?;
1346        Ok(self.with_provider("mistral", Arc::new(provider)))
1347    }
1348
1349    /// Add Azure OpenAI provider from environment.
1350    ///
1351    /// Reads:
1352    /// - `AZURE_OPENAI_RESOURCE_NAME` or `AZURE_OPENAI_ENDPOINT`
1353    /// - `AZURE_OPENAI_DEPLOYMENT_ID` or `AZURE_OPENAI_DEPLOYMENT`
1354    /// - `AZURE_OPENAI_API_KEY`
1355    /// - `AZURE_OPENAI_API_VERSION` (optional)
1356    #[cfg(feature = "azure")]
1357    pub fn with_azure_from_env(self) -> Self {
1358        match crate::providers::chat::azure::AzureOpenAIProvider::from_env() {
1359            Ok(provider) => self.with_provider("azure", Arc::new(provider)),
1360            Err(_) => self, // Skip if no configuration
1361        }
1362    }
1363
1364    /// Add Azure OpenAI provider with configuration.
1365    #[cfg(feature = "azure")]
1366    pub fn with_azure(self, config: crate::providers::chat::azure::AzureConfig) -> Result<Self> {
1367        let provider = crate::providers::chat::azure::AzureOpenAIProvider::new(config)?;
1368        Ok(self.with_provider("azure", Arc::new(provider)))
1369    }
1370
1371    /// Add AWS Bedrock provider from environment.
1372    ///
1373    /// Uses default AWS credential chain and reads region from:
1374    /// - `AWS_REGION` or `AWS_DEFAULT_REGION` environment variable
1375    /// - Falls back to "us-east-1" if not set
1376    ///
1377    /// The provider is initialized asynchronously during `build()`.
1378    #[cfg(feature = "bedrock")]
1379    pub fn with_bedrock_from_env(mut self) -> Self {
1380        self.pending_bedrock
1381            .push(("bedrock".to_string(), PendingBedrockConfig::FromEnv));
1382        self
1383    }
1384
1385    /// Add AWS Bedrock provider with specified region.
1386    ///
1387    /// The provider is initialized asynchronously during `build()`.
1388    #[cfg(feature = "bedrock")]
1389    pub fn with_bedrock_region(mut self, region: impl Into<String>) -> Self {
1390        self.pending_bedrock.push((
1391            "bedrock".to_string(),
1392            PendingBedrockConfig::WithRegion {
1393                region: region.into(),
1394            },
1395        ));
1396        self
1397    }
1398
1399    /// Add AWS Bedrock provider with builder (async).
1400    ///
1401    /// Note: This method remains async because it accepts a custom builder.
1402    /// For simple cases, use `with_bedrock_from_env()` or `with_bedrock_region()`.
1403    #[cfg(feature = "bedrock")]
1404    pub async fn with_bedrock(
1405        self,
1406        builder: crate::providers::chat::bedrock::BedrockBuilder,
1407    ) -> Result<Self> {
1408        let provider = builder.build().await?;
1409        Ok(self.with_provider("bedrock", Arc::new(provider)))
1410    }
1411
1412    // ========== OpenAI-Compatible Providers ==========
1413
1414    /// Add Together AI provider from environment.
1415    #[cfg(feature = "openai-compatible")]
1416    pub fn with_together_from_env(self) -> Self {
1417        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::together_from_env(
1418        ) {
1419            Ok(provider) => self.with_provider("together", Arc::new(provider)),
1420            Err(_) => self,
1421        }
1422    }
1423
1424    /// Add Together AI provider with API key.
1425    #[cfg(feature = "openai-compatible")]
1426    pub fn with_together(self, api_key: impl Into<String>) -> Result<Self> {
1427        let provider =
1428            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::together(api_key)?;
1429        Ok(self.with_provider("together", Arc::new(provider)))
1430    }
1431
1432    /// Add Fireworks AI provider from environment.
1433    #[cfg(all(feature = "openai-compatible", not(feature = "fireworks")))]
1434    pub fn with_fireworks_from_env(self) -> Self {
1435        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::fireworks_from_env() {
1436            Ok(provider) => self.with_provider("fireworks", Arc::new(provider)),
1437            Err(_) => self,
1438        }
1439    }
1440
1441    /// Add Fireworks AI provider with API key.
1442    #[cfg(all(feature = "openai-compatible", not(feature = "fireworks")))]
1443    pub fn with_fireworks(self, api_key: impl Into<String>) -> Result<Self> {
1444        let provider =
1445            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::fireworks(
1446                api_key,
1447            )?;
1448        Ok(self.with_provider("fireworks", Arc::new(provider)))
1449    }
1450
1451    /// Add Fireworks AI provider from environment (dedicated provider).
1452    #[cfg(feature = "fireworks")]
1453    pub fn with_fireworks_from_env(self) -> Self {
1454        match crate::providers::chat::fireworks::FireworksProvider::from_env() {
1455            Ok(provider) => self.with_provider("fireworks", Arc::new(provider)),
1456            Err(_) => self,
1457        }
1458    }
1459
1460    /// Add Fireworks AI provider with API key (dedicated provider).
1461    #[cfg(feature = "fireworks")]
1462    pub fn with_fireworks(self, api_key: impl Into<String>) -> Result<Self> {
1463        let provider = crate::providers::chat::fireworks::FireworksProvider::with_api_key(api_key)?;
1464        Ok(self.with_provider("fireworks", Arc::new(provider)))
1465    }
1466
1467    /// Add DeepSeek provider from environment.
1468    #[cfg(all(feature = "openai-compatible", not(feature = "deepseek")))]
1469    pub fn with_deepseek_from_env(self) -> Self {
1470        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::deepseek_from_env(
1471        ) {
1472            Ok(provider) => self.with_provider("deepseek", Arc::new(provider)),
1473            Err(_) => self,
1474        }
1475    }
1476
1477    /// Add DeepSeek provider with API key.
1478    #[cfg(all(feature = "openai-compatible", not(feature = "deepseek")))]
1479    pub fn with_deepseek(self, api_key: impl Into<String>) -> Result<Self> {
1480        let provider =
1481            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::deepseek(api_key)?;
1482        Ok(self.with_provider("deepseek", Arc::new(provider)))
1483    }
1484
1485    /// Add DeepSeek provider from environment (dedicated provider).
1486    #[cfg(feature = "deepseek")]
1487    pub fn with_deepseek_from_env(self) -> Self {
1488        match crate::providers::chat::deepseek::DeepSeekProvider::from_env() {
1489            Ok(provider) => self.with_provider("deepseek", Arc::new(provider)),
1490            Err(_) => self,
1491        }
1492    }
1493
1494    /// Add DeepSeek provider with API key (dedicated provider).
1495    #[cfg(feature = "deepseek")]
1496    pub fn with_deepseek(self, api_key: impl Into<String>) -> Result<Self> {
1497        let provider = crate::providers::chat::deepseek::DeepSeekProvider::with_api_key(api_key)?;
1498        Ok(self.with_provider("deepseek", Arc::new(provider)))
1499    }
1500
1501    /// Add Perplexity provider from environment.
1502    #[cfg(feature = "openai-compatible")]
1503    pub fn with_perplexity_from_env(self) -> Self {
1504        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::perplexity_from_env() {
1505            Ok(provider) => self.with_provider("perplexity", Arc::new(provider)),
1506            Err(_) => self,
1507        }
1508    }
1509
1510    /// Add Perplexity provider with API key.
1511    #[cfg(feature = "openai-compatible")]
1512    pub fn with_perplexity(self, api_key: impl Into<String>) -> Result<Self> {
1513        let provider =
1514            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::perplexity(
1515                api_key,
1516            )?;
1517        Ok(self.with_provider("perplexity", Arc::new(provider)))
1518    }
1519
1520    /// Add Anyscale provider from environment.
1521    #[cfg(feature = "openai-compatible")]
1522    pub fn with_anyscale_from_env(self) -> Self {
1523        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::anyscale_from_env(
1524        ) {
1525            Ok(provider) => self.with_provider("anyscale", Arc::new(provider)),
1526            Err(_) => self,
1527        }
1528    }
1529
1530    /// Add Anyscale provider with API key.
1531    #[cfg(feature = "openai-compatible")]
1532    pub fn with_anyscale(self, api_key: impl Into<String>) -> Result<Self> {
1533        let provider =
1534            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::anyscale(api_key)?;
1535        Ok(self.with_provider("anyscale", Arc::new(provider)))
1536    }
1537
1538    /// Add DeepInfra provider from environment.
1539    #[cfg(feature = "openai-compatible")]
1540    pub fn with_deepinfra_from_env(self) -> Self {
1541        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::deepinfra_from_env() {
1542            Ok(provider) => self.with_provider("deepinfra", Arc::new(provider)),
1543            Err(_) => self,
1544        }
1545    }
1546
1547    /// Add DeepInfra provider with API key.
1548    #[cfg(feature = "openai-compatible")]
1549    pub fn with_deepinfra(self, api_key: impl Into<String>) -> Result<Self> {
1550        let provider =
1551            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::deepinfra(
1552                api_key,
1553            )?;
1554        Ok(self.with_provider("deepinfra", Arc::new(provider)))
1555    }
1556
1557    /// Add Novita AI provider from environment.
1558    #[cfg(feature = "openai-compatible")]
1559    pub fn with_novita_from_env(self) -> Self {
1560        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::novita_from_env()
1561        {
1562            Ok(provider) => self.with_provider("novita", Arc::new(provider)),
1563            Err(_) => self,
1564        }
1565    }
1566
1567    /// Add Novita AI provider with API key.
1568    #[cfg(feature = "openai-compatible")]
1569    pub fn with_novita(self, api_key: impl Into<String>) -> Result<Self> {
1570        let provider =
1571            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::novita(api_key)?;
1572        Ok(self.with_provider("novita", Arc::new(provider)))
1573    }
1574
1575    /// Add Hyperbolic provider from environment.
1576    #[cfg(feature = "openai-compatible")]
1577    pub fn with_hyperbolic_from_env(self) -> Self {
1578        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::hyperbolic_from_env() {
1579            Ok(provider) => self.with_provider("hyperbolic", Arc::new(provider)),
1580            Err(_) => self,
1581        }
1582    }
1583
1584    /// Add Hyperbolic provider with API key.
1585    #[cfg(feature = "openai-compatible")]
1586    pub fn with_hyperbolic(self, api_key: impl Into<String>) -> Result<Self> {
1587        let provider =
1588            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::hyperbolic(
1589                api_key,
1590            )?;
1591        Ok(self.with_provider("hyperbolic", Arc::new(provider)))
1592    }
1593
1594    /// Add Cerebras provider from environment (via OpenAI-compatible).
1595    #[cfg(all(feature = "openai-compatible", not(feature = "cerebras")))]
1596    pub fn with_cerebras_from_env(self) -> Self {
1597        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::cerebras_from_env(
1598        ) {
1599            Ok(provider) => self.with_provider("cerebras", Arc::new(provider)),
1600            Err(_) => self,
1601        }
1602    }
1603
1604    /// Add Cerebras provider with API key (via OpenAI-compatible).
1605    #[cfg(all(feature = "openai-compatible", not(feature = "cerebras")))]
1606    pub fn with_cerebras(self, api_key: impl Into<String>) -> Result<Self> {
1607        let provider =
1608            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::cerebras(api_key)?;
1609        Ok(self.with_provider("cerebras", Arc::new(provider)))
1610    }
1611
1612    /// Add Cerebras provider from environment (dedicated provider).
1613    #[cfg(feature = "cerebras")]
1614    pub fn with_cerebras_from_env(self) -> Self {
1615        match crate::providers::chat::cerebras::CerebrasProvider::from_env() {
1616            Ok(provider) => self.with_provider("cerebras", Arc::new(provider)),
1617            Err(_) => self,
1618        }
1619    }
1620
1621    /// Add Cerebras provider with API key (dedicated provider).
1622    #[cfg(feature = "cerebras")]
1623    pub fn with_cerebras(self, api_key: impl Into<String>) -> Result<Self> {
1624        let provider = crate::providers::chat::cerebras::CerebrasProvider::with_api_key(api_key)?;
1625        Ok(self.with_provider("cerebras", Arc::new(provider)))
1626    }
1627
1628    // ========== Phase 2: Additional Tier 1 Providers ==========
1629
1630    /// Add Reka AI provider from environment.
1631    #[cfg(feature = "reka")]
1632    pub fn with_reka_from_env(self) -> Self {
1633        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::reka_from_env() {
1634            Ok(provider) => self.with_provider("reka", Arc::new(provider)),
1635            Err(_) => self,
1636        }
1637    }
1638
1639    /// Add Reka AI provider with API key.
1640    #[cfg(feature = "reka")]
1641    pub fn with_reka(self, api_key: impl Into<String>) -> Result<Self> {
1642        let provider =
1643            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::reka(api_key)?;
1644        Ok(self.with_provider("reka", Arc::new(provider)))
1645    }
1646
1647    /// Add Reka AI provider with custom config.
1648    #[cfg(feature = "reka")]
1649    pub fn with_reka_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1650        let provider =
1651            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::reka_config(
1652                config,
1653            )?;
1654        Ok(self.with_provider("reka", Arc::new(provider)))
1655    }
1656
1657    /// Add Nvidia NIM provider from environment.
1658    #[cfg(feature = "nvidia-nim")]
1659    pub fn with_nvidia_nim_from_env(self) -> Self {
1660        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::nvidia_nim_from_env() {
1661            Ok(provider) => self.with_provider("nvidia_nim", Arc::new(provider)),
1662            Err(_) => self,
1663        }
1664    }
1665
1666    /// Add Nvidia NIM provider with API key.
1667    #[cfg(feature = "nvidia-nim")]
1668    pub fn with_nvidia_nim(self, api_key: impl Into<String>) -> Result<Self> {
1669        let provider =
1670            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::nvidia_nim(
1671                api_key,
1672            )?;
1673        Ok(self.with_provider("nvidia_nim", Arc::new(provider)))
1674    }
1675
1676    /// Add Nvidia NIM provider with custom config.
1677    #[cfg(feature = "nvidia-nim")]
1678    pub fn with_nvidia_nim_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1679        let provider =
1680            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::nvidia_nim_config(
1681                config,
1682            )?;
1683        Ok(self.with_provider("nvidia_nim", Arc::new(provider)))
1684    }
1685
1686    /// Add Xinference provider from environment.
1687    #[cfg(feature = "xinference")]
1688    pub fn with_xinference_from_env(self) -> Self {
1689        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::xinference_from_env() {
1690            Ok(provider) => self.with_provider("xinference", Arc::new(provider)),
1691            Err(_) => self,
1692        }
1693    }
1694
1695    /// Add Xinference provider with API key.
1696    #[cfg(feature = "xinference")]
1697    pub fn with_xinference(self, api_key: impl Into<String>) -> Result<Self> {
1698        let provider =
1699            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::xinference(
1700                api_key,
1701            )?;
1702        Ok(self.with_provider("xinference", Arc::new(provider)))
1703    }
1704
1705    /// Add Xinference provider with custom config.
1706    #[cfg(feature = "xinference")]
1707    pub fn with_xinference_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1708        let provider =
1709            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::xinference_config(
1710                config,
1711            )?;
1712        Ok(self.with_provider("xinference", Arc::new(provider)))
1713    }
1714
1715    /// Add PublicAI provider from environment.
1716    #[cfg(feature = "public-ai")]
1717    pub fn with_public_ai_from_env(self) -> Self {
1718        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::public_ai_from_env() {
1719            Ok(provider) => self.with_provider("public_ai", Arc::new(provider)),
1720            Err(_) => self,
1721        }
1722    }
1723
1724    /// Add PublicAI provider with API key.
1725    #[cfg(feature = "public-ai")]
1726    pub fn with_public_ai(self, api_key: impl Into<String>) -> Result<Self> {
1727        let provider =
1728            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::public_ai(
1729                api_key,
1730            )?;
1731        Ok(self.with_provider("public_ai", Arc::new(provider)))
1732    }
1733
1734    /// Add PublicAI provider with custom config.
1735    #[cfg(feature = "public-ai")]
1736    pub fn with_public_ai_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1737        let provider =
1738            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::public_ai_config(
1739                config,
1740            )?;
1741        Ok(self.with_provider("public_ai", Arc::new(provider)))
1742    }
1743
1744    // ========== Phase 2.3 Providers ==========
1745
1746    /// Add Bytez provider from environment.
1747    #[cfg(feature = "bytez")]
1748    pub fn with_bytez_from_env(self) -> Self {
1749        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::bytez_from_env()
1750        {
1751            Ok(provider) => self.with_provider("bytez", Arc::new(provider)),
1752            Err(_) => self,
1753        }
1754    }
1755
1756    /// Add Bytez provider with API key.
1757    #[cfg(feature = "bytez")]
1758    pub fn with_bytez(self, api_key: impl Into<String>) -> Result<Self> {
1759        let provider =
1760            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::bytez(api_key)?;
1761        Ok(self.with_provider("bytez", Arc::new(provider)))
1762    }
1763
1764    /// Add Bytez provider with custom config.
1765    #[cfg(feature = "bytez")]
1766    pub fn with_bytez_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1767        let provider =
1768            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::bytez_config(
1769                config,
1770            )?;
1771        Ok(self.with_provider("bytez", Arc::new(provider)))
1772    }
1773
1774    /// Add Chutes provider from environment.
1775    #[cfg(feature = "chutes")]
1776    pub fn with_chutes_from_env(self) -> Self {
1777        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::chutes_from_env()
1778        {
1779            Ok(provider) => self.with_provider("chutes", Arc::new(provider)),
1780            Err(_) => self,
1781        }
1782    }
1783
1784    /// Add Chutes provider with API key.
1785    #[cfg(feature = "chutes")]
1786    pub fn with_chutes(self, api_key: impl Into<String>) -> Result<Self> {
1787        let provider =
1788            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::chutes(api_key)?;
1789        Ok(self.with_provider("chutes", Arc::new(provider)))
1790    }
1791
1792    /// Add Chutes provider with custom config.
1793    #[cfg(feature = "chutes")]
1794    pub fn with_chutes_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1795        let provider =
1796            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::chutes_config(
1797                config,
1798            )?;
1799        Ok(self.with_provider("chutes", Arc::new(provider)))
1800    }
1801
1802    /// Add CometAPI provider from environment.
1803    #[cfg(feature = "comet-api")]
1804    pub fn with_comet_api_from_env(self) -> Self {
1805        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::comet_api_from_env() {
1806            Ok(provider) => self.with_provider("comet_api", Arc::new(provider)),
1807            Err(_) => self,
1808        }
1809    }
1810
1811    /// Add CometAPI provider with API key.
1812    #[cfg(feature = "comet-api")]
1813    pub fn with_comet_api(self, api_key: impl Into<String>) -> Result<Self> {
1814        let provider =
1815            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::comet_api(
1816                api_key,
1817            )?;
1818        Ok(self.with_provider("comet_api", Arc::new(provider)))
1819    }
1820
1821    /// Add CometAPI provider with custom config.
1822    #[cfg(feature = "comet-api")]
1823    pub fn with_comet_api_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1824        let provider =
1825            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::comet_api_config(
1826                config,
1827            )?;
1828        Ok(self.with_provider("comet_api", Arc::new(provider)))
1829    }
1830
1831    /// Add CompactifAI provider from environment.
1832    #[cfg(feature = "compactifai")]
1833    pub fn with_compactifai_from_env(self) -> Self {
1834        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::compactifai_from_env()
1835        {
1836            Ok(provider) => self.with_provider("compactifai", Arc::new(provider)),
1837            Err(_) => self,
1838        }
1839    }
1840
1841    /// Add CompactifAI provider with API key.
1842    #[cfg(feature = "compactifai")]
1843    pub fn with_compactifai(self, api_key: impl Into<String>) -> Result<Self> {
1844        let provider =
1845            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::compactifai(
1846                api_key,
1847            )?;
1848        Ok(self.with_provider("compactifai", Arc::new(provider)))
1849    }
1850
1851    /// Add CompactifAI provider with custom config.
1852    #[cfg(feature = "compactifai")]
1853    pub fn with_compactifai_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1854        let provider =
1855            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::compactifai_config(
1856                config,
1857            )?;
1858        Ok(self.with_provider("compactifai", Arc::new(provider)))
1859    }
1860
1861    /// Add Synthetic provider from environment.
1862    #[cfg(feature = "synthetic")]
1863    pub fn with_synthetic_from_env(self) -> Self {
1864        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::synthetic_from_env() {
1865            Ok(provider) => self.with_provider("synthetic", Arc::new(provider)),
1866            Err(_) => self,
1867        }
1868    }
1869
1870    /// Add Synthetic provider with API key.
1871    #[cfg(feature = "synthetic")]
1872    pub fn with_synthetic(self, api_key: impl Into<String>) -> Result<Self> {
1873        let provider =
1874            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::synthetic(
1875                api_key,
1876            )?;
1877        Ok(self.with_provider("synthetic", Arc::new(provider)))
1878    }
1879
1880    /// Add Synthetic provider with custom config.
1881    #[cfg(feature = "synthetic")]
1882    pub fn with_synthetic_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1883        let provider =
1884            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::synthetic_config(
1885                config,
1886            )?;
1887        Ok(self.with_provider("synthetic", Arc::new(provider)))
1888    }
1889
1890    /// Add Morph provider from environment.
1891    #[cfg(feature = "morph")]
1892    pub fn with_morph_from_env(self) -> Self {
1893        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::morph_from_env()
1894        {
1895            Ok(provider) => self.with_provider("morph", Arc::new(provider)),
1896            Err(_) => self,
1897        }
1898    }
1899
1900    /// Add Morph provider with API key.
1901    #[cfg(feature = "morph")]
1902    pub fn with_morph(self, api_key: impl Into<String>) -> Result<Self> {
1903        let provider =
1904            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::morph(api_key)?;
1905        Ok(self.with_provider("morph", Arc::new(provider)))
1906    }
1907
1908    /// Add Morph provider with custom config.
1909    #[cfg(feature = "morph")]
1910    pub fn with_morph_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1911        let provider =
1912            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::morph_config(
1913                config,
1914            )?;
1915        Ok(self.with_provider("morph", Arc::new(provider)))
1916    }
1917
1918    /// Add Heroku AI provider from environment.
1919    #[cfg(feature = "heroku-ai")]
1920    pub fn with_heroku_ai_from_env(self) -> Self {
1921        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::heroku_ai_from_env() {
1922            Ok(provider) => self.with_provider("heroku_ai", Arc::new(provider)),
1923            Err(_) => self,
1924        }
1925    }
1926
1927    /// Add Heroku AI provider with API key.
1928    #[cfg(feature = "heroku-ai")]
1929    pub fn with_heroku_ai(self, api_key: impl Into<String>) -> Result<Self> {
1930        let provider =
1931            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::heroku_ai(
1932                api_key,
1933            )?;
1934        Ok(self.with_provider("heroku_ai", Arc::new(provider)))
1935    }
1936
1937    /// Add Heroku AI provider with custom config.
1938    #[cfg(feature = "heroku-ai")]
1939    pub fn with_heroku_ai_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1940        let provider =
1941            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::heroku_ai_config(
1942                config,
1943            )?;
1944        Ok(self.with_provider("heroku_ai", Arc::new(provider)))
1945    }
1946
1947    /// Add v0 (Vercel) provider from environment.
1948    #[cfg(feature = "v0")]
1949    pub fn with_v0_from_env(self) -> Self {
1950        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::v0_from_env() {
1951            Ok(provider) => self.with_provider("v0", Arc::new(provider)),
1952            Err(_) => self,
1953        }
1954    }
1955
1956    /// Add v0 (Vercel) provider with API key.
1957    #[cfg(feature = "v0")]
1958    pub fn with_v0(self, api_key: impl Into<String>) -> Result<Self> {
1959        let provider =
1960            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::v0(api_key)?;
1961        Ok(self.with_provider("v0", Arc::new(provider)))
1962    }
1963
1964    /// Add v0 (Vercel) provider with custom config.
1965    #[cfg(feature = "v0")]
1966    pub fn with_v0_config(self, config: crate::provider::ProviderConfig) -> Result<Self> {
1967        let provider =
1968            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::v0_config(config)?;
1969        Ok(self.with_provider("v0", Arc::new(provider)))
1970    }
1971
1972    /// Add a custom OpenAI-compatible provider.
1973    ///
1974    /// Use this for any provider that uses OpenAI's API format.
1975    ///
1976    /// # Arguments
1977    ///
1978    /// * `name` - Provider name for identification
1979    /// * `base_url` - Base URL (e.g., "<https://api.example.com/v1>")
1980    /// * `api_key` - Optional API key
1981    #[cfg(feature = "openai-compatible")]
1982    pub fn with_openai_compatible(
1983        self,
1984        name: impl Into<String>,
1985        base_url: impl Into<String>,
1986        api_key: Option<String>,
1987    ) -> Result<Self> {
1988        let name_str = name.into();
1989        let provider = crate::providers::chat::openai_compatible::OpenAICompatibleProvider::custom(
1990            name_str.clone(),
1991            base_url,
1992            api_key,
1993        )?;
1994        Ok(self.with_provider(name_str, Arc::new(provider)))
1995    }
1996
1997    // ========== Google Providers ==========
1998
1999    /// Add Google AI (Gemini) provider from environment.
2000    ///
2001    /// Reads: `GOOGLE_API_KEY`
2002    #[cfg(feature = "google")]
2003    pub fn with_google_from_env(self) -> Self {
2004        match crate::providers::chat::google::GoogleProvider::from_env() {
2005            Ok(provider) => self.with_provider("google", Arc::new(provider)),
2006            Err(_) => self,
2007        }
2008    }
2009
2010    /// Add Google AI (Gemini) provider with API key.
2011    #[cfg(feature = "google")]
2012    pub fn with_google(self, api_key: impl Into<String>) -> Result<Self> {
2013        let provider = crate::providers::chat::google::GoogleProvider::with_api_key(api_key)?;
2014        Ok(self.with_provider("google", Arc::new(provider)))
2015    }
2016
2017    /// Add Google AI (Gemini) provider with custom config.
2018    #[cfg(feature = "google")]
2019    pub fn with_google_config(self, config: ProviderConfig) -> Result<Self> {
2020        let provider = crate::providers::chat::google::GoogleProvider::new(config)?;
2021        Ok(self.with_provider("google", Arc::new(provider)))
2022    }
2023
2024    /// Add Google Vertex AI provider from environment.
2025    ///
2026    /// Uses Application Default Credentials (ADC) for authentication.
2027    /// Credentials are discovered automatically from:
2028    /// 1. `GOOGLE_APPLICATION_CREDENTIALS` environment variable
2029    /// 2. `~/.config/gcloud/application_default_credentials.json`
2030    /// 3. GCP metadata server (when running on GCP)
2031    ///
2032    /// Reads project and location from:
2033    /// - `GOOGLE_CLOUD_PROJECT` or `VERTEX_PROJECT`
2034    /// - `GOOGLE_CLOUD_LOCATION` or `VERTEX_LOCATION` (defaults to "us-central1")
2035    ///
2036    /// The provider is initialized asynchronously during `build()`.
2037    #[cfg(feature = "vertex")]
2038    pub fn with_vertex_from_env(mut self) -> Self {
2039        self.pending_vertex
2040            .push(("vertex".to_string(), PendingVertexConfig::FromEnv));
2041        self
2042    }
2043
2044    /// Add Google Vertex AI provider from a service account file.
2045    ///
2046    /// The provider is initialized asynchronously during `build()`.
2047    #[cfg(feature = "vertex")]
2048    pub fn with_vertex_service_account(
2049        mut self,
2050        path: impl AsRef<std::path::Path>,
2051        project_id: impl Into<String>,
2052        location: impl Into<String>,
2053    ) -> Self {
2054        self.pending_vertex.push((
2055            "vertex".to_string(),
2056            PendingVertexConfig::ServiceAccount {
2057                path: path.as_ref().to_path_buf(),
2058                project_id: project_id.into(),
2059                location: location.into(),
2060            },
2061        ));
2062        self
2063    }
2064
2065    /// Add Google Vertex AI provider with custom config.
2066    ///
2067    /// Note: This method accepts an already-initialized config, so it's synchronous.
2068    /// For ADC-based initialization, use `with_vertex_from_env()`.
2069    #[cfg(feature = "vertex")]
2070    pub fn with_vertex_config(
2071        self,
2072        config: crate::providers::chat::vertex::VertexConfig,
2073    ) -> Result<Self> {
2074        let provider = crate::providers::chat::vertex::VertexProvider::with_config(config)?;
2075        Ok(self.with_provider("vertex", Arc::new(provider)))
2076    }
2077
2078    // Vertex AI Partner Models (Phase 2)
2079
2080    /// Add Vertex AI with Anthropic Claude models from environment.
2081    ///
2082    /// The provider is initialized asynchronously during `build()`.
2083    #[cfg(feature = "vertex")]
2084    pub fn with_vertex_anthropic_from_env(mut self) -> Self {
2085        self.pending_vertex.push((
2086            "vertex-anthropic".to_string(),
2087            PendingVertexConfig::WithPublisher {
2088                publisher: "anthropic".to_string(),
2089            },
2090        ));
2091        self
2092    }
2093
2094    /// Add Vertex AI with Anthropic Claude models and custom config.
2095    #[cfg(feature = "vertex")]
2096    pub fn with_vertex_anthropic_config(
2097        self,
2098        mut config: crate::providers::chat::vertex::VertexConfig,
2099    ) -> Result<Self> {
2100        config.set_publisher("anthropic");
2101        let provider = crate::providers::chat::vertex::VertexProvider::with_config(config)?;
2102        Ok(self.with_provider("vertex-anthropic", Arc::new(provider)))
2103    }
2104
2105    /// Add Vertex AI with DeepSeek models from environment.
2106    ///
2107    /// The provider is initialized asynchronously during `build()`.
2108    #[cfg(feature = "vertex")]
2109    pub fn with_vertex_deepseek_from_env(mut self) -> Self {
2110        self.pending_vertex.push((
2111            "vertex-deepseek".to_string(),
2112            PendingVertexConfig::WithPublisher {
2113                publisher: "deepseek".to_string(),
2114            },
2115        ));
2116        self
2117    }
2118
2119    /// Add Vertex AI with DeepSeek models and custom config.
2120    #[cfg(feature = "vertex")]
2121    pub fn with_vertex_deepseek_config(
2122        self,
2123        mut config: crate::providers::chat::vertex::VertexConfig,
2124    ) -> Result<Self> {
2125        config.set_publisher("deepseek");
2126        let provider = crate::providers::chat::vertex::VertexProvider::with_config(config)?;
2127        Ok(self.with_provider("vertex-deepseek", Arc::new(provider)))
2128    }
2129
2130    /// Add Vertex AI with Meta Llama models from environment.
2131    ///
2132    /// The provider is initialized asynchronously during `build()`.
2133    #[cfg(feature = "vertex")]
2134    pub fn with_vertex_llama_from_env(mut self) -> Self {
2135        self.pending_vertex.push((
2136            "vertex-llama".to_string(),
2137            PendingVertexConfig::WithPublisher {
2138                publisher: "meta".to_string(),
2139            },
2140        ));
2141        self
2142    }
2143
2144    /// Add Vertex AI with Meta Llama models and custom config.
2145    #[cfg(feature = "vertex")]
2146    pub fn with_vertex_llama_config(
2147        self,
2148        mut config: crate::providers::chat::vertex::VertexConfig,
2149    ) -> Result<Self> {
2150        config.set_publisher("meta");
2151        let provider = crate::providers::chat::vertex::VertexProvider::with_config(config)?;
2152        Ok(self.with_provider("vertex-llama", Arc::new(provider)))
2153    }
2154
2155    /// Add Vertex AI with Mistral models from environment.
2156    ///
2157    /// The provider is initialized asynchronously during `build()`.
2158    #[cfg(feature = "vertex")]
2159    pub fn with_vertex_mistral_from_env(mut self) -> Self {
2160        self.pending_vertex.push((
2161            "vertex-mistral".to_string(),
2162            PendingVertexConfig::WithPublisher {
2163                publisher: "mistralai".to_string(),
2164            },
2165        ));
2166        self
2167    }
2168
2169    /// Add Vertex AI with Mistral models and custom config.
2170    #[cfg(feature = "vertex")]
2171    pub fn with_vertex_mistral_config(
2172        self,
2173        mut config: crate::providers::chat::vertex::VertexConfig,
2174    ) -> Result<Self> {
2175        config.set_publisher("mistralai");
2176        let provider = crate::providers::chat::vertex::VertexProvider::with_config(config)?;
2177        Ok(self.with_provider("vertex-mistral", Arc::new(provider)))
2178    }
2179
2180    /// Add Vertex AI with AI21 models from environment.
2181    ///
2182    /// Reads: `GOOGLE_APPLICATION_CREDENTIALS` or uses ADC.
2183    /// Also reads `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION`.
2184    #[cfg(feature = "vertex")]
2185    pub fn with_vertex_ai21_from_env(mut self) -> Self {
2186        self.pending_vertex.push((
2187            "vertex-ai21".to_string(),
2188            PendingVertexConfig::WithPublisher {
2189                publisher: "ai21labs".to_string(),
2190            },
2191        ));
2192        self
2193    }
2194
2195    /// Add Vertex AI with AI21 models and custom config.
2196    #[cfg(feature = "vertex")]
2197    pub fn with_vertex_ai21_config(
2198        self,
2199        mut config: crate::providers::chat::vertex::VertexConfig,
2200    ) -> Result<Self> {
2201        config.set_publisher("ai21labs");
2202        let provider = crate::providers::chat::vertex::VertexProvider::with_config(config)?;
2203        Ok(self.with_provider("vertex-ai21", Arc::new(provider)))
2204    }
2205
2206    // ========== Enterprise Providers ==========
2207
2208    /// Add Cohere provider from environment.
2209    ///
2210    /// Reads: `COHERE_API_KEY` or `CO_API_KEY`
2211    ///
2212    /// Also registers Cohere as an embedding provider for embed-* models.
2213    #[cfg(feature = "cohere")]
2214    pub fn with_cohere_from_env(mut self) -> Self {
2215        match crate::providers::chat::cohere::CohereProvider::from_env() {
2216            Ok(provider) => {
2217                let provider = Arc::new(provider);
2218                self.embedding_providers.insert(
2219                    "cohere".to_string(),
2220                    Arc::clone(&provider) as Arc<dyn EmbeddingProvider>,
2221                );
2222                self.with_provider("cohere", provider)
2223            }
2224            Err(_) => self,
2225        }
2226    }
2227
2228    /// Add Cohere provider with API key.
2229    ///
2230    /// Also registers Cohere as an embedding provider for embed-* models.
2231    #[cfg(feature = "cohere")]
2232    pub fn with_cohere(mut self, api_key: impl Into<String>) -> Result<Self> {
2233        let provider =
2234            Arc::new(crate::providers::chat::cohere::CohereProvider::with_api_key(api_key)?);
2235        self.embedding_providers.insert(
2236            "cohere".to_string(),
2237            Arc::clone(&provider) as Arc<dyn EmbeddingProvider>,
2238        );
2239        Ok(self.with_provider("cohere", provider))
2240    }
2241
2242    /// Add Cohere provider with custom config.
2243    ///
2244    /// Also registers Cohere as an embedding provider for embed-* models.
2245    #[cfg(feature = "cohere")]
2246    pub fn with_cohere_config(mut self, config: ProviderConfig) -> Result<Self> {
2247        let provider = Arc::new(crate::providers::chat::cohere::CohereProvider::new(config)?);
2248        self.embedding_providers.insert(
2249            "cohere".to_string(),
2250            Arc::clone(&provider) as Arc<dyn EmbeddingProvider>,
2251        );
2252        Ok(self.with_provider("cohere", provider))
2253    }
2254
2255    /// Add AI21 provider from environment.
2256    ///
2257    /// Reads: `AI21_API_KEY`
2258    #[cfg(feature = "ai21")]
2259    pub fn with_ai21_from_env(self) -> Self {
2260        match crate::providers::chat::ai21::AI21Provider::from_env() {
2261            Ok(provider) => self.with_provider("ai21", Arc::new(provider)),
2262            Err(_) => self,
2263        }
2264    }
2265
2266    /// Add AI21 provider with API key.
2267    #[cfg(feature = "ai21")]
2268    pub fn with_ai21(self, api_key: impl Into<String>) -> Result<Self> {
2269        let provider = crate::providers::chat::ai21::AI21Provider::with_api_key(api_key)?;
2270        Ok(self.with_provider("ai21", Arc::new(provider)))
2271    }
2272
2273    /// Add AI21 provider with custom config.
2274    #[cfg(feature = "ai21")]
2275    pub fn with_ai21_config(self, config: ProviderConfig) -> Result<Self> {
2276        let provider = crate::providers::chat::ai21::AI21Provider::new(config)?;
2277        Ok(self.with_provider("ai21", Arc::new(provider)))
2278    }
2279
2280    // ========== Inference Platforms ==========
2281
2282    /// Add HuggingFace Inference API provider from environment.
2283    ///
2284    /// Reads: `HUGGINGFACE_API_KEY` or `HF_TOKEN`
2285    #[cfg(feature = "huggingface")]
2286    pub fn with_huggingface_from_env(self) -> Self {
2287        match crate::providers::chat::huggingface::HuggingFaceProvider::from_env() {
2288            Ok(provider) => self.with_provider("huggingface", Arc::new(provider)),
2289            Err(_) => self,
2290        }
2291    }
2292
2293    /// Add HuggingFace Inference API provider with API key.
2294    #[cfg(feature = "huggingface")]
2295    pub fn with_huggingface(self, api_key: impl Into<String>) -> Result<Self> {
2296        let provider =
2297            crate::providers::chat::huggingface::HuggingFaceProvider::with_api_key(api_key)?;
2298        Ok(self.with_provider("huggingface", Arc::new(provider)))
2299    }
2300
2301    /// Add HuggingFace dedicated endpoint provider.
2302    #[cfg(feature = "huggingface")]
2303    pub fn with_huggingface_endpoint(
2304        self,
2305        endpoint_url: impl Into<String>,
2306        api_key: impl Into<String>,
2307    ) -> Result<Self> {
2308        let provider = crate::providers::chat::huggingface::HuggingFaceProvider::endpoint(
2309            endpoint_url,
2310            api_key,
2311        )?;
2312        Ok(self.with_provider("huggingface", Arc::new(provider)))
2313    }
2314
2315    /// Add HuggingFace provider with custom config.
2316    #[cfg(feature = "huggingface")]
2317    pub fn with_huggingface_config(self, config: ProviderConfig) -> Result<Self> {
2318        let provider = crate::providers::chat::huggingface::HuggingFaceProvider::new(config)?;
2319        Ok(self.with_provider("huggingface", Arc::new(provider)))
2320    }
2321
2322    /// Add Replicate provider from environment.
2323    ///
2324    /// Reads: `REPLICATE_API_TOKEN`
2325    #[cfg(feature = "replicate")]
2326    pub fn with_replicate_from_env(self) -> Self {
2327        match crate::providers::chat::replicate::ReplicateProvider::from_env() {
2328            Ok(provider) => self.with_provider("replicate", Arc::new(provider)),
2329            Err(_) => self,
2330        }
2331    }
2332
2333    /// Add Replicate provider with API token.
2334    #[cfg(feature = "replicate")]
2335    pub fn with_replicate(self, api_key: impl Into<String>) -> Result<Self> {
2336        let provider = crate::providers::chat::replicate::ReplicateProvider::with_api_key(api_key)?;
2337        Ok(self.with_provider("replicate", Arc::new(provider)))
2338    }
2339
2340    /// Add Replicate provider with custom config.
2341    #[cfg(feature = "replicate")]
2342    pub fn with_replicate_config(self, config: ProviderConfig) -> Result<Self> {
2343        let provider = crate::providers::chat::replicate::ReplicateProvider::new(config)?;
2344        Ok(self.with_provider("replicate", Arc::new(provider)))
2345    }
2346
2347    /// Add Baseten provider from environment.
2348    ///
2349    /// Reads: `BASETEN_API_KEY` and optionally `BASETEN_MODEL_ID`
2350    #[cfg(feature = "baseten")]
2351    pub fn with_baseten_from_env(self) -> Self {
2352        match crate::providers::chat::baseten::BasetenProvider::from_env() {
2353            Ok(provider) => self.with_provider("baseten", Arc::new(provider)),
2354            Err(_) => self,
2355        }
2356    }
2357
2358    /// Add Baseten provider with API key.
2359    #[cfg(feature = "baseten")]
2360    pub fn with_baseten(self, api_key: impl Into<String>) -> Result<Self> {
2361        let provider = crate::providers::chat::baseten::BasetenProvider::with_api_key(api_key)?;
2362        Ok(self.with_provider("baseten", Arc::new(provider)))
2363    }
2364
2365    /// Add Baseten provider with model ID and API key.
2366    #[cfg(feature = "baseten")]
2367    pub fn with_baseten_model(
2368        self,
2369        model_id: impl Into<String>,
2370        api_key: impl Into<String>,
2371    ) -> Result<Self> {
2372        let provider =
2373            crate::providers::chat::baseten::BasetenProvider::with_model(model_id, api_key)?;
2374        Ok(self.with_provider("baseten", Arc::new(provider)))
2375    }
2376
2377    /// Add Baseten provider with custom config.
2378    #[cfg(feature = "baseten")]
2379    pub fn with_baseten_config(self, config: ProviderConfig) -> Result<Self> {
2380        let provider = crate::providers::chat::baseten::BasetenProvider::new(config)?;
2381        Ok(self.with_provider("baseten", Arc::new(provider)))
2382    }
2383
2384    /// Add RunPod provider from environment.
2385    ///
2386    /// Reads: `RUNPOD_API_KEY` and `RUNPOD_ENDPOINT_ID`
2387    #[cfg(feature = "runpod")]
2388    pub fn with_runpod_from_env(self) -> Self {
2389        match crate::providers::chat::runpod::RunPodProvider::from_env() {
2390            Ok(provider) => self.with_provider("runpod", Arc::new(provider)),
2391            Err(_) => self,
2392        }
2393    }
2394
2395    /// Add RunPod provider with endpoint ID and API key.
2396    #[cfg(feature = "runpod")]
2397    pub fn with_runpod(
2398        self,
2399        endpoint_id: impl Into<String>,
2400        api_key: impl Into<String>,
2401    ) -> Result<Self> {
2402        let provider = crate::providers::chat::runpod::RunPodProvider::new(endpoint_id, api_key)?;
2403        Ok(self.with_provider("runpod", Arc::new(provider)))
2404    }
2405
2406    // ============ Cloud Providers ============
2407
2408    /// Add Cloudflare Workers AI provider from environment variables.
2409    ///
2410    /// Reads: `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`
2411    #[cfg(feature = "cloudflare")]
2412    pub fn with_cloudflare_from_env(self) -> Self {
2413        match crate::providers::chat::cloudflare::CloudflareProvider::from_env() {
2414            Ok(provider) => self.with_provider("cloudflare", Arc::new(provider)),
2415            Err(_) => self,
2416        }
2417    }
2418
2419    /// Add Cloudflare Workers AI provider with account ID and API token.
2420    #[cfg(feature = "cloudflare")]
2421    pub fn with_cloudflare(
2422        self,
2423        account_id: impl Into<String>,
2424        api_token: impl Into<String>,
2425    ) -> Result<Self> {
2426        let provider =
2427            crate::providers::chat::cloudflare::CloudflareProvider::new(account_id, api_token)?;
2428        Ok(self.with_provider("cloudflare", Arc::new(provider)))
2429    }
2430
2431    /// Add IBM watsonx.ai provider from environment variables.
2432    ///
2433    /// Reads: `WATSONX_API_KEY` and `WATSONX_PROJECT_ID`
2434    #[cfg(feature = "watsonx")]
2435    pub fn with_watsonx_from_env(self) -> Self {
2436        match crate::providers::chat::watsonx::WatsonxProvider::from_env() {
2437            Ok(provider) => self.with_provider("watsonx", Arc::new(provider)),
2438            Err(_) => self,
2439        }
2440    }
2441
2442    /// Add IBM watsonx.ai provider with API key and project ID.
2443    #[cfg(feature = "watsonx")]
2444    pub fn with_watsonx(
2445        self,
2446        api_key: impl Into<String>,
2447        project_id: impl Into<String>,
2448    ) -> Result<Self> {
2449        let provider = crate::providers::chat::watsonx::WatsonxProvider::new(api_key, project_id)?;
2450        Ok(self.with_provider("watsonx", Arc::new(provider)))
2451    }
2452
2453    /// Add Databricks provider from environment variables.
2454    ///
2455    /// Reads: `DATABRICKS_TOKEN` and `DATABRICKS_HOST`
2456    #[cfg(feature = "databricks")]
2457    pub fn with_databricks_from_env(self) -> Self {
2458        match crate::providers::chat::databricks::DatabricksProvider::from_env() {
2459            Ok(provider) => self.with_provider("databricks", Arc::new(provider)),
2460            Err(_) => self,
2461        }
2462    }
2463
2464    /// Add Databricks provider with host URL and token.
2465    #[cfg(feature = "databricks")]
2466    pub fn with_databricks(
2467        self,
2468        host: impl Into<String>,
2469        token: impl Into<String>,
2470    ) -> Result<Self> {
2471        let provider = crate::providers::chat::databricks::DatabricksProvider::new(host, token)?;
2472        Ok(self.with_provider("databricks", Arc::new(provider)))
2473    }
2474
2475    // ============ Specialized/Fast Inference Providers ============
2476
2477    /// Add SambaNova provider from environment variables.
2478    ///
2479    /// Reads: `SAMBANOVA_API_KEY`
2480    #[cfg(feature = "sambanova")]
2481    pub fn with_sambanova_from_env(self) -> Self {
2482        match crate::providers::chat::sambanova::SambaNovaProvider::from_env() {
2483            Ok(provider) => self.with_provider("sambanova", Arc::new(provider)),
2484            Err(_) => self,
2485        }
2486    }
2487
2488    /// Add SambaNova provider with API key.
2489    #[cfg(feature = "sambanova")]
2490    pub fn with_sambanova(self, api_key: impl Into<String>) -> Result<Self> {
2491        let provider = crate::providers::chat::sambanova::SambaNovaProvider::with_api_key(api_key)?;
2492        Ok(self.with_provider("sambanova", Arc::new(provider)))
2493    }
2494
2495    // ========== OpenAI-Compatible Providers (Phase 1 Expansion) ==========
2496
2497    /// Add xAI (Grok) provider from environment.
2498    #[cfg(feature = "openai-compatible")]
2499    pub fn with_xai_from_env(self) -> Self {
2500        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::xai_from_env() {
2501            Ok(provider) => self.with_provider("xai", Arc::new(provider)),
2502            Err(_) => self,
2503        }
2504    }
2505
2506    /// Add xAI (Grok) provider with API key.
2507    #[cfg(feature = "openai-compatible")]
2508    pub fn with_xai(self, api_key: impl Into<String>) -> Result<Self> {
2509        let provider =
2510            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::xai(api_key)?;
2511        Ok(self.with_provider("xai", Arc::new(provider)))
2512    }
2513
2514    /// Add Lambda Labs provider from environment.
2515    #[cfg(feature = "openai-compatible")]
2516    pub fn with_lambda_from_env(self) -> Self {
2517        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::lambda_from_env()
2518        {
2519            Ok(provider) => self.with_provider("lambda", Arc::new(provider)),
2520            Err(_) => self,
2521        }
2522    }
2523
2524    /// Add Lambda Labs provider with API key.
2525    #[cfg(feature = "openai-compatible")]
2526    pub fn with_lambda(self, api_key: impl Into<String>) -> Result<Self> {
2527        let provider =
2528            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::lambda(api_key)?;
2529        Ok(self.with_provider("lambda", Arc::new(provider)))
2530    }
2531
2532    /// Add Friendli provider from environment.
2533    #[cfg(feature = "openai-compatible")]
2534    pub fn with_friendli_from_env(self) -> Self {
2535        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::friendli_from_env(
2536        ) {
2537            Ok(provider) => self.with_provider("friendli", Arc::new(provider)),
2538            Err(_) => self,
2539        }
2540    }
2541
2542    /// Add Friendli provider with API key.
2543    #[cfg(feature = "openai-compatible")]
2544    pub fn with_friendli(self, api_key: impl Into<String>) -> Result<Self> {
2545        let provider =
2546            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::friendli(api_key)?;
2547        Ok(self.with_provider("friendli", Arc::new(provider)))
2548    }
2549
2550    /// Add Volcengine (ByteDance) provider from environment.
2551    #[cfg(feature = "openai-compatible")]
2552    pub fn with_volcengine_from_env(self) -> Self {
2553        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::volcengine_from_env() {
2554            Ok(provider) => self.with_provider("volcengine", Arc::new(provider)),
2555            Err(_) => self,
2556        }
2557    }
2558
2559    /// Add Volcengine (ByteDance) provider with API key.
2560    #[cfg(feature = "openai-compatible")]
2561    pub fn with_volcengine(self, api_key: impl Into<String>) -> Result<Self> {
2562        let provider =
2563            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::volcengine(
2564                api_key,
2565            )?;
2566        Ok(self.with_provider("volcengine", Arc::new(provider)))
2567    }
2568
2569    /// Add Meta Llama API provider from environment.
2570    #[cfg(feature = "openai-compatible")]
2571    pub fn with_meta_llama_from_env(self) -> Self {
2572        match crate::providers::chat::openai_compatible::OpenAICompatibleProvider::meta_llama_from_env() {
2573            Ok(provider) => self.with_provider("meta_llama", Arc::new(provider)),
2574            Err(_) => self,
2575        }
2576    }
2577
2578    /// Add Meta Llama API provider with API key.
2579    #[cfg(feature = "openai-compatible")]
2580    pub fn with_meta_llama(self, api_key: impl Into<String>) -> Result<Self> {
2581        let provider =
2582            crate::providers::chat::openai_compatible::OpenAICompatibleProvider::meta_llama(
2583                api_key,
2584            )?;
2585        Ok(self.with_provider("meta_llama", Arc::new(provider)))
2586    }
2587
2588    // ========== Custom Providers (Phase 1) ==========
2589
2590    /// Add DataRobot provider from environment.
2591    #[cfg(feature = "datarobot")]
2592    pub fn with_datarobot_from_env(self) -> Self {
2593        match crate::providers::DataRobotProvider::from_env() {
2594            Ok(provider) => self.with_provider("datarobot", Arc::new(provider)),
2595            Err(_) => self,
2596        }
2597    }
2598
2599    /// Add DataRobot provider with API key.
2600    #[cfg(feature = "datarobot")]
2601    pub fn with_datarobot(self, api_key: impl Into<String>) -> Result<Self> {
2602        let provider = crate::providers::DataRobotProvider::with_api_key(api_key)?;
2603        Ok(self.with_provider("datarobot", Arc::new(provider)))
2604    }
2605
2606    /// Add Stability AI provider from environment.
2607    #[cfg(feature = "stability")]
2608    pub fn with_stability_from_env(self) -> Self {
2609        match crate::providers::StabilityProvider::from_env() {
2610            Ok(provider) => self.with_provider("stability", Arc::new(provider)),
2611            Err(_) => self,
2612        }
2613    }
2614
2615    /// Add Stability AI provider with API key.
2616    #[cfg(feature = "stability")]
2617    pub fn with_stability(self, api_key: impl Into<String>) -> Result<Self> {
2618        let provider = crate::providers::StabilityProvider::with_api_key(api_key)?;
2619        Ok(self.with_provider("stability", Arc::new(provider)))
2620    }
2621
2622    // ========== Specialized APIs (Phase 2.3B) ==========
2623
2624    /// Add RunwayML provider from environment.
2625    #[cfg(feature = "runwayml")]
2626    pub fn with_runwayml_from_env(self) -> Self {
2627        match crate::providers::RunwayMLProvider::from_env() {
2628            Ok(provider) => self.with_provider("runwayml", Arc::new(provider)),
2629            Err(_) => self,
2630        }
2631    }
2632
2633    /// Add RunwayML provider with API key.
2634    #[cfg(feature = "runwayml")]
2635    pub fn with_runwayml(self, api_key: impl Into<String>) -> Result<Self> {
2636        let provider = crate::providers::RunwayMLProvider::with_api_key(api_key)?;
2637        Ok(self.with_provider("runwayml", Arc::new(provider)))
2638    }
2639
2640    /// Add Recraft provider from environment.
2641    #[cfg(feature = "recraft")]
2642    pub fn with_recraft_from_env(self) -> Self {
2643        match crate::providers::RecraftProvider::from_env() {
2644            Ok(provider) => self.with_provider("recraft", Arc::new(provider)),
2645            Err(_) => self,
2646        }
2647    }
2648
2649    /// Add Recraft provider with API key.
2650    #[cfg(feature = "recraft")]
2651    pub fn with_recraft(self, api_key: impl Into<String>) -> Result<Self> {
2652        let provider = crate::providers::RecraftProvider::with_api_key(api_key)?;
2653        Ok(self.with_provider("recraft", Arc::new(provider)))
2654    }
2655
2656    // ========== Embedding Providers ==========
2657
2658    /// Add Voyage AI provider from environment.
2659    #[cfg(feature = "voyage")]
2660    pub fn with_voyage_from_env(self) -> Self {
2661        match crate::providers::VoyageProvider::from_env() {
2662            Ok(provider) => self.with_provider("voyage", Arc::new(provider)),
2663            Err(_) => self,
2664        }
2665    }
2666
2667    /// Add Voyage AI provider with API key.
2668    #[cfg(feature = "voyage")]
2669    pub fn with_voyage(self, api_key: impl Into<String>) -> Result<Self> {
2670        let provider = crate::providers::VoyageProvider::with_api_key(api_key)?;
2671        Ok(self.with_provider("voyage", Arc::new(provider)))
2672    }
2673
2674    /// Add Jina AI provider from environment.
2675    #[cfg(feature = "jina")]
2676    pub fn with_jina_from_env(self) -> Self {
2677        match crate::providers::JinaProvider::from_env() {
2678            Ok(provider) => self.with_provider("jina", Arc::new(provider)),
2679            Err(_) => self,
2680        }
2681    }
2682
2683    /// Add Jina AI provider with API key.
2684    #[cfg(feature = "jina")]
2685    pub fn with_jina(self, api_key: impl Into<String>) -> Result<Self> {
2686        let provider = crate::providers::JinaProvider::with_api_key(api_key)?;
2687        Ok(self.with_provider("jina", Arc::new(provider)))
2688    }
2689
2690    // ========== Cloud Providers (Phase 3) ==========
2691
2692    /// Add AWS SageMaker provider from environment.
2693    #[cfg(feature = "sagemaker")]
2694    pub async fn with_sagemaker_from_env(self) -> Result<Self> {
2695        let provider = crate::providers::SageMakerProvider::from_env().await?;
2696        Ok(self.with_provider("sagemaker", Arc::new(provider)))
2697    }
2698
2699    /// Add AWS SageMaker provider with explicit configuration.
2700    #[cfg(feature = "sagemaker")]
2701    pub async fn with_sagemaker(self, region: &str, endpoint_name: &str) -> Result<Self> {
2702        let provider = crate::providers::SageMakerProvider::new(region, endpoint_name).await?;
2703        Ok(self.with_provider("sagemaker", Arc::new(provider)))
2704    }
2705
2706    /// Add Snowflake Cortex provider from environment.
2707    #[cfg(feature = "snowflake")]
2708    pub async fn with_snowflake_from_env(self) -> Result<Self> {
2709        let provider = crate::providers::SnowflakeProvider::from_env().await?;
2710        Ok(self.with_provider("snowflake", Arc::new(provider)))
2711    }
2712
2713    /// Add Snowflake Cortex provider with explicit configuration.
2714    #[cfg(feature = "snowflake")]
2715    pub async fn with_snowflake(
2716        self,
2717        account: &str,
2718        user: &str,
2719        password: &str,
2720        database: &str,
2721        schema: &str,
2722        warehouse: &str,
2723    ) -> Result<Self> {
2724        let provider = crate::providers::SnowflakeProvider::new(
2725            account, user, password, database, schema, warehouse,
2726        )
2727        .await?;
2728        Ok(self.with_provider("snowflake", Arc::new(provider)))
2729    }
2730
2731    // ========== Specialized Providers (Phase 4) ==========
2732
2733    /// Get OpenAI Realtime provider from environment variable `OPENAI_API_KEY`.
2734    #[cfg(feature = "openai-realtime")]
2735    pub fn openai_realtime_from_env(&self) -> Result<crate::providers::RealtimeProvider> {
2736        crate::providers::RealtimeProvider::from_env()
2737    }
2738
2739    /// Get OpenAI Realtime provider with explicit API key.
2740    #[cfg(feature = "openai-realtime")]
2741    pub fn openai_realtime(&self, api_key: &str) -> crate::providers::RealtimeProvider {
2742        crate::providers::RealtimeProvider::new(api_key, "gpt-4o-realtime-preview")
2743    }
2744
2745    /// Get OpenAI Realtime provider with explicit API key and model.
2746    #[cfg(feature = "openai-realtime")]
2747    pub fn openai_realtime_with_model(
2748        &self,
2749        api_key: &str,
2750        model: &str,
2751    ) -> crate::providers::RealtimeProvider {
2752        crate::providers::RealtimeProvider::new(api_key, model)
2753    }
2754
2755    // ========== Regional Providers (Chinese Market) ==========
2756
2757    /// Add Baidu Wenxin provider from environment variables.
2758    ///
2759    /// Reads `BAIDU_API_KEY` and `BAIDU_SECRET_KEY` environment variables.
2760    #[cfg(feature = "baidu")]
2761    pub fn with_baidu_from_env(self) -> Self {
2762        match crate::providers::BaiduProvider::from_env() {
2763            Ok(provider) => self.with_provider("baidu", Arc::new(provider)),
2764            Err(_) => self,
2765        }
2766    }
2767
2768    /// Add Baidu Wenxin provider with explicit credentials.
2769    #[cfg(feature = "baidu")]
2770    pub fn with_baidu(
2771        self,
2772        api_key: impl Into<String>,
2773        secret_key: impl Into<String>,
2774    ) -> Result<Self> {
2775        let provider = crate::providers::BaiduProvider::new(&api_key.into(), &secret_key.into())?;
2776        Ok(self.with_provider("baidu", Arc::new(provider)))
2777    }
2778
2779    /// Add Alibaba DashScope provider from environment variable.
2780    ///
2781    /// Reads `ALIBABA_API_KEY` environment variable.
2782    /// Supports multiple model families: Qwen, Llama, Mistral, Baichuan, and Qwen Code models.
2783    #[cfg(feature = "alibaba")]
2784    pub fn with_alibaba_from_env(self) -> Self {
2785        match crate::providers::AlibabaProvider::from_env() {
2786            Ok(provider) => self.with_provider("alibaba", Arc::new(provider)),
2787            Err(_) => self,
2788        }
2789    }
2790
2791    /// Add Alibaba DashScope provider with explicit API key.
2792    /// Supports multiple model families: Qwen, Llama, Mistral, Baichuan, and Qwen Code models.
2793    #[cfg(feature = "alibaba")]
2794    pub fn with_alibaba(self, api_key: impl Into<String>) -> Result<Self> {
2795        let provider = crate::providers::AlibabaProvider::new(&api_key.into())?;
2796        Ok(self.with_provider("alibaba", Arc::new(provider)))
2797    }
2798
2799    // ========== Missing Provider Methods (Gap Fix) ==========
2800
2801    /// Add OpenRouter provider from environment.
2802    /// Reads OPENROUTER_API_KEY from environment.
2803    #[cfg(feature = "openrouter")]
2804    pub fn with_openrouter_from_env(self) -> Self {
2805        match crate::providers::chat::openrouter::OpenRouterProvider::from_env() {
2806            Ok(provider) => self.with_provider("openrouter", Arc::new(provider)),
2807            Err(_) => self,
2808        }
2809    }
2810
2811    /// Add OpenRouter provider with API key.
2812    #[cfg(feature = "openrouter")]
2813    pub fn with_openrouter(self, api_key: impl Into<String>) -> Result<Self> {
2814        let provider =
2815            crate::providers::chat::openrouter::OpenRouterProvider::with_api_key(api_key)?;
2816        Ok(self.with_provider("openrouter", Arc::new(provider)))
2817    }
2818
2819    /// Add Ollama provider (local, no API key required).
2820    #[cfg(feature = "ollama")]
2821    pub fn with_ollama(self) -> Result<Self> {
2822        let provider = crate::providers::chat::ollama::OllamaProvider::new(Default::default())?;
2823        Ok(self.with_provider("ollama", Arc::new(provider)))
2824    }
2825
2826    /// Add Ollama provider with custom base URL.
2827    #[cfg(feature = "ollama")]
2828    pub fn with_ollama_url(self, base_url: impl Into<String>) -> Result<Self> {
2829        let config = crate::provider::ProviderConfig {
2830            base_url: Some(base_url.into()),
2831            ..Default::default()
2832        };
2833        let provider = crate::providers::chat::ollama::OllamaProvider::new(config)?;
2834        Ok(self.with_provider("ollama", Arc::new(provider)))
2835    }
2836
2837    /// Add Maritaca provider from environment.
2838    /// Reads MARITACA_API_KEY from environment.
2839    #[cfg(feature = "maritaca")]
2840    pub fn with_maritaca_from_env(self) -> Self {
2841        match crate::providers::chat::maritaca::MaritacaProvider::from_env() {
2842            Ok(provider) => self.with_provider("maritaca", Arc::new(provider)),
2843            Err(_) => self,
2844        }
2845    }
2846
2847    /// Add Maritaca provider with API key.
2848    #[cfg(feature = "maritaca")]
2849    pub fn with_maritaca(self, api_key: impl Into<String>) -> Result<Self> {
2850        let provider = crate::providers::chat::maritaca::MaritacaProvider::with_api_key(api_key)?;
2851        Ok(self.with_provider("maritaca", Arc::new(provider)))
2852    }
2853
2854    /// Add LightOn provider from environment.
2855    /// Reads LIGHTON_API_KEY from environment.
2856    #[cfg(feature = "lighton")]
2857    pub fn with_lighton_from_env(self) -> Self {
2858        match crate::providers::chat::lighton::LightOnProvider::from_env() {
2859            Ok(provider) => self.with_provider("lighton", Arc::new(provider)),
2860            Err(_) => self,
2861        }
2862    }
2863
2864    /// Add LightOn provider with API key.
2865    #[cfg(feature = "lighton")]
2866    pub fn with_lighton(self, api_key: impl Into<String>) -> Result<Self> {
2867        let provider = crate::providers::chat::lighton::LightOnProvider::with_api_key(api_key)?;
2868        Ok(self.with_provider("lighton", Arc::new(provider)))
2869    }
2870
2871    /// Build the client.
2872    ///
2873    /// This is an async method that initializes all pending providers (Vertex AI, Bedrock)
2874    /// that require async credential discovery.
2875    ///
2876    /// If retry configuration was set via `with_retry()` or `with_default_retry()`,
2877    /// all providers will be wrapped with automatic retry logic.
2878    #[allow(unused_mut)] // mut needed when vertex feature is enabled
2879    pub async fn build(mut self) -> Result<LLMKitClient> {
2880        // Initialize pending Vertex providers
2881        #[cfg(feature = "vertex")]
2882        {
2883            for (name, config) in self.pending_vertex {
2884                let result = match config {
2885                    PendingVertexConfig::FromEnv => {
2886                        match crate::providers::chat::vertex::VertexConfig::from_env().await {
2887                            Ok(cfg) => {
2888                                crate::providers::chat::vertex::VertexProvider::with_config(cfg)
2889                            }
2890                            Err(_) => continue, // Skip silently if env not configured
2891                        }
2892                    }
2893                    PendingVertexConfig::ServiceAccount {
2894                        path,
2895                        project_id,
2896                        location,
2897                    } => {
2898                        match crate::providers::chat::vertex::VertexConfig::from_service_account_file(
2899                            &path,
2900                            &project_id,
2901                            &location,
2902                        )
2903                        .await
2904                        {
2905                            Ok(cfg) => {
2906                                crate::providers::chat::vertex::VertexProvider::with_config(cfg)
2907                            }
2908                            Err(_) => continue, // Skip silently if service account fails
2909                        }
2910                    }
2911                    PendingVertexConfig::WithPublisher { publisher } => {
2912                        match crate::providers::chat::vertex::VertexConfig::from_env_with_publisher(
2913                            &publisher,
2914                        )
2915                        .await
2916                        {
2917                            Ok(cfg) => {
2918                                crate::providers::chat::vertex::VertexProvider::with_config(cfg)
2919                            }
2920                            Err(_) => continue, // Skip silently if env not configured
2921                        }
2922                    }
2923                };
2924
2925                if let Ok(provider) = result {
2926                    self.providers.insert(name, Arc::new(provider));
2927                }
2928            }
2929        }
2930
2931        // Initialize pending Bedrock providers
2932        #[cfg(feature = "bedrock")]
2933        {
2934            for (name, config) in self.pending_bedrock {
2935                let result = match config {
2936                    PendingBedrockConfig::FromEnv => {
2937                        crate::providers::chat::bedrock::BedrockProvider::from_env_region().await
2938                    }
2939                    PendingBedrockConfig::WithRegion { region } => {
2940                        crate::providers::chat::bedrock::BedrockProvider::from_env(&region).await
2941                    }
2942                };
2943
2944                if let Ok(provider) = result {
2945                    self.providers.insert(name, Arc::new(provider));
2946                }
2947            }
2948        }
2949
2950        if self.providers.is_empty() {
2951            return Err(Error::config("No providers configured"));
2952        }
2953
2954        // Wrap providers with retry logic if configured
2955        let providers = if let Some(retry_config) = self.retry_config {
2956            self.providers
2957                .into_iter()
2958                .map(|(name, provider)| {
2959                    let retrying = DynamicRetryingProvider {
2960                        inner: provider,
2961                        config: retry_config.clone(),
2962                    };
2963                    (name, Arc::new(retrying) as Arc<dyn Provider>)
2964                })
2965                .collect()
2966        } else {
2967            self.providers
2968        };
2969
2970        Ok(LLMKitClient {
2971            providers,
2972            embedding_providers: self.embedding_providers,
2973            speech_providers: self.speech_providers,
2974            transcription_providers: self.transcription_providers,
2975            image_providers: self.image_providers,
2976            video_providers: self.video_providers,
2977            ranking_providers: self.ranking_providers,
2978            moderation_providers: self.moderation_providers,
2979            classification_providers: self.classification_providers,
2980            default_provider: self.default_provider,
2981        })
2982    }
2983}
2984
2985impl Default for ClientBuilder {
2986    fn default() -> Self {
2987        Self::new()
2988    }
2989}
2990
2991#[cfg(test)]
2992mod tests {
2993    use super::*;
2994
2995    #[test]
2996    fn test_model_parsing_valid_format() {
2997        // Test valid "provider/model" format
2998        let (provider, model) =
2999            parse_model_identifier("anthropic/claude-sonnet-4-20250514").unwrap();
3000        assert_eq!(provider, "anthropic");
3001        assert_eq!(model, "claude-sonnet-4-20250514");
3002
3003        let (provider, model) = parse_model_identifier("openai/gpt-4o").unwrap();
3004        assert_eq!(provider, "openai");
3005        assert_eq!(model, "gpt-4o");
3006
3007        let (provider, model) = parse_model_identifier("groq/llama-3.3-70b-versatile").unwrap();
3008        assert_eq!(provider, "groq");
3009        assert_eq!(model, "llama-3.3-70b-versatile");
3010
3011        let (provider, model) = parse_model_identifier("vertex/gemini-pro").unwrap();
3012        assert_eq!(provider, "vertex");
3013        assert_eq!(model, "gemini-pro");
3014
3015        let (provider, model) = parse_model_identifier("mistral/mistral-large-latest").unwrap();
3016        assert_eq!(provider, "mistral");
3017        assert_eq!(model, "mistral-large-latest");
3018
3019        // Test new Phase 2.3 providers
3020        let (provider, model) = parse_model_identifier("baidu/ERNIE-Bot-Ultra").unwrap();
3021        assert_eq!(provider, "baidu");
3022        assert_eq!(model, "ERNIE-Bot-Ultra");
3023
3024        let (provider, model) = parse_model_identifier("alibaba/qwen-max").unwrap();
3025        assert_eq!(provider, "alibaba");
3026        assert_eq!(model, "qwen-max");
3027
3028        let (provider, model) = parse_model_identifier("runwayml/gen4_turbo").unwrap();
3029        assert_eq!(provider, "runwayml");
3030        assert_eq!(model, "gen4_turbo");
3031
3032        let (provider, model) = parse_model_identifier("recraft/recraft-v3").unwrap();
3033        assert_eq!(provider, "recraft");
3034        assert_eq!(model, "recraft-v3");
3035    }
3036
3037    #[test]
3038    fn test_model_parsing_requires_provider() {
3039        // Models without provider prefix should return an error
3040        assert!(parse_model_identifier("claude-sonnet-4-20250514").is_err());
3041        assert!(parse_model_identifier("gpt-4o").is_err());
3042        assert!(parse_model_identifier("mistral-large").is_err());
3043        assert!(parse_model_identifier("model").is_err());
3044        assert!(parse_model_identifier("").is_err());
3045    }
3046
3047    #[test]
3048    fn test_model_parsing_invalid_provider_format() {
3049        // HuggingFace-style models with hyphens in org name are not valid provider format
3050        // "meta-llama" contains "-" so it's NOT treated as a provider prefix
3051        assert!(parse_model_identifier("meta-llama/Llama-3.2-3B-Instruct").is_err());
3052
3053        // Model with dots in prefix (not a valid provider)
3054        assert!(parse_model_identifier("v1.2.3/model").is_err());
3055
3056        // Model with colons in prefix (not a valid provider)
3057        assert!(parse_model_identifier("namespace:tag/model").is_err());
3058    }
3059
3060    #[test]
3061    fn test_model_parsing_valid_provider_like_names() {
3062        // "mistralai" looks like a valid provider name (no special chars)
3063        // This parses successfully - resolve_provider will error if not registered
3064        let (provider, model) = parse_model_identifier("mistralai/Mistral-7B-v0.1").unwrap();
3065        assert_eq!(provider, "mistralai");
3066        assert_eq!(model, "Mistral-7B-v0.1");
3067    }
3068}