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)]
227#[non_exhaustive]
228pub enum ChatModel {
229 #[serde(rename = "gpt-6.1-sol")]
239 Gpt6_1Sol,
240
241 #[serde(rename = "gpt-6-astra")]
247 Gpt6Astra,
248
249 #[serde(rename = "gpt-6-sol")]
255 Gpt6Sol,
256
257 #[serde(rename = "gpt-6-luna")]
263 Gpt6Luna,
264
265 #[serde(rename = "gpt-5.6")]
268 Gpt5_6,
269
270 #[serde(rename = "gpt-5.6-sol")]
276 Gpt5_6Sol,
277
278 #[serde(rename = "gpt-5.6-terra")]
283 Gpt5_6Terra,
284
285 #[serde(rename = "gpt-5.6-luna")]
290 Gpt5_6Luna,
291
292 #[serde(rename = "gpt-5.5")]
298 Gpt5_5,
299
300 #[serde(rename = "gpt-5.5-pro")]
305 Gpt5_5Pro,
306
307 #[serde(rename = "gpt-5.4")]
313 Gpt5_4,
314
315 #[serde(rename = "gpt-5.4-pro")]
320 Gpt5_4Pro,
321
322 #[serde(rename = "gpt-5.4-mini")]
327 Gpt5_4Mini,
328
329 #[serde(rename = "gpt-5.4-nano")]
334 Gpt5_4Nano,
335
336 #[serde(rename = "gpt-5.3-chat-latest")]
343 Gpt5_3ChatLatest,
344
345 #[serde(rename = "gpt-5.3-codex")]
352 Gpt5_3Codex,
353
354 #[serde(rename = "gpt-5.2-codex")]
360 Gpt5_2Codex,
361
362 #[serde(rename = "gpt-5.1-codex")]
366 Gpt5_1Codex,
367
368 #[serde(rename = "gpt-5-search-api")]
373 Gpt5SearchApi,
374
375 #[serde(rename = "gpt-4o-search-preview")]
377 Gpt4oSearchPreview,
378
379 #[serde(rename = "gpt-4o-mini-search-preview")]
381 Gpt4oMiniSearchPreview,
382
383 #[serde(rename = "gpt-audio")]
388 GptAudio,
389
390 #[serde(rename = "gpt-audio-1.5")]
392 GptAudio1_5,
393
394 #[serde(rename = "gpt-audio-mini")]
396 GptAudioMini,
397
398 #[serde(rename = "gpt-5")]
401 Gpt5,
402
403 #[serde(rename = "gpt-5-pro")]
407 Gpt5Pro,
408
409 #[serde(rename = "gpt-5.2")]
415 Gpt5_2,
416
417 #[serde(rename = "gpt-5.2-chat-latest")]
424 Gpt5_2ChatLatest,
425
426 #[serde(rename = "gpt-5.2-pro")]
431 Gpt5_2Pro,
432
433 #[serde(rename = "gpt-5.1")]
438 Gpt5_1,
439
440 #[serde(rename = "gpt-5.1-chat-latest")]
445 Gpt5_1ChatLatest,
446
447 #[serde(rename = "gpt-5.1-codex-max")]
452 Gpt5_1CodexMax,
453
454 #[serde(rename = "gpt-5-mini")]
456 Gpt5Mini,
457
458 #[serde(rename = "gpt-5-nano")]
460 Gpt5Nano,
461
462 #[serde(rename = "gpt-4.1")]
465 Gpt4_1,
466
467 #[serde(rename = "gpt-4.1-mini")]
469 Gpt4_1Mini,
470
471 #[serde(rename = "gpt-4.1-nano")]
473 Gpt4_1Nano,
474
475 #[serde(rename = "gpt-4o")]
478 Gpt4o,
479
480 #[serde(rename = "gpt-4o-mini")]
482 #[default]
483 Gpt4oMini,
484
485 #[serde(rename = "gpt-4o-audio-preview")]
487 Gpt4oAudioPreview,
488
489 #[serde(rename = "gpt-4-turbo")]
492 Gpt4Turbo,
493
494 #[serde(rename = "gpt-4")]
496 Gpt4,
497
498 #[serde(rename = "gpt-3.5-turbo")]
501 Gpt3_5Turbo,
502
503 #[serde(rename = "gpt-3.5-turbo-16k")]
505 Gpt3_5Turbo16k,
506
507 #[serde(rename = "o1")]
510 O1,
511
512 #[serde(rename = "o1-pro")]
514 O1Pro,
515
516 #[serde(rename = "o3")]
518 O3,
519
520 #[serde(rename = "o3-pro")]
524 O3Pro,
525
526 #[serde(rename = "o3-mini")]
528 O3Mini,
529
530 #[serde(rename = "o4-mini")]
532 O4Mini,
533
534 #[serde(untagged)]
537 Custom(String),
538}
539
540impl ChatModel {
541 pub fn as_str(&self) -> &str {
553 match self {
554 Self::Gpt6_1Sol => "gpt-6.1-sol",
556 Self::Gpt6Astra => "gpt-6-astra",
557 Self::Gpt6Sol => "gpt-6-sol",
558 Self::Gpt6Luna => "gpt-6-luna",
559 Self::Gpt5_6 => "gpt-5.6",
561 Self::Gpt5_6Sol => "gpt-5.6-sol",
562 Self::Gpt5_6Terra => "gpt-5.6-terra",
563 Self::Gpt5_6Luna => "gpt-5.6-luna",
564 Self::Gpt5_5 => "gpt-5.5",
566 Self::Gpt5_5Pro => "gpt-5.5-pro",
567 Self::Gpt5_4 => "gpt-5.4",
569 Self::Gpt5_4Pro => "gpt-5.4-pro",
570 Self::Gpt5_4Mini => "gpt-5.4-mini",
571 Self::Gpt5_4Nano => "gpt-5.4-nano",
572 Self::Gpt5_3ChatLatest => "gpt-5.3-chat-latest",
574 Self::Gpt5_3Codex => "gpt-5.3-codex",
576 Self::Gpt5_2Codex => "gpt-5.2-codex",
577 Self::Gpt5_1Codex => "gpt-5.1-codex",
578 Self::Gpt5SearchApi => "gpt-5-search-api",
580 Self::Gpt4oSearchPreview => "gpt-4o-search-preview",
581 Self::Gpt4oMiniSearchPreview => "gpt-4o-mini-search-preview",
582 Self::GptAudio => "gpt-audio",
584 Self::GptAudio1_5 => "gpt-audio-1.5",
585 Self::GptAudioMini => "gpt-audio-mini",
586 Self::Gpt5 => "gpt-5",
588 Self::Gpt5Pro => "gpt-5-pro",
589 Self::Gpt5_2 => "gpt-5.2",
590 Self::Gpt5_2ChatLatest => "gpt-5.2-chat-latest",
591 Self::Gpt5_2Pro => "gpt-5.2-pro",
592 Self::Gpt5_1 => "gpt-5.1",
593 Self::Gpt5_1ChatLatest => "gpt-5.1-chat-latest",
594 Self::Gpt5_1CodexMax => "gpt-5.1-codex-max",
595 Self::Gpt5Mini => "gpt-5-mini",
596 Self::Gpt5Nano => "gpt-5-nano",
597 Self::Gpt4_1 => "gpt-4.1",
599 Self::Gpt4_1Mini => "gpt-4.1-mini",
600 Self::Gpt4_1Nano => "gpt-4.1-nano",
601 Self::Gpt4o => "gpt-4o",
603 Self::Gpt4oMini => "gpt-4o-mini",
604 Self::Gpt4oAudioPreview => "gpt-4o-audio-preview",
605 Self::Gpt4Turbo => "gpt-4-turbo",
607 Self::Gpt4 => "gpt-4",
608 Self::Gpt3_5Turbo => "gpt-3.5-turbo",
610 Self::Gpt3_5Turbo16k => "gpt-3.5-turbo-16k",
611 Self::O1 => "o1",
613 Self::O1Pro => "o1-pro",
614 Self::O3 => "o3",
615 Self::O3Pro => "o3-pro",
616 Self::O3Mini => "o3-mini",
617 Self::O4Mini => "o4-mini",
618 Self::Custom(s) => s.as_str(),
620 }
621 }
622
623 pub fn is_reasoning_model(&self) -> bool {
642 matches!(
643 self,
644 Self::Gpt6_1Sol | Self::Gpt6Astra | Self::Gpt6Sol | Self::Gpt6Luna |
646 Self::Gpt5_6 | Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna |
648 Self::Gpt5_5 | Self::Gpt5_5Pro |
650 Self::Gpt5_4 | Self::Gpt5_4Pro | Self::Gpt5_4Mini | Self::Gpt5_4Nano |
652 Self::Gpt5_3Codex | Self::Gpt5_2Codex | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax |
654 Self::Gpt5 | Self::Gpt5Pro | Self::Gpt5_2 | Self::Gpt5_2Pro | Self::Gpt5_1 | Self::Gpt5Mini | Self::Gpt5Nano |
656 Self::O1 | Self::O1Pro | Self::O3 | Self::O3Pro | Self::O3Mini | Self::O4Mini
658 ) || matches!(
659 self,
660 Self::Custom(s) if !s.ends_with("-chat-latest")
664 && !s.contains("-search")
665 && (s.starts_with("gpt-5") || s.starts_with("o1") || s.starts_with("o3") || s.starts_with("o4"))
666 )
667 }
668
669 pub fn is_search_model(&self) -> bool {
686 matches!(self, Self::Gpt5SearchApi | Self::Gpt4oSearchPreview | Self::Gpt4oMiniSearchPreview)
687 || matches!(self, Self::Custom(s) if s.contains("-search"))
688 }
689
690 pub fn parameter_support(&self) -> ParameterSupport {
714 if self.is_search_model() {
715 ParameterSupport::search_model()
716 } else if self.is_reasoning_model() {
717 ParameterSupport::reasoning_model()
718 } else {
719 ParameterSupport::standard_model()
720 }
721 }
722
723 pub fn custom(model_id: impl Into<String>) -> Self {
736 Self::Custom(model_id.into())
737 }
738}
739
740impl std::fmt::Display for ChatModel {
741 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
742 write!(f, "{}", self.as_str())
743 }
744}
745
746impl From<&str> for ChatModel {
747 fn from(s: &str) -> Self {
748 match s {
749 "gpt-6.1-sol" => Self::Gpt6_1Sol,
751 "gpt-6-astra" => Self::Gpt6Astra,
752 "gpt-6-sol" => Self::Gpt6Sol,
753 "gpt-6-luna" => Self::Gpt6Luna,
754 "gpt-5.6" => Self::Gpt5_6,
756 "gpt-5.6-sol" => Self::Gpt5_6Sol,
757 "gpt-5.6-terra" => Self::Gpt5_6Terra,
758 "gpt-5.6-luna" => Self::Gpt5_6Luna,
759 "gpt-5.5" => Self::Gpt5_5,
761 "gpt-5.5-pro" => Self::Gpt5_5Pro,
762 "gpt-5.4" => Self::Gpt5_4,
764 "gpt-5.4-pro" => Self::Gpt5_4Pro,
765 "gpt-5.4-mini" => Self::Gpt5_4Mini,
766 "gpt-5.4-nano" => Self::Gpt5_4Nano,
767 "gpt-5.3-chat-latest" => Self::Gpt5_3ChatLatest,
769 "gpt-5.3-codex" => Self::Gpt5_3Codex,
771 "gpt-5.2-codex" => Self::Gpt5_2Codex,
772 "gpt-5.1-codex" => Self::Gpt5_1Codex,
773 "gpt-5-search-api" => Self::Gpt5SearchApi,
775 "gpt-4o-search-preview" => Self::Gpt4oSearchPreview,
776 "gpt-4o-mini-search-preview" => Self::Gpt4oMiniSearchPreview,
777 "gpt-audio" => Self::GptAudio,
779 "gpt-audio-1.5" => Self::GptAudio1_5,
780 "gpt-audio-mini" => Self::GptAudioMini,
781 "gpt-5" => Self::Gpt5,
783 "gpt-5-pro" => Self::Gpt5Pro,
784 "gpt-5.2" => Self::Gpt5_2,
785 "gpt-5.2-chat-latest" => Self::Gpt5_2ChatLatest,
786 "gpt-5.2-pro" => Self::Gpt5_2Pro,
787 "gpt-5.1" => Self::Gpt5_1,
788 "gpt-5.1-chat-latest" => Self::Gpt5_1ChatLatest,
789 "gpt-5.1-codex-max" => Self::Gpt5_1CodexMax,
790 "gpt-5-mini" => Self::Gpt5Mini,
791 "gpt-5-nano" => Self::Gpt5Nano,
792 "gpt-4.1" => Self::Gpt4_1,
794 "gpt-4.1-mini" => Self::Gpt4_1Mini,
795 "gpt-4.1-nano" => Self::Gpt4_1Nano,
796 "gpt-4o" => Self::Gpt4o,
798 "gpt-4o-mini" => Self::Gpt4oMini,
799 "gpt-4o-audio-preview" => Self::Gpt4oAudioPreview,
800 "gpt-4-turbo" => Self::Gpt4Turbo,
802 "gpt-4" => Self::Gpt4,
803 "gpt-3.5-turbo" => Self::Gpt3_5Turbo,
805 "gpt-3.5-turbo-16k" => Self::Gpt3_5Turbo16k,
806 "o1" => Self::O1,
808 "o1-pro" => Self::O1Pro,
809 "o3" => Self::O3,
810 "o3-pro" => Self::O3Pro,
811 "o3-mini" => Self::O3Mini,
812 "o4-mini" => Self::O4Mini,
813 other => Self::Custom(other.to_string()),
815 }
816 }
817}
818
819impl From<String> for ChatModel {
820 fn from(s: String) -> Self {
821 Self::from(s.as_str())
822 }
823}
824
825#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
854#[non_exhaustive]
855pub enum EmbeddingModel {
856 #[serde(rename = "text-embedding-3-small")]
862 #[default]
863 TextEmbedding3Small,
864
865 #[serde(rename = "text-embedding-3-large")]
871 TextEmbedding3Large,
872
873 #[serde(rename = "text-embedding-ada-002")]
879 TextEmbeddingAda002,
880}
881
882impl EmbeddingModel {
883 pub fn as_str(&self) -> &str {
885 match self {
886 Self::TextEmbedding3Small => "text-embedding-3-small",
887 Self::TextEmbedding3Large => "text-embedding-3-large",
888 Self::TextEmbeddingAda002 => "text-embedding-ada-002",
889 }
890 }
891
892 pub fn dimensions(&self) -> usize {
897 match self {
898 Self::TextEmbedding3Small => 1536,
899 Self::TextEmbedding3Large => 3072,
900 Self::TextEmbeddingAda002 => 1536,
901 }
902 }
903}
904
905impl std::fmt::Display for EmbeddingModel {
906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907 write!(f, "{}", self.as_str())
908 }
909}
910
911impl From<&str> for EmbeddingModel {
912 fn from(s: &str) -> Self {
913 match s {
914 "text-embedding-3-small" => Self::TextEmbedding3Small,
915 "text-embedding-3-large" => Self::TextEmbedding3Large,
916 "text-embedding-ada-002" => Self::TextEmbeddingAda002,
917 _ => Self::TextEmbedding3Small, }
919 }
920}
921
922#[allow(non_camel_case_types)]
948#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
949#[non_exhaustive]
950pub enum RealtimeModel {
951 #[serde(rename = "gpt-realtime-2.1")]
957 GptRealtime2_1,
958
959 #[serde(rename = "gpt-realtime-2.1-mini")]
961 GptRealtime2_1Mini,
962
963 #[serde(rename = "gpt-realtime-2")]
965 GptRealtime2,
966
967 #[serde(rename = "gpt-realtime")]
969 GptRealtime,
970
971 #[serde(rename = "gpt-realtime-mini")]
973 GptRealtimeMini,
974
975 #[serde(rename = "gpt-realtime-1.5")]
977 GptRealtime1_5,
978
979 #[serde(rename = "gpt-realtime-translate")]
983 GptRealtimeTranslate,
984
985 #[serde(rename = "gpt-realtime-2025-08-28")]
991 #[default]
992 GptRealtime_2025_08_28,
993
994 #[serde(untagged)]
996 Custom(String),
997}
998
999impl RealtimeModel {
1000 pub fn as_str(&self) -> &str {
1002 match self {
1003 Self::GptRealtime2_1 => "gpt-realtime-2.1",
1004 Self::GptRealtime2_1Mini => "gpt-realtime-2.1-mini",
1005 Self::GptRealtime2 => "gpt-realtime-2",
1006 Self::GptRealtime => "gpt-realtime",
1007 Self::GptRealtimeMini => "gpt-realtime-mini",
1008 Self::GptRealtime1_5 => "gpt-realtime-1.5",
1009 Self::GptRealtimeTranslate => "gpt-realtime-translate",
1010 Self::GptRealtime_2025_08_28 => "gpt-realtime-2025-08-28",
1011 Self::Custom(s) => s.as_str(),
1012 }
1013 }
1014
1015 pub fn custom(model_id: impl Into<String>) -> Self {
1017 Self::Custom(model_id.into())
1018 }
1019}
1020
1021impl std::fmt::Display for RealtimeModel {
1022 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1023 write!(f, "{}", self.as_str())
1024 }
1025}
1026
1027impl From<&str> for RealtimeModel {
1028 fn from(s: &str) -> Self {
1029 match s {
1030 "gpt-realtime-2.1" => Self::GptRealtime2_1,
1031 "gpt-realtime-2.1-mini" => Self::GptRealtime2_1Mini,
1032 "gpt-realtime-2" => Self::GptRealtime2,
1033 "gpt-realtime" => Self::GptRealtime,
1034 "gpt-realtime-mini" => Self::GptRealtimeMini,
1035 "gpt-realtime-1.5" => Self::GptRealtime1_5,
1036 "gpt-realtime-translate" => Self::GptRealtimeTranslate,
1037 "gpt-realtime-2025-08-28" => Self::GptRealtime_2025_08_28,
1038 other => Self::Custom(other.to_string()),
1039 }
1040 }
1041}
1042
1043#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1049#[non_exhaustive]
1050pub enum LiveModel {
1051 #[serde(rename = "gpt-live-1")]
1053 #[default]
1054 GptLive1,
1055
1056 #[serde(untagged)]
1058 Custom(String),
1059}
1060
1061impl LiveModel {
1062 pub fn as_str(&self) -> &str {
1064 match self {
1065 Self::GptLive1 => "gpt-live-1",
1066 Self::Custom(model) => model,
1067 }
1068 }
1069
1070 pub fn custom(model: impl Into<String>) -> Self {
1072 Self::Custom(model.into())
1073 }
1074}
1075
1076impl std::fmt::Display for LiveModel {
1077 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1078 formatter.write_str(self.as_str())
1079 }
1080}
1081
1082impl From<&str> for LiveModel {
1083 fn from(model: &str) -> Self {
1084 match model {
1085 "gpt-live-1" => Self::GptLive1,
1086 other => Self::Custom(other.to_string()),
1087 }
1088 }
1089}
1090
1091#[allow(non_camel_case_types)]
1131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1132#[non_exhaustive]
1133pub enum FineTuningModel {
1134 #[serde(rename = "gpt-4.1-2025-04-14")]
1137 Gpt41_2025_04_14,
1138
1139 #[serde(rename = "gpt-4.1-mini-2025-04-14")]
1141 Gpt41Mini_2025_04_14,
1142
1143 #[serde(rename = "gpt-4.1-nano-2025-04-14")]
1145 Gpt41Nano_2025_04_14,
1146
1147 #[serde(rename = "gpt-4o-mini-2024-07-18")]
1150 #[default]
1151 Gpt4oMini_2024_07_18,
1152
1153 #[serde(rename = "gpt-4o-2024-08-06")]
1155 Gpt4o_2024_08_06,
1156
1157 #[serde(rename = "gpt-4-0613")]
1160 Gpt4_0613,
1161
1162 #[serde(rename = "gpt-3.5-turbo-0125")]
1165 Gpt35Turbo_0125,
1166
1167 #[serde(rename = "gpt-3.5-turbo-1106")]
1169 Gpt35Turbo_1106,
1170
1171 #[serde(rename = "gpt-3.5-turbo-0613")]
1173 Gpt35Turbo_0613,
1174
1175 #[serde(rename = "babbage-002")]
1177 Babbage002,
1178
1179 #[serde(rename = "davinci-002")]
1181 Davinci002,
1182}
1183
1184impl FineTuningModel {
1185 pub fn as_str(&self) -> &str {
1187 match self {
1188 Self::Gpt41_2025_04_14 => "gpt-4.1-2025-04-14",
1190 Self::Gpt41Mini_2025_04_14 => "gpt-4.1-mini-2025-04-14",
1191 Self::Gpt41Nano_2025_04_14 => "gpt-4.1-nano-2025-04-14",
1192 Self::Gpt4oMini_2024_07_18 => "gpt-4o-mini-2024-07-18",
1194 Self::Gpt4o_2024_08_06 => "gpt-4o-2024-08-06",
1195 Self::Gpt4_0613 => "gpt-4-0613",
1197 Self::Gpt35Turbo_0125 => "gpt-3.5-turbo-0125",
1199 Self::Gpt35Turbo_1106 => "gpt-3.5-turbo-1106",
1200 Self::Gpt35Turbo_0613 => "gpt-3.5-turbo-0613",
1201 Self::Babbage002 => "babbage-002",
1202 Self::Davinci002 => "davinci-002",
1203 }
1204 }
1205}
1206
1207impl std::fmt::Display for FineTuningModel {
1208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1209 write!(f, "{}", self.as_str())
1210 }
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215 use super::*;
1216
1217 #[test]
1218 fn test_chat_model_as_str() {
1219 assert_eq!(ChatModel::Gpt4oMini.as_str(), "gpt-4o-mini");
1220 assert_eq!(ChatModel::O3Mini.as_str(), "o3-mini");
1221 assert_eq!(ChatModel::Gpt4_1.as_str(), "gpt-4.1");
1222 assert_eq!(ChatModel::Gpt5_2.as_str(), "gpt-5.2");
1224 assert_eq!(ChatModel::Gpt5_2ChatLatest.as_str(), "gpt-5.2-chat-latest");
1225 assert_eq!(ChatModel::Gpt5_2Pro.as_str(), "gpt-5.2-pro");
1226 assert_eq!(ChatModel::Gpt5_1.as_str(), "gpt-5.1");
1227 assert_eq!(ChatModel::Gpt5_1CodexMax.as_str(), "gpt-5.1-codex-max");
1228 assert_eq!(ChatModel::Gpt5Mini.as_str(), "gpt-5-mini");
1229 }
1230
1231 #[test]
1232 fn test_chat_model_is_reasoning() {
1233 assert!(ChatModel::O1.is_reasoning_model());
1235 assert!(ChatModel::O3.is_reasoning_model());
1236 assert!(ChatModel::O3Mini.is_reasoning_model());
1237 assert!(ChatModel::O4Mini.is_reasoning_model());
1238 assert!(ChatModel::Gpt5_2.is_reasoning_model());
1241 assert!(!ChatModel::Gpt5_2ChatLatest.is_reasoning_model());
1242 assert!(ChatModel::Gpt5_2Pro.is_reasoning_model());
1243 assert!(ChatModel::Gpt5_1.is_reasoning_model());
1244 assert!(ChatModel::Gpt5_1CodexMax.is_reasoning_model());
1245 assert!(ChatModel::Gpt5Mini.is_reasoning_model());
1246 assert!(!ChatModel::Gpt4oMini.is_reasoning_model());
1248 assert!(!ChatModel::Gpt4_1.is_reasoning_model());
1249 }
1250
1251 #[test]
1252 fn test_chat_model_from_str() {
1253 assert_eq!(ChatModel::from("gpt-4o-mini"), ChatModel::Gpt4oMini);
1254 assert_eq!(ChatModel::from("o3-mini"), ChatModel::O3Mini);
1255 assert_eq!(ChatModel::from("gpt-5.2"), ChatModel::Gpt5_2);
1257 assert_eq!(ChatModel::from("gpt-5.2-chat-latest"), ChatModel::Gpt5_2ChatLatest);
1258 assert_eq!(ChatModel::from("gpt-5.2-pro"), ChatModel::Gpt5_2Pro);
1259 assert_eq!(ChatModel::from("gpt-5.1"), ChatModel::Gpt5_1);
1260 assert_eq!(ChatModel::from("gpt-5.1-codex-max"), ChatModel::Gpt5_1CodexMax);
1261 assert_eq!(ChatModel::from("gpt-5-mini"), ChatModel::Gpt5Mini);
1262 assert!(matches!(ChatModel::from("unknown-model"), ChatModel::Custom(_)));
1264 }
1265
1266 #[test]
1267 fn test_chat_model_custom() {
1268 let custom = ChatModel::custom("ft:gpt-4o-mini:org::123");
1269 assert_eq!(custom.as_str(), "ft:gpt-4o-mini:org::123");
1270 }
1271
1272 #[test]
1273 fn test_chat_model_custom_gpt5_is_reasoning() {
1274 let custom_gpt5 = ChatModel::custom("gpt-5.3-preview");
1276 assert!(custom_gpt5.is_reasoning_model());
1277 }
1278
1279 #[test]
1280 fn test_embedding_model_dimensions() {
1281 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1282 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1283 }
1284
1285 #[test]
1286 fn test_realtime_model_as_str() {
1287 assert_eq!(RealtimeModel::GptRealtime_2025_08_28.as_str(), "gpt-realtime-2025-08-28");
1288 }
1289
1290 #[test]
1291 fn test_fine_tuning_model_as_str() {
1292 assert_eq!(FineTuningModel::Gpt4oMini_2024_07_18.as_str(), "gpt-4o-mini-2024-07-18");
1293 assert_eq!(FineTuningModel::Gpt41_2025_04_14.as_str(), "gpt-4.1-2025-04-14");
1294 }
1295
1296 #[test]
1297 fn test_chat_model_serialization() {
1298 let model = ChatModel::Gpt4oMini;
1299 let json = serde_json::to_string(&model).unwrap();
1300 assert_eq!(json, "\"gpt-4o-mini\"");
1301 let gpt52 = ChatModel::Gpt5_2;
1303 let json = serde_json::to_string(&gpt52).unwrap();
1304 assert_eq!(json, "\"gpt-5.2\"");
1305 }
1306
1307 #[test]
1308 fn test_chat_model_deserialization() {
1309 let model: ChatModel = serde_json::from_str("\"gpt-4o-mini\"").unwrap();
1310 assert_eq!(model, ChatModel::Gpt4oMini);
1311 let gpt52: ChatModel = serde_json::from_str("\"gpt-5.2\"").unwrap();
1313 assert_eq!(gpt52, ChatModel::Gpt5_2);
1314 }
1315
1316 #[test]
1317 fn test_parameter_support_standard_model() {
1318 let model = ChatModel::Gpt4oMini;
1319 let support = model.parameter_support();
1320
1321 assert_eq!(support.temperature, ParameterRestriction::Any);
1323 assert_eq!(support.frequency_penalty, ParameterRestriction::Any);
1324 assert_eq!(support.presence_penalty, ParameterRestriction::Any);
1325 assert_eq!(support.top_p, ParameterRestriction::Any);
1326 assert!(support.logprobs);
1327 assert!(support.top_logprobs);
1328 assert!(support.logit_bias);
1329 assert!(support.n_multiple);
1330 assert!(!support.reasoning); }
1332
1333 #[test]
1334 fn test_parameter_support_reasoning_model() {
1335 let model = ChatModel::O3Mini;
1336 let support = model.parameter_support();
1337
1338 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1340 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0));
1341 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0));
1342 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0));
1343 assert!(!support.logprobs);
1344 assert!(!support.top_logprobs);
1345 assert!(!support.logit_bias);
1346 assert!(support.n_multiple); assert!(support.reasoning); }
1349
1350 #[test]
1351 fn test_parameter_support_gpt5_model() {
1352 let model = ChatModel::Gpt5_2;
1354 let support = model.parameter_support();
1355
1356 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1357 assert!(!support.logprobs);
1358 assert!(support.reasoning);
1359 }
1360
1361 #[test]
1366 fn test_all_o_series_models_are_reasoning() {
1367 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1369
1370 for model in o_series {
1371 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1372 }
1373 }
1374
1375 #[test]
1376 fn test_all_gpt5_models_are_reasoning() {
1377 let gpt5_series =
1381 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1382
1383 for model in gpt5_series {
1384 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1385 }
1386 }
1387
1388 #[test]
1389 fn test_all_standard_models_are_not_reasoning() {
1390 let standard_models = vec![
1392 ChatModel::Gpt4oMini,
1393 ChatModel::Gpt4o,
1394 ChatModel::Gpt4oAudioPreview,
1395 ChatModel::Gpt4Turbo,
1396 ChatModel::Gpt4,
1397 ChatModel::Gpt3_5Turbo,
1398 ChatModel::Gpt4_1,
1399 ChatModel::Gpt4_1Mini,
1400 ChatModel::Gpt4_1Nano,
1401 ];
1402
1403 for model in standard_models {
1404 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1405 }
1406 }
1407
1408 #[test]
1413 fn test_custom_o1_models_are_reasoning() {
1414 let custom_o1_variants = vec!["o1-mini", "o1-preview", "o1-pro-2025", "o1-high"];
1415
1416 for model_str in custom_o1_variants {
1417 let model = ChatModel::custom(model_str);
1418 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1419 }
1420 }
1421
1422 #[test]
1423 fn test_custom_o3_models_are_reasoning() {
1424 let custom_o3_variants = vec!["o3-preview", "o3-high", "o3-2025-01-15"];
1425
1426 for model_str in custom_o3_variants {
1427 let model = ChatModel::custom(model_str);
1428 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1429 }
1430 }
1431
1432 #[test]
1433 fn test_custom_o4_models_are_reasoning() {
1434 let custom_o4_variants = vec!["o4-preview", "o4-mini-2025", "o4-high"];
1435
1436 for model_str in custom_o4_variants {
1437 let model = ChatModel::custom(model_str);
1438 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1439 }
1440 }
1441
1442 #[test]
1443 fn test_custom_gpt5_models_are_reasoning() {
1444 let custom_gpt5_variants = vec!["gpt-5.3", "gpt-5.3-preview", "gpt-5-turbo", "gpt-5.0"];
1445
1446 for model_str in custom_gpt5_variants {
1447 let model = ChatModel::custom(model_str);
1448 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1449 }
1450 }
1451
1452 #[test]
1453 fn test_custom_standard_models_are_not_reasoning() {
1454 let custom_standard_variants = vec![
1455 "ft:gpt-4o-mini:org::123",
1456 "gpt-4o-2025-01-15",
1457 "gpt-4-turbo-preview",
1458 "gpt-3.5-turbo-instruct",
1459 "text-davinci-003",
1460 "claude-3-opus", ];
1462
1463 for model_str in custom_standard_variants {
1464 let model = ChatModel::custom(model_str);
1465 assert!(!model.is_reasoning_model(), "Expected custom model '{}' to NOT be a reasoning model", model_str);
1466 }
1467 }
1468
1469 #[test]
1474 fn test_parameter_support_all_o_series() {
1475 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1476
1477 for model in o_series {
1478 let support = model.parameter_support();
1479
1480 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1481 assert_eq!(
1482 support.frequency_penalty,
1483 ParameterRestriction::FixedValue(0.0),
1484 "{} should only support frequency_penalty=0.0",
1485 model.as_str()
1486 );
1487 assert_eq!(
1488 support.presence_penalty,
1489 ParameterRestriction::FixedValue(0.0),
1490 "{} should only support presence_penalty=0.0",
1491 model.as_str()
1492 );
1493 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should only support top_p=1.0", model.as_str());
1494 assert!(!support.logprobs, "{} should not support logprobs", model.as_str());
1495 assert!(!support.top_logprobs, "{} should not support top_logprobs", model.as_str());
1496 assert!(!support.logit_bias, "{} should not support logit_bias", model.as_str());
1497 assert!(support.n_multiple, "{} should accept n > 1", model.as_str());
1498 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1499 }
1500 }
1501
1502 #[test]
1503 fn test_parameter_support_all_gpt5_series() {
1504 let gpt5_series =
1507 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1508
1509 for model in gpt5_series {
1510 let support = model.parameter_support();
1511
1512 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1513 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1514 }
1515 }
1516
1517 #[test]
1518 fn test_parameter_support_all_standard_gpt4_series() {
1519 let gpt4_series = vec![
1520 ChatModel::Gpt4oMini,
1521 ChatModel::Gpt4o,
1522 ChatModel::Gpt4Turbo,
1523 ChatModel::Gpt4,
1524 ChatModel::Gpt4_1,
1525 ChatModel::Gpt4_1Mini,
1526 ChatModel::Gpt4_1Nano,
1527 ];
1528
1529 for model in gpt4_series {
1530 let support = model.parameter_support();
1531
1532 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should support any temperature", model.as_str());
1533 assert_eq!(support.frequency_penalty, ParameterRestriction::Any, "{} should support any frequency_penalty", model.as_str());
1534 assert_eq!(support.presence_penalty, ParameterRestriction::Any, "{} should support any presence_penalty", model.as_str());
1535 assert!(support.logprobs, "{} should support logprobs", model.as_str());
1536 assert!(support.top_logprobs, "{} should support top_logprobs", model.as_str());
1537 assert!(support.logit_bias, "{} should support logit_bias", model.as_str());
1538 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1539 assert!(!support.reasoning, "{} should NOT support reasoning parameter", model.as_str());
1540 }
1541 }
1542
1543 #[test]
1548 fn test_parameter_restriction_equality() {
1549 assert_eq!(ParameterRestriction::Any, ParameterRestriction::Any);
1550 assert_eq!(ParameterRestriction::NotSupported, ParameterRestriction::NotSupported);
1551 assert_eq!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(1.0));
1552
1553 assert_ne!(ParameterRestriction::Any, ParameterRestriction::NotSupported);
1554 assert_ne!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(0.0));
1555 }
1556
1557 #[test]
1558 fn test_parameter_support_factory_methods() {
1559 let standard = ParameterSupport::standard_model();
1560 assert_eq!(standard.temperature, ParameterRestriction::Any);
1561 assert!(standard.logprobs);
1562 assert!(!standard.reasoning);
1563
1564 let reasoning = ParameterSupport::reasoning_model();
1565 assert_eq!(reasoning.temperature, ParameterRestriction::FixedValue(1.0));
1566 assert!(!reasoning.logprobs);
1567 assert!(reasoning.reasoning);
1568 }
1569
1570 #[test]
1575 fn test_all_gpt5_model_string_roundtrip() {
1576 let gpt5_models = vec![
1577 ("gpt-5.2", ChatModel::Gpt5_2),
1578 ("gpt-5.2-chat-latest", ChatModel::Gpt5_2ChatLatest),
1579 ("gpt-5.2-pro", ChatModel::Gpt5_2Pro),
1580 ("gpt-5.1", ChatModel::Gpt5_1),
1581 ("gpt-5.1-chat-latest", ChatModel::Gpt5_1ChatLatest),
1582 ("gpt-5.1-codex-max", ChatModel::Gpt5_1CodexMax),
1583 ("gpt-5-mini", ChatModel::Gpt5Mini),
1584 ("gpt-5-nano", ChatModel::Gpt5Nano),
1585 ];
1586
1587 for (model_str, expected_model) in gpt5_models {
1588 let parsed = ChatModel::from(model_str);
1590 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1591
1592 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1594
1595 let json = serde_json::to_string(&expected_model).unwrap();
1597 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1598 assert_eq!(deserialized, expected_model, "Serialization roundtrip failed for {}", model_str);
1599 }
1600 }
1601
1602 #[test]
1603 fn test_all_o_series_model_string_roundtrip() {
1604 let o_series_models = vec![
1605 ("o1", ChatModel::O1),
1606 ("o1-pro", ChatModel::O1Pro),
1607 ("o3", ChatModel::O3),
1608 ("o3-mini", ChatModel::O3Mini),
1609 ("o4-mini", ChatModel::O4Mini),
1610 ];
1611
1612 for (model_str, expected_model) in o_series_models {
1613 let parsed = ChatModel::from(model_str);
1614 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1615 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1616 }
1617 }
1618
1619 #[test]
1624 fn test_embedding_model_string_roundtrip() {
1625 let embedding_models = vec![
1626 ("text-embedding-3-small", EmbeddingModel::TextEmbedding3Small),
1627 ("text-embedding-3-large", EmbeddingModel::TextEmbedding3Large),
1628 ("text-embedding-ada-002", EmbeddingModel::TextEmbeddingAda002),
1629 ];
1630
1631 for (model_str, expected_model) in embedding_models {
1632 let parsed = EmbeddingModel::from(model_str);
1633 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1634 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1635 }
1636 }
1637
1638 #[test]
1639 fn test_embedding_model_all_dimensions() {
1640 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1641 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1642 assert_eq!(EmbeddingModel::TextEmbeddingAda002.dimensions(), 1536);
1643 }
1644
1645 #[test]
1650 fn test_realtime_model_string_roundtrip() {
1651 let realtime_models = vec![("gpt-realtime-2025-08-28", RealtimeModel::GptRealtime_2025_08_28)];
1652
1653 for (model_str, expected_model) in realtime_models {
1654 let parsed = RealtimeModel::from(model_str);
1655 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1656 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1657 }
1658 }
1659
1660 #[test]
1661 fn test_realtime_model_custom() {
1662 let custom = RealtimeModel::custom("gpt-4o-realtime-2025");
1663 assert_eq!(custom.as_str(), "gpt-4o-realtime-2025");
1664 assert!(matches!(custom, RealtimeModel::Custom(_)));
1665 }
1666
1667 #[test]
1672 fn test_fine_tuning_model_as_str_all_variants() {
1673 let fine_tuning_models = vec![
1674 ("gpt-4.1-2025-04-14", FineTuningModel::Gpt41_2025_04_14),
1675 ("gpt-4.1-mini-2025-04-14", FineTuningModel::Gpt41Mini_2025_04_14),
1676 ("gpt-4.1-nano-2025-04-14", FineTuningModel::Gpt41Nano_2025_04_14),
1677 ("gpt-4o-mini-2024-07-18", FineTuningModel::Gpt4oMini_2024_07_18),
1678 ("gpt-4o-2024-08-06", FineTuningModel::Gpt4o_2024_08_06),
1679 ("gpt-4-0613", FineTuningModel::Gpt4_0613),
1680 ("gpt-3.5-turbo-0125", FineTuningModel::Gpt35Turbo_0125),
1681 ("gpt-3.5-turbo-1106", FineTuningModel::Gpt35Turbo_1106),
1682 ("gpt-3.5-turbo-0613", FineTuningModel::Gpt35Turbo_0613),
1683 ];
1684
1685 for (model_str, expected_model) in fine_tuning_models {
1686 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1687 }
1688 }
1689
1690 #[test]
1691 fn test_fine_tuning_model_serialization_roundtrip() {
1692 let models = vec![FineTuningModel::Gpt41_2025_04_14, FineTuningModel::Gpt4oMini_2024_07_18, FineTuningModel::Gpt35Turbo_0125];
1693
1694 for model in models {
1695 let json = serde_json::to_string(&model).unwrap();
1696 let deserialized: FineTuningModel = serde_json::from_str(&json).unwrap();
1697 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1698 }
1699 }
1700
1701 fn new_chat_models() -> Vec<(ChatModel, &'static str)> {
1711 vec![
1712 (ChatModel::Gpt6_1Sol, "gpt-6.1-sol"),
1714 (ChatModel::Gpt6Astra, "gpt-6-astra"),
1716 (ChatModel::Gpt6Sol, "gpt-6-sol"),
1717 (ChatModel::Gpt6Luna, "gpt-6-luna"),
1718 (ChatModel::Gpt5_6, "gpt-5.6"),
1720 (ChatModel::Gpt5_6Sol, "gpt-5.6-sol"),
1721 (ChatModel::Gpt5_6Terra, "gpt-5.6-terra"),
1722 (ChatModel::Gpt5_6Luna, "gpt-5.6-luna"),
1723 (ChatModel::Gpt5_5, "gpt-5.5"),
1725 (ChatModel::Gpt5_5Pro, "gpt-5.5-pro"),
1726 (ChatModel::Gpt5_4, "gpt-5.4"),
1728 (ChatModel::Gpt5_4Pro, "gpt-5.4-pro"),
1729 (ChatModel::Gpt5_4Mini, "gpt-5.4-mini"),
1730 (ChatModel::Gpt5_4Nano, "gpt-5.4-nano"),
1731 (ChatModel::Gpt5_3ChatLatest, "gpt-5.3-chat-latest"),
1733 (ChatModel::Gpt5_3Codex, "gpt-5.3-codex"),
1735 (ChatModel::Gpt5_2Codex, "gpt-5.2-codex"),
1736 (ChatModel::Gpt5_1Codex, "gpt-5.1-codex"),
1737 ]
1738 }
1739
1740 #[test]
1741 fn test_new_chat_models_as_str() {
1742 for (model, expected) in new_chat_models() {
1743 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1744 }
1745 }
1746
1747 #[test]
1748 fn test_new_chat_models_from_str_roundtrip() {
1749 for (model, model_str) in new_chat_models() {
1750 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1751 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1753 }
1754
1755 assert!(matches!(ChatModel::from("gpt-6.1"), ChatModel::Custom(_)), "gpt-6.1 must not be treated as an API model alias");
1756 assert!(matches!(ChatModel::from("gpt-6"), ChatModel::Custom(_)), "gpt-6 must not be treated as an API model alias");
1757 }
1758
1759 #[test]
1760 fn test_new_chat_models_serialization_roundtrip() {
1761 for (model, model_str) in new_chat_models() {
1762 let json = serde_json::to_string(&model).unwrap();
1763 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1764 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1765 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1766 }
1767 }
1768
1769 #[test]
1772 fn test_new_models_are_reasoning_models() {
1773 let reasoning = vec![
1774 ChatModel::Gpt6_1Sol,
1775 ChatModel::Gpt6Astra,
1776 ChatModel::Gpt6Sol,
1777 ChatModel::Gpt6Luna,
1778 ChatModel::Gpt5_6,
1779 ChatModel::Gpt5_6Sol,
1780 ChatModel::Gpt5_6Terra,
1781 ChatModel::Gpt5_6Luna,
1782 ChatModel::Gpt5_5,
1783 ChatModel::Gpt5_5Pro,
1784 ChatModel::Gpt5_4,
1785 ChatModel::Gpt5_4Pro,
1786 ChatModel::Gpt5_4Mini,
1787 ChatModel::Gpt5_4Nano,
1788 ChatModel::Gpt5_3Codex,
1789 ChatModel::Gpt5_2Codex,
1790 ChatModel::Gpt5_1Codex,
1791 ];
1792
1793 for model in reasoning {
1794 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1795 let support = model.parameter_support();
1796 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should pin temperature to 1.0", model.as_str());
1797 assert!(support.reasoning, "{} should support the reasoning parameter", model.as_str());
1798 }
1799 }
1800
1801 #[test]
1805 fn test_chat_latest_models_are_not_reasoning_models() {
1806 let chat_latest = vec![ChatModel::Gpt5_3ChatLatest, ChatModel::Gpt5_2ChatLatest, ChatModel::Gpt5_1ChatLatest];
1807
1808 for model in chat_latest {
1809 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1810 let support = model.parameter_support();
1811 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1812 assert_eq!(support.top_p, ParameterRestriction::Any, "{} should accept any top_p", model.as_str());
1813 assert!(!support.reasoning, "{} should NOT support the reasoning parameter", model.as_str());
1814 }
1815 }
1816
1817 #[test]
1820 fn test_custom_chat_latest_is_not_reasoning_model() {
1821 assert!(!ChatModel::custom("gpt-5.7-chat-latest").is_reasoning_model());
1822 assert!(ChatModel::custom("gpt-5.7").is_reasoning_model());
1824 assert!(ChatModel::custom("gpt-5.7-codex").is_reasoning_model());
1825 }
1826
1827 fn new_realtime_models() -> Vec<(RealtimeModel, &'static str)> {
1832 vec![
1833 (RealtimeModel::GptRealtime2_1, "gpt-realtime-2.1"),
1834 (RealtimeModel::GptRealtime2_1Mini, "gpt-realtime-2.1-mini"),
1835 (RealtimeModel::GptRealtime2, "gpt-realtime-2"),
1836 (RealtimeModel::GptRealtime1_5, "gpt-realtime-1.5"),
1837 (RealtimeModel::GptRealtimeTranslate, "gpt-realtime-translate"),
1838 ]
1839 }
1840
1841 #[test]
1842 fn test_new_realtime_models_as_str() {
1843 for (model, expected) in new_realtime_models() {
1844 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1845 }
1846 }
1847
1848 #[test]
1849 fn test_new_realtime_models_from_str_roundtrip() {
1850 for (model, model_str) in new_realtime_models() {
1851 assert_eq!(RealtimeModel::from(model_str), model, "Failed to parse {}", model_str);
1852 }
1853 }
1854
1855 #[test]
1859 fn test_realtime_model_default_is_unchanged() {
1860 assert_eq!(RealtimeModel::default(), RealtimeModel::GptRealtime_2025_08_28);
1861 }
1862
1863 #[test]
1864 fn test_new_realtime_models_serialization_roundtrip() {
1865 for (model, model_str) in new_realtime_models() {
1866 let json = serde_json::to_string(&model).unwrap();
1867 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1868 let deserialized: RealtimeModel = serde_json::from_str(&json).unwrap();
1869 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1870 }
1871 }
1872
1873 fn previously_missing_chat_models() -> Vec<(ChatModel, &'static str)> {
1881 vec![
1882 (ChatModel::Gpt5, "gpt-5"),
1883 (ChatModel::Gpt5Pro, "gpt-5-pro"),
1884 (ChatModel::O3Pro, "o3-pro"),
1885 (ChatModel::Gpt5SearchApi, "gpt-5-search-api"),
1886 (ChatModel::Gpt4oSearchPreview, "gpt-4o-search-preview"),
1887 (ChatModel::Gpt4oMiniSearchPreview, "gpt-4o-mini-search-preview"),
1888 (ChatModel::GptAudio, "gpt-audio"),
1889 (ChatModel::GptAudio1_5, "gpt-audio-1.5"),
1890 (ChatModel::GptAudioMini, "gpt-audio-mini"),
1891 (ChatModel::Gpt3_5Turbo16k, "gpt-3.5-turbo-16k"),
1892 ]
1893 }
1894
1895 #[test]
1896 fn test_previously_missing_chat_models_as_str() {
1897 for (model, expected) in previously_missing_chat_models() {
1898 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1899 }
1900 }
1901
1902 #[test]
1903 fn test_previously_missing_chat_models_from_str_roundtrip() {
1904 for (model, model_str) in previously_missing_chat_models() {
1905 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1906 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1907 }
1908 }
1909
1910 #[test]
1911 fn test_previously_missing_chat_models_serialization_roundtrip() {
1912 for (model, model_str) in previously_missing_chat_models() {
1913 let json = serde_json::to_string(&model).unwrap();
1914 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1915 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1916 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1917 }
1918 }
1919
1920 #[test]
1923 fn test_gpt5_base_and_pro_models_are_reasoning() {
1924 for model in [ChatModel::Gpt5, ChatModel::Gpt5Pro, ChatModel::O3Pro] {
1925 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1926 assert!(model.parameter_support().reasoning, "{} should support the reasoning parameter", model.as_str());
1927 }
1928 }
1929
1930 #[test]
1932 fn test_audio_chat_models_are_standard() {
1933 for model in [ChatModel::GptAudio, ChatModel::GptAudio1_5, ChatModel::GptAudioMini, ChatModel::Gpt3_5Turbo16k] {
1934 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1935 let support = model.parameter_support();
1936 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1937 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1938 }
1939 }
1940
1941 #[test]
1944 fn test_search_models_reject_sampling_but_have_no_reasoning() {
1945 let search_models = [ChatModel::Gpt5SearchApi, ChatModel::Gpt4oSearchPreview, ChatModel::Gpt4oMiniSearchPreview];
1946
1947 for model in search_models {
1948 assert!(model.is_search_model(), "Expected {} to be a search model", model.as_str());
1949 assert!(!model.is_reasoning_model(), "Search model {} must not be classed as reasoning", model.as_str());
1950
1951 let support = model.parameter_support();
1952 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should reject custom temperature", model.as_str());
1953 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should reject custom top_p", model.as_str());
1954 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject frequency_penalty", model.as_str());
1955 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject presence_penalty", model.as_str());
1956 assert!(!support.logprobs, "{} should reject logprobs", model.as_str());
1957 assert!(!support.n_multiple, "{} should reject n > 1", model.as_str());
1958 assert!(!support.reasoning, "{} exposes no reasoning parameter", model.as_str());
1959 }
1960 }
1961
1962 #[test]
1964 fn test_non_search_models_are_not_search_models() {
1965 for model in [ChatModel::Gpt5_6Sol, ChatModel::Gpt4oMini, ChatModel::O3Mini, ChatModel::Gpt5_2ChatLatest] {
1966 assert!(!model.is_search_model(), "Expected {} to NOT be a search model", model.as_str());
1967 }
1968 }
1969
1970 #[test]
1971 fn test_previously_missing_realtime_models() {
1972 for (model, expected) in [(RealtimeModel::GptRealtime, "gpt-realtime"), (RealtimeModel::GptRealtimeMini, "gpt-realtime-mini")] {
1973 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1974 assert_eq!(RealtimeModel::from(expected), model, "Failed to parse {}", expected);
1975 let json = serde_json::to_string(&model).unwrap();
1976 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1977 }
1978 }
1979
1980 #[test]
1981 fn test_legacy_fine_tuning_base_models() {
1982 for (model, expected) in [(FineTuningModel::Babbage002, "babbage-002"), (FineTuningModel::Davinci002, "davinci-002")] {
1983 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1984 let json = serde_json::to_string(&model).unwrap();
1985 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1986 }
1987 }
1988}