rstructor/backend/
any_client.rs1use async_trait::async_trait;
11use serde::de::DeserializeOwned;
12
13use crate::backend::usage::{GenerateResult, MaterializeResult};
14use crate::backend::{LLMClient, MediaFile, ModelInfo};
15use crate::error::{ApiErrorKind, RStructorError, Result};
16use crate::model::Instructor;
17
18#[cfg(feature = "anthropic")]
19use crate::backend::anthropic::AnthropicClient;
20#[cfg(feature = "gemini")]
21use crate::backend::gemini::GeminiClient;
22#[cfg(feature = "grok")]
23use crate::backend::grok::GrokClient;
24#[cfg(feature = "openai")]
25use crate::backend::openai::OpenAIClient;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum Provider {
32 #[cfg(feature = "openai")]
34 OpenAI,
35 #[cfg(feature = "anthropic")]
37 Anthropic,
38 #[cfg(feature = "grok")]
40 Grok,
41 #[cfg(feature = "gemini")]
43 Gemini,
44}
45
46#[derive(Clone)]
91pub enum AnyClient {
92 #[cfg(feature = "openai")]
94 OpenAI(OpenAIClient),
95 #[cfg(feature = "anthropic")]
97 Anthropic(AnthropicClient),
98 #[cfg(feature = "grok")]
100 Grok(GrokClient),
101 #[cfg(feature = "gemini")]
103 Gemini(GeminiClient),
104}
105
106impl AnyClient {
107 pub fn from_env_for(provider: Provider) -> Result<Self> {
113 match provider {
114 #[cfg(feature = "openai")]
115 Provider::OpenAI => Ok(Self::OpenAI(OpenAIClient::from_env()?)),
116 #[cfg(feature = "anthropic")]
117 Provider::Anthropic => Ok(Self::Anthropic(AnthropicClient::from_env()?)),
118 #[cfg(feature = "grok")]
119 Provider::Grok => Ok(Self::Grok(GrokClient::from_env()?)),
120 #[cfg(feature = "gemini")]
121 Provider::Gemini => Ok(Self::Gemini(GeminiClient::from_env()?)),
122 }
123 }
124
125 #[must_use]
127 pub fn provider(&self) -> Provider {
128 match self {
129 #[cfg(feature = "openai")]
130 Self::OpenAI(_) => Provider::OpenAI,
131 #[cfg(feature = "anthropic")]
132 Self::Anthropic(_) => Provider::Anthropic,
133 #[cfg(feature = "grok")]
134 Self::Grok(_) => Provider::Grok,
135 #[cfg(feature = "gemini")]
136 Self::Gemini(_) => Provider::Gemini,
137 }
138 }
139}
140
141#[cfg(feature = "openai")]
142impl From<OpenAIClient> for AnyClient {
143 fn from(client: OpenAIClient) -> Self {
144 Self::OpenAI(client)
145 }
146}
147
148#[cfg(feature = "anthropic")]
149impl From<AnthropicClient> for AnyClient {
150 fn from(client: AnthropicClient) -> Self {
151 Self::Anthropic(client)
152 }
153}
154
155#[cfg(feature = "grok")]
156impl From<GrokClient> for AnyClient {
157 fn from(client: GrokClient) -> Self {
158 Self::Grok(client)
159 }
160}
161
162#[cfg(feature = "gemini")]
163impl From<GeminiClient> for AnyClient {
164 fn from(client: GeminiClient) -> Self {
165 Self::Gemini(client)
166 }
167}
168
169macro_rules! dispatch {
171 ($self:expr, $client:ident => $call:expr) => {
172 match $self {
173 #[cfg(feature = "openai")]
174 Self::OpenAI($client) => $call,
175 #[cfg(feature = "anthropic")]
176 Self::Anthropic($client) => $call,
177 #[cfg(feature = "grok")]
178 Self::Grok($client) => $call,
179 #[cfg(feature = "gemini")]
180 Self::Gemini($client) => $call,
181 }
182 };
183}
184
185#[async_trait]
186impl LLMClient for AnyClient {
187 async fn materialize<T>(&self, prompt: &str) -> Result<T>
188 where
189 T: Instructor + DeserializeOwned + Send + 'static,
190 {
191 dispatch!(self, c => c.materialize(prompt).await)
192 }
193
194 async fn materialize_with_media<T>(&self, prompt: &str, media: &[MediaFile]) -> Result<T>
195 where
196 T: Instructor + DeserializeOwned + Send + 'static,
197 {
198 dispatch!(self, c => c.materialize_with_media(prompt, media).await)
199 }
200
201 async fn materialize_with_metadata<T>(&self, prompt: &str) -> Result<MaterializeResult<T>>
202 where
203 T: Instructor + DeserializeOwned + Send + 'static,
204 {
205 dispatch!(self, c => c.materialize_with_metadata(prompt).await)
206 }
207
208 async fn generate(&self, prompt: &str) -> Result<String> {
209 dispatch!(self, c => c.generate(prompt).await)
210 }
211
212 async fn generate_with_media(&self, prompt: &str, media: &[MediaFile]) -> Result<String> {
213 dispatch!(self, c => c.generate_with_media(prompt, media).await)
214 }
215
216 async fn generate_with_metadata(&self, prompt: &str) -> Result<GenerateResult> {
217 dispatch!(self, c => c.generate_with_metadata(prompt).await)
218 }
219
220 fn from_env() -> Result<Self> {
231 #[cfg(feature = "openai")]
232 if std::env::var("OPENAI_API_KEY").is_ok() {
233 return Ok(Self::OpenAI(OpenAIClient::from_env()?));
234 }
235 #[cfg(feature = "anthropic")]
236 if std::env::var("ANTHROPIC_API_KEY").is_ok() {
237 return Ok(Self::Anthropic(AnthropicClient::from_env()?));
238 }
239 #[cfg(feature = "grok")]
240 if std::env::var("XAI_API_KEY").is_ok() {
241 return Ok(Self::Grok(GrokClient::from_env()?));
242 }
243 #[cfg(feature = "gemini")]
244 if std::env::var("GEMINI_API_KEY").is_ok() {
245 return Ok(Self::Gemini(GeminiClient::from_env()?));
246 }
247 Err(RStructorError::api_error(
248 "AnyClient",
249 ApiErrorKind::AuthenticationFailed,
250 ))
251 }
252
253 async fn list_models(&self) -> Result<Vec<ModelInfo>> {
254 dispatch!(self, c => c.list_models().await)
255 }
256}