Skip to main content

artificial_core/
model.rs

1//! Model identifiers used throughout the **artificial** workspace.
2//!
3//! The enum hierarchy keeps the *public* API blissfully simple while allowing
4//! each provider crate to map the variants onto its own naming scheme.  As a
5//! consequence you never have to type literal strings such as `"gpt-4o-mini"`
6//! in your application code—pick an enum variant instead and let the adapter
7//! translate it.
8//!
9//! # Adding more models
10//!
11//! 1. **Provider–specific enum**
12//!    Add the variant to the sub-enum (`OpenAiModel`, `AnthropicModel`, …).
13//! 2. **Mapping layer**
14//!    Update the mapping function in the provider crate
15//!    (`artificial-openai::model_map::map_model`, etc.).
16//! 3. **Compile-time safety**
17//!    The compiler will tell you if you forgot to handle the new variant in
18//!    `From<T> for Model` or in provider match statements.
19//!
20//! # Example
21//!
22//! ```rust
23//! use artificial_core::model::{Model, OpenAiModel};
24//! assert_eq!(Model::from(OpenAiModel::Gpt4oMini),
25//!            Model::OpenAi(OpenAiModel::Gpt4oMini));
26//! ```
27
28use std::str::FromStr;
29
30/// Universal identifier for an LLM model.
31///
32/// * `OpenAi` – Enumerated list of officially supported OpenAI models.
33/// * `Custom` – Any provider / model name not yet covered by a dedicated enum. Use this if you run a self-hosted or beta model.
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub enum Model {
36    /// Built-in OpenAI models (chat completion API).
37    OpenAi(OpenAiModel),
38    /// Fully qualified provider/model ID (`"provider:model-name"` or similar).
39    Custom(&'static str),
40}
41
42/// Exhaustive list of models **officially** supported by the OpenAI back-end.
43///
44/// Keeping the list small avoids accidental typos while still allowing
45/// arbitrary model names through [`Model::Custom`].
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub enum OpenAiModel {
48    Gpt5,
49    Gpt5Nano,
50    Gpt5Mini,
51    Gpt5Pro,
52    Gpt5_1,
53    Gpt5_1Codex,
54    Gpt5_1CodexMini,
55    Gpt5_1CodexMax,
56    Gpt5_2,
57    Gpt5_2Pro,
58    Gpt5_2Codex,
59    Gpt5_3,
60    Gpt5_3Codex,
61    Gpt5_4,
62    Gpt5_4Pro,
63    Gpt5Codex,
64    Gpt4_1,
65    Gpt4_1Mini,
66    Gpt4_1Nano,
67    Gpt4o,
68    Gpt4oMini,
69    O3,
70    O3Mini,
71    O4Mini,
72}
73
74impl From<OpenAiModel> for Model {
75    fn from(val: OpenAiModel) -> Self {
76        Model::OpenAi(val)
77    }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct ModelParseError(pub String);
82
83impl std::fmt::Display for ModelParseError {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(f, "unknown model identifier: {}", self.0)
86    }
87}
88
89impl std::error::Error for ModelParseError {}
90
91impl AsRef<str> for OpenAiModel {
92    fn as_ref(&self) -> &str {
93        match self {
94            OpenAiModel::Gpt5 => "gpt-5",
95            OpenAiModel::Gpt5Nano => "gpt-5-nano",
96            OpenAiModel::Gpt5Mini => "gpt-5-mini",
97            OpenAiModel::Gpt5Pro => "gpt-5-pro",
98            OpenAiModel::Gpt5_1 => "gpt-5.1",
99            OpenAiModel::Gpt5_1Codex => "gpt-5.1-codex",
100            OpenAiModel::Gpt5_1CodexMini => "gpt-5.1-codex-mini",
101            OpenAiModel::Gpt5_1CodexMax => "gpt-5.1-codex-max",
102            OpenAiModel::Gpt5_2 => "gpt-5.2",
103            OpenAiModel::Gpt5_2Pro => "gpt-5.2-pro",
104            OpenAiModel::Gpt5_2Codex => "gpt-5.2-codex",
105            OpenAiModel::Gpt5_3 => "gpt-5.3",
106            OpenAiModel::Gpt5_3Codex => "gpt-5.3-codex",
107            OpenAiModel::Gpt5_4 => "gpt-5.4",
108            OpenAiModel::Gpt5_4Pro => "gpt-5.4-pro",
109            OpenAiModel::Gpt5Codex => "gpt-5-codex",
110            OpenAiModel::Gpt4_1 => "gpt-4.1",
111            OpenAiModel::Gpt4_1Mini => "gpt-4.1-mini",
112            OpenAiModel::Gpt4_1Nano => "gpt-4.1-nano",
113            OpenAiModel::Gpt4o => "gpt-4o",
114            OpenAiModel::Gpt4oMini => "gpt-4o-mini",
115            OpenAiModel::O3 => "o3",
116            OpenAiModel::O3Mini => "o3-mini",
117            OpenAiModel::O4Mini => "o4-mini",
118        }
119    }
120}
121
122impl FromStr for OpenAiModel {
123    type Err = ModelParseError;
124
125    fn from_str(s: &str) -> Result<Self, Self::Err> {
126        match s {
127            "gpt-5" => Ok(OpenAiModel::Gpt5),
128            "gpt-5-nano" => Ok(OpenAiModel::Gpt5Nano),
129            "gpt-5-mini" => Ok(OpenAiModel::Gpt5Mini),
130            "gpt-5-pro" => Ok(OpenAiModel::Gpt5Pro),
131            "gpt-5.1" => Ok(OpenAiModel::Gpt5_1),
132            "gpt-5.1-codex" => Ok(OpenAiModel::Gpt5_1Codex),
133            "gpt-5.1-codex-mini" => Ok(OpenAiModel::Gpt5_1CodexMini),
134            "gpt-5.1-codex-max" => Ok(OpenAiModel::Gpt5_1CodexMax),
135            "gpt-5.2" => Ok(OpenAiModel::Gpt5_2),
136            "gpt-5.2-pro" => Ok(OpenAiModel::Gpt5_2Pro),
137            "gpt-5.2-codex" => Ok(OpenAiModel::Gpt5_2Codex),
138            "gpt-5.3" => Ok(OpenAiModel::Gpt5_3),
139            "gpt-5.3-codex" => Ok(OpenAiModel::Gpt5_3Codex),
140            "gpt-5.4" => Ok(OpenAiModel::Gpt5_4),
141            "gpt-5.4-pro" => Ok(OpenAiModel::Gpt5_4Pro),
142            "gpt-5-codex" => Ok(OpenAiModel::Gpt5Codex),
143            "gpt-4.1" => Ok(OpenAiModel::Gpt4_1),
144            "gpt-4.1-mini" => Ok(OpenAiModel::Gpt4_1Mini),
145            "gpt-4.1-nano" => Ok(OpenAiModel::Gpt4_1Nano),
146            "gpt-4o" => Ok(OpenAiModel::Gpt4o),
147            "gpt-4o-mini" => Ok(OpenAiModel::Gpt4oMini),
148            "o3" => Ok(OpenAiModel::O3),
149            "o3-mini" => Ok(OpenAiModel::O3Mini),
150            "o4-mini" => Ok(OpenAiModel::O4Mini),
151            _ => Err(ModelParseError(s.to_string())),
152        }
153    }
154}
155
156impl AsRef<str> for Model {
157    fn as_ref(&self) -> &str {
158        match self {
159            Model::OpenAi(model) => model.as_ref(),
160            Model::Custom(custom) => custom,
161        }
162    }
163}
164
165impl FromStr for Model {
166    type Err = ModelParseError;
167
168    fn from_str(s: &str) -> Result<Self, Self::Err> {
169        Ok(Model::OpenAi(OpenAiModel::from_str(s)?))
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::{Model, OpenAiModel};
176    use std::str::FromStr;
177
178    #[test]
179    fn openai_model_from_str_roundtrips() {
180        let models = [
181            "gpt-5",
182            "gpt-5-nano",
183            "gpt-5-mini",
184            "gpt-5-pro",
185            "gpt-5.1",
186            "gpt-5.1-codex",
187            "gpt-5.1-codex-mini",
188            "gpt-5.1-codex-max",
189            "gpt-5.2",
190            "gpt-5.2-pro",
191            "gpt-5.2-codex",
192            "gpt-5.3",
193            "gpt-5.3-codex",
194            "gpt-5.4",
195            "gpt-5.4-pro",
196            "gpt-5-codex",
197            "gpt-4.1",
198            "gpt-4.1-mini",
199            "gpt-4.1-nano",
200            "gpt-4o",
201            "gpt-4o-mini",
202            "o3",
203            "o3-mini",
204            "o4-mini",
205        ];
206
207        for model in models {
208            let parsed = OpenAiModel::from_str(model).expect("model should parse");
209            assert_eq!(parsed.as_ref(), model);
210        }
211    }
212
213    #[test]
214    fn model_as_ref_covers_openai_and_custom() {
215        let openai = Model::OpenAi(OpenAiModel::Gpt5Mini);
216        assert_eq!(openai.as_ref(), "gpt-5-mini");
217
218        let custom = Model::Custom("provider:custom-1");
219        assert_eq!(custom.as_ref(), "provider:custom-1");
220    }
221}