1use std::borrow::Cow;
7
8use bitflags::bitflags;
9use schemars::{json_schema, JsonSchema, Schema, SchemaGenerator};
10use serde::{Deserialize, Serialize};
11
12bitflags! {
13 #[derive(Copy, Debug, Default, Clone, Eq, PartialEq, Hash)]
14 pub struct ModelType: u16 {
15 const CHAT = 1 << 0;
17 const COMPLETIONS = 1 << 1;
19 const RESPONSES = 1 << 2;
21 const EMBEDDINGS = 1 << 3;
23 const RERANK = 1 << 4;
25 const GENERATE = 1 << 5;
27 const VISION = 1 << 6;
29 const TOOLS = 1 << 7;
31 const REASONING = 1 << 8;
33 const IMAGE_GEN = 1 << 9;
35 const AUDIO = 1 << 10;
37 const MODERATION = 1 << 11;
39
40 const LLM = Self::CHAT.bits() | Self::COMPLETIONS.bits()
42 | Self::RESPONSES.bits() | Self::TOOLS.bits();
43
44 const VISION_LLM = Self::LLM.bits() | Self::VISION.bits();
46
47 const REASONING_LLM = Self::LLM.bits() | Self::REASONING.bits();
49
50 const FULL_LLM = Self::VISION_LLM.bits() | Self::REASONING.bits();
52
53 const EMBED_MODEL = Self::EMBEDDINGS.bits();
55
56 const RERANK_MODEL = Self::RERANK.bits();
58
59 const IMAGE_MODEL = Self::IMAGE_GEN.bits();
61
62 const AUDIO_MODEL = Self::AUDIO.bits();
64
65 const MODERATION_MODEL = Self::MODERATION.bits();
67 }
68}
69
70const CAPABILITY_NAMES: &[(ModelType, &str)] = &[
72 (ModelType::CHAT, "chat"),
73 (ModelType::COMPLETIONS, "completions"),
74 (ModelType::RESPONSES, "responses"),
75 (ModelType::EMBEDDINGS, "embeddings"),
76 (ModelType::RERANK, "rerank"),
77 (ModelType::GENERATE, "generate"),
78 (ModelType::VISION, "vision"),
79 (ModelType::TOOLS, "tools"),
80 (ModelType::REASONING, "reasoning"),
81 (ModelType::IMAGE_GEN, "image_gen"),
82 (ModelType::AUDIO, "audio"),
83 (ModelType::MODERATION, "moderation"),
84];
85
86impl ModelType {
87 #[inline]
89 pub fn supports_chat(self) -> bool {
90 self.contains(Self::CHAT)
91 }
92
93 #[inline]
95 pub fn supports_completions(self) -> bool {
96 self.contains(Self::COMPLETIONS)
97 }
98
99 #[inline]
101 pub fn supports_responses(self) -> bool {
102 self.contains(Self::RESPONSES)
103 }
104
105 #[inline]
107 pub fn supports_embeddings(self) -> bool {
108 self.contains(Self::EMBEDDINGS)
109 }
110
111 #[inline]
113 pub fn supports_rerank(self) -> bool {
114 self.contains(Self::RERANK)
115 }
116
117 #[inline]
119 pub fn supports_generate(self) -> bool {
120 self.contains(Self::GENERATE)
121 }
122
123 #[inline]
125 pub fn supports_vision(self) -> bool {
126 self.contains(Self::VISION)
127 }
128
129 #[inline]
131 pub fn supports_tools(self) -> bool {
132 self.contains(Self::TOOLS)
133 }
134
135 #[inline]
137 pub fn supports_reasoning(self) -> bool {
138 self.contains(Self::REASONING)
139 }
140
141 #[inline]
143 pub fn supports_image_gen(self) -> bool {
144 self.contains(Self::IMAGE_GEN)
145 }
146
147 #[inline]
149 pub fn supports_audio(self) -> bool {
150 self.contains(Self::AUDIO)
151 }
152
153 #[inline]
155 pub fn supports_moderation(self) -> bool {
156 self.contains(Self::MODERATION)
157 }
158
159 pub fn supports_endpoint(self, endpoint: Endpoint) -> bool {
161 match endpoint {
162 Endpoint::Chat => self.supports_chat(),
163 Endpoint::Completions => self.supports_completions(),
164 Endpoint::Responses => self.supports_responses(),
165 Endpoint::Embeddings => self.supports_embeddings(),
166 Endpoint::Rerank => self.supports_rerank(),
167 Endpoint::Generate => self.supports_generate(),
168 Endpoint::Models => true,
169 }
170 }
171
172 pub fn as_capability_names(self) -> Vec<&'static str> {
174 let mut result = Vec::with_capacity(CAPABILITY_NAMES.len());
175 for &(flag, name) in CAPABILITY_NAMES {
176 if self.contains(flag) {
177 result.push(name);
178 }
179 }
180 result
181 }
182
183 #[inline]
185 pub fn is_llm(self) -> bool {
186 self.supports_chat()
187 }
188
189 #[inline]
191 pub fn is_embedding_model(self) -> bool {
192 self.supports_embeddings() && !self.supports_chat()
193 }
194
195 #[inline]
197 pub fn is_reranker(self) -> bool {
198 self.supports_rerank() && !self.supports_chat()
199 }
200
201 #[inline]
203 pub fn is_image_model(self) -> bool {
204 self.supports_image_gen() && !self.supports_chat()
205 }
206
207 #[inline]
209 pub fn is_audio_model(self) -> bool {
210 self.supports_audio() && !self.supports_chat()
211 }
212
213 #[inline]
215 pub fn is_moderation_model(self) -> bool {
216 self.supports_moderation() && !self.supports_chat()
217 }
218}
219
220impl std::fmt::Display for ModelType {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 let names = self.as_capability_names();
223 if names.is_empty() {
224 write!(f, "none")
225 } else {
226 write!(f, "{}", names.join(","))
227 }
228 }
229}
230
231impl Serialize for ModelType {
232 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
233 where
234 S: serde::Serializer,
235 {
236 use serde::ser::SerializeSeq;
237 let names = self.as_capability_names();
238 let mut seq = serializer.serialize_seq(Some(names.len()))?;
239 for name in names {
240 seq.serialize_element(name)?;
241 }
242 seq.end()
243 }
244}
245
246impl<'de> Deserialize<'de> for ModelType {
247 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
248 where
249 D: serde::Deserializer<'de>,
250 {
251 use serde::de;
252
253 struct ModelTypeVisitor;
254
255 impl<'de> de::Visitor<'de> for ModelTypeVisitor {
256 type Value = ModelType;
257
258 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 f.write_str("an array of capability names or a u16 bitfield")
260 }
261
262 fn visit_u64<E: de::Error>(self, v: u64) -> Result<ModelType, E> {
264 let bits = u16::try_from(v)
265 .map_err(|_| E::custom(format!("ModelType bits out of u16 range: {v}")))?;
266 ModelType::from_bits(bits)
267 .ok_or_else(|| E::custom(format!("invalid ModelType bits: {bits}")))
268 }
269
270 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<ModelType, A::Error> {
272 let mut model_type = ModelType::empty();
273 while let Some(name) = seq.next_element::<String>()? {
274 let flag = CAPABILITY_NAMES
275 .iter()
276 .find(|(_, n)| *n == name.as_str())
277 .map(|(f, _)| *f)
278 .ok_or_else(|| {
279 de::Error::custom(format!("unknown ModelType capability: {name}"))
280 })?;
281 model_type |= flag;
282 }
283 Ok(model_type)
284 }
285 }
286
287 deserializer.deserialize_any(ModelTypeVisitor)
288 }
289}
290
291impl JsonSchema for ModelType {
293 fn schema_name() -> Cow<'static, str> {
294 "ModelType".into()
295 }
296
297 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
298 json_schema!({
299 "type": "array",
300 "description": "Bitflag capabilities serialized as an array of capability names",
301 "items": {
302 "type": "string",
303 "enum": [
304 "chat",
305 "completions",
306 "responses",
307 "embeddings",
308 "rerank",
309 "generate",
310 "vision",
311 "tools",
312 "reasoning",
313 "image_gen",
314 "audio",
315 "moderation"
316 ]
317 }
318 })
319 }
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, schemars::JsonSchema)]
324#[serde(rename_all = "lowercase")]
325pub enum Endpoint {
326 Chat,
328 Completions,
330 Responses,
332 Embeddings,
334 Rerank,
336 Generate,
338 Models,
340}
341
342impl Endpoint {
343 pub fn path(self) -> &'static str {
345 match self {
346 Endpoint::Chat => "/v1/chat/completions",
347 Endpoint::Completions => "/v1/completions",
348 Endpoint::Responses => "/v1/responses",
349 Endpoint::Embeddings => "/v1/embeddings",
350 Endpoint::Rerank => "/v1/rerank",
351 Endpoint::Generate => "/generate",
352 Endpoint::Models => "/v1/models",
353 }
354 }
355
356 pub fn from_path(path: &str) -> Option<Self> {
358 let path = path.trim_end_matches('/');
359 match path {
360 "/v1/chat/completions" => Some(Endpoint::Chat),
361 "/v1/completions" => Some(Endpoint::Completions),
362 "/v1/responses" => Some(Endpoint::Responses),
363 "/v1/embeddings" => Some(Endpoint::Embeddings),
364 "/v1/rerank" => Some(Endpoint::Rerank),
365 "/generate" => Some(Endpoint::Generate),
366 "/v1/models" => Some(Endpoint::Models),
367 _ => None,
368 }
369 }
370
371 pub fn required_capability(self) -> Option<ModelType> {
373 match self {
374 Endpoint::Chat => Some(ModelType::CHAT),
375 Endpoint::Completions => Some(ModelType::COMPLETIONS),
376 Endpoint::Responses => Some(ModelType::RESPONSES),
377 Endpoint::Embeddings => Some(ModelType::EMBEDDINGS),
378 Endpoint::Rerank => Some(ModelType::RERANK),
379 Endpoint::Generate => Some(ModelType::GENERATE),
380 Endpoint::Models => None,
381 }
382 }
383}
384
385impl std::fmt::Display for Endpoint {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 match self {
388 Endpoint::Chat => write!(f, "chat"),
389 Endpoint::Completions => write!(f, "completions"),
390 Endpoint::Responses => write!(f, "responses"),
391 Endpoint::Embeddings => write!(f, "embeddings"),
392 Endpoint::Rerank => write!(f, "rerank"),
393 Endpoint::Generate => write!(f, "generate"),
394 Endpoint::Models => write!(f, "models"),
395 }
396 }
397}