1use std::collections::HashMap;
4use std::fmt;
5
6use ferrin_spec::EmbeddingModelRef;
7use ferrin_spec::ImageModelRef;
8use ferrin_spec::LanguageModelRef;
9use ferrin_spec::ModelKind;
10use ferrin_spec::NoSuchModelError;
11use ferrin_spec::Provider;
12use ferrin_spec::ProviderId;
13use ferrin_spec::ProviderRef;
14use ferrin_spec::RerankingModelRef;
15use ferrin_spec::SpeechModelRef;
16use ferrin_spec::SpeechTranslationModelRef;
17use ferrin_spec::TranscriptionModelRef;
18use ferrin_spec::VideoModelRef;
19
20#[must_use]
22pub fn custom_provider(id: impl Into<ProviderId>) -> CustomProviderBuilder {
23 CustomProviderBuilder {
24 provider: CustomProvider {
25 id: id.into(),
26 language_models: HashMap::new(),
27 embedding_models: HashMap::new(),
28 image_models: HashMap::new(),
29 transcription_models: HashMap::new(),
30 speech_models: HashMap::new(),
31 reranking_models: HashMap::new(),
32 video_models: HashMap::new(),
33 speech_translation_models: HashMap::new(),
34 fallback: None,
35 },
36 }
37}
38
39#[derive(Clone)]
42pub struct CustomProvider {
43 id: ProviderId,
44 language_models: HashMap<String, LanguageModelRef>,
45 embedding_models: HashMap<String, EmbeddingModelRef>,
46 image_models: HashMap<String, ImageModelRef>,
47 transcription_models: HashMap<String, TranscriptionModelRef>,
48 speech_models: HashMap<String, SpeechModelRef>,
49 reranking_models: HashMap<String, RerankingModelRef>,
50 video_models: HashMap<String, VideoModelRef>,
51 speech_translation_models: HashMap<String, SpeechTranslationModelRef>,
52 fallback: Option<ProviderRef>,
53}
54
55impl fmt::Debug for CustomProvider {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.debug_struct("CustomProvider")
58 .field("id", &self.id)
59 .field(
60 "language_models",
61 &self.language_models.keys().collect::<Vec<_>>(),
62 )
63 .field(
64 "embedding_models",
65 &self.embedding_models.keys().collect::<Vec<_>>(),
66 )
67 .field(
68 "image_models",
69 &self.image_models.keys().collect::<Vec<_>>(),
70 )
71 .field("fallback", &self.fallback.is_some())
72 .finish_non_exhaustive()
73 }
74}
75
76macro_rules! resolve {
77 ($self:ident, $map:ident, $fallback:ident, $model_id:expr, $kind:expr) => {{
78 if let Some(model) = $self.$map.get($model_id) {
79 return Ok(model.clone());
80 }
81 match &$self.fallback {
82 Some(fallback) => fallback.$fallback($model_id),
83 None => Err(NoSuchModelError::new($model_id, $kind).with_provider(&$self.id)),
84 }
85 }};
86}
87
88impl Provider for CustomProvider {
89 fn provider_id(&self) -> &ProviderId {
90 &self.id
91 }
92
93 fn language_model(&self, model_id: &str) -> Result<LanguageModelRef, NoSuchModelError> {
94 resolve!(
95 self,
96 language_models,
97 language_model,
98 model_id,
99 ModelKind::Language
100 )
101 }
102
103 fn embedding_model(&self, model_id: &str) -> Result<EmbeddingModelRef, NoSuchModelError> {
104 resolve!(
105 self,
106 embedding_models,
107 embedding_model,
108 model_id,
109 ModelKind::Embedding
110 )
111 }
112
113 fn image_model(&self, model_id: &str) -> Result<ImageModelRef, NoSuchModelError> {
114 resolve!(self, image_models, image_model, model_id, ModelKind::Image)
115 }
116
117 fn transcription_model(
118 &self,
119 model_id: &str,
120 ) -> Result<TranscriptionModelRef, NoSuchModelError> {
121 resolve!(
122 self,
123 transcription_models,
124 transcription_model,
125 model_id,
126 ModelKind::Transcription
127 )
128 }
129
130 fn speech_model(&self, model_id: &str) -> Result<SpeechModelRef, NoSuchModelError> {
131 resolve!(
132 self,
133 speech_models,
134 speech_model,
135 model_id,
136 ModelKind::Speech
137 )
138 }
139
140 fn reranking_model(&self, model_id: &str) -> Result<RerankingModelRef, NoSuchModelError> {
141 resolve!(
142 self,
143 reranking_models,
144 reranking_model,
145 model_id,
146 ModelKind::Reranking
147 )
148 }
149
150 fn video_model(&self, model_id: &str) -> Result<VideoModelRef, NoSuchModelError> {
151 resolve!(self, video_models, video_model, model_id, ModelKind::Video)
152 }
153
154 fn speech_translation_model(
155 &self,
156 model_id: &str,
157 ) -> Result<SpeechTranslationModelRef, NoSuchModelError> {
158 resolve!(
159 self,
160 speech_translation_models,
161 speech_translation_model,
162 model_id,
163 ModelKind::SpeechTranslation
164 )
165 }
166
167 fn realtime(&self) -> Option<ferrin_spec::RealtimeFactoryRef> {
168 self.fallback
169 .as_ref()
170 .and_then(|fallback| fallback.realtime())
171 }
172
173 fn files(&self) -> Option<ferrin_spec::FilesRef> {
174 self.fallback.as_ref().and_then(|fallback| fallback.files())
175 }
176
177 fn skills(&self) -> Option<ferrin_spec::SkillsRef> {
178 self.fallback
179 .as_ref()
180 .and_then(|fallback| fallback.skills())
181 }
182
183 fn batch(&self) -> Option<ferrin_spec::BatchRef> {
184 self.fallback.as_ref().and_then(|fallback| fallback.batch())
185 }
186}
187
188#[derive(Debug)]
190pub struct CustomProviderBuilder {
191 provider: CustomProvider,
192}
193
194impl CustomProviderBuilder {
195 #[must_use]
197 pub fn language_model(
198 mut self,
199 id: impl Into<String>,
200 model: impl Into<LanguageModelRef>,
201 ) -> Self {
202 self.provider
203 .language_models
204 .insert(id.into(), model.into());
205 self
206 }
207
208 #[must_use]
210 pub fn embedding_model(
211 mut self,
212 id: impl Into<String>,
213 model: impl Into<EmbeddingModelRef>,
214 ) -> Self {
215 self.provider
216 .embedding_models
217 .insert(id.into(), model.into());
218 self
219 }
220
221 #[must_use]
223 pub fn image_model(mut self, id: impl Into<String>, model: impl Into<ImageModelRef>) -> Self {
224 self.provider.image_models.insert(id.into(), model.into());
225 self
226 }
227
228 #[must_use]
230 pub fn transcription_model(
231 mut self,
232 id: impl Into<String>,
233 model: impl Into<TranscriptionModelRef>,
234 ) -> Self {
235 self.provider
236 .transcription_models
237 .insert(id.into(), model.into());
238 self
239 }
240
241 #[must_use]
243 pub fn speech_model(mut self, id: impl Into<String>, model: impl Into<SpeechModelRef>) -> Self {
244 self.provider.speech_models.insert(id.into(), model.into());
245 self
246 }
247
248 #[must_use]
250 pub fn reranking_model(
251 mut self,
252 id: impl Into<String>,
253 model: impl Into<RerankingModelRef>,
254 ) -> Self {
255 self.provider
256 .reranking_models
257 .insert(id.into(), model.into());
258 self
259 }
260
261 #[must_use]
263 pub fn video_model(mut self, id: impl Into<String>, model: impl Into<VideoModelRef>) -> Self {
264 self.provider.video_models.insert(id.into(), model.into());
265 self
266 }
267
268 #[must_use]
270 pub fn speech_translation_model(
271 mut self,
272 id: impl Into<String>,
273 model: impl Into<SpeechTranslationModelRef>,
274 ) -> Self {
275 self.provider
276 .speech_translation_models
277 .insert(id.into(), model.into());
278 self
279 }
280
281 #[must_use]
283 pub fn fallback(mut self, provider: ProviderRef) -> Self {
284 self.provider.fallback = Some(provider);
285 self
286 }
287
288 #[must_use]
290 pub fn build(self) -> CustomProvider {
291 self.provider
292 }
293}