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 {
133 Self {
134 temperature: ParameterRestriction::FixedValue(1.0),
135 frequency_penalty: ParameterRestriction::FixedValue(0.0),
136 presence_penalty: ParameterRestriction::FixedValue(0.0),
137 logprobs: false,
138 top_logprobs: false,
139 logit_bias: false,
140 n_multiple: false,
141 top_p: ParameterRestriction::FixedValue(1.0),
142 reasoning: true,
143 }
144 }
145
146 pub fn search_model() -> Self {
153 Self { reasoning: false, ..Self::reasoning_model() }
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
218pub enum ChatModel {
219 #[serde(rename = "gpt-5.6")]
222 Gpt5_6,
223
224 #[serde(rename = "gpt-5.6-sol")]
230 Gpt5_6Sol,
231
232 #[serde(rename = "gpt-5.6-terra")]
237 Gpt5_6Terra,
238
239 #[serde(rename = "gpt-5.6-luna")]
244 Gpt5_6Luna,
245
246 #[serde(rename = "gpt-5.5")]
252 Gpt5_5,
253
254 #[serde(rename = "gpt-5.5-pro")]
259 Gpt5_5Pro,
260
261 #[serde(rename = "gpt-5.4")]
267 Gpt5_4,
268
269 #[serde(rename = "gpt-5.4-pro")]
274 Gpt5_4Pro,
275
276 #[serde(rename = "gpt-5.4-mini")]
281 Gpt5_4Mini,
282
283 #[serde(rename = "gpt-5.4-nano")]
288 Gpt5_4Nano,
289
290 #[serde(rename = "gpt-5.3-chat-latest")]
297 Gpt5_3ChatLatest,
298
299 #[serde(rename = "gpt-5.3-codex")]
306 Gpt5_3Codex,
307
308 #[serde(rename = "gpt-5.2-codex")]
314 Gpt5_2Codex,
315
316 #[serde(rename = "gpt-5.1-codex")]
320 Gpt5_1Codex,
321
322 #[serde(rename = "gpt-5-search-api")]
327 Gpt5SearchApi,
328
329 #[serde(rename = "gpt-4o-search-preview")]
331 Gpt4oSearchPreview,
332
333 #[serde(rename = "gpt-4o-mini-search-preview")]
335 Gpt4oMiniSearchPreview,
336
337 #[serde(rename = "gpt-audio")]
342 GptAudio,
343
344 #[serde(rename = "gpt-audio-1.5")]
346 GptAudio1_5,
347
348 #[serde(rename = "gpt-audio-mini")]
350 GptAudioMini,
351
352 #[serde(rename = "gpt-5")]
355 Gpt5,
356
357 #[serde(rename = "gpt-5-pro")]
361 Gpt5Pro,
362
363 #[serde(rename = "gpt-5.2")]
369 Gpt5_2,
370
371 #[serde(rename = "gpt-5.2-chat-latest")]
378 Gpt5_2ChatLatest,
379
380 #[serde(rename = "gpt-5.2-pro")]
385 Gpt5_2Pro,
386
387 #[serde(rename = "gpt-5.1")]
392 Gpt5_1,
393
394 #[serde(rename = "gpt-5.1-chat-latest")]
399 Gpt5_1ChatLatest,
400
401 #[serde(rename = "gpt-5.1-codex-max")]
406 Gpt5_1CodexMax,
407
408 #[serde(rename = "gpt-5-mini")]
410 Gpt5Mini,
411
412 #[serde(rename = "gpt-5-nano")]
414 Gpt5Nano,
415
416 #[serde(rename = "gpt-4.1")]
419 Gpt4_1,
420
421 #[serde(rename = "gpt-4.1-mini")]
423 Gpt4_1Mini,
424
425 #[serde(rename = "gpt-4.1-nano")]
427 Gpt4_1Nano,
428
429 #[serde(rename = "gpt-4o")]
432 Gpt4o,
433
434 #[serde(rename = "gpt-4o-mini")]
436 #[default]
437 Gpt4oMini,
438
439 #[serde(rename = "gpt-4o-audio-preview")]
441 Gpt4oAudioPreview,
442
443 #[serde(rename = "gpt-4-turbo")]
446 Gpt4Turbo,
447
448 #[serde(rename = "gpt-4")]
450 Gpt4,
451
452 #[serde(rename = "gpt-3.5-turbo")]
455 Gpt3_5Turbo,
456
457 #[serde(rename = "gpt-3.5-turbo-16k")]
459 Gpt3_5Turbo16k,
460
461 #[serde(rename = "o1")]
464 O1,
465
466 #[serde(rename = "o1-pro")]
468 O1Pro,
469
470 #[serde(rename = "o3")]
472 O3,
473
474 #[serde(rename = "o3-pro")]
478 O3Pro,
479
480 #[serde(rename = "o3-mini")]
482 O3Mini,
483
484 #[serde(rename = "o4-mini")]
486 O4Mini,
487
488 #[serde(untagged)]
491 Custom(String),
492}
493
494impl ChatModel {
495 pub fn as_str(&self) -> &str {
507 match self {
508 Self::Gpt5_6 => "gpt-5.6",
510 Self::Gpt5_6Sol => "gpt-5.6-sol",
511 Self::Gpt5_6Terra => "gpt-5.6-terra",
512 Self::Gpt5_6Luna => "gpt-5.6-luna",
513 Self::Gpt5_5 => "gpt-5.5",
515 Self::Gpt5_5Pro => "gpt-5.5-pro",
516 Self::Gpt5_4 => "gpt-5.4",
518 Self::Gpt5_4Pro => "gpt-5.4-pro",
519 Self::Gpt5_4Mini => "gpt-5.4-mini",
520 Self::Gpt5_4Nano => "gpt-5.4-nano",
521 Self::Gpt5_3ChatLatest => "gpt-5.3-chat-latest",
523 Self::Gpt5_3Codex => "gpt-5.3-codex",
525 Self::Gpt5_2Codex => "gpt-5.2-codex",
526 Self::Gpt5_1Codex => "gpt-5.1-codex",
527 Self::Gpt5SearchApi => "gpt-5-search-api",
529 Self::Gpt4oSearchPreview => "gpt-4o-search-preview",
530 Self::Gpt4oMiniSearchPreview => "gpt-4o-mini-search-preview",
531 Self::GptAudio => "gpt-audio",
533 Self::GptAudio1_5 => "gpt-audio-1.5",
534 Self::GptAudioMini => "gpt-audio-mini",
535 Self::Gpt5 => "gpt-5",
537 Self::Gpt5Pro => "gpt-5-pro",
538 Self::Gpt5_2 => "gpt-5.2",
539 Self::Gpt5_2ChatLatest => "gpt-5.2-chat-latest",
540 Self::Gpt5_2Pro => "gpt-5.2-pro",
541 Self::Gpt5_1 => "gpt-5.1",
542 Self::Gpt5_1ChatLatest => "gpt-5.1-chat-latest",
543 Self::Gpt5_1CodexMax => "gpt-5.1-codex-max",
544 Self::Gpt5Mini => "gpt-5-mini",
545 Self::Gpt5Nano => "gpt-5-nano",
546 Self::Gpt4_1 => "gpt-4.1",
548 Self::Gpt4_1Mini => "gpt-4.1-mini",
549 Self::Gpt4_1Nano => "gpt-4.1-nano",
550 Self::Gpt4o => "gpt-4o",
552 Self::Gpt4oMini => "gpt-4o-mini",
553 Self::Gpt4oAudioPreview => "gpt-4o-audio-preview",
554 Self::Gpt4Turbo => "gpt-4-turbo",
556 Self::Gpt4 => "gpt-4",
557 Self::Gpt3_5Turbo => "gpt-3.5-turbo",
559 Self::Gpt3_5Turbo16k => "gpt-3.5-turbo-16k",
560 Self::O1 => "o1",
562 Self::O1Pro => "o1-pro",
563 Self::O3 => "o3",
564 Self::O3Pro => "o3-pro",
565 Self::O3Mini => "o3-mini",
566 Self::O4Mini => "o4-mini",
567 Self::Custom(s) => s.as_str(),
569 }
570 }
571
572 pub fn is_reasoning_model(&self) -> bool {
591 matches!(
592 self,
593 Self::Gpt5_6 | Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna |
595 Self::Gpt5_5 | Self::Gpt5_5Pro |
597 Self::Gpt5_4 | Self::Gpt5_4Pro | Self::Gpt5_4Mini | Self::Gpt5_4Nano |
599 Self::Gpt5_3Codex | Self::Gpt5_2Codex | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax |
601 Self::Gpt5 | Self::Gpt5Pro | Self::Gpt5_2 | Self::Gpt5_2Pro | Self::Gpt5_1 | Self::Gpt5Mini | Self::Gpt5Nano |
603 Self::O1 | Self::O1Pro | Self::O3 | Self::O3Pro | Self::O3Mini | Self::O4Mini
605 ) || matches!(
606 self,
607 Self::Custom(s) if !s.ends_with("-chat-latest")
611 && !s.contains("-search")
612 && (s.starts_with("gpt-5") || s.starts_with("o1") || s.starts_with("o3") || s.starts_with("o4"))
613 )
614 }
615
616 pub fn is_search_model(&self) -> bool {
633 matches!(self, Self::Gpt5SearchApi | Self::Gpt4oSearchPreview | Self::Gpt4oMiniSearchPreview)
634 || matches!(self, Self::Custom(s) if s.contains("-search"))
635 }
636
637 pub fn parameter_support(&self) -> ParameterSupport {
661 if self.is_search_model() {
662 ParameterSupport::search_model()
663 } else if self.is_reasoning_model() {
664 ParameterSupport::reasoning_model()
665 } else {
666 ParameterSupport::standard_model()
667 }
668 }
669
670 pub fn custom(model_id: impl Into<String>) -> Self {
683 Self::Custom(model_id.into())
684 }
685}
686
687impl std::fmt::Display for ChatModel {
688 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
689 write!(f, "{}", self.as_str())
690 }
691}
692
693impl From<&str> for ChatModel {
694 fn from(s: &str) -> Self {
695 match s {
696 "gpt-5.6" => Self::Gpt5_6,
698 "gpt-5.6-sol" => Self::Gpt5_6Sol,
699 "gpt-5.6-terra" => Self::Gpt5_6Terra,
700 "gpt-5.6-luna" => Self::Gpt5_6Luna,
701 "gpt-5.5" => Self::Gpt5_5,
703 "gpt-5.5-pro" => Self::Gpt5_5Pro,
704 "gpt-5.4" => Self::Gpt5_4,
706 "gpt-5.4-pro" => Self::Gpt5_4Pro,
707 "gpt-5.4-mini" => Self::Gpt5_4Mini,
708 "gpt-5.4-nano" => Self::Gpt5_4Nano,
709 "gpt-5.3-chat-latest" => Self::Gpt5_3ChatLatest,
711 "gpt-5.3-codex" => Self::Gpt5_3Codex,
713 "gpt-5.2-codex" => Self::Gpt5_2Codex,
714 "gpt-5.1-codex" => Self::Gpt5_1Codex,
715 "gpt-5-search-api" => Self::Gpt5SearchApi,
717 "gpt-4o-search-preview" => Self::Gpt4oSearchPreview,
718 "gpt-4o-mini-search-preview" => Self::Gpt4oMiniSearchPreview,
719 "gpt-audio" => Self::GptAudio,
721 "gpt-audio-1.5" => Self::GptAudio1_5,
722 "gpt-audio-mini" => Self::GptAudioMini,
723 "gpt-5" => Self::Gpt5,
725 "gpt-5-pro" => Self::Gpt5Pro,
726 "gpt-5.2" => Self::Gpt5_2,
727 "gpt-5.2-chat-latest" => Self::Gpt5_2ChatLatest,
728 "gpt-5.2-pro" => Self::Gpt5_2Pro,
729 "gpt-5.1" => Self::Gpt5_1,
730 "gpt-5.1-chat-latest" => Self::Gpt5_1ChatLatest,
731 "gpt-5.1-codex-max" => Self::Gpt5_1CodexMax,
732 "gpt-5-mini" => Self::Gpt5Mini,
733 "gpt-5-nano" => Self::Gpt5Nano,
734 "gpt-4.1" => Self::Gpt4_1,
736 "gpt-4.1-mini" => Self::Gpt4_1Mini,
737 "gpt-4.1-nano" => Self::Gpt4_1Nano,
738 "gpt-4o" => Self::Gpt4o,
740 "gpt-4o-mini" => Self::Gpt4oMini,
741 "gpt-4o-audio-preview" => Self::Gpt4oAudioPreview,
742 "gpt-4-turbo" => Self::Gpt4Turbo,
744 "gpt-4" => Self::Gpt4,
745 "gpt-3.5-turbo" => Self::Gpt3_5Turbo,
747 "gpt-3.5-turbo-16k" => Self::Gpt3_5Turbo16k,
748 "o1" => Self::O1,
750 "o1-pro" => Self::O1Pro,
751 "o3" => Self::O3,
752 "o3-pro" => Self::O3Pro,
753 "o3-mini" => Self::O3Mini,
754 "o4-mini" => Self::O4Mini,
755 other => Self::Custom(other.to_string()),
757 }
758 }
759}
760
761impl From<String> for ChatModel {
762 fn from(s: String) -> Self {
763 Self::from(s.as_str())
764 }
765}
766
767#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
796pub enum EmbeddingModel {
797 #[serde(rename = "text-embedding-3-small")]
803 #[default]
804 TextEmbedding3Small,
805
806 #[serde(rename = "text-embedding-3-large")]
812 TextEmbedding3Large,
813
814 #[serde(rename = "text-embedding-ada-002")]
820 TextEmbeddingAda002,
821}
822
823impl EmbeddingModel {
824 pub fn as_str(&self) -> &str {
826 match self {
827 Self::TextEmbedding3Small => "text-embedding-3-small",
828 Self::TextEmbedding3Large => "text-embedding-3-large",
829 Self::TextEmbeddingAda002 => "text-embedding-ada-002",
830 }
831 }
832
833 pub fn dimensions(&self) -> usize {
838 match self {
839 Self::TextEmbedding3Small => 1536,
840 Self::TextEmbedding3Large => 3072,
841 Self::TextEmbeddingAda002 => 1536,
842 }
843 }
844}
845
846impl std::fmt::Display for EmbeddingModel {
847 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
848 write!(f, "{}", self.as_str())
849 }
850}
851
852impl From<&str> for EmbeddingModel {
853 fn from(s: &str) -> Self {
854 match s {
855 "text-embedding-3-small" => Self::TextEmbedding3Small,
856 "text-embedding-3-large" => Self::TextEmbedding3Large,
857 "text-embedding-ada-002" => Self::TextEmbeddingAda002,
858 _ => Self::TextEmbedding3Small, }
860 }
861}
862
863#[allow(non_camel_case_types)]
889#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
890pub enum RealtimeModel {
891 #[serde(rename = "gpt-realtime-2.1")]
897 GptRealtime2_1,
898
899 #[serde(rename = "gpt-realtime-2.1-mini")]
901 GptRealtime2_1Mini,
902
903 #[serde(rename = "gpt-realtime-2")]
905 GptRealtime2,
906
907 #[serde(rename = "gpt-realtime")]
909 GptRealtime,
910
911 #[serde(rename = "gpt-realtime-mini")]
913 GptRealtimeMini,
914
915 #[serde(rename = "gpt-realtime-1.5")]
917 GptRealtime1_5,
918
919 #[serde(rename = "gpt-realtime-translate")]
923 GptRealtimeTranslate,
924
925 #[serde(rename = "gpt-realtime-2025-08-28")]
931 #[default]
932 GptRealtime_2025_08_28,
933
934 #[serde(untagged)]
936 Custom(String),
937}
938
939impl RealtimeModel {
940 pub fn as_str(&self) -> &str {
942 match self {
943 Self::GptRealtime2_1 => "gpt-realtime-2.1",
944 Self::GptRealtime2_1Mini => "gpt-realtime-2.1-mini",
945 Self::GptRealtime2 => "gpt-realtime-2",
946 Self::GptRealtime => "gpt-realtime",
947 Self::GptRealtimeMini => "gpt-realtime-mini",
948 Self::GptRealtime1_5 => "gpt-realtime-1.5",
949 Self::GptRealtimeTranslate => "gpt-realtime-translate",
950 Self::GptRealtime_2025_08_28 => "gpt-realtime-2025-08-28",
951 Self::Custom(s) => s.as_str(),
952 }
953 }
954
955 pub fn custom(model_id: impl Into<String>) -> Self {
957 Self::Custom(model_id.into())
958 }
959}
960
961impl std::fmt::Display for RealtimeModel {
962 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
963 write!(f, "{}", self.as_str())
964 }
965}
966
967impl From<&str> for RealtimeModel {
968 fn from(s: &str) -> Self {
969 match s {
970 "gpt-realtime-2.1" => Self::GptRealtime2_1,
971 "gpt-realtime-2.1-mini" => Self::GptRealtime2_1Mini,
972 "gpt-realtime-2" => Self::GptRealtime2,
973 "gpt-realtime" => Self::GptRealtime,
974 "gpt-realtime-mini" => Self::GptRealtimeMini,
975 "gpt-realtime-1.5" => Self::GptRealtime1_5,
976 "gpt-realtime-translate" => Self::GptRealtimeTranslate,
977 "gpt-realtime-2025-08-28" => Self::GptRealtime_2025_08_28,
978 other => Self::Custom(other.to_string()),
979 }
980 }
981}
982
983#[allow(non_camel_case_types)]
1023#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1024pub enum FineTuningModel {
1025 #[serde(rename = "gpt-4.1-2025-04-14")]
1028 Gpt41_2025_04_14,
1029
1030 #[serde(rename = "gpt-4.1-mini-2025-04-14")]
1032 Gpt41Mini_2025_04_14,
1033
1034 #[serde(rename = "gpt-4.1-nano-2025-04-14")]
1036 Gpt41Nano_2025_04_14,
1037
1038 #[serde(rename = "gpt-4o-mini-2024-07-18")]
1041 #[default]
1042 Gpt4oMini_2024_07_18,
1043
1044 #[serde(rename = "gpt-4o-2024-08-06")]
1046 Gpt4o_2024_08_06,
1047
1048 #[serde(rename = "gpt-4-0613")]
1051 Gpt4_0613,
1052
1053 #[serde(rename = "gpt-3.5-turbo-0125")]
1056 Gpt35Turbo_0125,
1057
1058 #[serde(rename = "gpt-3.5-turbo-1106")]
1060 Gpt35Turbo_1106,
1061
1062 #[serde(rename = "gpt-3.5-turbo-0613")]
1064 Gpt35Turbo_0613,
1065
1066 #[serde(rename = "babbage-002")]
1068 Babbage002,
1069
1070 #[serde(rename = "davinci-002")]
1072 Davinci002,
1073}
1074
1075impl FineTuningModel {
1076 pub fn as_str(&self) -> &str {
1078 match self {
1079 Self::Gpt41_2025_04_14 => "gpt-4.1-2025-04-14",
1081 Self::Gpt41Mini_2025_04_14 => "gpt-4.1-mini-2025-04-14",
1082 Self::Gpt41Nano_2025_04_14 => "gpt-4.1-nano-2025-04-14",
1083 Self::Gpt4oMini_2024_07_18 => "gpt-4o-mini-2024-07-18",
1085 Self::Gpt4o_2024_08_06 => "gpt-4o-2024-08-06",
1086 Self::Gpt4_0613 => "gpt-4-0613",
1088 Self::Gpt35Turbo_0125 => "gpt-3.5-turbo-0125",
1090 Self::Gpt35Turbo_1106 => "gpt-3.5-turbo-1106",
1091 Self::Gpt35Turbo_0613 => "gpt-3.5-turbo-0613",
1092 Self::Babbage002 => "babbage-002",
1093 Self::Davinci002 => "davinci-002",
1094 }
1095 }
1096}
1097
1098impl std::fmt::Display for FineTuningModel {
1099 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1100 write!(f, "{}", self.as_str())
1101 }
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106 use super::*;
1107
1108 #[test]
1109 fn test_chat_model_as_str() {
1110 assert_eq!(ChatModel::Gpt4oMini.as_str(), "gpt-4o-mini");
1111 assert_eq!(ChatModel::O3Mini.as_str(), "o3-mini");
1112 assert_eq!(ChatModel::Gpt4_1.as_str(), "gpt-4.1");
1113 assert_eq!(ChatModel::Gpt5_2.as_str(), "gpt-5.2");
1115 assert_eq!(ChatModel::Gpt5_2ChatLatest.as_str(), "gpt-5.2-chat-latest");
1116 assert_eq!(ChatModel::Gpt5_2Pro.as_str(), "gpt-5.2-pro");
1117 assert_eq!(ChatModel::Gpt5_1.as_str(), "gpt-5.1");
1118 assert_eq!(ChatModel::Gpt5_1CodexMax.as_str(), "gpt-5.1-codex-max");
1119 assert_eq!(ChatModel::Gpt5Mini.as_str(), "gpt-5-mini");
1120 }
1121
1122 #[test]
1123 fn test_chat_model_is_reasoning() {
1124 assert!(ChatModel::O1.is_reasoning_model());
1126 assert!(ChatModel::O3.is_reasoning_model());
1127 assert!(ChatModel::O3Mini.is_reasoning_model());
1128 assert!(ChatModel::O4Mini.is_reasoning_model());
1129 assert!(ChatModel::Gpt5_2.is_reasoning_model());
1132 assert!(!ChatModel::Gpt5_2ChatLatest.is_reasoning_model());
1133 assert!(ChatModel::Gpt5_2Pro.is_reasoning_model());
1134 assert!(ChatModel::Gpt5_1.is_reasoning_model());
1135 assert!(ChatModel::Gpt5_1CodexMax.is_reasoning_model());
1136 assert!(ChatModel::Gpt5Mini.is_reasoning_model());
1137 assert!(!ChatModel::Gpt4oMini.is_reasoning_model());
1139 assert!(!ChatModel::Gpt4_1.is_reasoning_model());
1140 }
1141
1142 #[test]
1143 fn test_chat_model_from_str() {
1144 assert_eq!(ChatModel::from("gpt-4o-mini"), ChatModel::Gpt4oMini);
1145 assert_eq!(ChatModel::from("o3-mini"), ChatModel::O3Mini);
1146 assert_eq!(ChatModel::from("gpt-5.2"), ChatModel::Gpt5_2);
1148 assert_eq!(ChatModel::from("gpt-5.2-chat-latest"), ChatModel::Gpt5_2ChatLatest);
1149 assert_eq!(ChatModel::from("gpt-5.2-pro"), ChatModel::Gpt5_2Pro);
1150 assert_eq!(ChatModel::from("gpt-5.1"), ChatModel::Gpt5_1);
1151 assert_eq!(ChatModel::from("gpt-5.1-codex-max"), ChatModel::Gpt5_1CodexMax);
1152 assert_eq!(ChatModel::from("gpt-5-mini"), ChatModel::Gpt5Mini);
1153 assert!(matches!(ChatModel::from("unknown-model"), ChatModel::Custom(_)));
1155 }
1156
1157 #[test]
1158 fn test_chat_model_custom() {
1159 let custom = ChatModel::custom("ft:gpt-4o-mini:org::123");
1160 assert_eq!(custom.as_str(), "ft:gpt-4o-mini:org::123");
1161 }
1162
1163 #[test]
1164 fn test_chat_model_custom_gpt5_is_reasoning() {
1165 let custom_gpt5 = ChatModel::custom("gpt-5.3-preview");
1167 assert!(custom_gpt5.is_reasoning_model());
1168 }
1169
1170 #[test]
1171 fn test_embedding_model_dimensions() {
1172 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1173 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1174 }
1175
1176 #[test]
1177 fn test_realtime_model_as_str() {
1178 assert_eq!(RealtimeModel::GptRealtime_2025_08_28.as_str(), "gpt-realtime-2025-08-28");
1179 }
1180
1181 #[test]
1182 fn test_fine_tuning_model_as_str() {
1183 assert_eq!(FineTuningModel::Gpt4oMini_2024_07_18.as_str(), "gpt-4o-mini-2024-07-18");
1184 assert_eq!(FineTuningModel::Gpt41_2025_04_14.as_str(), "gpt-4.1-2025-04-14");
1185 }
1186
1187 #[test]
1188 fn test_chat_model_serialization() {
1189 let model = ChatModel::Gpt4oMini;
1190 let json = serde_json::to_string(&model).unwrap();
1191 assert_eq!(json, "\"gpt-4o-mini\"");
1192 let gpt52 = ChatModel::Gpt5_2;
1194 let json = serde_json::to_string(&gpt52).unwrap();
1195 assert_eq!(json, "\"gpt-5.2\"");
1196 }
1197
1198 #[test]
1199 fn test_chat_model_deserialization() {
1200 let model: ChatModel = serde_json::from_str("\"gpt-4o-mini\"").unwrap();
1201 assert_eq!(model, ChatModel::Gpt4oMini);
1202 let gpt52: ChatModel = serde_json::from_str("\"gpt-5.2\"").unwrap();
1204 assert_eq!(gpt52, ChatModel::Gpt5_2);
1205 }
1206
1207 #[test]
1208 fn test_parameter_support_standard_model() {
1209 let model = ChatModel::Gpt4oMini;
1210 let support = model.parameter_support();
1211
1212 assert_eq!(support.temperature, ParameterRestriction::Any);
1214 assert_eq!(support.frequency_penalty, ParameterRestriction::Any);
1215 assert_eq!(support.presence_penalty, ParameterRestriction::Any);
1216 assert_eq!(support.top_p, ParameterRestriction::Any);
1217 assert!(support.logprobs);
1218 assert!(support.top_logprobs);
1219 assert!(support.logit_bias);
1220 assert!(support.n_multiple);
1221 assert!(!support.reasoning); }
1223
1224 #[test]
1225 fn test_parameter_support_reasoning_model() {
1226 let model = ChatModel::O3Mini;
1227 let support = model.parameter_support();
1228
1229 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1231 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0));
1232 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0));
1233 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0));
1234 assert!(!support.logprobs);
1235 assert!(!support.top_logprobs);
1236 assert!(!support.logit_bias);
1237 assert!(!support.n_multiple);
1238 assert!(support.reasoning); }
1240
1241 #[test]
1242 fn test_parameter_support_gpt5_model() {
1243 let model = ChatModel::Gpt5_2;
1245 let support = model.parameter_support();
1246
1247 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1248 assert!(!support.logprobs);
1249 assert!(support.reasoning);
1250 }
1251
1252 #[test]
1257 fn test_all_o_series_models_are_reasoning() {
1258 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1260
1261 for model in o_series {
1262 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1263 }
1264 }
1265
1266 #[test]
1267 fn test_all_gpt5_models_are_reasoning() {
1268 let gpt5_series =
1272 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1273
1274 for model in gpt5_series {
1275 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1276 }
1277 }
1278
1279 #[test]
1280 fn test_all_standard_models_are_not_reasoning() {
1281 let standard_models = vec![
1283 ChatModel::Gpt4oMini,
1284 ChatModel::Gpt4o,
1285 ChatModel::Gpt4oAudioPreview,
1286 ChatModel::Gpt4Turbo,
1287 ChatModel::Gpt4,
1288 ChatModel::Gpt3_5Turbo,
1289 ChatModel::Gpt4_1,
1290 ChatModel::Gpt4_1Mini,
1291 ChatModel::Gpt4_1Nano,
1292 ];
1293
1294 for model in standard_models {
1295 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1296 }
1297 }
1298
1299 #[test]
1304 fn test_custom_o1_models_are_reasoning() {
1305 let custom_o1_variants = vec!["o1-mini", "o1-preview", "o1-pro-2025", "o1-high"];
1306
1307 for model_str in custom_o1_variants {
1308 let model = ChatModel::custom(model_str);
1309 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1310 }
1311 }
1312
1313 #[test]
1314 fn test_custom_o3_models_are_reasoning() {
1315 let custom_o3_variants = vec!["o3-preview", "o3-high", "o3-2025-01-15"];
1316
1317 for model_str in custom_o3_variants {
1318 let model = ChatModel::custom(model_str);
1319 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1320 }
1321 }
1322
1323 #[test]
1324 fn test_custom_o4_models_are_reasoning() {
1325 let custom_o4_variants = vec!["o4-preview", "o4-mini-2025", "o4-high"];
1326
1327 for model_str in custom_o4_variants {
1328 let model = ChatModel::custom(model_str);
1329 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1330 }
1331 }
1332
1333 #[test]
1334 fn test_custom_gpt5_models_are_reasoning() {
1335 let custom_gpt5_variants = vec!["gpt-5.3", "gpt-5.3-preview", "gpt-5-turbo", "gpt-5.0"];
1336
1337 for model_str in custom_gpt5_variants {
1338 let model = ChatModel::custom(model_str);
1339 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1340 }
1341 }
1342
1343 #[test]
1344 fn test_custom_standard_models_are_not_reasoning() {
1345 let custom_standard_variants = vec![
1346 "ft:gpt-4o-mini:org::123",
1347 "gpt-4o-2025-01-15",
1348 "gpt-4-turbo-preview",
1349 "gpt-3.5-turbo-instruct",
1350 "text-davinci-003",
1351 "claude-3-opus", ];
1353
1354 for model_str in custom_standard_variants {
1355 let model = ChatModel::custom(model_str);
1356 assert!(!model.is_reasoning_model(), "Expected custom model '{}' to NOT be a reasoning model", model_str);
1357 }
1358 }
1359
1360 #[test]
1365 fn test_parameter_support_all_o_series() {
1366 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1367
1368 for model in o_series {
1369 let support = model.parameter_support();
1370
1371 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1372 assert_eq!(
1373 support.frequency_penalty,
1374 ParameterRestriction::FixedValue(0.0),
1375 "{} should only support frequency_penalty=0.0",
1376 model.as_str()
1377 );
1378 assert_eq!(
1379 support.presence_penalty,
1380 ParameterRestriction::FixedValue(0.0),
1381 "{} should only support presence_penalty=0.0",
1382 model.as_str()
1383 );
1384 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should only support top_p=1.0", model.as_str());
1385 assert!(!support.logprobs, "{} should not support logprobs", model.as_str());
1386 assert!(!support.top_logprobs, "{} should not support top_logprobs", model.as_str());
1387 assert!(!support.logit_bias, "{} should not support logit_bias", model.as_str());
1388 assert!(!support.n_multiple, "{} should only support n=1", model.as_str());
1389 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1390 }
1391 }
1392
1393 #[test]
1394 fn test_parameter_support_all_gpt5_series() {
1395 let gpt5_series =
1398 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1399
1400 for model in gpt5_series {
1401 let support = model.parameter_support();
1402
1403 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1404 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1405 }
1406 }
1407
1408 #[test]
1409 fn test_parameter_support_all_standard_gpt4_series() {
1410 let gpt4_series = vec![
1411 ChatModel::Gpt4oMini,
1412 ChatModel::Gpt4o,
1413 ChatModel::Gpt4Turbo,
1414 ChatModel::Gpt4,
1415 ChatModel::Gpt4_1,
1416 ChatModel::Gpt4_1Mini,
1417 ChatModel::Gpt4_1Nano,
1418 ];
1419
1420 for model in gpt4_series {
1421 let support = model.parameter_support();
1422
1423 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should support any temperature", model.as_str());
1424 assert_eq!(support.frequency_penalty, ParameterRestriction::Any, "{} should support any frequency_penalty", model.as_str());
1425 assert_eq!(support.presence_penalty, ParameterRestriction::Any, "{} should support any presence_penalty", model.as_str());
1426 assert!(support.logprobs, "{} should support logprobs", model.as_str());
1427 assert!(support.top_logprobs, "{} should support top_logprobs", model.as_str());
1428 assert!(support.logit_bias, "{} should support logit_bias", model.as_str());
1429 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1430 assert!(!support.reasoning, "{} should NOT support reasoning parameter", model.as_str());
1431 }
1432 }
1433
1434 #[test]
1439 fn test_parameter_restriction_equality() {
1440 assert_eq!(ParameterRestriction::Any, ParameterRestriction::Any);
1441 assert_eq!(ParameterRestriction::NotSupported, ParameterRestriction::NotSupported);
1442 assert_eq!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(1.0));
1443
1444 assert_ne!(ParameterRestriction::Any, ParameterRestriction::NotSupported);
1445 assert_ne!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(0.0));
1446 }
1447
1448 #[test]
1449 fn test_parameter_support_factory_methods() {
1450 let standard = ParameterSupport::standard_model();
1451 assert_eq!(standard.temperature, ParameterRestriction::Any);
1452 assert!(standard.logprobs);
1453 assert!(!standard.reasoning);
1454
1455 let reasoning = ParameterSupport::reasoning_model();
1456 assert_eq!(reasoning.temperature, ParameterRestriction::FixedValue(1.0));
1457 assert!(!reasoning.logprobs);
1458 assert!(reasoning.reasoning);
1459 }
1460
1461 #[test]
1466 fn test_all_gpt5_model_string_roundtrip() {
1467 let gpt5_models = vec![
1468 ("gpt-5.2", ChatModel::Gpt5_2),
1469 ("gpt-5.2-chat-latest", ChatModel::Gpt5_2ChatLatest),
1470 ("gpt-5.2-pro", ChatModel::Gpt5_2Pro),
1471 ("gpt-5.1", ChatModel::Gpt5_1),
1472 ("gpt-5.1-chat-latest", ChatModel::Gpt5_1ChatLatest),
1473 ("gpt-5.1-codex-max", ChatModel::Gpt5_1CodexMax),
1474 ("gpt-5-mini", ChatModel::Gpt5Mini),
1475 ("gpt-5-nano", ChatModel::Gpt5Nano),
1476 ];
1477
1478 for (model_str, expected_model) in gpt5_models {
1479 let parsed = ChatModel::from(model_str);
1481 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1482
1483 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1485
1486 let json = serde_json::to_string(&expected_model).unwrap();
1488 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1489 assert_eq!(deserialized, expected_model, "Serialization roundtrip failed for {}", model_str);
1490 }
1491 }
1492
1493 #[test]
1494 fn test_all_o_series_model_string_roundtrip() {
1495 let o_series_models = vec![
1496 ("o1", ChatModel::O1),
1497 ("o1-pro", ChatModel::O1Pro),
1498 ("o3", ChatModel::O3),
1499 ("o3-mini", ChatModel::O3Mini),
1500 ("o4-mini", ChatModel::O4Mini),
1501 ];
1502
1503 for (model_str, expected_model) in o_series_models {
1504 let parsed = ChatModel::from(model_str);
1505 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1506 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1507 }
1508 }
1509
1510 #[test]
1515 fn test_embedding_model_string_roundtrip() {
1516 let embedding_models = vec![
1517 ("text-embedding-3-small", EmbeddingModel::TextEmbedding3Small),
1518 ("text-embedding-3-large", EmbeddingModel::TextEmbedding3Large),
1519 ("text-embedding-ada-002", EmbeddingModel::TextEmbeddingAda002),
1520 ];
1521
1522 for (model_str, expected_model) in embedding_models {
1523 let parsed = EmbeddingModel::from(model_str);
1524 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1525 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1526 }
1527 }
1528
1529 #[test]
1530 fn test_embedding_model_all_dimensions() {
1531 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1532 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1533 assert_eq!(EmbeddingModel::TextEmbeddingAda002.dimensions(), 1536);
1534 }
1535
1536 #[test]
1541 fn test_realtime_model_string_roundtrip() {
1542 let realtime_models = vec![("gpt-realtime-2025-08-28", RealtimeModel::GptRealtime_2025_08_28)];
1543
1544 for (model_str, expected_model) in realtime_models {
1545 let parsed = RealtimeModel::from(model_str);
1546 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1547 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1548 }
1549 }
1550
1551 #[test]
1552 fn test_realtime_model_custom() {
1553 let custom = RealtimeModel::custom("gpt-4o-realtime-2025");
1554 assert_eq!(custom.as_str(), "gpt-4o-realtime-2025");
1555 assert!(matches!(custom, RealtimeModel::Custom(_)));
1556 }
1557
1558 #[test]
1563 fn test_fine_tuning_model_as_str_all_variants() {
1564 let fine_tuning_models = vec![
1565 ("gpt-4.1-2025-04-14", FineTuningModel::Gpt41_2025_04_14),
1566 ("gpt-4.1-mini-2025-04-14", FineTuningModel::Gpt41Mini_2025_04_14),
1567 ("gpt-4.1-nano-2025-04-14", FineTuningModel::Gpt41Nano_2025_04_14),
1568 ("gpt-4o-mini-2024-07-18", FineTuningModel::Gpt4oMini_2024_07_18),
1569 ("gpt-4o-2024-08-06", FineTuningModel::Gpt4o_2024_08_06),
1570 ("gpt-4-0613", FineTuningModel::Gpt4_0613),
1571 ("gpt-3.5-turbo-0125", FineTuningModel::Gpt35Turbo_0125),
1572 ("gpt-3.5-turbo-1106", FineTuningModel::Gpt35Turbo_1106),
1573 ("gpt-3.5-turbo-0613", FineTuningModel::Gpt35Turbo_0613),
1574 ];
1575
1576 for (model_str, expected_model) in fine_tuning_models {
1577 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1578 }
1579 }
1580
1581 #[test]
1582 fn test_fine_tuning_model_serialization_roundtrip() {
1583 let models = vec![FineTuningModel::Gpt41_2025_04_14, FineTuningModel::Gpt4oMini_2024_07_18, FineTuningModel::Gpt35Turbo_0125];
1584
1585 for model in models {
1586 let json = serde_json::to_string(&model).unwrap();
1587 let deserialized: FineTuningModel = serde_json::from_str(&json).unwrap();
1588 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1589 }
1590 }
1591
1592 fn gpt5_3_to_5_6_models() -> Vec<(ChatModel, &'static str)> {
1602 vec![
1603 (ChatModel::Gpt5_6, "gpt-5.6"),
1605 (ChatModel::Gpt5_6Sol, "gpt-5.6-sol"),
1606 (ChatModel::Gpt5_6Terra, "gpt-5.6-terra"),
1607 (ChatModel::Gpt5_6Luna, "gpt-5.6-luna"),
1608 (ChatModel::Gpt5_5, "gpt-5.5"),
1610 (ChatModel::Gpt5_5Pro, "gpt-5.5-pro"),
1611 (ChatModel::Gpt5_4, "gpt-5.4"),
1613 (ChatModel::Gpt5_4Pro, "gpt-5.4-pro"),
1614 (ChatModel::Gpt5_4Mini, "gpt-5.4-mini"),
1615 (ChatModel::Gpt5_4Nano, "gpt-5.4-nano"),
1616 (ChatModel::Gpt5_3ChatLatest, "gpt-5.3-chat-latest"),
1618 (ChatModel::Gpt5_3Codex, "gpt-5.3-codex"),
1620 (ChatModel::Gpt5_2Codex, "gpt-5.2-codex"),
1621 (ChatModel::Gpt5_1Codex, "gpt-5.1-codex"),
1622 ]
1623 }
1624
1625 #[test]
1626 fn test_new_chat_models_as_str() {
1627 for (model, expected) in gpt5_3_to_5_6_models() {
1628 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1629 }
1630 }
1631
1632 #[test]
1633 fn test_new_chat_models_from_str_roundtrip() {
1634 for (model, model_str) in gpt5_3_to_5_6_models() {
1635 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1636 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1638 }
1639 }
1640
1641 #[test]
1642 fn test_new_chat_models_serialization_roundtrip() {
1643 for (model, model_str) in gpt5_3_to_5_6_models() {
1644 let json = serde_json::to_string(&model).unwrap();
1645 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1646 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1647 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1648 }
1649 }
1650
1651 #[test]
1654 fn test_new_gpt5_models_are_reasoning_models() {
1655 let reasoning = vec![
1656 ChatModel::Gpt5_6,
1657 ChatModel::Gpt5_6Sol,
1658 ChatModel::Gpt5_6Terra,
1659 ChatModel::Gpt5_6Luna,
1660 ChatModel::Gpt5_5,
1661 ChatModel::Gpt5_5Pro,
1662 ChatModel::Gpt5_4,
1663 ChatModel::Gpt5_4Pro,
1664 ChatModel::Gpt5_4Mini,
1665 ChatModel::Gpt5_4Nano,
1666 ChatModel::Gpt5_3Codex,
1667 ChatModel::Gpt5_2Codex,
1668 ChatModel::Gpt5_1Codex,
1669 ];
1670
1671 for model in reasoning {
1672 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1673 let support = model.parameter_support();
1674 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should pin temperature to 1.0", model.as_str());
1675 assert!(support.reasoning, "{} should support the reasoning parameter", model.as_str());
1676 }
1677 }
1678
1679 #[test]
1683 fn test_chat_latest_models_are_not_reasoning_models() {
1684 let chat_latest = vec![ChatModel::Gpt5_3ChatLatest, ChatModel::Gpt5_2ChatLatest, ChatModel::Gpt5_1ChatLatest];
1685
1686 for model in chat_latest {
1687 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1688 let support = model.parameter_support();
1689 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1690 assert_eq!(support.top_p, ParameterRestriction::Any, "{} should accept any top_p", model.as_str());
1691 assert!(!support.reasoning, "{} should NOT support the reasoning parameter", model.as_str());
1692 }
1693 }
1694
1695 #[test]
1698 fn test_custom_chat_latest_is_not_reasoning_model() {
1699 assert!(!ChatModel::custom("gpt-5.7-chat-latest").is_reasoning_model());
1700 assert!(ChatModel::custom("gpt-5.7").is_reasoning_model());
1702 assert!(ChatModel::custom("gpt-5.7-codex").is_reasoning_model());
1703 }
1704
1705 fn new_realtime_models() -> Vec<(RealtimeModel, &'static str)> {
1710 vec![
1711 (RealtimeModel::GptRealtime2_1, "gpt-realtime-2.1"),
1712 (RealtimeModel::GptRealtime2_1Mini, "gpt-realtime-2.1-mini"),
1713 (RealtimeModel::GptRealtime2, "gpt-realtime-2"),
1714 (RealtimeModel::GptRealtime1_5, "gpt-realtime-1.5"),
1715 (RealtimeModel::GptRealtimeTranslate, "gpt-realtime-translate"),
1716 ]
1717 }
1718
1719 #[test]
1720 fn test_new_realtime_models_as_str() {
1721 for (model, expected) in new_realtime_models() {
1722 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1723 }
1724 }
1725
1726 #[test]
1727 fn test_new_realtime_models_from_str_roundtrip() {
1728 for (model, model_str) in new_realtime_models() {
1729 assert_eq!(RealtimeModel::from(model_str), model, "Failed to parse {}", model_str);
1730 }
1731 }
1732
1733 #[test]
1737 fn test_realtime_model_default_is_unchanged() {
1738 assert_eq!(RealtimeModel::default(), RealtimeModel::GptRealtime_2025_08_28);
1739 }
1740
1741 #[test]
1742 fn test_new_realtime_models_serialization_roundtrip() {
1743 for (model, model_str) in new_realtime_models() {
1744 let json = serde_json::to_string(&model).unwrap();
1745 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1746 let deserialized: RealtimeModel = serde_json::from_str(&json).unwrap();
1747 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1748 }
1749 }
1750
1751 fn previously_missing_chat_models() -> Vec<(ChatModel, &'static str)> {
1759 vec![
1760 (ChatModel::Gpt5, "gpt-5"),
1761 (ChatModel::Gpt5Pro, "gpt-5-pro"),
1762 (ChatModel::O3Pro, "o3-pro"),
1763 (ChatModel::Gpt5SearchApi, "gpt-5-search-api"),
1764 (ChatModel::Gpt4oSearchPreview, "gpt-4o-search-preview"),
1765 (ChatModel::Gpt4oMiniSearchPreview, "gpt-4o-mini-search-preview"),
1766 (ChatModel::GptAudio, "gpt-audio"),
1767 (ChatModel::GptAudio1_5, "gpt-audio-1.5"),
1768 (ChatModel::GptAudioMini, "gpt-audio-mini"),
1769 (ChatModel::Gpt3_5Turbo16k, "gpt-3.5-turbo-16k"),
1770 ]
1771 }
1772
1773 #[test]
1774 fn test_previously_missing_chat_models_as_str() {
1775 for (model, expected) in previously_missing_chat_models() {
1776 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1777 }
1778 }
1779
1780 #[test]
1781 fn test_previously_missing_chat_models_from_str_roundtrip() {
1782 for (model, model_str) in previously_missing_chat_models() {
1783 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1784 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1785 }
1786 }
1787
1788 #[test]
1789 fn test_previously_missing_chat_models_serialization_roundtrip() {
1790 for (model, model_str) in previously_missing_chat_models() {
1791 let json = serde_json::to_string(&model).unwrap();
1792 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1793 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1794 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1795 }
1796 }
1797
1798 #[test]
1801 fn test_gpt5_base_and_pro_models_are_reasoning() {
1802 for model in [ChatModel::Gpt5, ChatModel::Gpt5Pro, ChatModel::O3Pro] {
1803 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1804 assert!(model.parameter_support().reasoning, "{} should support the reasoning parameter", model.as_str());
1805 }
1806 }
1807
1808 #[test]
1810 fn test_audio_chat_models_are_standard() {
1811 for model in [ChatModel::GptAudio, ChatModel::GptAudio1_5, ChatModel::GptAudioMini, ChatModel::Gpt3_5Turbo16k] {
1812 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1813 let support = model.parameter_support();
1814 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1815 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1816 }
1817 }
1818
1819 #[test]
1822 fn test_search_models_reject_sampling_but_have_no_reasoning() {
1823 let search_models = [ChatModel::Gpt5SearchApi, ChatModel::Gpt4oSearchPreview, ChatModel::Gpt4oMiniSearchPreview];
1824
1825 for model in search_models {
1826 assert!(model.is_search_model(), "Expected {} to be a search model", model.as_str());
1827 assert!(!model.is_reasoning_model(), "Search model {} must not be classed as reasoning", model.as_str());
1828
1829 let support = model.parameter_support();
1830 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should reject custom temperature", model.as_str());
1831 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should reject custom top_p", model.as_str());
1832 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject frequency_penalty", model.as_str());
1833 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject presence_penalty", model.as_str());
1834 assert!(!support.logprobs, "{} should reject logprobs", model.as_str());
1835 assert!(!support.n_multiple, "{} should reject n > 1", model.as_str());
1836 assert!(!support.reasoning, "{} exposes no reasoning parameter", model.as_str());
1837 }
1838 }
1839
1840 #[test]
1842 fn test_non_search_models_are_not_search_models() {
1843 for model in [ChatModel::Gpt5_6Sol, ChatModel::Gpt4oMini, ChatModel::O3Mini, ChatModel::Gpt5_2ChatLatest] {
1844 assert!(!model.is_search_model(), "Expected {} to NOT be a search model", model.as_str());
1845 }
1846 }
1847
1848 #[test]
1849 fn test_previously_missing_realtime_models() {
1850 for (model, expected) in [(RealtimeModel::GptRealtime, "gpt-realtime"), (RealtimeModel::GptRealtimeMini, "gpt-realtime-mini")] {
1851 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1852 assert_eq!(RealtimeModel::from(expected), model, "Failed to parse {}", expected);
1853 let json = serde_json::to_string(&model).unwrap();
1854 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1855 }
1856 }
1857
1858 #[test]
1859 fn test_legacy_fine_tuning_base_models() {
1860 for (model, expected) in [(FineTuningModel::Babbage002, "babbage-002"), (FineTuningModel::Davinci002, "davinci-002")] {
1861 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1862 let json = serde_json::to_string(&model).unwrap();
1863 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1864 }
1865 }
1866}