1#![deny(missing_docs)]
2pub use rullama_core::provider::{ChatOptions, Provider};
10
11#[cfg(feature = "native")]
13pub mod http_client;
14#[cfg(feature = "native")]
15pub mod rate_limiter;
16
17#[cfg(feature = "native")]
18pub use http_client::RateLimitedClient;
19#[cfg(feature = "native")]
20pub use rate_limiter::RateLimiter;
21
22#[cfg(feature = "native")]
26pub mod openai_chat;
27
28#[cfg(feature = "native")]
30pub mod openai_responses;
31
32#[cfg(feature = "native")]
34pub mod anthropic;
35
36#[cfg(feature = "native")]
38pub mod gemini;
39
40#[cfg(feature = "native")]
42pub mod ollama;
43
44pub mod registry;
50
51#[cfg(feature = "native")]
55pub mod model_listing;
56
57#[cfg(feature = "native")]
59pub mod chat_factory;
60
61pub mod local_llm;
65
66#[cfg(feature = "native")]
72pub use anthropic::AnthropicClient;
73#[cfg(feature = "native")]
74pub use gemini::GoogleClient;
75#[cfg(feature = "native")]
76pub use ollama::OllamaProvider;
77#[cfg(feature = "native")]
78pub use openai_chat::OpenAiClient;
79
80#[cfg(feature = "native")]
82pub use anthropic::chat::AnthropicChatProvider;
83#[cfg(feature = "native")]
84pub use gemini::chat::GoogleChatProvider;
85#[cfg(feature = "native")]
86pub use ollama::chat::OllamaChatProvider;
87#[cfg(feature = "native")]
88pub use openai_chat::chat::OpenAiChatProvider;
89#[cfg(feature = "native")]
90pub use openai_responses::OpenAiResponsesProvider;
91
92#[cfg(feature = "native")]
96pub use model_listing::{AvailableModel, ModelCapability, ModelLister, create_model_lister};
97
98#[cfg(feature = "native")]
100pub use chat_factory::ChatProviderFactory;
101
102pub use local_llm::*;
104
105use serde::{Deserialize, Serialize};
106use std::fmt;
107use std::str::FromStr;
108
109#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
111#[serde(rename_all = "lowercase")]
112pub enum ProviderType {
113 Anthropic,
115 OpenAI,
117 Google,
119 Groq,
121 Ollama,
123 Together,
125 Fireworks,
127 Anyscale,
129 Bedrock,
131 VertexAI,
133 ElevenLabs,
135 Deepgram,
137 Azure,
139 Fish,
141 Cartesia,
143 Murf,
145 OpenAiResponses,
147 MiniMax,
149 Custom,
151}
152
153impl ProviderType {
154 pub fn default_model(&self) -> &'static str {
156 match self {
157 Self::Anthropic => "claude-sonnet-4-6",
158 Self::OpenAI => "gpt-5-mini",
159 Self::Google => "gemini-2.5-flash",
160 Self::Groq => "llama-3.3-70b-versatile",
161 Self::Ollama => "llama3.3",
162 Self::Together => "meta-llama/Llama-3.1-8B-Instruct",
163 Self::Fireworks => "accounts/fireworks/models/llama-v3p1-8b-instruct",
164 Self::Anyscale => "meta-llama/Meta-Llama-3.1-8B-Instruct",
165 Self::Bedrock => "anthropic.claude-sonnet-4-6-v1:0",
166 Self::VertexAI => "claude-sonnet-4-6",
167 Self::ElevenLabs => "eleven_multilingual_v2",
168 Self::Deepgram => "nova-2",
169 Self::Azure => "en-US-JennyNeural",
170 Self::Fish => "default",
171 Self::Cartesia => "sonic-english",
172 Self::Murf => "en-US-natalie",
173 Self::OpenAiResponses => "gpt-5-mini",
174 Self::MiniMax => "MiniMax-M2.7",
175 Self::Custom => "claude-sonnet-4-6",
176 }
177 }
178
179 pub fn from_str_opt(s: &str) -> Option<Self> {
181 match s.to_lowercase().as_str() {
182 "anthropic" => Some(Self::Anthropic),
183 "openai" => Some(Self::OpenAI),
184 "google" | "gemini" => Some(Self::Google),
185 "groq" => Some(Self::Groq),
186 "ollama" => Some(Self::Ollama),
187 "together" => Some(Self::Together),
188 "fireworks" => Some(Self::Fireworks),
189 "anyscale" => Some(Self::Anyscale),
190 "bedrock" => Some(Self::Bedrock),
191 "vertex-ai" | "vertexai" | "vertex_ai" => Some(Self::VertexAI),
192 "elevenlabs" => Some(Self::ElevenLabs),
193 "deepgram" => Some(Self::Deepgram),
194 "azure" => Some(Self::Azure),
195 "fish" => Some(Self::Fish),
196 "cartesia" => Some(Self::Cartesia),
197 "murf" => Some(Self::Murf),
198 "openai-responses" | "openai_responses" => Some(Self::OpenAiResponses),
199 "minimax" => Some(Self::MiniMax),
200 "custom" => Some(Self::Custom),
201 _ => None,
202 }
203 }
204
205 pub fn as_str(&self) -> &'static str {
207 match self {
208 Self::Anthropic => "anthropic",
209 Self::OpenAI => "openai",
210 Self::Google => "google",
211 Self::Groq => "groq",
212 Self::Ollama => "ollama",
213 Self::Together => "together",
214 Self::Fireworks => "fireworks",
215 Self::Anyscale => "anyscale",
216 Self::Bedrock => "bedrock",
217 Self::VertexAI => "vertex-ai",
218 Self::ElevenLabs => "elevenlabs",
219 Self::Deepgram => "deepgram",
220 Self::Azure => "azure",
221 Self::Fish => "fish",
222 Self::Cartesia => "cartesia",
223 Self::Murf => "murf",
224 Self::OpenAiResponses => "openai-responses",
225 Self::MiniMax => "minimax",
226 Self::Custom => "custom",
227 }
228 }
229
230 pub fn requires_api_key(&self) -> bool {
232 !matches!(self, Self::Ollama | Self::Bedrock | Self::VertexAI)
233 }
234}
235
236impl fmt::Display for ProviderType {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 write!(f, "{}", self.as_str())
239 }
240}
241
242impl FromStr for ProviderType {
243 type Err = anyhow::Error;
244
245 fn from_str(s: &str) -> Result<Self, Self::Err> {
246 Self::from_str_opt(s).ok_or_else(|| anyhow::anyhow!("Unknown provider: {}", s))
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct ProviderConfig {
253 pub provider: ProviderType,
255 pub model: String,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub api_key: Option<String>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub base_url: Option<String>,
263 #[serde(flatten)]
265 pub options: std::collections::HashMap<String, serde_json::Value>,
266 #[cfg(feature = "telemetry")]
268 #[serde(skip)]
269 pub analytics_collector: Option<std::sync::Arc<rullama_telemetry::AnalyticsCollector>>,
270}
271
272impl ProviderConfig {
273 pub fn new(provider: ProviderType, model: String) -> Self {
275 Self {
276 provider,
277 model,
278 api_key: None,
279 base_url: None,
280 options: std::collections::HashMap::new(),
281 #[cfg(feature = "telemetry")]
282 analytics_collector: None,
283 }
284 }
285
286 pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
288 self.api_key = Some(api_key.into());
289 self
290 }
291
292 pub fn with_base_url<S: Into<String>>(mut self, base_url: S) -> Self {
294 self.base_url = Some(base_url.into());
295 self
296 }
297
298 pub fn with_option(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
300 self.options.insert(key.into(), value);
301 self
302 }
303
304 pub fn with_region(self, region: impl Into<String>) -> Self {
306 self.with_option("region", serde_json::Value::String(region.into()))
307 }
308
309 pub fn with_project_id(self, project_id: impl Into<String>) -> Self {
311 self.with_option("project_id", serde_json::Value::String(project_id.into()))
312 }
313
314 #[cfg(feature = "telemetry")]
316 pub fn with_analytics(
317 mut self,
318 collector: std::sync::Arc<rullama_telemetry::AnalyticsCollector>,
319 ) -> Self {
320 self.analytics_collector = Some(collector);
321 self
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_provider_type_default_model() {
331 assert_eq!(ProviderType::Anthropic.default_model(), "claude-sonnet-4-6");
332 assert_eq!(ProviderType::OpenAI.default_model(), "gpt-5-mini");
333 assert_eq!(ProviderType::Google.default_model(), "gemini-2.5-flash");
334 assert_eq!(
335 ProviderType::Groq.default_model(),
336 "llama-3.3-70b-versatile"
337 );
338 assert_eq!(ProviderType::Ollama.default_model(), "llama3.3");
339 assert_eq!(ProviderType::MiniMax.default_model(), "MiniMax-M2.7");
340 }
341
342 #[test]
343 fn test_provider_type_from_str() {
344 assert_eq!(
345 ProviderType::from_str_opt("anthropic"),
346 Some(ProviderType::Anthropic)
347 );
348 assert_eq!(
349 ProviderType::from_str_opt("openai"),
350 Some(ProviderType::OpenAI)
351 );
352 assert_eq!(
353 ProviderType::from_str_opt("google"),
354 Some(ProviderType::Google)
355 );
356 assert_eq!(
357 ProviderType::from_str_opt("gemini"),
358 Some(ProviderType::Google)
359 );
360 assert_eq!(ProviderType::from_str_opt("groq"), Some(ProviderType::Groq));
361 assert_eq!(
362 ProviderType::from_str_opt("ollama"),
363 Some(ProviderType::Ollama)
364 );
365 assert_eq!(
366 ProviderType::from_str_opt("together"),
367 Some(ProviderType::Together)
368 );
369 assert_eq!(
370 ProviderType::from_str_opt("fireworks"),
371 Some(ProviderType::Fireworks)
372 );
373 assert_eq!(
374 ProviderType::from_str_opt("anyscale"),
375 Some(ProviderType::Anyscale)
376 );
377 assert_eq!(
378 ProviderType::from_str_opt("elevenlabs"),
379 Some(ProviderType::ElevenLabs)
380 );
381 assert_eq!(
382 ProviderType::from_str_opt("deepgram"),
383 Some(ProviderType::Deepgram)
384 );
385 assert_eq!(
386 ProviderType::from_str_opt("custom"),
387 Some(ProviderType::Custom)
388 );
389 assert_eq!(
390 ProviderType::from_str_opt("minimax"),
391 Some(ProviderType::MiniMax)
392 );
393 assert_eq!(ProviderType::from_str_opt("unknown"), None);
394 }
395
396 #[test]
397 fn test_provider_type_requires_api_key() {
398 assert!(ProviderType::Anthropic.requires_api_key());
399 assert!(ProviderType::OpenAI.requires_api_key());
400 assert!(!ProviderType::Ollama.requires_api_key());
401 assert!(ProviderType::ElevenLabs.requires_api_key());
402 assert!(ProviderType::MiniMax.requires_api_key());
403 }
404
405 #[test]
406 fn test_provider_config() {
407 let config = ProviderConfig::new(ProviderType::Anthropic, "claude-3".to_string())
408 .with_api_key("sk-test")
409 .with_base_url("https://api.example.com");
410 assert_eq!(config.provider, ProviderType::Anthropic);
411 assert_eq!(config.api_key, Some("sk-test".to_string()));
412 assert_eq!(config.base_url, Some("https://api.example.com".to_string()));
413 }
414}