1use serde::{Deserialize, Serialize};
44
45#[derive(Debug, Clone, PartialEq)]
54pub enum ParameterRestriction {
55 Any,
57 FixedValue(f64),
59 NotSupported,
61}
62
63#[derive(Debug, Clone)]
83pub struct ParameterSupport {
84 pub temperature: ParameterRestriction,
86 pub frequency_penalty: ParameterRestriction,
88 pub presence_penalty: ParameterRestriction,
90 pub logprobs: bool,
92 pub top_logprobs: bool,
94 pub logit_bias: bool,
96 pub n_multiple: bool,
98 pub top_p: ParameterRestriction,
100 pub reasoning: bool,
102}
103
104impl ParameterSupport {
105 pub fn standard_model() -> Self {
109 Self {
110 temperature: ParameterRestriction::Any,
111 frequency_penalty: ParameterRestriction::Any,
112 presence_penalty: ParameterRestriction::Any,
113 logprobs: true,
114 top_logprobs: true,
115 logit_bias: true,
116 n_multiple: true,
117 top_p: ParameterRestriction::Any,
118 reasoning: false,
119 }
120 }
121
122 pub fn reasoning_model() -> Self {
134 Self {
135 temperature: ParameterRestriction::FixedValue(1.0),
136 frequency_penalty: ParameterRestriction::FixedValue(0.0),
137 presence_penalty: ParameterRestriction::FixedValue(0.0),
138 logprobs: false,
139 top_logprobs: false,
140 logit_bias: false,
141 n_multiple: true,
142 top_p: ParameterRestriction::FixedValue(1.0),
143 reasoning: true,
144 }
145 }
146
147 pub fn search_model() -> Self {
155 Self { n_multiple: false, reasoning: false, ..Self::reasoning_model() }
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
226#[non_exhaustive]
227pub enum ChatModel {
228 #[serde(rename = "gpt-6-astra")]
235 Gpt6Astra,
236
237 #[serde(rename = "gpt-6-sol")]
243 Gpt6Sol,
244
245 #[serde(rename = "gpt-6-luna")]
251 Gpt6Luna,
252
253 #[serde(rename = "gpt-5.6")]
256 Gpt5_6,
257
258 #[serde(rename = "gpt-5.6-sol")]
264 Gpt5_6Sol,
265
266 #[serde(rename = "gpt-5.6-terra")]
271 Gpt5_6Terra,
272
273 #[serde(rename = "gpt-5.6-luna")]
278 Gpt5_6Luna,
279
280 #[serde(rename = "gpt-5.5")]
286 Gpt5_5,
287
288 #[serde(rename = "gpt-5.5-pro")]
293 Gpt5_5Pro,
294
295 #[serde(rename = "gpt-5.4")]
301 Gpt5_4,
302
303 #[serde(rename = "gpt-5.4-pro")]
308 Gpt5_4Pro,
309
310 #[serde(rename = "gpt-5.4-mini")]
315 Gpt5_4Mini,
316
317 #[serde(rename = "gpt-5.4-nano")]
322 Gpt5_4Nano,
323
324 #[serde(rename = "gpt-5.3-chat-latest")]
331 Gpt5_3ChatLatest,
332
333 #[serde(rename = "gpt-5.3-codex")]
340 Gpt5_3Codex,
341
342 #[serde(rename = "gpt-5.2-codex")]
348 Gpt5_2Codex,
349
350 #[serde(rename = "gpt-5.1-codex")]
354 Gpt5_1Codex,
355
356 #[serde(rename = "gpt-5-search-api")]
361 Gpt5SearchApi,
362
363 #[serde(rename = "gpt-4o-search-preview")]
365 Gpt4oSearchPreview,
366
367 #[serde(rename = "gpt-4o-mini-search-preview")]
369 Gpt4oMiniSearchPreview,
370
371 #[serde(rename = "gpt-audio")]
376 GptAudio,
377
378 #[serde(rename = "gpt-audio-1.5")]
380 GptAudio1_5,
381
382 #[serde(rename = "gpt-audio-mini")]
384 GptAudioMini,
385
386 #[serde(rename = "gpt-5")]
389 Gpt5,
390
391 #[serde(rename = "gpt-5-pro")]
395 Gpt5Pro,
396
397 #[serde(rename = "gpt-5.2")]
403 Gpt5_2,
404
405 #[serde(rename = "gpt-5.2-chat-latest")]
412 Gpt5_2ChatLatest,
413
414 #[serde(rename = "gpt-5.2-pro")]
419 Gpt5_2Pro,
420
421 #[serde(rename = "gpt-5.1")]
426 Gpt5_1,
427
428 #[serde(rename = "gpt-5.1-chat-latest")]
433 Gpt5_1ChatLatest,
434
435 #[serde(rename = "gpt-5.1-codex-max")]
440 Gpt5_1CodexMax,
441
442 #[serde(rename = "gpt-5-mini")]
444 Gpt5Mini,
445
446 #[serde(rename = "gpt-5-nano")]
448 Gpt5Nano,
449
450 #[serde(rename = "gpt-4.1")]
453 Gpt4_1,
454
455 #[serde(rename = "gpt-4.1-mini")]
457 Gpt4_1Mini,
458
459 #[serde(rename = "gpt-4.1-nano")]
461 Gpt4_1Nano,
462
463 #[serde(rename = "gpt-4o")]
466 Gpt4o,
467
468 #[serde(rename = "gpt-4o-mini")]
470 #[default]
471 Gpt4oMini,
472
473 #[serde(rename = "gpt-4o-audio-preview")]
475 Gpt4oAudioPreview,
476
477 #[serde(rename = "gpt-4-turbo")]
480 Gpt4Turbo,
481
482 #[serde(rename = "gpt-4")]
484 Gpt4,
485
486 #[serde(rename = "gpt-3.5-turbo")]
489 Gpt3_5Turbo,
490
491 #[serde(rename = "gpt-3.5-turbo-16k")]
493 Gpt3_5Turbo16k,
494
495 #[serde(rename = "o1")]
498 O1,
499
500 #[serde(rename = "o1-pro")]
502 O1Pro,
503
504 #[serde(rename = "o3")]
506 O3,
507
508 #[serde(rename = "o3-pro")]
512 O3Pro,
513
514 #[serde(rename = "o3-mini")]
516 O3Mini,
517
518 #[serde(rename = "o4-mini")]
520 O4Mini,
521
522 #[serde(untagged)]
525 Custom(String),
526}
527
528impl ChatModel {
529 pub fn as_str(&self) -> &str {
541 match self {
542 Self::Gpt6Astra => "gpt-6-astra",
544 Self::Gpt6Sol => "gpt-6-sol",
545 Self::Gpt6Luna => "gpt-6-luna",
546 Self::Gpt5_6 => "gpt-5.6",
548 Self::Gpt5_6Sol => "gpt-5.6-sol",
549 Self::Gpt5_6Terra => "gpt-5.6-terra",
550 Self::Gpt5_6Luna => "gpt-5.6-luna",
551 Self::Gpt5_5 => "gpt-5.5",
553 Self::Gpt5_5Pro => "gpt-5.5-pro",
554 Self::Gpt5_4 => "gpt-5.4",
556 Self::Gpt5_4Pro => "gpt-5.4-pro",
557 Self::Gpt5_4Mini => "gpt-5.4-mini",
558 Self::Gpt5_4Nano => "gpt-5.4-nano",
559 Self::Gpt5_3ChatLatest => "gpt-5.3-chat-latest",
561 Self::Gpt5_3Codex => "gpt-5.3-codex",
563 Self::Gpt5_2Codex => "gpt-5.2-codex",
564 Self::Gpt5_1Codex => "gpt-5.1-codex",
565 Self::Gpt5SearchApi => "gpt-5-search-api",
567 Self::Gpt4oSearchPreview => "gpt-4o-search-preview",
568 Self::Gpt4oMiniSearchPreview => "gpt-4o-mini-search-preview",
569 Self::GptAudio => "gpt-audio",
571 Self::GptAudio1_5 => "gpt-audio-1.5",
572 Self::GptAudioMini => "gpt-audio-mini",
573 Self::Gpt5 => "gpt-5",
575 Self::Gpt5Pro => "gpt-5-pro",
576 Self::Gpt5_2 => "gpt-5.2",
577 Self::Gpt5_2ChatLatest => "gpt-5.2-chat-latest",
578 Self::Gpt5_2Pro => "gpt-5.2-pro",
579 Self::Gpt5_1 => "gpt-5.1",
580 Self::Gpt5_1ChatLatest => "gpt-5.1-chat-latest",
581 Self::Gpt5_1CodexMax => "gpt-5.1-codex-max",
582 Self::Gpt5Mini => "gpt-5-mini",
583 Self::Gpt5Nano => "gpt-5-nano",
584 Self::Gpt4_1 => "gpt-4.1",
586 Self::Gpt4_1Mini => "gpt-4.1-mini",
587 Self::Gpt4_1Nano => "gpt-4.1-nano",
588 Self::Gpt4o => "gpt-4o",
590 Self::Gpt4oMini => "gpt-4o-mini",
591 Self::Gpt4oAudioPreview => "gpt-4o-audio-preview",
592 Self::Gpt4Turbo => "gpt-4-turbo",
594 Self::Gpt4 => "gpt-4",
595 Self::Gpt3_5Turbo => "gpt-3.5-turbo",
597 Self::Gpt3_5Turbo16k => "gpt-3.5-turbo-16k",
598 Self::O1 => "o1",
600 Self::O1Pro => "o1-pro",
601 Self::O3 => "o3",
602 Self::O3Pro => "o3-pro",
603 Self::O3Mini => "o3-mini",
604 Self::O4Mini => "o4-mini",
605 Self::Custom(s) => s.as_str(),
607 }
608 }
609
610 pub fn is_reasoning_model(&self) -> bool {
629 matches!(
630 self,
631 Self::Gpt6Astra | Self::Gpt6Sol | Self::Gpt6Luna |
633 Self::Gpt5_6 | Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna |
635 Self::Gpt5_5 | Self::Gpt5_5Pro |
637 Self::Gpt5_4 | Self::Gpt5_4Pro | Self::Gpt5_4Mini | Self::Gpt5_4Nano |
639 Self::Gpt5_3Codex | Self::Gpt5_2Codex | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax |
641 Self::Gpt5 | Self::Gpt5Pro | Self::Gpt5_2 | Self::Gpt5_2Pro | Self::Gpt5_1 | Self::Gpt5Mini | Self::Gpt5Nano |
643 Self::O1 | Self::O1Pro | Self::O3 | Self::O3Pro | Self::O3Mini | Self::O4Mini
645 ) || matches!(
646 self,
647 Self::Custom(s) if !s.ends_with("-chat-latest")
651 && !s.contains("-search")
652 && (s.starts_with("gpt-5") || s.starts_with("o1") || s.starts_with("o3") || s.starts_with("o4"))
653 )
654 }
655
656 pub fn is_search_model(&self) -> bool {
673 matches!(self, Self::Gpt5SearchApi | Self::Gpt4oSearchPreview | Self::Gpt4oMiniSearchPreview)
674 || matches!(self, Self::Custom(s) if s.contains("-search"))
675 }
676
677 pub fn parameter_support(&self) -> ParameterSupport {
701 if self.is_search_model() {
702 ParameterSupport::search_model()
703 } else if self.is_reasoning_model() {
704 ParameterSupport::reasoning_model()
705 } else {
706 ParameterSupport::standard_model()
707 }
708 }
709
710 pub fn custom(model_id: impl Into<String>) -> Self {
723 Self::Custom(model_id.into())
724 }
725}
726
727impl std::fmt::Display for ChatModel {
728 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
729 write!(f, "{}", self.as_str())
730 }
731}
732
733impl From<&str> for ChatModel {
734 fn from(s: &str) -> Self {
735 match s {
736 "gpt-6-astra" => Self::Gpt6Astra,
738 "gpt-6-sol" => Self::Gpt6Sol,
739 "gpt-6-luna" => Self::Gpt6Luna,
740 "gpt-5.6" => Self::Gpt5_6,
742 "gpt-5.6-sol" => Self::Gpt5_6Sol,
743 "gpt-5.6-terra" => Self::Gpt5_6Terra,
744 "gpt-5.6-luna" => Self::Gpt5_6Luna,
745 "gpt-5.5" => Self::Gpt5_5,
747 "gpt-5.5-pro" => Self::Gpt5_5Pro,
748 "gpt-5.4" => Self::Gpt5_4,
750 "gpt-5.4-pro" => Self::Gpt5_4Pro,
751 "gpt-5.4-mini" => Self::Gpt5_4Mini,
752 "gpt-5.4-nano" => Self::Gpt5_4Nano,
753 "gpt-5.3-chat-latest" => Self::Gpt5_3ChatLatest,
755 "gpt-5.3-codex" => Self::Gpt5_3Codex,
757 "gpt-5.2-codex" => Self::Gpt5_2Codex,
758 "gpt-5.1-codex" => Self::Gpt5_1Codex,
759 "gpt-5-search-api" => Self::Gpt5SearchApi,
761 "gpt-4o-search-preview" => Self::Gpt4oSearchPreview,
762 "gpt-4o-mini-search-preview" => Self::Gpt4oMiniSearchPreview,
763 "gpt-audio" => Self::GptAudio,
765 "gpt-audio-1.5" => Self::GptAudio1_5,
766 "gpt-audio-mini" => Self::GptAudioMini,
767 "gpt-5" => Self::Gpt5,
769 "gpt-5-pro" => Self::Gpt5Pro,
770 "gpt-5.2" => Self::Gpt5_2,
771 "gpt-5.2-chat-latest" => Self::Gpt5_2ChatLatest,
772 "gpt-5.2-pro" => Self::Gpt5_2Pro,
773 "gpt-5.1" => Self::Gpt5_1,
774 "gpt-5.1-chat-latest" => Self::Gpt5_1ChatLatest,
775 "gpt-5.1-codex-max" => Self::Gpt5_1CodexMax,
776 "gpt-5-mini" => Self::Gpt5Mini,
777 "gpt-5-nano" => Self::Gpt5Nano,
778 "gpt-4.1" => Self::Gpt4_1,
780 "gpt-4.1-mini" => Self::Gpt4_1Mini,
781 "gpt-4.1-nano" => Self::Gpt4_1Nano,
782 "gpt-4o" => Self::Gpt4o,
784 "gpt-4o-mini" => Self::Gpt4oMini,
785 "gpt-4o-audio-preview" => Self::Gpt4oAudioPreview,
786 "gpt-4-turbo" => Self::Gpt4Turbo,
788 "gpt-4" => Self::Gpt4,
789 "gpt-3.5-turbo" => Self::Gpt3_5Turbo,
791 "gpt-3.5-turbo-16k" => Self::Gpt3_5Turbo16k,
792 "o1" => Self::O1,
794 "o1-pro" => Self::O1Pro,
795 "o3" => Self::O3,
796 "o3-pro" => Self::O3Pro,
797 "o3-mini" => Self::O3Mini,
798 "o4-mini" => Self::O4Mini,
799 other => Self::Custom(other.to_string()),
801 }
802 }
803}
804
805impl From<String> for ChatModel {
806 fn from(s: String) -> Self {
807 Self::from(s.as_str())
808 }
809}
810
811#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
840#[non_exhaustive]
841pub enum EmbeddingModel {
842 #[serde(rename = "text-embedding-3-small")]
848 #[default]
849 TextEmbedding3Small,
850
851 #[serde(rename = "text-embedding-3-large")]
857 TextEmbedding3Large,
858
859 #[serde(rename = "text-embedding-ada-002")]
865 TextEmbeddingAda002,
866}
867
868impl EmbeddingModel {
869 pub fn as_str(&self) -> &str {
871 match self {
872 Self::TextEmbedding3Small => "text-embedding-3-small",
873 Self::TextEmbedding3Large => "text-embedding-3-large",
874 Self::TextEmbeddingAda002 => "text-embedding-ada-002",
875 }
876 }
877
878 pub fn dimensions(&self) -> usize {
883 match self {
884 Self::TextEmbedding3Small => 1536,
885 Self::TextEmbedding3Large => 3072,
886 Self::TextEmbeddingAda002 => 1536,
887 }
888 }
889}
890
891impl std::fmt::Display for EmbeddingModel {
892 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
893 write!(f, "{}", self.as_str())
894 }
895}
896
897impl From<&str> for EmbeddingModel {
898 fn from(s: &str) -> Self {
899 match s {
900 "text-embedding-3-small" => Self::TextEmbedding3Small,
901 "text-embedding-3-large" => Self::TextEmbedding3Large,
902 "text-embedding-ada-002" => Self::TextEmbeddingAda002,
903 _ => Self::TextEmbedding3Small, }
905 }
906}
907
908#[allow(non_camel_case_types)]
934#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
935#[non_exhaustive]
936pub enum RealtimeModel {
937 #[serde(rename = "gpt-realtime-2.1")]
943 GptRealtime2_1,
944
945 #[serde(rename = "gpt-realtime-2.1-mini")]
947 GptRealtime2_1Mini,
948
949 #[serde(rename = "gpt-realtime-2")]
951 GptRealtime2,
952
953 #[serde(rename = "gpt-realtime")]
955 GptRealtime,
956
957 #[serde(rename = "gpt-realtime-mini")]
959 GptRealtimeMini,
960
961 #[serde(rename = "gpt-realtime-1.5")]
963 GptRealtime1_5,
964
965 #[serde(rename = "gpt-realtime-translate")]
969 GptRealtimeTranslate,
970
971 #[serde(rename = "gpt-realtime-2025-08-28")]
977 #[default]
978 GptRealtime_2025_08_28,
979
980 #[serde(untagged)]
982 Custom(String),
983}
984
985impl RealtimeModel {
986 pub fn as_str(&self) -> &str {
988 match self {
989 Self::GptRealtime2_1 => "gpt-realtime-2.1",
990 Self::GptRealtime2_1Mini => "gpt-realtime-2.1-mini",
991 Self::GptRealtime2 => "gpt-realtime-2",
992 Self::GptRealtime => "gpt-realtime",
993 Self::GptRealtimeMini => "gpt-realtime-mini",
994 Self::GptRealtime1_5 => "gpt-realtime-1.5",
995 Self::GptRealtimeTranslate => "gpt-realtime-translate",
996 Self::GptRealtime_2025_08_28 => "gpt-realtime-2025-08-28",
997 Self::Custom(s) => s.as_str(),
998 }
999 }
1000
1001 pub fn custom(model_id: impl Into<String>) -> Self {
1003 Self::Custom(model_id.into())
1004 }
1005}
1006
1007impl std::fmt::Display for RealtimeModel {
1008 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1009 write!(f, "{}", self.as_str())
1010 }
1011}
1012
1013impl From<&str> for RealtimeModel {
1014 fn from(s: &str) -> Self {
1015 match s {
1016 "gpt-realtime-2.1" => Self::GptRealtime2_1,
1017 "gpt-realtime-2.1-mini" => Self::GptRealtime2_1Mini,
1018 "gpt-realtime-2" => Self::GptRealtime2,
1019 "gpt-realtime" => Self::GptRealtime,
1020 "gpt-realtime-mini" => Self::GptRealtimeMini,
1021 "gpt-realtime-1.5" => Self::GptRealtime1_5,
1022 "gpt-realtime-translate" => Self::GptRealtimeTranslate,
1023 "gpt-realtime-2025-08-28" => Self::GptRealtime_2025_08_28,
1024 other => Self::Custom(other.to_string()),
1025 }
1026 }
1027}
1028
1029#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1035#[non_exhaustive]
1036pub enum LiveModel {
1037 #[serde(rename = "gpt-live-1")]
1039 #[default]
1040 GptLive1,
1041
1042 #[serde(untagged)]
1044 Custom(String),
1045}
1046
1047impl LiveModel {
1048 pub fn as_str(&self) -> &str {
1050 match self {
1051 Self::GptLive1 => "gpt-live-1",
1052 Self::Custom(model) => model,
1053 }
1054 }
1055
1056 pub fn custom(model: impl Into<String>) -> Self {
1058 Self::Custom(model.into())
1059 }
1060}
1061
1062impl std::fmt::Display for LiveModel {
1063 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1064 formatter.write_str(self.as_str())
1065 }
1066}
1067
1068impl From<&str> for LiveModel {
1069 fn from(model: &str) -> Self {
1070 match model {
1071 "gpt-live-1" => Self::GptLive1,
1072 other => Self::Custom(other.to_string()),
1073 }
1074 }
1075}
1076
1077#[allow(non_camel_case_types)]
1117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1118#[non_exhaustive]
1119pub enum FineTuningModel {
1120 #[serde(rename = "gpt-4.1-2025-04-14")]
1123 Gpt41_2025_04_14,
1124
1125 #[serde(rename = "gpt-4.1-mini-2025-04-14")]
1127 Gpt41Mini_2025_04_14,
1128
1129 #[serde(rename = "gpt-4.1-nano-2025-04-14")]
1131 Gpt41Nano_2025_04_14,
1132
1133 #[serde(rename = "gpt-4o-mini-2024-07-18")]
1136 #[default]
1137 Gpt4oMini_2024_07_18,
1138
1139 #[serde(rename = "gpt-4o-2024-08-06")]
1141 Gpt4o_2024_08_06,
1142
1143 #[serde(rename = "gpt-4-0613")]
1146 Gpt4_0613,
1147
1148 #[serde(rename = "gpt-3.5-turbo-0125")]
1151 Gpt35Turbo_0125,
1152
1153 #[serde(rename = "gpt-3.5-turbo-1106")]
1155 Gpt35Turbo_1106,
1156
1157 #[serde(rename = "gpt-3.5-turbo-0613")]
1159 Gpt35Turbo_0613,
1160
1161 #[serde(rename = "babbage-002")]
1163 Babbage002,
1164
1165 #[serde(rename = "davinci-002")]
1167 Davinci002,
1168}
1169
1170impl FineTuningModel {
1171 pub fn as_str(&self) -> &str {
1173 match self {
1174 Self::Gpt41_2025_04_14 => "gpt-4.1-2025-04-14",
1176 Self::Gpt41Mini_2025_04_14 => "gpt-4.1-mini-2025-04-14",
1177 Self::Gpt41Nano_2025_04_14 => "gpt-4.1-nano-2025-04-14",
1178 Self::Gpt4oMini_2024_07_18 => "gpt-4o-mini-2024-07-18",
1180 Self::Gpt4o_2024_08_06 => "gpt-4o-2024-08-06",
1181 Self::Gpt4_0613 => "gpt-4-0613",
1183 Self::Gpt35Turbo_0125 => "gpt-3.5-turbo-0125",
1185 Self::Gpt35Turbo_1106 => "gpt-3.5-turbo-1106",
1186 Self::Gpt35Turbo_0613 => "gpt-3.5-turbo-0613",
1187 Self::Babbage002 => "babbage-002",
1188 Self::Davinci002 => "davinci-002",
1189 }
1190 }
1191}
1192
1193impl std::fmt::Display for FineTuningModel {
1194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1195 write!(f, "{}", self.as_str())
1196 }
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use super::*;
1202
1203 #[test]
1204 fn test_chat_model_as_str() {
1205 assert_eq!(ChatModel::Gpt4oMini.as_str(), "gpt-4o-mini");
1206 assert_eq!(ChatModel::O3Mini.as_str(), "o3-mini");
1207 assert_eq!(ChatModel::Gpt4_1.as_str(), "gpt-4.1");
1208 assert_eq!(ChatModel::Gpt5_2.as_str(), "gpt-5.2");
1210 assert_eq!(ChatModel::Gpt5_2ChatLatest.as_str(), "gpt-5.2-chat-latest");
1211 assert_eq!(ChatModel::Gpt5_2Pro.as_str(), "gpt-5.2-pro");
1212 assert_eq!(ChatModel::Gpt5_1.as_str(), "gpt-5.1");
1213 assert_eq!(ChatModel::Gpt5_1CodexMax.as_str(), "gpt-5.1-codex-max");
1214 assert_eq!(ChatModel::Gpt5Mini.as_str(), "gpt-5-mini");
1215 }
1216
1217 #[test]
1218 fn test_chat_model_is_reasoning() {
1219 assert!(ChatModel::O1.is_reasoning_model());
1221 assert!(ChatModel::O3.is_reasoning_model());
1222 assert!(ChatModel::O3Mini.is_reasoning_model());
1223 assert!(ChatModel::O4Mini.is_reasoning_model());
1224 assert!(ChatModel::Gpt5_2.is_reasoning_model());
1227 assert!(!ChatModel::Gpt5_2ChatLatest.is_reasoning_model());
1228 assert!(ChatModel::Gpt5_2Pro.is_reasoning_model());
1229 assert!(ChatModel::Gpt5_1.is_reasoning_model());
1230 assert!(ChatModel::Gpt5_1CodexMax.is_reasoning_model());
1231 assert!(ChatModel::Gpt5Mini.is_reasoning_model());
1232 assert!(!ChatModel::Gpt4oMini.is_reasoning_model());
1234 assert!(!ChatModel::Gpt4_1.is_reasoning_model());
1235 }
1236
1237 #[test]
1238 fn test_chat_model_from_str() {
1239 assert_eq!(ChatModel::from("gpt-4o-mini"), ChatModel::Gpt4oMini);
1240 assert_eq!(ChatModel::from("o3-mini"), ChatModel::O3Mini);
1241 assert_eq!(ChatModel::from("gpt-5.2"), ChatModel::Gpt5_2);
1243 assert_eq!(ChatModel::from("gpt-5.2-chat-latest"), ChatModel::Gpt5_2ChatLatest);
1244 assert_eq!(ChatModel::from("gpt-5.2-pro"), ChatModel::Gpt5_2Pro);
1245 assert_eq!(ChatModel::from("gpt-5.1"), ChatModel::Gpt5_1);
1246 assert_eq!(ChatModel::from("gpt-5.1-codex-max"), ChatModel::Gpt5_1CodexMax);
1247 assert_eq!(ChatModel::from("gpt-5-mini"), ChatModel::Gpt5Mini);
1248 assert!(matches!(ChatModel::from("unknown-model"), ChatModel::Custom(_)));
1250 }
1251
1252 #[test]
1253 fn test_chat_model_custom() {
1254 let custom = ChatModel::custom("ft:gpt-4o-mini:org::123");
1255 assert_eq!(custom.as_str(), "ft:gpt-4o-mini:org::123");
1256 }
1257
1258 #[test]
1259 fn test_chat_model_custom_gpt5_is_reasoning() {
1260 let custom_gpt5 = ChatModel::custom("gpt-5.3-preview");
1262 assert!(custom_gpt5.is_reasoning_model());
1263 }
1264
1265 #[test]
1266 fn test_embedding_model_dimensions() {
1267 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1268 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1269 }
1270
1271 #[test]
1272 fn test_realtime_model_as_str() {
1273 assert_eq!(RealtimeModel::GptRealtime_2025_08_28.as_str(), "gpt-realtime-2025-08-28");
1274 }
1275
1276 #[test]
1277 fn test_fine_tuning_model_as_str() {
1278 assert_eq!(FineTuningModel::Gpt4oMini_2024_07_18.as_str(), "gpt-4o-mini-2024-07-18");
1279 assert_eq!(FineTuningModel::Gpt41_2025_04_14.as_str(), "gpt-4.1-2025-04-14");
1280 }
1281
1282 #[test]
1283 fn test_chat_model_serialization() {
1284 let model = ChatModel::Gpt4oMini;
1285 let json = serde_json::to_string(&model).unwrap();
1286 assert_eq!(json, "\"gpt-4o-mini\"");
1287 let gpt52 = ChatModel::Gpt5_2;
1289 let json = serde_json::to_string(&gpt52).unwrap();
1290 assert_eq!(json, "\"gpt-5.2\"");
1291 }
1292
1293 #[test]
1294 fn test_chat_model_deserialization() {
1295 let model: ChatModel = serde_json::from_str("\"gpt-4o-mini\"").unwrap();
1296 assert_eq!(model, ChatModel::Gpt4oMini);
1297 let gpt52: ChatModel = serde_json::from_str("\"gpt-5.2\"").unwrap();
1299 assert_eq!(gpt52, ChatModel::Gpt5_2);
1300 }
1301
1302 #[test]
1303 fn test_parameter_support_standard_model() {
1304 let model = ChatModel::Gpt4oMini;
1305 let support = model.parameter_support();
1306
1307 assert_eq!(support.temperature, ParameterRestriction::Any);
1309 assert_eq!(support.frequency_penalty, ParameterRestriction::Any);
1310 assert_eq!(support.presence_penalty, ParameterRestriction::Any);
1311 assert_eq!(support.top_p, ParameterRestriction::Any);
1312 assert!(support.logprobs);
1313 assert!(support.top_logprobs);
1314 assert!(support.logit_bias);
1315 assert!(support.n_multiple);
1316 assert!(!support.reasoning); }
1318
1319 #[test]
1320 fn test_parameter_support_reasoning_model() {
1321 let model = ChatModel::O3Mini;
1322 let support = model.parameter_support();
1323
1324 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1326 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0));
1327 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0));
1328 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0));
1329 assert!(!support.logprobs);
1330 assert!(!support.top_logprobs);
1331 assert!(!support.logit_bias);
1332 assert!(support.n_multiple); assert!(support.reasoning); }
1335
1336 #[test]
1337 fn test_parameter_support_gpt5_model() {
1338 let model = ChatModel::Gpt5_2;
1340 let support = model.parameter_support();
1341
1342 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1343 assert!(!support.logprobs);
1344 assert!(support.reasoning);
1345 }
1346
1347 #[test]
1352 fn test_all_o_series_models_are_reasoning() {
1353 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1355
1356 for model in o_series {
1357 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1358 }
1359 }
1360
1361 #[test]
1362 fn test_all_gpt5_models_are_reasoning() {
1363 let gpt5_series =
1367 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1368
1369 for model in gpt5_series {
1370 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1371 }
1372 }
1373
1374 #[test]
1375 fn test_all_standard_models_are_not_reasoning() {
1376 let standard_models = vec![
1378 ChatModel::Gpt4oMini,
1379 ChatModel::Gpt4o,
1380 ChatModel::Gpt4oAudioPreview,
1381 ChatModel::Gpt4Turbo,
1382 ChatModel::Gpt4,
1383 ChatModel::Gpt3_5Turbo,
1384 ChatModel::Gpt4_1,
1385 ChatModel::Gpt4_1Mini,
1386 ChatModel::Gpt4_1Nano,
1387 ];
1388
1389 for model in standard_models {
1390 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1391 }
1392 }
1393
1394 #[test]
1399 fn test_custom_o1_models_are_reasoning() {
1400 let custom_o1_variants = vec!["o1-mini", "o1-preview", "o1-pro-2025", "o1-high"];
1401
1402 for model_str in custom_o1_variants {
1403 let model = ChatModel::custom(model_str);
1404 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1405 }
1406 }
1407
1408 #[test]
1409 fn test_custom_o3_models_are_reasoning() {
1410 let custom_o3_variants = vec!["o3-preview", "o3-high", "o3-2025-01-15"];
1411
1412 for model_str in custom_o3_variants {
1413 let model = ChatModel::custom(model_str);
1414 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1415 }
1416 }
1417
1418 #[test]
1419 fn test_custom_o4_models_are_reasoning() {
1420 let custom_o4_variants = vec!["o4-preview", "o4-mini-2025", "o4-high"];
1421
1422 for model_str in custom_o4_variants {
1423 let model = ChatModel::custom(model_str);
1424 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1425 }
1426 }
1427
1428 #[test]
1429 fn test_custom_gpt5_models_are_reasoning() {
1430 let custom_gpt5_variants = vec!["gpt-5.3", "gpt-5.3-preview", "gpt-5-turbo", "gpt-5.0"];
1431
1432 for model_str in custom_gpt5_variants {
1433 let model = ChatModel::custom(model_str);
1434 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1435 }
1436 }
1437
1438 #[test]
1439 fn test_custom_standard_models_are_not_reasoning() {
1440 let custom_standard_variants = vec![
1441 "ft:gpt-4o-mini:org::123",
1442 "gpt-4o-2025-01-15",
1443 "gpt-4-turbo-preview",
1444 "gpt-3.5-turbo-instruct",
1445 "text-davinci-003",
1446 "claude-3-opus", ];
1448
1449 for model_str in custom_standard_variants {
1450 let model = ChatModel::custom(model_str);
1451 assert!(!model.is_reasoning_model(), "Expected custom model '{}' to NOT be a reasoning model", model_str);
1452 }
1453 }
1454
1455 #[test]
1460 fn test_parameter_support_all_o_series() {
1461 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1462
1463 for model in o_series {
1464 let support = model.parameter_support();
1465
1466 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1467 assert_eq!(
1468 support.frequency_penalty,
1469 ParameterRestriction::FixedValue(0.0),
1470 "{} should only support frequency_penalty=0.0",
1471 model.as_str()
1472 );
1473 assert_eq!(
1474 support.presence_penalty,
1475 ParameterRestriction::FixedValue(0.0),
1476 "{} should only support presence_penalty=0.0",
1477 model.as_str()
1478 );
1479 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should only support top_p=1.0", model.as_str());
1480 assert!(!support.logprobs, "{} should not support logprobs", model.as_str());
1481 assert!(!support.top_logprobs, "{} should not support top_logprobs", model.as_str());
1482 assert!(!support.logit_bias, "{} should not support logit_bias", model.as_str());
1483 assert!(support.n_multiple, "{} should accept n > 1", model.as_str());
1484 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1485 }
1486 }
1487
1488 #[test]
1489 fn test_parameter_support_all_gpt5_series() {
1490 let gpt5_series =
1493 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1494
1495 for model in gpt5_series {
1496 let support = model.parameter_support();
1497
1498 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1499 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1500 }
1501 }
1502
1503 #[test]
1504 fn test_parameter_support_all_standard_gpt4_series() {
1505 let gpt4_series = vec![
1506 ChatModel::Gpt4oMini,
1507 ChatModel::Gpt4o,
1508 ChatModel::Gpt4Turbo,
1509 ChatModel::Gpt4,
1510 ChatModel::Gpt4_1,
1511 ChatModel::Gpt4_1Mini,
1512 ChatModel::Gpt4_1Nano,
1513 ];
1514
1515 for model in gpt4_series {
1516 let support = model.parameter_support();
1517
1518 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should support any temperature", model.as_str());
1519 assert_eq!(support.frequency_penalty, ParameterRestriction::Any, "{} should support any frequency_penalty", model.as_str());
1520 assert_eq!(support.presence_penalty, ParameterRestriction::Any, "{} should support any presence_penalty", model.as_str());
1521 assert!(support.logprobs, "{} should support logprobs", model.as_str());
1522 assert!(support.top_logprobs, "{} should support top_logprobs", model.as_str());
1523 assert!(support.logit_bias, "{} should support logit_bias", model.as_str());
1524 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1525 assert!(!support.reasoning, "{} should NOT support reasoning parameter", model.as_str());
1526 }
1527 }
1528
1529 #[test]
1534 fn test_parameter_restriction_equality() {
1535 assert_eq!(ParameterRestriction::Any, ParameterRestriction::Any);
1536 assert_eq!(ParameterRestriction::NotSupported, ParameterRestriction::NotSupported);
1537 assert_eq!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(1.0));
1538
1539 assert_ne!(ParameterRestriction::Any, ParameterRestriction::NotSupported);
1540 assert_ne!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(0.0));
1541 }
1542
1543 #[test]
1544 fn test_parameter_support_factory_methods() {
1545 let standard = ParameterSupport::standard_model();
1546 assert_eq!(standard.temperature, ParameterRestriction::Any);
1547 assert!(standard.logprobs);
1548 assert!(!standard.reasoning);
1549
1550 let reasoning = ParameterSupport::reasoning_model();
1551 assert_eq!(reasoning.temperature, ParameterRestriction::FixedValue(1.0));
1552 assert!(!reasoning.logprobs);
1553 assert!(reasoning.reasoning);
1554 }
1555
1556 #[test]
1561 fn test_all_gpt5_model_string_roundtrip() {
1562 let gpt5_models = vec![
1563 ("gpt-5.2", ChatModel::Gpt5_2),
1564 ("gpt-5.2-chat-latest", ChatModel::Gpt5_2ChatLatest),
1565 ("gpt-5.2-pro", ChatModel::Gpt5_2Pro),
1566 ("gpt-5.1", ChatModel::Gpt5_1),
1567 ("gpt-5.1-chat-latest", ChatModel::Gpt5_1ChatLatest),
1568 ("gpt-5.1-codex-max", ChatModel::Gpt5_1CodexMax),
1569 ("gpt-5-mini", ChatModel::Gpt5Mini),
1570 ("gpt-5-nano", ChatModel::Gpt5Nano),
1571 ];
1572
1573 for (model_str, expected_model) in gpt5_models {
1574 let parsed = ChatModel::from(model_str);
1576 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1577
1578 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1580
1581 let json = serde_json::to_string(&expected_model).unwrap();
1583 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1584 assert_eq!(deserialized, expected_model, "Serialization roundtrip failed for {}", model_str);
1585 }
1586 }
1587
1588 #[test]
1589 fn test_all_o_series_model_string_roundtrip() {
1590 let o_series_models = vec![
1591 ("o1", ChatModel::O1),
1592 ("o1-pro", ChatModel::O1Pro),
1593 ("o3", ChatModel::O3),
1594 ("o3-mini", ChatModel::O3Mini),
1595 ("o4-mini", ChatModel::O4Mini),
1596 ];
1597
1598 for (model_str, expected_model) in o_series_models {
1599 let parsed = ChatModel::from(model_str);
1600 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1601 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1602 }
1603 }
1604
1605 #[test]
1610 fn test_embedding_model_string_roundtrip() {
1611 let embedding_models = vec![
1612 ("text-embedding-3-small", EmbeddingModel::TextEmbedding3Small),
1613 ("text-embedding-3-large", EmbeddingModel::TextEmbedding3Large),
1614 ("text-embedding-ada-002", EmbeddingModel::TextEmbeddingAda002),
1615 ];
1616
1617 for (model_str, expected_model) in embedding_models {
1618 let parsed = EmbeddingModel::from(model_str);
1619 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1620 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1621 }
1622 }
1623
1624 #[test]
1625 fn test_embedding_model_all_dimensions() {
1626 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1627 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1628 assert_eq!(EmbeddingModel::TextEmbeddingAda002.dimensions(), 1536);
1629 }
1630
1631 #[test]
1636 fn test_realtime_model_string_roundtrip() {
1637 let realtime_models = vec![("gpt-realtime-2025-08-28", RealtimeModel::GptRealtime_2025_08_28)];
1638
1639 for (model_str, expected_model) in realtime_models {
1640 let parsed = RealtimeModel::from(model_str);
1641 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1642 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1643 }
1644 }
1645
1646 #[test]
1647 fn test_realtime_model_custom() {
1648 let custom = RealtimeModel::custom("gpt-4o-realtime-2025");
1649 assert_eq!(custom.as_str(), "gpt-4o-realtime-2025");
1650 assert!(matches!(custom, RealtimeModel::Custom(_)));
1651 }
1652
1653 #[test]
1658 fn test_fine_tuning_model_as_str_all_variants() {
1659 let fine_tuning_models = vec![
1660 ("gpt-4.1-2025-04-14", FineTuningModel::Gpt41_2025_04_14),
1661 ("gpt-4.1-mini-2025-04-14", FineTuningModel::Gpt41Mini_2025_04_14),
1662 ("gpt-4.1-nano-2025-04-14", FineTuningModel::Gpt41Nano_2025_04_14),
1663 ("gpt-4o-mini-2024-07-18", FineTuningModel::Gpt4oMini_2024_07_18),
1664 ("gpt-4o-2024-08-06", FineTuningModel::Gpt4o_2024_08_06),
1665 ("gpt-4-0613", FineTuningModel::Gpt4_0613),
1666 ("gpt-3.5-turbo-0125", FineTuningModel::Gpt35Turbo_0125),
1667 ("gpt-3.5-turbo-1106", FineTuningModel::Gpt35Turbo_1106),
1668 ("gpt-3.5-turbo-0613", FineTuningModel::Gpt35Turbo_0613),
1669 ];
1670
1671 for (model_str, expected_model) in fine_tuning_models {
1672 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1673 }
1674 }
1675
1676 #[test]
1677 fn test_fine_tuning_model_serialization_roundtrip() {
1678 let models = vec![FineTuningModel::Gpt41_2025_04_14, FineTuningModel::Gpt4oMini_2024_07_18, FineTuningModel::Gpt35Turbo_0125];
1679
1680 for model in models {
1681 let json = serde_json::to_string(&model).unwrap();
1682 let deserialized: FineTuningModel = serde_json::from_str(&json).unwrap();
1683 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1684 }
1685 }
1686
1687 fn new_chat_models() -> Vec<(ChatModel, &'static str)> {
1697 vec![
1698 (ChatModel::Gpt6Astra, "gpt-6-astra"),
1700 (ChatModel::Gpt6Sol, "gpt-6-sol"),
1701 (ChatModel::Gpt6Luna, "gpt-6-luna"),
1702 (ChatModel::Gpt5_6, "gpt-5.6"),
1704 (ChatModel::Gpt5_6Sol, "gpt-5.6-sol"),
1705 (ChatModel::Gpt5_6Terra, "gpt-5.6-terra"),
1706 (ChatModel::Gpt5_6Luna, "gpt-5.6-luna"),
1707 (ChatModel::Gpt5_5, "gpt-5.5"),
1709 (ChatModel::Gpt5_5Pro, "gpt-5.5-pro"),
1710 (ChatModel::Gpt5_4, "gpt-5.4"),
1712 (ChatModel::Gpt5_4Pro, "gpt-5.4-pro"),
1713 (ChatModel::Gpt5_4Mini, "gpt-5.4-mini"),
1714 (ChatModel::Gpt5_4Nano, "gpt-5.4-nano"),
1715 (ChatModel::Gpt5_3ChatLatest, "gpt-5.3-chat-latest"),
1717 (ChatModel::Gpt5_3Codex, "gpt-5.3-codex"),
1719 (ChatModel::Gpt5_2Codex, "gpt-5.2-codex"),
1720 (ChatModel::Gpt5_1Codex, "gpt-5.1-codex"),
1721 ]
1722 }
1723
1724 #[test]
1725 fn test_new_chat_models_as_str() {
1726 for (model, expected) in new_chat_models() {
1727 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1728 }
1729 }
1730
1731 #[test]
1732 fn test_new_chat_models_from_str_roundtrip() {
1733 for (model, model_str) in new_chat_models() {
1734 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1735 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1737 }
1738
1739 assert!(matches!(ChatModel::from("gpt-6"), ChatModel::Custom(_)), "gpt-6 must not be treated as an API model alias");
1740 }
1741
1742 #[test]
1743 fn test_new_chat_models_serialization_roundtrip() {
1744 for (model, model_str) in new_chat_models() {
1745 let json = serde_json::to_string(&model).unwrap();
1746 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1747 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1748 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1749 }
1750 }
1751
1752 #[test]
1755 fn test_new_models_are_reasoning_models() {
1756 let reasoning = vec![
1757 ChatModel::Gpt6Astra,
1758 ChatModel::Gpt6Sol,
1759 ChatModel::Gpt6Luna,
1760 ChatModel::Gpt5_6,
1761 ChatModel::Gpt5_6Sol,
1762 ChatModel::Gpt5_6Terra,
1763 ChatModel::Gpt5_6Luna,
1764 ChatModel::Gpt5_5,
1765 ChatModel::Gpt5_5Pro,
1766 ChatModel::Gpt5_4,
1767 ChatModel::Gpt5_4Pro,
1768 ChatModel::Gpt5_4Mini,
1769 ChatModel::Gpt5_4Nano,
1770 ChatModel::Gpt5_3Codex,
1771 ChatModel::Gpt5_2Codex,
1772 ChatModel::Gpt5_1Codex,
1773 ];
1774
1775 for model in reasoning {
1776 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1777 let support = model.parameter_support();
1778 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should pin temperature to 1.0", model.as_str());
1779 assert!(support.reasoning, "{} should support the reasoning parameter", model.as_str());
1780 }
1781 }
1782
1783 #[test]
1787 fn test_chat_latest_models_are_not_reasoning_models() {
1788 let chat_latest = vec![ChatModel::Gpt5_3ChatLatest, ChatModel::Gpt5_2ChatLatest, ChatModel::Gpt5_1ChatLatest];
1789
1790 for model in chat_latest {
1791 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1792 let support = model.parameter_support();
1793 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1794 assert_eq!(support.top_p, ParameterRestriction::Any, "{} should accept any top_p", model.as_str());
1795 assert!(!support.reasoning, "{} should NOT support the reasoning parameter", model.as_str());
1796 }
1797 }
1798
1799 #[test]
1802 fn test_custom_chat_latest_is_not_reasoning_model() {
1803 assert!(!ChatModel::custom("gpt-5.7-chat-latest").is_reasoning_model());
1804 assert!(ChatModel::custom("gpt-5.7").is_reasoning_model());
1806 assert!(ChatModel::custom("gpt-5.7-codex").is_reasoning_model());
1807 }
1808
1809 fn new_realtime_models() -> Vec<(RealtimeModel, &'static str)> {
1814 vec![
1815 (RealtimeModel::GptRealtime2_1, "gpt-realtime-2.1"),
1816 (RealtimeModel::GptRealtime2_1Mini, "gpt-realtime-2.1-mini"),
1817 (RealtimeModel::GptRealtime2, "gpt-realtime-2"),
1818 (RealtimeModel::GptRealtime1_5, "gpt-realtime-1.5"),
1819 (RealtimeModel::GptRealtimeTranslate, "gpt-realtime-translate"),
1820 ]
1821 }
1822
1823 #[test]
1824 fn test_new_realtime_models_as_str() {
1825 for (model, expected) in new_realtime_models() {
1826 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1827 }
1828 }
1829
1830 #[test]
1831 fn test_new_realtime_models_from_str_roundtrip() {
1832 for (model, model_str) in new_realtime_models() {
1833 assert_eq!(RealtimeModel::from(model_str), model, "Failed to parse {}", model_str);
1834 }
1835 }
1836
1837 #[test]
1841 fn test_realtime_model_default_is_unchanged() {
1842 assert_eq!(RealtimeModel::default(), RealtimeModel::GptRealtime_2025_08_28);
1843 }
1844
1845 #[test]
1846 fn test_new_realtime_models_serialization_roundtrip() {
1847 for (model, model_str) in new_realtime_models() {
1848 let json = serde_json::to_string(&model).unwrap();
1849 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1850 let deserialized: RealtimeModel = serde_json::from_str(&json).unwrap();
1851 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1852 }
1853 }
1854
1855 fn previously_missing_chat_models() -> Vec<(ChatModel, &'static str)> {
1863 vec![
1864 (ChatModel::Gpt5, "gpt-5"),
1865 (ChatModel::Gpt5Pro, "gpt-5-pro"),
1866 (ChatModel::O3Pro, "o3-pro"),
1867 (ChatModel::Gpt5SearchApi, "gpt-5-search-api"),
1868 (ChatModel::Gpt4oSearchPreview, "gpt-4o-search-preview"),
1869 (ChatModel::Gpt4oMiniSearchPreview, "gpt-4o-mini-search-preview"),
1870 (ChatModel::GptAudio, "gpt-audio"),
1871 (ChatModel::GptAudio1_5, "gpt-audio-1.5"),
1872 (ChatModel::GptAudioMini, "gpt-audio-mini"),
1873 (ChatModel::Gpt3_5Turbo16k, "gpt-3.5-turbo-16k"),
1874 ]
1875 }
1876
1877 #[test]
1878 fn test_previously_missing_chat_models_as_str() {
1879 for (model, expected) in previously_missing_chat_models() {
1880 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1881 }
1882 }
1883
1884 #[test]
1885 fn test_previously_missing_chat_models_from_str_roundtrip() {
1886 for (model, model_str) in previously_missing_chat_models() {
1887 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1888 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1889 }
1890 }
1891
1892 #[test]
1893 fn test_previously_missing_chat_models_serialization_roundtrip() {
1894 for (model, model_str) in previously_missing_chat_models() {
1895 let json = serde_json::to_string(&model).unwrap();
1896 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1897 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1898 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1899 }
1900 }
1901
1902 #[test]
1905 fn test_gpt5_base_and_pro_models_are_reasoning() {
1906 for model in [ChatModel::Gpt5, ChatModel::Gpt5Pro, ChatModel::O3Pro] {
1907 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1908 assert!(model.parameter_support().reasoning, "{} should support the reasoning parameter", model.as_str());
1909 }
1910 }
1911
1912 #[test]
1914 fn test_audio_chat_models_are_standard() {
1915 for model in [ChatModel::GptAudio, ChatModel::GptAudio1_5, ChatModel::GptAudioMini, ChatModel::Gpt3_5Turbo16k] {
1916 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1917 let support = model.parameter_support();
1918 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1919 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1920 }
1921 }
1922
1923 #[test]
1926 fn test_search_models_reject_sampling_but_have_no_reasoning() {
1927 let search_models = [ChatModel::Gpt5SearchApi, ChatModel::Gpt4oSearchPreview, ChatModel::Gpt4oMiniSearchPreview];
1928
1929 for model in search_models {
1930 assert!(model.is_search_model(), "Expected {} to be a search model", model.as_str());
1931 assert!(!model.is_reasoning_model(), "Search model {} must not be classed as reasoning", model.as_str());
1932
1933 let support = model.parameter_support();
1934 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should reject custom temperature", model.as_str());
1935 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should reject custom top_p", model.as_str());
1936 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject frequency_penalty", model.as_str());
1937 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject presence_penalty", model.as_str());
1938 assert!(!support.logprobs, "{} should reject logprobs", model.as_str());
1939 assert!(!support.n_multiple, "{} should reject n > 1", model.as_str());
1940 assert!(!support.reasoning, "{} exposes no reasoning parameter", model.as_str());
1941 }
1942 }
1943
1944 #[test]
1946 fn test_non_search_models_are_not_search_models() {
1947 for model in [ChatModel::Gpt5_6Sol, ChatModel::Gpt4oMini, ChatModel::O3Mini, ChatModel::Gpt5_2ChatLatest] {
1948 assert!(!model.is_search_model(), "Expected {} to NOT be a search model", model.as_str());
1949 }
1950 }
1951
1952 #[test]
1953 fn test_previously_missing_realtime_models() {
1954 for (model, expected) in [(RealtimeModel::GptRealtime, "gpt-realtime"), (RealtimeModel::GptRealtimeMini, "gpt-realtime-mini")] {
1955 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1956 assert_eq!(RealtimeModel::from(expected), model, "Failed to parse {}", expected);
1957 let json = serde_json::to_string(&model).unwrap();
1958 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1959 }
1960 }
1961
1962 #[test]
1963 fn test_legacy_fine_tuning_base_models() {
1964 for (model, expected) in [(FineTuningModel::Babbage002, "babbage-002"), (FineTuningModel::Davinci002, "davinci-002")] {
1965 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1966 let json = serde_json::to_string(&model).unwrap();
1967 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1968 }
1969 }
1970}