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)]
218#[non_exhaustive]
219pub enum ChatModel {
220 #[serde(rename = "gpt-5.6")]
223 Gpt5_6,
224
225 #[serde(rename = "gpt-5.6-sol")]
231 Gpt5_6Sol,
232
233 #[serde(rename = "gpt-5.6-terra")]
238 Gpt5_6Terra,
239
240 #[serde(rename = "gpt-5.6-luna")]
245 Gpt5_6Luna,
246
247 #[serde(rename = "gpt-5.5")]
253 Gpt5_5,
254
255 #[serde(rename = "gpt-5.5-pro")]
260 Gpt5_5Pro,
261
262 #[serde(rename = "gpt-5.4")]
268 Gpt5_4,
269
270 #[serde(rename = "gpt-5.4-pro")]
275 Gpt5_4Pro,
276
277 #[serde(rename = "gpt-5.4-mini")]
282 Gpt5_4Mini,
283
284 #[serde(rename = "gpt-5.4-nano")]
289 Gpt5_4Nano,
290
291 #[serde(rename = "gpt-5.3-chat-latest")]
298 Gpt5_3ChatLatest,
299
300 #[serde(rename = "gpt-5.3-codex")]
307 Gpt5_3Codex,
308
309 #[serde(rename = "gpt-5.2-codex")]
315 Gpt5_2Codex,
316
317 #[serde(rename = "gpt-5.1-codex")]
321 Gpt5_1Codex,
322
323 #[serde(rename = "gpt-5-search-api")]
328 Gpt5SearchApi,
329
330 #[serde(rename = "gpt-4o-search-preview")]
332 Gpt4oSearchPreview,
333
334 #[serde(rename = "gpt-4o-mini-search-preview")]
336 Gpt4oMiniSearchPreview,
337
338 #[serde(rename = "gpt-audio")]
343 GptAudio,
344
345 #[serde(rename = "gpt-audio-1.5")]
347 GptAudio1_5,
348
349 #[serde(rename = "gpt-audio-mini")]
351 GptAudioMini,
352
353 #[serde(rename = "gpt-5")]
356 Gpt5,
357
358 #[serde(rename = "gpt-5-pro")]
362 Gpt5Pro,
363
364 #[serde(rename = "gpt-5.2")]
370 Gpt5_2,
371
372 #[serde(rename = "gpt-5.2-chat-latest")]
379 Gpt5_2ChatLatest,
380
381 #[serde(rename = "gpt-5.2-pro")]
386 Gpt5_2Pro,
387
388 #[serde(rename = "gpt-5.1")]
393 Gpt5_1,
394
395 #[serde(rename = "gpt-5.1-chat-latest")]
400 Gpt5_1ChatLatest,
401
402 #[serde(rename = "gpt-5.1-codex-max")]
407 Gpt5_1CodexMax,
408
409 #[serde(rename = "gpt-5-mini")]
411 Gpt5Mini,
412
413 #[serde(rename = "gpt-5-nano")]
415 Gpt5Nano,
416
417 #[serde(rename = "gpt-4.1")]
420 Gpt4_1,
421
422 #[serde(rename = "gpt-4.1-mini")]
424 Gpt4_1Mini,
425
426 #[serde(rename = "gpt-4.1-nano")]
428 Gpt4_1Nano,
429
430 #[serde(rename = "gpt-4o")]
433 Gpt4o,
434
435 #[serde(rename = "gpt-4o-mini")]
437 #[default]
438 Gpt4oMini,
439
440 #[serde(rename = "gpt-4o-audio-preview")]
442 Gpt4oAudioPreview,
443
444 #[serde(rename = "gpt-4-turbo")]
447 Gpt4Turbo,
448
449 #[serde(rename = "gpt-4")]
451 Gpt4,
452
453 #[serde(rename = "gpt-3.5-turbo")]
456 Gpt3_5Turbo,
457
458 #[serde(rename = "gpt-3.5-turbo-16k")]
460 Gpt3_5Turbo16k,
461
462 #[serde(rename = "o1")]
465 O1,
466
467 #[serde(rename = "o1-pro")]
469 O1Pro,
470
471 #[serde(rename = "o3")]
473 O3,
474
475 #[serde(rename = "o3-pro")]
479 O3Pro,
480
481 #[serde(rename = "o3-mini")]
483 O3Mini,
484
485 #[serde(rename = "o4-mini")]
487 O4Mini,
488
489 #[serde(untagged)]
492 Custom(String),
493}
494
495impl ChatModel {
496 pub fn as_str(&self) -> &str {
508 match self {
509 Self::Gpt5_6 => "gpt-5.6",
511 Self::Gpt5_6Sol => "gpt-5.6-sol",
512 Self::Gpt5_6Terra => "gpt-5.6-terra",
513 Self::Gpt5_6Luna => "gpt-5.6-luna",
514 Self::Gpt5_5 => "gpt-5.5",
516 Self::Gpt5_5Pro => "gpt-5.5-pro",
517 Self::Gpt5_4 => "gpt-5.4",
519 Self::Gpt5_4Pro => "gpt-5.4-pro",
520 Self::Gpt5_4Mini => "gpt-5.4-mini",
521 Self::Gpt5_4Nano => "gpt-5.4-nano",
522 Self::Gpt5_3ChatLatest => "gpt-5.3-chat-latest",
524 Self::Gpt5_3Codex => "gpt-5.3-codex",
526 Self::Gpt5_2Codex => "gpt-5.2-codex",
527 Self::Gpt5_1Codex => "gpt-5.1-codex",
528 Self::Gpt5SearchApi => "gpt-5-search-api",
530 Self::Gpt4oSearchPreview => "gpt-4o-search-preview",
531 Self::Gpt4oMiniSearchPreview => "gpt-4o-mini-search-preview",
532 Self::GptAudio => "gpt-audio",
534 Self::GptAudio1_5 => "gpt-audio-1.5",
535 Self::GptAudioMini => "gpt-audio-mini",
536 Self::Gpt5 => "gpt-5",
538 Self::Gpt5Pro => "gpt-5-pro",
539 Self::Gpt5_2 => "gpt-5.2",
540 Self::Gpt5_2ChatLatest => "gpt-5.2-chat-latest",
541 Self::Gpt5_2Pro => "gpt-5.2-pro",
542 Self::Gpt5_1 => "gpt-5.1",
543 Self::Gpt5_1ChatLatest => "gpt-5.1-chat-latest",
544 Self::Gpt5_1CodexMax => "gpt-5.1-codex-max",
545 Self::Gpt5Mini => "gpt-5-mini",
546 Self::Gpt5Nano => "gpt-5-nano",
547 Self::Gpt4_1 => "gpt-4.1",
549 Self::Gpt4_1Mini => "gpt-4.1-mini",
550 Self::Gpt4_1Nano => "gpt-4.1-nano",
551 Self::Gpt4o => "gpt-4o",
553 Self::Gpt4oMini => "gpt-4o-mini",
554 Self::Gpt4oAudioPreview => "gpt-4o-audio-preview",
555 Self::Gpt4Turbo => "gpt-4-turbo",
557 Self::Gpt4 => "gpt-4",
558 Self::Gpt3_5Turbo => "gpt-3.5-turbo",
560 Self::Gpt3_5Turbo16k => "gpt-3.5-turbo-16k",
561 Self::O1 => "o1",
563 Self::O1Pro => "o1-pro",
564 Self::O3 => "o3",
565 Self::O3Pro => "o3-pro",
566 Self::O3Mini => "o3-mini",
567 Self::O4Mini => "o4-mini",
568 Self::Custom(s) => s.as_str(),
570 }
571 }
572
573 pub fn is_reasoning_model(&self) -> bool {
592 matches!(
593 self,
594 Self::Gpt5_6 | Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna |
596 Self::Gpt5_5 | Self::Gpt5_5Pro |
598 Self::Gpt5_4 | Self::Gpt5_4Pro | Self::Gpt5_4Mini | Self::Gpt5_4Nano |
600 Self::Gpt5_3Codex | Self::Gpt5_2Codex | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax |
602 Self::Gpt5 | Self::Gpt5Pro | Self::Gpt5_2 | Self::Gpt5_2Pro | Self::Gpt5_1 | Self::Gpt5Mini | Self::Gpt5Nano |
604 Self::O1 | Self::O1Pro | Self::O3 | Self::O3Pro | Self::O3Mini | Self::O4Mini
606 ) || matches!(
607 self,
608 Self::Custom(s) if !s.ends_with("-chat-latest")
612 && !s.contains("-search")
613 && (s.starts_with("gpt-5") || s.starts_with("o1") || s.starts_with("o3") || s.starts_with("o4"))
614 )
615 }
616
617 pub fn is_search_model(&self) -> bool {
634 matches!(self, Self::Gpt5SearchApi | Self::Gpt4oSearchPreview | Self::Gpt4oMiniSearchPreview)
635 || matches!(self, Self::Custom(s) if s.contains("-search"))
636 }
637
638 pub fn parameter_support(&self) -> ParameterSupport {
662 if self.is_search_model() {
663 ParameterSupport::search_model()
664 } else if self.is_reasoning_model() {
665 ParameterSupport::reasoning_model()
666 } else {
667 ParameterSupport::standard_model()
668 }
669 }
670
671 pub fn custom(model_id: impl Into<String>) -> Self {
684 Self::Custom(model_id.into())
685 }
686}
687
688impl std::fmt::Display for ChatModel {
689 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
690 write!(f, "{}", self.as_str())
691 }
692}
693
694impl From<&str> for ChatModel {
695 fn from(s: &str) -> Self {
696 match s {
697 "gpt-5.6" => Self::Gpt5_6,
699 "gpt-5.6-sol" => Self::Gpt5_6Sol,
700 "gpt-5.6-terra" => Self::Gpt5_6Terra,
701 "gpt-5.6-luna" => Self::Gpt5_6Luna,
702 "gpt-5.5" => Self::Gpt5_5,
704 "gpt-5.5-pro" => Self::Gpt5_5Pro,
705 "gpt-5.4" => Self::Gpt5_4,
707 "gpt-5.4-pro" => Self::Gpt5_4Pro,
708 "gpt-5.4-mini" => Self::Gpt5_4Mini,
709 "gpt-5.4-nano" => Self::Gpt5_4Nano,
710 "gpt-5.3-chat-latest" => Self::Gpt5_3ChatLatest,
712 "gpt-5.3-codex" => Self::Gpt5_3Codex,
714 "gpt-5.2-codex" => Self::Gpt5_2Codex,
715 "gpt-5.1-codex" => Self::Gpt5_1Codex,
716 "gpt-5-search-api" => Self::Gpt5SearchApi,
718 "gpt-4o-search-preview" => Self::Gpt4oSearchPreview,
719 "gpt-4o-mini-search-preview" => Self::Gpt4oMiniSearchPreview,
720 "gpt-audio" => Self::GptAudio,
722 "gpt-audio-1.5" => Self::GptAudio1_5,
723 "gpt-audio-mini" => Self::GptAudioMini,
724 "gpt-5" => Self::Gpt5,
726 "gpt-5-pro" => Self::Gpt5Pro,
727 "gpt-5.2" => Self::Gpt5_2,
728 "gpt-5.2-chat-latest" => Self::Gpt5_2ChatLatest,
729 "gpt-5.2-pro" => Self::Gpt5_2Pro,
730 "gpt-5.1" => Self::Gpt5_1,
731 "gpt-5.1-chat-latest" => Self::Gpt5_1ChatLatest,
732 "gpt-5.1-codex-max" => Self::Gpt5_1CodexMax,
733 "gpt-5-mini" => Self::Gpt5Mini,
734 "gpt-5-nano" => Self::Gpt5Nano,
735 "gpt-4.1" => Self::Gpt4_1,
737 "gpt-4.1-mini" => Self::Gpt4_1Mini,
738 "gpt-4.1-nano" => Self::Gpt4_1Nano,
739 "gpt-4o" => Self::Gpt4o,
741 "gpt-4o-mini" => Self::Gpt4oMini,
742 "gpt-4o-audio-preview" => Self::Gpt4oAudioPreview,
743 "gpt-4-turbo" => Self::Gpt4Turbo,
745 "gpt-4" => Self::Gpt4,
746 "gpt-3.5-turbo" => Self::Gpt3_5Turbo,
748 "gpt-3.5-turbo-16k" => Self::Gpt3_5Turbo16k,
749 "o1" => Self::O1,
751 "o1-pro" => Self::O1Pro,
752 "o3" => Self::O3,
753 "o3-pro" => Self::O3Pro,
754 "o3-mini" => Self::O3Mini,
755 "o4-mini" => Self::O4Mini,
756 other => Self::Custom(other.to_string()),
758 }
759 }
760}
761
762impl From<String> for ChatModel {
763 fn from(s: String) -> Self {
764 Self::from(s.as_str())
765 }
766}
767
768#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
797#[non_exhaustive]
798pub enum EmbeddingModel {
799 #[serde(rename = "text-embedding-3-small")]
805 #[default]
806 TextEmbedding3Small,
807
808 #[serde(rename = "text-embedding-3-large")]
814 TextEmbedding3Large,
815
816 #[serde(rename = "text-embedding-ada-002")]
822 TextEmbeddingAda002,
823}
824
825impl EmbeddingModel {
826 pub fn as_str(&self) -> &str {
828 match self {
829 Self::TextEmbedding3Small => "text-embedding-3-small",
830 Self::TextEmbedding3Large => "text-embedding-3-large",
831 Self::TextEmbeddingAda002 => "text-embedding-ada-002",
832 }
833 }
834
835 pub fn dimensions(&self) -> usize {
840 match self {
841 Self::TextEmbedding3Small => 1536,
842 Self::TextEmbedding3Large => 3072,
843 Self::TextEmbeddingAda002 => 1536,
844 }
845 }
846}
847
848impl std::fmt::Display for EmbeddingModel {
849 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
850 write!(f, "{}", self.as_str())
851 }
852}
853
854impl From<&str> for EmbeddingModel {
855 fn from(s: &str) -> Self {
856 match s {
857 "text-embedding-3-small" => Self::TextEmbedding3Small,
858 "text-embedding-3-large" => Self::TextEmbedding3Large,
859 "text-embedding-ada-002" => Self::TextEmbeddingAda002,
860 _ => Self::TextEmbedding3Small, }
862 }
863}
864
865#[allow(non_camel_case_types)]
891#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
892#[non_exhaustive]
893pub enum RealtimeModel {
894 #[serde(rename = "gpt-realtime-2.1")]
900 GptRealtime2_1,
901
902 #[serde(rename = "gpt-realtime-2.1-mini")]
904 GptRealtime2_1Mini,
905
906 #[serde(rename = "gpt-realtime-2")]
908 GptRealtime2,
909
910 #[serde(rename = "gpt-realtime")]
912 GptRealtime,
913
914 #[serde(rename = "gpt-realtime-mini")]
916 GptRealtimeMini,
917
918 #[serde(rename = "gpt-realtime-1.5")]
920 GptRealtime1_5,
921
922 #[serde(rename = "gpt-realtime-translate")]
926 GptRealtimeTranslate,
927
928 #[serde(rename = "gpt-realtime-2025-08-28")]
934 #[default]
935 GptRealtime_2025_08_28,
936
937 #[serde(untagged)]
939 Custom(String),
940}
941
942impl RealtimeModel {
943 pub fn as_str(&self) -> &str {
945 match self {
946 Self::GptRealtime2_1 => "gpt-realtime-2.1",
947 Self::GptRealtime2_1Mini => "gpt-realtime-2.1-mini",
948 Self::GptRealtime2 => "gpt-realtime-2",
949 Self::GptRealtime => "gpt-realtime",
950 Self::GptRealtimeMini => "gpt-realtime-mini",
951 Self::GptRealtime1_5 => "gpt-realtime-1.5",
952 Self::GptRealtimeTranslate => "gpt-realtime-translate",
953 Self::GptRealtime_2025_08_28 => "gpt-realtime-2025-08-28",
954 Self::Custom(s) => s.as_str(),
955 }
956 }
957
958 pub fn custom(model_id: impl Into<String>) -> Self {
960 Self::Custom(model_id.into())
961 }
962}
963
964impl std::fmt::Display for RealtimeModel {
965 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
966 write!(f, "{}", self.as_str())
967 }
968}
969
970impl From<&str> for RealtimeModel {
971 fn from(s: &str) -> Self {
972 match s {
973 "gpt-realtime-2.1" => Self::GptRealtime2_1,
974 "gpt-realtime-2.1-mini" => Self::GptRealtime2_1Mini,
975 "gpt-realtime-2" => Self::GptRealtime2,
976 "gpt-realtime" => Self::GptRealtime,
977 "gpt-realtime-mini" => Self::GptRealtimeMini,
978 "gpt-realtime-1.5" => Self::GptRealtime1_5,
979 "gpt-realtime-translate" => Self::GptRealtimeTranslate,
980 "gpt-realtime-2025-08-28" => Self::GptRealtime_2025_08_28,
981 other => Self::Custom(other.to_string()),
982 }
983 }
984}
985
986#[allow(non_camel_case_types)]
1026#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1027#[non_exhaustive]
1028pub enum FineTuningModel {
1029 #[serde(rename = "gpt-4.1-2025-04-14")]
1032 Gpt41_2025_04_14,
1033
1034 #[serde(rename = "gpt-4.1-mini-2025-04-14")]
1036 Gpt41Mini_2025_04_14,
1037
1038 #[serde(rename = "gpt-4.1-nano-2025-04-14")]
1040 Gpt41Nano_2025_04_14,
1041
1042 #[serde(rename = "gpt-4o-mini-2024-07-18")]
1045 #[default]
1046 Gpt4oMini_2024_07_18,
1047
1048 #[serde(rename = "gpt-4o-2024-08-06")]
1050 Gpt4o_2024_08_06,
1051
1052 #[serde(rename = "gpt-4-0613")]
1055 Gpt4_0613,
1056
1057 #[serde(rename = "gpt-3.5-turbo-0125")]
1060 Gpt35Turbo_0125,
1061
1062 #[serde(rename = "gpt-3.5-turbo-1106")]
1064 Gpt35Turbo_1106,
1065
1066 #[serde(rename = "gpt-3.5-turbo-0613")]
1068 Gpt35Turbo_0613,
1069
1070 #[serde(rename = "babbage-002")]
1072 Babbage002,
1073
1074 #[serde(rename = "davinci-002")]
1076 Davinci002,
1077}
1078
1079impl FineTuningModel {
1080 pub fn as_str(&self) -> &str {
1082 match self {
1083 Self::Gpt41_2025_04_14 => "gpt-4.1-2025-04-14",
1085 Self::Gpt41Mini_2025_04_14 => "gpt-4.1-mini-2025-04-14",
1086 Self::Gpt41Nano_2025_04_14 => "gpt-4.1-nano-2025-04-14",
1087 Self::Gpt4oMini_2024_07_18 => "gpt-4o-mini-2024-07-18",
1089 Self::Gpt4o_2024_08_06 => "gpt-4o-2024-08-06",
1090 Self::Gpt4_0613 => "gpt-4-0613",
1092 Self::Gpt35Turbo_0125 => "gpt-3.5-turbo-0125",
1094 Self::Gpt35Turbo_1106 => "gpt-3.5-turbo-1106",
1095 Self::Gpt35Turbo_0613 => "gpt-3.5-turbo-0613",
1096 Self::Babbage002 => "babbage-002",
1097 Self::Davinci002 => "davinci-002",
1098 }
1099 }
1100}
1101
1102impl std::fmt::Display for FineTuningModel {
1103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1104 write!(f, "{}", self.as_str())
1105 }
1106}
1107
1108#[cfg(test)]
1109mod tests {
1110 use super::*;
1111
1112 #[test]
1113 fn test_chat_model_as_str() {
1114 assert_eq!(ChatModel::Gpt4oMini.as_str(), "gpt-4o-mini");
1115 assert_eq!(ChatModel::O3Mini.as_str(), "o3-mini");
1116 assert_eq!(ChatModel::Gpt4_1.as_str(), "gpt-4.1");
1117 assert_eq!(ChatModel::Gpt5_2.as_str(), "gpt-5.2");
1119 assert_eq!(ChatModel::Gpt5_2ChatLatest.as_str(), "gpt-5.2-chat-latest");
1120 assert_eq!(ChatModel::Gpt5_2Pro.as_str(), "gpt-5.2-pro");
1121 assert_eq!(ChatModel::Gpt5_1.as_str(), "gpt-5.1");
1122 assert_eq!(ChatModel::Gpt5_1CodexMax.as_str(), "gpt-5.1-codex-max");
1123 assert_eq!(ChatModel::Gpt5Mini.as_str(), "gpt-5-mini");
1124 }
1125
1126 #[test]
1127 fn test_chat_model_is_reasoning() {
1128 assert!(ChatModel::O1.is_reasoning_model());
1130 assert!(ChatModel::O3.is_reasoning_model());
1131 assert!(ChatModel::O3Mini.is_reasoning_model());
1132 assert!(ChatModel::O4Mini.is_reasoning_model());
1133 assert!(ChatModel::Gpt5_2.is_reasoning_model());
1136 assert!(!ChatModel::Gpt5_2ChatLatest.is_reasoning_model());
1137 assert!(ChatModel::Gpt5_2Pro.is_reasoning_model());
1138 assert!(ChatModel::Gpt5_1.is_reasoning_model());
1139 assert!(ChatModel::Gpt5_1CodexMax.is_reasoning_model());
1140 assert!(ChatModel::Gpt5Mini.is_reasoning_model());
1141 assert!(!ChatModel::Gpt4oMini.is_reasoning_model());
1143 assert!(!ChatModel::Gpt4_1.is_reasoning_model());
1144 }
1145
1146 #[test]
1147 fn test_chat_model_from_str() {
1148 assert_eq!(ChatModel::from("gpt-4o-mini"), ChatModel::Gpt4oMini);
1149 assert_eq!(ChatModel::from("o3-mini"), ChatModel::O3Mini);
1150 assert_eq!(ChatModel::from("gpt-5.2"), ChatModel::Gpt5_2);
1152 assert_eq!(ChatModel::from("gpt-5.2-chat-latest"), ChatModel::Gpt5_2ChatLatest);
1153 assert_eq!(ChatModel::from("gpt-5.2-pro"), ChatModel::Gpt5_2Pro);
1154 assert_eq!(ChatModel::from("gpt-5.1"), ChatModel::Gpt5_1);
1155 assert_eq!(ChatModel::from("gpt-5.1-codex-max"), ChatModel::Gpt5_1CodexMax);
1156 assert_eq!(ChatModel::from("gpt-5-mini"), ChatModel::Gpt5Mini);
1157 assert!(matches!(ChatModel::from("unknown-model"), ChatModel::Custom(_)));
1159 }
1160
1161 #[test]
1162 fn test_chat_model_custom() {
1163 let custom = ChatModel::custom("ft:gpt-4o-mini:org::123");
1164 assert_eq!(custom.as_str(), "ft:gpt-4o-mini:org::123");
1165 }
1166
1167 #[test]
1168 fn test_chat_model_custom_gpt5_is_reasoning() {
1169 let custom_gpt5 = ChatModel::custom("gpt-5.3-preview");
1171 assert!(custom_gpt5.is_reasoning_model());
1172 }
1173
1174 #[test]
1175 fn test_embedding_model_dimensions() {
1176 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1177 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1178 }
1179
1180 #[test]
1181 fn test_realtime_model_as_str() {
1182 assert_eq!(RealtimeModel::GptRealtime_2025_08_28.as_str(), "gpt-realtime-2025-08-28");
1183 }
1184
1185 #[test]
1186 fn test_fine_tuning_model_as_str() {
1187 assert_eq!(FineTuningModel::Gpt4oMini_2024_07_18.as_str(), "gpt-4o-mini-2024-07-18");
1188 assert_eq!(FineTuningModel::Gpt41_2025_04_14.as_str(), "gpt-4.1-2025-04-14");
1189 }
1190
1191 #[test]
1192 fn test_chat_model_serialization() {
1193 let model = ChatModel::Gpt4oMini;
1194 let json = serde_json::to_string(&model).unwrap();
1195 assert_eq!(json, "\"gpt-4o-mini\"");
1196 let gpt52 = ChatModel::Gpt5_2;
1198 let json = serde_json::to_string(&gpt52).unwrap();
1199 assert_eq!(json, "\"gpt-5.2\"");
1200 }
1201
1202 #[test]
1203 fn test_chat_model_deserialization() {
1204 let model: ChatModel = serde_json::from_str("\"gpt-4o-mini\"").unwrap();
1205 assert_eq!(model, ChatModel::Gpt4oMini);
1206 let gpt52: ChatModel = serde_json::from_str("\"gpt-5.2\"").unwrap();
1208 assert_eq!(gpt52, ChatModel::Gpt5_2);
1209 }
1210
1211 #[test]
1212 fn test_parameter_support_standard_model() {
1213 let model = ChatModel::Gpt4oMini;
1214 let support = model.parameter_support();
1215
1216 assert_eq!(support.temperature, ParameterRestriction::Any);
1218 assert_eq!(support.frequency_penalty, ParameterRestriction::Any);
1219 assert_eq!(support.presence_penalty, ParameterRestriction::Any);
1220 assert_eq!(support.top_p, ParameterRestriction::Any);
1221 assert!(support.logprobs);
1222 assert!(support.top_logprobs);
1223 assert!(support.logit_bias);
1224 assert!(support.n_multiple);
1225 assert!(!support.reasoning); }
1227
1228 #[test]
1229 fn test_parameter_support_reasoning_model() {
1230 let model = ChatModel::O3Mini;
1231 let support = model.parameter_support();
1232
1233 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1235 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0));
1236 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0));
1237 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0));
1238 assert!(!support.logprobs);
1239 assert!(!support.top_logprobs);
1240 assert!(!support.logit_bias);
1241 assert!(!support.n_multiple);
1242 assert!(support.reasoning); }
1244
1245 #[test]
1246 fn test_parameter_support_gpt5_model() {
1247 let model = ChatModel::Gpt5_2;
1249 let support = model.parameter_support();
1250
1251 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0));
1252 assert!(!support.logprobs);
1253 assert!(support.reasoning);
1254 }
1255
1256 #[test]
1261 fn test_all_o_series_models_are_reasoning() {
1262 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1264
1265 for model in o_series {
1266 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1267 }
1268 }
1269
1270 #[test]
1271 fn test_all_gpt5_models_are_reasoning() {
1272 let gpt5_series =
1276 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1277
1278 for model in gpt5_series {
1279 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1280 }
1281 }
1282
1283 #[test]
1284 fn test_all_standard_models_are_not_reasoning() {
1285 let standard_models = vec![
1287 ChatModel::Gpt4oMini,
1288 ChatModel::Gpt4o,
1289 ChatModel::Gpt4oAudioPreview,
1290 ChatModel::Gpt4Turbo,
1291 ChatModel::Gpt4,
1292 ChatModel::Gpt3_5Turbo,
1293 ChatModel::Gpt4_1,
1294 ChatModel::Gpt4_1Mini,
1295 ChatModel::Gpt4_1Nano,
1296 ];
1297
1298 for model in standard_models {
1299 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1300 }
1301 }
1302
1303 #[test]
1308 fn test_custom_o1_models_are_reasoning() {
1309 let custom_o1_variants = vec!["o1-mini", "o1-preview", "o1-pro-2025", "o1-high"];
1310
1311 for model_str in custom_o1_variants {
1312 let model = ChatModel::custom(model_str);
1313 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1314 }
1315 }
1316
1317 #[test]
1318 fn test_custom_o3_models_are_reasoning() {
1319 let custom_o3_variants = vec!["o3-preview", "o3-high", "o3-2025-01-15"];
1320
1321 for model_str in custom_o3_variants {
1322 let model = ChatModel::custom(model_str);
1323 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1324 }
1325 }
1326
1327 #[test]
1328 fn test_custom_o4_models_are_reasoning() {
1329 let custom_o4_variants = vec!["o4-preview", "o4-mini-2025", "o4-high"];
1330
1331 for model_str in custom_o4_variants {
1332 let model = ChatModel::custom(model_str);
1333 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1334 }
1335 }
1336
1337 #[test]
1338 fn test_custom_gpt5_models_are_reasoning() {
1339 let custom_gpt5_variants = vec!["gpt-5.3", "gpt-5.3-preview", "gpt-5-turbo", "gpt-5.0"];
1340
1341 for model_str in custom_gpt5_variants {
1342 let model = ChatModel::custom(model_str);
1343 assert!(model.is_reasoning_model(), "Expected custom model '{}' to be a reasoning model", model_str);
1344 }
1345 }
1346
1347 #[test]
1348 fn test_custom_standard_models_are_not_reasoning() {
1349 let custom_standard_variants = vec![
1350 "ft:gpt-4o-mini:org::123",
1351 "gpt-4o-2025-01-15",
1352 "gpt-4-turbo-preview",
1353 "gpt-3.5-turbo-instruct",
1354 "text-davinci-003",
1355 "claude-3-opus", ];
1357
1358 for model_str in custom_standard_variants {
1359 let model = ChatModel::custom(model_str);
1360 assert!(!model.is_reasoning_model(), "Expected custom model '{}' to NOT be a reasoning model", model_str);
1361 }
1362 }
1363
1364 #[test]
1369 fn test_parameter_support_all_o_series() {
1370 let o_series = vec![ChatModel::O1, ChatModel::O1Pro, ChatModel::O3, ChatModel::O3Mini, ChatModel::O4Mini];
1371
1372 for model in o_series {
1373 let support = model.parameter_support();
1374
1375 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1376 assert_eq!(
1377 support.frequency_penalty,
1378 ParameterRestriction::FixedValue(0.0),
1379 "{} should only support frequency_penalty=0.0",
1380 model.as_str()
1381 );
1382 assert_eq!(
1383 support.presence_penalty,
1384 ParameterRestriction::FixedValue(0.0),
1385 "{} should only support presence_penalty=0.0",
1386 model.as_str()
1387 );
1388 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should only support top_p=1.0", model.as_str());
1389 assert!(!support.logprobs, "{} should not support logprobs", model.as_str());
1390 assert!(!support.top_logprobs, "{} should not support top_logprobs", model.as_str());
1391 assert!(!support.logit_bias, "{} should not support logit_bias", model.as_str());
1392 assert!(!support.n_multiple, "{} should only support n=1", model.as_str());
1393 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1394 }
1395 }
1396
1397 #[test]
1398 fn test_parameter_support_all_gpt5_series() {
1399 let gpt5_series =
1402 vec![ChatModel::Gpt5_2, ChatModel::Gpt5_2Pro, ChatModel::Gpt5_1, ChatModel::Gpt5_1CodexMax, ChatModel::Gpt5Mini, ChatModel::Gpt5Nano];
1403
1404 for model in gpt5_series {
1405 let support = model.parameter_support();
1406
1407 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should only support temperature=1.0", model.as_str());
1408 assert!(support.reasoning, "{} should support reasoning parameter", model.as_str());
1409 }
1410 }
1411
1412 #[test]
1413 fn test_parameter_support_all_standard_gpt4_series() {
1414 let gpt4_series = vec![
1415 ChatModel::Gpt4oMini,
1416 ChatModel::Gpt4o,
1417 ChatModel::Gpt4Turbo,
1418 ChatModel::Gpt4,
1419 ChatModel::Gpt4_1,
1420 ChatModel::Gpt4_1Mini,
1421 ChatModel::Gpt4_1Nano,
1422 ];
1423
1424 for model in gpt4_series {
1425 let support = model.parameter_support();
1426
1427 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should support any temperature", model.as_str());
1428 assert_eq!(support.frequency_penalty, ParameterRestriction::Any, "{} should support any frequency_penalty", model.as_str());
1429 assert_eq!(support.presence_penalty, ParameterRestriction::Any, "{} should support any presence_penalty", model.as_str());
1430 assert!(support.logprobs, "{} should support logprobs", model.as_str());
1431 assert!(support.top_logprobs, "{} should support top_logprobs", model.as_str());
1432 assert!(support.logit_bias, "{} should support logit_bias", model.as_str());
1433 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1434 assert!(!support.reasoning, "{} should NOT support reasoning parameter", model.as_str());
1435 }
1436 }
1437
1438 #[test]
1443 fn test_parameter_restriction_equality() {
1444 assert_eq!(ParameterRestriction::Any, ParameterRestriction::Any);
1445 assert_eq!(ParameterRestriction::NotSupported, ParameterRestriction::NotSupported);
1446 assert_eq!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(1.0));
1447
1448 assert_ne!(ParameterRestriction::Any, ParameterRestriction::NotSupported);
1449 assert_ne!(ParameterRestriction::FixedValue(1.0), ParameterRestriction::FixedValue(0.0));
1450 }
1451
1452 #[test]
1453 fn test_parameter_support_factory_methods() {
1454 let standard = ParameterSupport::standard_model();
1455 assert_eq!(standard.temperature, ParameterRestriction::Any);
1456 assert!(standard.logprobs);
1457 assert!(!standard.reasoning);
1458
1459 let reasoning = ParameterSupport::reasoning_model();
1460 assert_eq!(reasoning.temperature, ParameterRestriction::FixedValue(1.0));
1461 assert!(!reasoning.logprobs);
1462 assert!(reasoning.reasoning);
1463 }
1464
1465 #[test]
1470 fn test_all_gpt5_model_string_roundtrip() {
1471 let gpt5_models = vec![
1472 ("gpt-5.2", ChatModel::Gpt5_2),
1473 ("gpt-5.2-chat-latest", ChatModel::Gpt5_2ChatLatest),
1474 ("gpt-5.2-pro", ChatModel::Gpt5_2Pro),
1475 ("gpt-5.1", ChatModel::Gpt5_1),
1476 ("gpt-5.1-chat-latest", ChatModel::Gpt5_1ChatLatest),
1477 ("gpt-5.1-codex-max", ChatModel::Gpt5_1CodexMax),
1478 ("gpt-5-mini", ChatModel::Gpt5Mini),
1479 ("gpt-5-nano", ChatModel::Gpt5Nano),
1480 ];
1481
1482 for (model_str, expected_model) in gpt5_models {
1483 let parsed = ChatModel::from(model_str);
1485 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1486
1487 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1489
1490 let json = serde_json::to_string(&expected_model).unwrap();
1492 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1493 assert_eq!(deserialized, expected_model, "Serialization roundtrip failed for {}", model_str);
1494 }
1495 }
1496
1497 #[test]
1498 fn test_all_o_series_model_string_roundtrip() {
1499 let o_series_models = vec![
1500 ("o1", ChatModel::O1),
1501 ("o1-pro", ChatModel::O1Pro),
1502 ("o3", ChatModel::O3),
1503 ("o3-mini", ChatModel::O3Mini),
1504 ("o4-mini", ChatModel::O4Mini),
1505 ];
1506
1507 for (model_str, expected_model) in o_series_models {
1508 let parsed = ChatModel::from(model_str);
1509 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1510 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1511 }
1512 }
1513
1514 #[test]
1519 fn test_embedding_model_string_roundtrip() {
1520 let embedding_models = vec![
1521 ("text-embedding-3-small", EmbeddingModel::TextEmbedding3Small),
1522 ("text-embedding-3-large", EmbeddingModel::TextEmbedding3Large),
1523 ("text-embedding-ada-002", EmbeddingModel::TextEmbeddingAda002),
1524 ];
1525
1526 for (model_str, expected_model) in embedding_models {
1527 let parsed = EmbeddingModel::from(model_str);
1528 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1529 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1530 }
1531 }
1532
1533 #[test]
1534 fn test_embedding_model_all_dimensions() {
1535 assert_eq!(EmbeddingModel::TextEmbedding3Small.dimensions(), 1536);
1536 assert_eq!(EmbeddingModel::TextEmbedding3Large.dimensions(), 3072);
1537 assert_eq!(EmbeddingModel::TextEmbeddingAda002.dimensions(), 1536);
1538 }
1539
1540 #[test]
1545 fn test_realtime_model_string_roundtrip() {
1546 let realtime_models = vec![("gpt-realtime-2025-08-28", RealtimeModel::GptRealtime_2025_08_28)];
1547
1548 for (model_str, expected_model) in realtime_models {
1549 let parsed = RealtimeModel::from(model_str);
1550 assert_eq!(parsed, expected_model, "Failed to parse '{}'", model_str);
1551 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1552 }
1553 }
1554
1555 #[test]
1556 fn test_realtime_model_custom() {
1557 let custom = RealtimeModel::custom("gpt-4o-realtime-2025");
1558 assert_eq!(custom.as_str(), "gpt-4o-realtime-2025");
1559 assert!(matches!(custom, RealtimeModel::Custom(_)));
1560 }
1561
1562 #[test]
1567 fn test_fine_tuning_model_as_str_all_variants() {
1568 let fine_tuning_models = vec![
1569 ("gpt-4.1-2025-04-14", FineTuningModel::Gpt41_2025_04_14),
1570 ("gpt-4.1-mini-2025-04-14", FineTuningModel::Gpt41Mini_2025_04_14),
1571 ("gpt-4.1-nano-2025-04-14", FineTuningModel::Gpt41Nano_2025_04_14),
1572 ("gpt-4o-mini-2024-07-18", FineTuningModel::Gpt4oMini_2024_07_18),
1573 ("gpt-4o-2024-08-06", FineTuningModel::Gpt4o_2024_08_06),
1574 ("gpt-4-0613", FineTuningModel::Gpt4_0613),
1575 ("gpt-3.5-turbo-0125", FineTuningModel::Gpt35Turbo_0125),
1576 ("gpt-3.5-turbo-1106", FineTuningModel::Gpt35Turbo_1106),
1577 ("gpt-3.5-turbo-0613", FineTuningModel::Gpt35Turbo_0613),
1578 ];
1579
1580 for (model_str, expected_model) in fine_tuning_models {
1581 assert_eq!(expected_model.as_str(), model_str, "Failed to convert {:?} to string", expected_model);
1582 }
1583 }
1584
1585 #[test]
1586 fn test_fine_tuning_model_serialization_roundtrip() {
1587 let models = vec![FineTuningModel::Gpt41_2025_04_14, FineTuningModel::Gpt4oMini_2024_07_18, FineTuningModel::Gpt35Turbo_0125];
1588
1589 for model in models {
1590 let json = serde_json::to_string(&model).unwrap();
1591 let deserialized: FineTuningModel = serde_json::from_str(&json).unwrap();
1592 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1593 }
1594 }
1595
1596 fn gpt5_3_to_5_6_models() -> Vec<(ChatModel, &'static str)> {
1606 vec![
1607 (ChatModel::Gpt5_6, "gpt-5.6"),
1609 (ChatModel::Gpt5_6Sol, "gpt-5.6-sol"),
1610 (ChatModel::Gpt5_6Terra, "gpt-5.6-terra"),
1611 (ChatModel::Gpt5_6Luna, "gpt-5.6-luna"),
1612 (ChatModel::Gpt5_5, "gpt-5.5"),
1614 (ChatModel::Gpt5_5Pro, "gpt-5.5-pro"),
1615 (ChatModel::Gpt5_4, "gpt-5.4"),
1617 (ChatModel::Gpt5_4Pro, "gpt-5.4-pro"),
1618 (ChatModel::Gpt5_4Mini, "gpt-5.4-mini"),
1619 (ChatModel::Gpt5_4Nano, "gpt-5.4-nano"),
1620 (ChatModel::Gpt5_3ChatLatest, "gpt-5.3-chat-latest"),
1622 (ChatModel::Gpt5_3Codex, "gpt-5.3-codex"),
1624 (ChatModel::Gpt5_2Codex, "gpt-5.2-codex"),
1625 (ChatModel::Gpt5_1Codex, "gpt-5.1-codex"),
1626 ]
1627 }
1628
1629 #[test]
1630 fn test_new_chat_models_as_str() {
1631 for (model, expected) in gpt5_3_to_5_6_models() {
1632 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1633 }
1634 }
1635
1636 #[test]
1637 fn test_new_chat_models_from_str_roundtrip() {
1638 for (model, model_str) in gpt5_3_to_5_6_models() {
1639 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1640 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1642 }
1643 }
1644
1645 #[test]
1646 fn test_new_chat_models_serialization_roundtrip() {
1647 for (model, model_str) in gpt5_3_to_5_6_models() {
1648 let json = serde_json::to_string(&model).unwrap();
1649 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1650 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1651 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1652 }
1653 }
1654
1655 #[test]
1658 fn test_new_gpt5_models_are_reasoning_models() {
1659 let reasoning = vec![
1660 ChatModel::Gpt5_6,
1661 ChatModel::Gpt5_6Sol,
1662 ChatModel::Gpt5_6Terra,
1663 ChatModel::Gpt5_6Luna,
1664 ChatModel::Gpt5_5,
1665 ChatModel::Gpt5_5Pro,
1666 ChatModel::Gpt5_4,
1667 ChatModel::Gpt5_4Pro,
1668 ChatModel::Gpt5_4Mini,
1669 ChatModel::Gpt5_4Nano,
1670 ChatModel::Gpt5_3Codex,
1671 ChatModel::Gpt5_2Codex,
1672 ChatModel::Gpt5_1Codex,
1673 ];
1674
1675 for model in reasoning {
1676 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1677 let support = model.parameter_support();
1678 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should pin temperature to 1.0", model.as_str());
1679 assert!(support.reasoning, "{} should support the reasoning parameter", model.as_str());
1680 }
1681 }
1682
1683 #[test]
1687 fn test_chat_latest_models_are_not_reasoning_models() {
1688 let chat_latest = vec![ChatModel::Gpt5_3ChatLatest, ChatModel::Gpt5_2ChatLatest, ChatModel::Gpt5_1ChatLatest];
1689
1690 for model in chat_latest {
1691 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1692 let support = model.parameter_support();
1693 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1694 assert_eq!(support.top_p, ParameterRestriction::Any, "{} should accept any top_p", model.as_str());
1695 assert!(!support.reasoning, "{} should NOT support the reasoning parameter", model.as_str());
1696 }
1697 }
1698
1699 #[test]
1702 fn test_custom_chat_latest_is_not_reasoning_model() {
1703 assert!(!ChatModel::custom("gpt-5.7-chat-latest").is_reasoning_model());
1704 assert!(ChatModel::custom("gpt-5.7").is_reasoning_model());
1706 assert!(ChatModel::custom("gpt-5.7-codex").is_reasoning_model());
1707 }
1708
1709 fn new_realtime_models() -> Vec<(RealtimeModel, &'static str)> {
1714 vec![
1715 (RealtimeModel::GptRealtime2_1, "gpt-realtime-2.1"),
1716 (RealtimeModel::GptRealtime2_1Mini, "gpt-realtime-2.1-mini"),
1717 (RealtimeModel::GptRealtime2, "gpt-realtime-2"),
1718 (RealtimeModel::GptRealtime1_5, "gpt-realtime-1.5"),
1719 (RealtimeModel::GptRealtimeTranslate, "gpt-realtime-translate"),
1720 ]
1721 }
1722
1723 #[test]
1724 fn test_new_realtime_models_as_str() {
1725 for (model, expected) in new_realtime_models() {
1726 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1727 }
1728 }
1729
1730 #[test]
1731 fn test_new_realtime_models_from_str_roundtrip() {
1732 for (model, model_str) in new_realtime_models() {
1733 assert_eq!(RealtimeModel::from(model_str), model, "Failed to parse {}", model_str);
1734 }
1735 }
1736
1737 #[test]
1741 fn test_realtime_model_default_is_unchanged() {
1742 assert_eq!(RealtimeModel::default(), RealtimeModel::GptRealtime_2025_08_28);
1743 }
1744
1745 #[test]
1746 fn test_new_realtime_models_serialization_roundtrip() {
1747 for (model, model_str) in new_realtime_models() {
1748 let json = serde_json::to_string(&model).unwrap();
1749 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1750 let deserialized: RealtimeModel = serde_json::from_str(&json).unwrap();
1751 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1752 }
1753 }
1754
1755 fn previously_missing_chat_models() -> Vec<(ChatModel, &'static str)> {
1763 vec![
1764 (ChatModel::Gpt5, "gpt-5"),
1765 (ChatModel::Gpt5Pro, "gpt-5-pro"),
1766 (ChatModel::O3Pro, "o3-pro"),
1767 (ChatModel::Gpt5SearchApi, "gpt-5-search-api"),
1768 (ChatModel::Gpt4oSearchPreview, "gpt-4o-search-preview"),
1769 (ChatModel::Gpt4oMiniSearchPreview, "gpt-4o-mini-search-preview"),
1770 (ChatModel::GptAudio, "gpt-audio"),
1771 (ChatModel::GptAudio1_5, "gpt-audio-1.5"),
1772 (ChatModel::GptAudioMini, "gpt-audio-mini"),
1773 (ChatModel::Gpt3_5Turbo16k, "gpt-3.5-turbo-16k"),
1774 ]
1775 }
1776
1777 #[test]
1778 fn test_previously_missing_chat_models_as_str() {
1779 for (model, expected) in previously_missing_chat_models() {
1780 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1781 }
1782 }
1783
1784 #[test]
1785 fn test_previously_missing_chat_models_from_str_roundtrip() {
1786 for (model, model_str) in previously_missing_chat_models() {
1787 assert_eq!(ChatModel::from(model_str), model, "Failed to parse {}", model_str);
1788 assert_eq!(ChatModel::from(model_str).as_str(), model_str, "Roundtrip failed for {}", model_str);
1789 }
1790 }
1791
1792 #[test]
1793 fn test_previously_missing_chat_models_serialization_roundtrip() {
1794 for (model, model_str) in previously_missing_chat_models() {
1795 let json = serde_json::to_string(&model).unwrap();
1796 assert_eq!(json, format!("\"{}\"", model_str), "Wrong serialization for {:?}", model);
1797 let deserialized: ChatModel = serde_json::from_str(&json).unwrap();
1798 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1799 }
1800 }
1801
1802 #[test]
1805 fn test_gpt5_base_and_pro_models_are_reasoning() {
1806 for model in [ChatModel::Gpt5, ChatModel::Gpt5Pro, ChatModel::O3Pro] {
1807 assert!(model.is_reasoning_model(), "Expected {} to be a reasoning model", model.as_str());
1808 assert!(model.parameter_support().reasoning, "{} should support the reasoning parameter", model.as_str());
1809 }
1810 }
1811
1812 #[test]
1814 fn test_audio_chat_models_are_standard() {
1815 for model in [ChatModel::GptAudio, ChatModel::GptAudio1_5, ChatModel::GptAudioMini, ChatModel::Gpt3_5Turbo16k] {
1816 assert!(!model.is_reasoning_model(), "Expected {} to NOT be a reasoning model", model.as_str());
1817 let support = model.parameter_support();
1818 assert_eq!(support.temperature, ParameterRestriction::Any, "{} should accept any temperature", model.as_str());
1819 assert!(support.n_multiple, "{} should support n > 1", model.as_str());
1820 }
1821 }
1822
1823 #[test]
1826 fn test_search_models_reject_sampling_but_have_no_reasoning() {
1827 let search_models = [ChatModel::Gpt5SearchApi, ChatModel::Gpt4oSearchPreview, ChatModel::Gpt4oMiniSearchPreview];
1828
1829 for model in search_models {
1830 assert!(model.is_search_model(), "Expected {} to be a search model", model.as_str());
1831 assert!(!model.is_reasoning_model(), "Search model {} must not be classed as reasoning", model.as_str());
1832
1833 let support = model.parameter_support();
1834 assert_eq!(support.temperature, ParameterRestriction::FixedValue(1.0), "{} should reject custom temperature", model.as_str());
1835 assert_eq!(support.top_p, ParameterRestriction::FixedValue(1.0), "{} should reject custom top_p", model.as_str());
1836 assert_eq!(support.frequency_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject frequency_penalty", model.as_str());
1837 assert_eq!(support.presence_penalty, ParameterRestriction::FixedValue(0.0), "{} should reject presence_penalty", model.as_str());
1838 assert!(!support.logprobs, "{} should reject logprobs", model.as_str());
1839 assert!(!support.n_multiple, "{} should reject n > 1", model.as_str());
1840 assert!(!support.reasoning, "{} exposes no reasoning parameter", model.as_str());
1841 }
1842 }
1843
1844 #[test]
1846 fn test_non_search_models_are_not_search_models() {
1847 for model in [ChatModel::Gpt5_6Sol, ChatModel::Gpt4oMini, ChatModel::O3Mini, ChatModel::Gpt5_2ChatLatest] {
1848 assert!(!model.is_search_model(), "Expected {} to NOT be a search model", model.as_str());
1849 }
1850 }
1851
1852 #[test]
1853 fn test_previously_missing_realtime_models() {
1854 for (model, expected) in [(RealtimeModel::GptRealtime, "gpt-realtime"), (RealtimeModel::GptRealtimeMini, "gpt-realtime-mini")] {
1855 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1856 assert_eq!(RealtimeModel::from(expected), model, "Failed to parse {}", expected);
1857 let json = serde_json::to_string(&model).unwrap();
1858 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1859 }
1860 }
1861
1862 #[test]
1863 fn test_legacy_fine_tuning_base_models() {
1864 for (model, expected) in [(FineTuningModel::Babbage002, "babbage-002"), (FineTuningModel::Davinci002, "davinci-002")] {
1865 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1866 let json = serde_json::to_string(&model).unwrap();
1867 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1868 }
1869 }
1870}