Compare commits

...
Author SHA1 Message Date
Brandon Thomas 40b2da473c tests 2026-04-21 21:03:58 -04:00
Brandon Thomas a59e971452 gpt-image-2 enums 2026-04-21 21:00:52 -04:00
6 changed files with 124 additions and 21 deletions
@@ -17,6 +17,8 @@ pub enum CommonImageModel {
GptImage1,
#[serde(rename = "gpt_image_1p5")]
GptImage1p5,
#[serde(rename = "gpt_image_2")]
GptImage2,
#[serde(rename = "nano_banana")]
NanoBanana,
#[serde(rename = "nano_banana_2")]
@@ -112,6 +112,7 @@ impl GenerateImageRequest {
CommonImageModel::Seedream5Lite => plan_generate_image_artcraft_seedream_5_lite(self),
CommonImageModel::QwenEdit2511Angles => plan_generate_image_artcraft_qwen_edit_2511_angles(self),
CommonImageModel::Flux2LoraAngles => plan_generate_image_artcraft_flux_2_lora_angles(self),
_ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", self.model))),
}
}
@@ -1,5 +1,5 @@
use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::Seedance2p0BatchCount;
use seedance2pro_client::requests::generate_video::generate_video::{KinoviGenerateVideoRequest, KinoviBatchCount, KinoviModelType, KinoviOutputResolution, KinoviAspectRatio};
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
use crate::api::common_resolution::CommonResolution;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0;
@@ -1,8 +1,14 @@
use utoipa::ToSchema;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
/// Image models available for generation.
/// Mirrors artcraft_router::api::common_image_model::CommonImageModel.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[serde(rename_all = "snake_case")]
pub enum CommonImageModel {
#[serde(rename = "flux_1_dev")]
@@ -17,6 +23,8 @@ pub enum CommonImageModel {
GptImage1,
#[serde(rename = "gpt_image_1p5")]
GptImage1p5,
#[serde(rename = "gpt_image_2")]
GptImage2,
#[serde(rename = "nano_banana")]
NanoBanana,
#[serde(rename = "nano_banana_2")]
@@ -41,6 +49,7 @@ impl CommonImageModel {
Self::FluxPro11Ultra => CommonModelType::FluxPro11Ultra,
Self::GptImage1 => CommonModelType::GptImage1,
Self::GptImage1p5 => CommonModelType::GptImage1p5,
Self::GptImage2 => CommonModelType::GptImage2,
Self::NanoBanana => CommonModelType::NanoBanana,
Self::NanoBanana2 => CommonModelType::NanoBanana2,
Self::NanoBananaPro => CommonModelType::NanoBananaPro,
@@ -54,26 +63,106 @@ impl CommonImageModel {
#[cfg(test)]
mod tests {
use super::*;
use crate::common::generation::common_model_class::CommonModelClass;
use crate::common::generation::common_model_type::CommonModelType;
use crate::test_helpers::assert_serialization;
#[test]
fn all_image_models_convert_to_common_model_type() {
let models = [
(CommonImageModel::Flux1Dev, CommonModelType::Flux1Dev),
(CommonImageModel::Flux1Schnell, CommonModelType::Flux1Schnell),
(CommonImageModel::FluxPro11, CommonModelType::FluxPro11),
(CommonImageModel::FluxPro11Ultra, CommonModelType::FluxPro11Ultra),
(CommonImageModel::GptImage1, CommonModelType::GptImage1),
(CommonImageModel::GptImage1p5, CommonModelType::GptImage1p5),
(CommonImageModel::NanoBanana, CommonModelType::NanoBanana),
(CommonImageModel::NanoBanana2, CommonModelType::NanoBanana2),
(CommonImageModel::NanoBananaPro, CommonModelType::NanoBananaPro),
(CommonImageModel::Seedream4, CommonModelType::Seedream4),
(CommonImageModel::Seedream4p5, CommonModelType::Seedream4p5),
(CommonImageModel::Seedream5Lite, CommonModelType::Seedream5Lite),
];
for (image_model, expected) in models {
assert_eq!(image_model.to_common_model_type(), expected);
mod explicit_checks {
use super::*;
#[test]
fn serialization() {
assert_serialization(CommonImageModel::Flux1Dev, "flux_1_dev");
assert_serialization(CommonImageModel::Flux1Schnell, "flux_1_schnell");
assert_serialization(CommonImageModel::FluxPro11, "flux_pro_1p1");
assert_serialization(CommonImageModel::FluxPro11Ultra, "flux_pro_1p1_ultra");
assert_serialization(CommonImageModel::GptImage1, "gpt_image_1");
assert_serialization(CommonImageModel::GptImage1p5, "gpt_image_1p5");
assert_serialization(CommonImageModel::GptImage2, "gpt_image_2");
assert_serialization(CommonImageModel::NanoBanana, "nano_banana");
assert_serialization(CommonImageModel::NanoBanana2, "nano_banana_2");
assert_serialization(CommonImageModel::NanoBananaPro, "nano_banana_pro");
assert_serialization(CommonImageModel::Seedream4, "seedream_4");
assert_serialization(CommonImageModel::Seedream4p5, "seedream_4p5");
assert_serialization(CommonImageModel::Seedream5Lite, "seedream_5_lite");
}
#[test]
fn all_image_models_convert_to_common_model_type() {
let models = [
(CommonImageModel::Flux1Dev, CommonModelType::Flux1Dev),
(CommonImageModel::Flux1Schnell, CommonModelType::Flux1Schnell),
(CommonImageModel::FluxPro11, CommonModelType::FluxPro11),
(CommonImageModel::FluxPro11Ultra, CommonModelType::FluxPro11Ultra),
(CommonImageModel::GptImage1, CommonModelType::GptImage1),
(CommonImageModel::GptImage1p5, CommonModelType::GptImage1p5),
(CommonImageModel::GptImage2, CommonModelType::GptImage2),
(CommonImageModel::NanoBanana, CommonModelType::NanoBanana),
(CommonImageModel::NanoBanana2, CommonModelType::NanoBanana2),
(CommonImageModel::NanoBananaPro, CommonModelType::NanoBananaPro),
(CommonImageModel::Seedream4, CommonModelType::Seedream4),
(CommonImageModel::Seedream4p5, CommonModelType::Seedream4p5),
(CommonImageModel::Seedream5Lite, CommonModelType::Seedream5Lite),
];
for (image_model, expected) in models {
assert_eq!(image_model.to_common_model_type(), expected);
}
}
#[test]
fn all_image_models_map_to_image_class() {
use strum::IntoEnumIterator;
for variant in CommonImageModel::iter() {
let model_type = variant.to_common_model_type();
assert_eq!(
model_type.get_model_class(), CommonModelClass::Image,
"{:?} mapped to {:?} which has class {:?}, expected Image",
variant, model_type, model_type.get_model_class(),
);
}
}
}
mod mechanical_checks {
use super::*;
#[test]
fn variant_count() {
use strum::IntoEnumIterator;
assert_eq!(CommonImageModel::iter().len(), 13);
}
#[test]
fn serde_round_trip() {
use strum::IntoEnumIterator;
for variant in CommonImageModel::iter() {
let json = serde_json::to_string(&variant).unwrap();
let deserialized: CommonImageModel = serde_json::from_str(&json).unwrap();
assert_eq!(variant, deserialized, "round-trip failed for {:?}", variant);
}
}
#[test]
fn serialized_names_are_lowercase_alphanumeric_underscore() {
use strum::IntoEnumIterator;
let valid = regex::Regex::new(r"^[a-z0-9_]+$").unwrap();
for variant in CommonImageModel::iter() {
let json = serde_json::to_string(&variant).unwrap().replace('"', "");
assert!(
valid.is_match(&json),
"{:?} serializes to {:?} which contains invalid characters",
variant, json,
);
}
}
#[test]
fn to_common_model_type_covers_all_variants() {
use strum::IntoEnumIterator;
// This test ensures to_common_model_type doesn't panic for any variant.
for variant in CommonImageModel::iter() {
let _ = variant.to_common_model_type();
}
}
}
}
@@ -48,6 +48,8 @@ pub enum CommonModelType {
GptImage1,
#[serde(rename = "gpt_image_1p5")]
GptImage1p5,
#[serde(rename = "gpt_image_2")]
GptImage2,
// Generic grok image model without a version
#[serde(rename = "grok_image")]
GrokImage,
@@ -176,6 +178,7 @@ impl CommonModelType {
Self::Flux2LoraAngles => "flux_2_lora_angles",
Self::GptImage1 => "gpt_image_1",
Self::GptImage1p5 => "gpt_image_1p5",
Self::GptImage2 => "gpt_image_2",
Self::GrokImage => "grok_image",
Self::Recraft3 => "recraft_3",
Self::SeedEdit3 => "seededit_3",
@@ -243,6 +246,7 @@ impl CommonModelType {
"flux_2_lora_angles" => Ok(Self::Flux2LoraAngles),
"gpt_image_1" => Ok(Self::GptImage1),
"gpt_image_1p5" => Ok(Self::GptImage1p5),
"gpt_image_2" => Ok(Self::GptImage2),
"grok_image" => Ok(Self::GrokImage),
"recraft_3" => Ok(Self::Recraft3),
"seededit_3" => Ok(Self::SeedEdit3),
@@ -314,6 +318,7 @@ impl CommonModelType {
Self::Flux2LoraAngles,
Self::GptImage1,
Self::GptImage1p5,
Self::GptImage2,
Self::GrokImage,
Self::Recraft3,
Self::SeedEdit3,
@@ -382,6 +387,7 @@ impl CommonModelType {
Self::Flux2LoraAngles => CommonModelClass::Image,
Self::GptImage1 => CommonModelClass::Image,
Self::GptImage1p5 => CommonModelClass::Image,
Self::GptImage2 => CommonModelClass::Image,
Self::GrokImage => CommonModelClass::Image,
Self::Recraft3 => CommonModelClass::Image,
Self::SeedEdit3 => CommonModelClass::Image,
@@ -460,6 +466,7 @@ mod tests {
assert_serialization(CommonModelType::Flux2LoraAngles, "flux_2_lora_angles");
assert_serialization(CommonModelType::GptImage1, "gpt_image_1");
assert_serialization(CommonModelType::GptImage1p5, "gpt_image_1p5");
assert_serialization(CommonModelType::GptImage2, "gpt_image_2");
assert_serialization(CommonModelType::GrokImage, "grok_image");
assert_serialization(CommonModelType::Recraft3, "recraft_3");
assert_serialization(CommonModelType::SeedEdit3, "seededit_3");
@@ -523,6 +530,7 @@ mod tests {
assert_eq!(CommonModelType::Flux2LoraAngles.to_str(), "flux_2_lora_angles");
assert_eq!(CommonModelType::GptImage1.to_str(), "gpt_image_1");
assert_eq!(CommonModelType::GptImage1p5.to_str(), "gpt_image_1p5");
assert_eq!(CommonModelType::GptImage2.to_str(), "gpt_image_2");
assert_eq!(CommonModelType::GrokImage.to_str(), "grok_image");
assert_eq!(CommonModelType::Recraft3.to_str(), "recraft_3");
assert_eq!(CommonModelType::SeedEdit3.to_str(), "seededit_3");
@@ -588,6 +596,7 @@ mod tests {
assert_eq!(CommonModelType::from_str("flux_2_lora_angles").unwrap(), CommonModelType::Flux2LoraAngles);
assert_eq!(CommonModelType::from_str("gpt_image_1").unwrap(), CommonModelType::GptImage1);
assert_eq!(CommonModelType::from_str("gpt_image_1p5").unwrap(), CommonModelType::GptImage1p5);
assert_eq!(CommonModelType::from_str("gpt_image_2").unwrap(), CommonModelType::GptImage2);
assert_eq!(CommonModelType::from_str("grok_image").unwrap(), CommonModelType::GrokImage);
assert_eq!(CommonModelType::from_str("recraft_3").unwrap(), CommonModelType::Recraft3);
assert_eq!(CommonModelType::from_str("seededit_3").unwrap(), CommonModelType::SeedEdit3);
@@ -642,7 +651,7 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = CommonModelType::all_variants();
assert_eq!(variants.len(), 55);
assert_eq!(variants.len(), 56);
// Image models
assert_eq!(variants.pop_first(), Some(CommonModelType::Flux1Dev));
assert_eq!(variants.pop_first(), Some(CommonModelType::Flux1Schnell));
@@ -654,6 +663,7 @@ mod tests {
assert_eq!(variants.pop_first(), Some(CommonModelType::Flux2LoraAngles));
assert_eq!(variants.pop_first(), Some(CommonModelType::GptImage1));
assert_eq!(variants.pop_first(), Some(CommonModelType::GptImage1p5));
assert_eq!(variants.pop_first(), Some(CommonModelType::GptImage2));
assert_eq!(variants.pop_first(), Some(CommonModelType::GrokImage));
assert_eq!(variants.pop_first(), Some(CommonModelType::Recraft3));
assert_eq!(variants.pop_first(), Some(CommonModelType::SeedEdit3));
@@ -138,6 +138,7 @@ fn map_image_model(model: CommonImageModel) -> Result<RouterImageModel, HandlerE
CommonImageModel::Seedream4 => RouterImageModel::Seedream4,
CommonImageModel::Seedream4p5 => RouterImageModel::Seedream4p5,
CommonImageModel::Seedream5Lite => RouterImageModel::Seedream5Lite,
CommonImageModel::GptImage2 => RouterImageModel::GptImage2,
};
Ok(router_model)
}