provider按参数分层并公开组合校验入口

- submit 改为接收生成参数,图片输入由调用方解析后单独传入
- 公开 validate_text_to_model_params / validate_image_to_model_params 供平台层在副作用前预检
- 参数到 provider 校验选项的映射收敛为 From 实现,去掉重复拼装
- smoke 示例跟随新签名
This commit is contained in:
2026-09-21 12:39:20 +08:00
parent e0108f7d57
commit 7790a181c4
9 changed files with 153 additions and 117 deletions
@@ -17,9 +17,9 @@ use std::{env, time::Duration};
use platform_tripo::TripoProviderClient;
use shared_contracts::model3d::{
common::{Model3dModelVersion, Model3dTaskStatus, Model3dTextureQuality},
image_to_model::Model3dImageToModelRequest,
image_to_model::Model3dImageToModelParams,
multiview_to_model::{Model3dMultiviewInputs, Model3dMultiviewToModelRequest},
text_to_model::Model3dTextToModelRequest,
text_to_model::Model3dTextToModelParams,
};
use tokio::{fs::File, io::AsyncWriteExt};
use tripo3d_sdk::{ClientOptions, TripoClient, WaitOptions};
@@ -46,7 +46,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
))?;
let text = client
.submit(&Model3dTextToModelRequest {
.submit(&Model3dTextToModelParams {
prompt: "a low-poly wooden treasure chest".to_string(),
model: Model3dModelVersion::H31,
negative_prompt: None,
@@ -72,29 +72,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("text_to_model task_id={}", text.task_id);
let image = client
.submit_image_to_model(&Model3dImageToModelRequest {
input: SAMPLE_IMAGE_URL.to_string(),
model: Model3dModelVersion::H31,
enable_image_autofix: None,
model_seed: None,
texture_seed: None,
texture: Some(true),
pbr: Some(true),
texture_quality: Some(Model3dTextureQuality::Standard),
texture_version: None,
delight: None,
texture_alignment: None,
geometry_quality: None,
face_limit: None,
auto_size: None,
orientation: None,
quad: None,
smart_low_poly: None,
generate_parts: None,
compress: None,
export_uv: None,
export_orientation: None,
})
.submit_image_to_model(
SAMPLE_IMAGE_URL,
&Model3dImageToModelParams {
model: Model3dModelVersion::H31,
enable_image_autofix: None,
model_seed: None,
texture_seed: None,
texture: Some(true),
pbr: Some(true),
texture_quality: Some(Model3dTextureQuality::Standard),
texture_version: None,
delight: None,
texture_alignment: None,
geometry_quality: None,
face_limit: None,
auto_size: None,
orientation: None,
quad: None,
smart_low_poly: None,
generate_parts: None,
compress: None,
export_uv: None,
export_orientation: None,
},
)
.await?;
println!("image_to_model task_id={}", image.task_id);
@@ -2,6 +2,8 @@ use shared_contracts::model3d::common::{
Model3dCompression, Model3dGeometryQuality, Model3dModelVersion, Model3dTextureQuality,
Model3dTextureVersion,
};
use shared_contracts::model3d::image_to_model::Model3dImageToModelParams;
use shared_contracts::model3d::text_to_model::Model3dTextToModelParams;
use super::{TripoError, TripoField, TripoValidationReason};
@@ -20,6 +22,44 @@ pub(crate) struct TripoGenerationOptions {
pub compress: Option<Model3dCompression>,
}
impl From<&Model3dTextToModelParams> for TripoGenerationOptions {
fn from(params: &Model3dTextToModelParams) -> Self {
Self {
model: params.model,
texture: params.texture,
pbr: params.pbr,
texture_quality: params.texture_quality,
texture_version: params.texture_version,
geometry_quality: params.geometry_quality,
face_limit: params.face_limit,
auto_size: params.auto_size,
quad: params.quad,
smart_low_poly: params.smart_low_poly,
generate_parts: params.generate_parts,
compress: params.compress,
}
}
}
impl From<&Model3dImageToModelParams> for TripoGenerationOptions {
fn from(params: &Model3dImageToModelParams) -> Self {
Self {
model: params.model,
texture: params.texture,
pbr: params.pbr,
texture_quality: params.texture_quality,
texture_version: params.texture_version,
geometry_quality: params.geometry_quality,
face_limit: params.face_limit,
auto_size: params.auto_size,
quad: params.quad,
smart_low_poly: params.smart_low_poly,
generate_parts: params.generate_parts,
compress: params.compress,
}
}
}
pub(crate) fn validate_generation_options(
options: &TripoGenerationOptions,
) -> Result<(), TripoError> {
@@ -2,87 +2,79 @@ use shared_contracts::model3d::common::{
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dInputOrientation,
Model3dTextureAlignment, Model3dTextureQuality, Model3dTextureVersion,
};
use shared_contracts::model3d::image_to_model::Model3dImageToModelRequest;
use shared_contracts::model3d::image_to_model::Model3dImageToModelParams;
use tripo3d_sdk::{models::FileInput, params::ImageToModelParams};
use crate::common::{
TripoError, TripoField, TripoGenerationOptions, TripoProviderClient, TripoTaskHandle,
TripoValidationReason, validate_generation_options,
TripoError, TripoField, TripoProviderClient, TripoTaskHandle, TripoValidationReason,
};
use super::validation::validate_image_to_model_params;
impl TripoProviderClient {
/// 图片输入由调用方解析后传入:provider 只负责把它交给 SDK
/// 站内资源 / 素材的归属校验属于平台层。
pub async fn submit_image_to_model(
&self,
request: &Model3dImageToModelRequest,
input: &str,
params: &Model3dImageToModelParams,
) -> Result<TripoTaskHandle, TripoError> {
if request.input.trim().is_empty() {
let input = input.trim();
if input.is_empty() {
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Input),
reason: TripoValidationReason::Required,
message: "input must not be blank".into(),
});
}
validate_generation_options(&TripoGenerationOptions {
model: request.model,
texture: request.texture,
pbr: request.pbr,
texture_quality: request.texture_quality,
texture_version: request.texture_version,
geometry_quality: request.geometry_quality,
face_limit: request.face_limit,
auto_size: request.auto_size,
quad: request.quad,
smart_low_poly: request.smart_low_poly,
generate_parts: request.generate_parts,
compress: request.compress,
})?;
validate_image_to_model_params(params)?;
let task_id = self
.client
.image_to_model(to_sdk_params(request))
.image_to_model(to_sdk_params(input, params))
.await
.map_err(TripoError::from)?;
Ok(TripoTaskHandle { task_id })
}
}
fn to_sdk_params(request: &Model3dImageToModelRequest) -> ImageToModelParams {
fn to_sdk_params(input: &str, params: &Model3dImageToModelParams) -> ImageToModelParams {
ImageToModelParams {
input: FileInput::from(request.input.trim()),
model: Some(request.model.as_str().to_owned()),
enable_image_autofix: request.enable_image_autofix,
model_seed: request.model_seed,
texture_seed: request.texture_seed,
texture: request.texture,
pbr: request.pbr,
texture_quality: request
input: FileInput::from(input),
model: Some(params.model.as_str().to_owned()),
enable_image_autofix: params.enable_image_autofix,
model_seed: params.model_seed,
texture_seed: params.texture_seed,
texture: params.texture,
pbr: params.pbr,
texture_quality: params
.texture_quality
.map(Model3dTextureQuality::as_str)
.map(str::to_owned),
extra: extra_fields(request.texture_version, request.delight),
texture_alignment: request
extra: extra_fields(params.texture_version, params.delight),
texture_alignment: params
.texture_alignment
.map(Model3dTextureAlignment::as_str)
.map(str::to_owned),
geometry_quality: request
geometry_quality: params
.geometry_quality
.map(Model3dGeometryQuality::as_str)
.map(str::to_owned),
face_limit: request.face_limit,
auto_size: request.auto_size,
orientation: request
face_limit: params.face_limit,
auto_size: params.auto_size,
orientation: params
.orientation
.map(Model3dInputOrientation::as_str)
.map(str::to_owned),
quad: request.quad,
smart_low_poly: request.smart_low_poly,
generate_parts: request.generate_parts,
compress: request
quad: params.quad,
smart_low_poly: params.smart_low_poly,
generate_parts: params.generate_parts,
compress: params
.compress
.map(Model3dCompression::as_str)
.map(str::to_owned),
export_uv: request.export_uv,
export_orientation: request
export_uv: params.export_uv,
export_orientation: params
.export_orientation
.map(Model3dExportOrientation::as_str)
.map(str::to_owned),
@@ -1,2 +1,5 @@
mod client;
pub mod result;
mod validation;
pub use validation::validate_image_to_model_params;
@@ -0,0 +1,11 @@
use shared_contracts::model3d::image_to_model::Model3dImageToModelParams;
use crate::common::{TripoError, validate_generation_options};
/// image-to-model 的请求预检:只覆盖生成参数与模型能力组合,
/// 图片本体是否可解析由调用方保证(provider 在 submit 时仍会拒绝空输入)。
pub fn validate_image_to_model_params(
params: &Model3dImageToModelParams,
) -> Result<(), TripoError> {
validate_generation_options(&params.into())
}
@@ -13,3 +13,5 @@ pub use common::{
TripoTaskFailure, TripoTaskHandle, TripoTaskOutput, TripoTaskSnapshot, TripoTaskType, TripoUrl,
TripoValidationReason,
};
pub use image_to_model::validate_image_to_model_params;
pub use text_to_model::validate_text_to_model_params;
@@ -3,55 +3,55 @@ use shared_contracts::model3d::common::{
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dTextureQuality,
Model3dTextureVersion,
};
use shared_contracts::model3d::text_to_model::Model3dTextToModelRequest;
use shared_contracts::model3d::text_to_model::Model3dTextToModelParams;
use super::validation::validate_text_to_model_request;
use super::validation::validate_text_to_model_params;
impl TripoProviderClient {
pub async fn submit(
&self,
request: &Model3dTextToModelRequest,
params: &Model3dTextToModelParams,
) -> Result<TripoTaskHandle, TripoError> {
validate_text_to_model_request(request)?;
validate_text_to_model_params(params)?;
let task_id = self
.client
.text_to_model(to_sdk_params(request))
.text_to_model(to_sdk_params(params))
.await
.map_err(TripoError::from)?;
Ok(TripoTaskHandle { task_id })
}
}
fn to_sdk_params(request: &Model3dTextToModelRequest) -> tripo3d_sdk::params::TextToModelParams {
fn to_sdk_params(params: &Model3dTextToModelParams) -> tripo3d_sdk::params::TextToModelParams {
tripo3d_sdk::params::TextToModelParams {
prompt: request.prompt.clone(),
model: Some(request.model.as_str().to_owned()),
negative_prompt: request.negative_prompt.clone(),
image_seed: request.image_seed,
model_seed: request.model_seed,
texture_seed: request.texture_seed,
texture: request.texture,
pbr: request.pbr,
texture_quality: request
prompt: params.prompt.clone(),
model: Some(params.model.as_str().to_owned()),
negative_prompt: params.negative_prompt.clone(),
image_seed: params.image_seed,
model_seed: params.model_seed,
texture_seed: params.texture_seed,
texture: params.texture,
pbr: params.pbr,
texture_quality: params
.texture_quality
.map(Model3dTextureQuality::as_str)
.map(str::to_owned),
extra: extra_fields(request.texture_version, request.delight),
geometry_quality: request
extra: extra_fields(params.texture_version, params.delight),
geometry_quality: params
.geometry_quality
.map(Model3dGeometryQuality::as_str)
.map(str::to_owned),
face_limit: request.face_limit,
auto_size: request.auto_size,
quad: request.quad,
smart_low_poly: request.smart_low_poly,
generate_parts: request.generate_parts,
compress: request
face_limit: params.face_limit,
auto_size: params.auto_size,
quad: params.quad,
smart_low_poly: params.smart_low_poly,
generate_parts: params.generate_parts,
compress: params
.compress
.map(Model3dCompression::as_str)
.map(str::to_owned),
export_uv: request.export_uv,
export_orientation: request
export_uv: params.export_uv,
export_orientation: params
.export_orientation
.map(Model3dExportOrientation::as_str)
.map(str::to_owned),
@@ -1,3 +1,5 @@
mod client;
pub mod result;
mod validation;
pub use validation::validate_text_to_model_params;
@@ -1,14 +1,11 @@
use shared_contracts::model3d::text_to_model::Model3dTextToModelRequest;
use shared_contracts::model3d::text_to_model::Model3dTextToModelParams;
use crate::common::{
TripoError, TripoField, TripoGenerationOptions, TripoValidationReason,
validate_generation_options,
};
use crate::common::{TripoError, TripoField, TripoValidationReason, validate_generation_options};
pub(crate) fn validate_text_to_model_request(
request: &Model3dTextToModelRequest,
) -> Result<(), TripoError> {
if request.prompt.trim().is_empty() {
/// text-to-model 的请求预检:调用方在产生 provider 副作用之前调用,
/// 用于把参数错误映射成调用方的入参错误,而不是等 submit 失败。
pub fn validate_text_to_model_params(params: &Model3dTextToModelParams) -> Result<(), TripoError> {
if params.prompt.trim().is_empty() {
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Prompt),
reason: TripoValidationReason::Required,
@@ -16,14 +13,14 @@ pub(crate) fn validate_text_to_model_request(
});
}
if request.prompt.chars().count() > 1024 {
if params.prompt.chars().count() > 1024 {
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Prompt),
reason: TripoValidationReason::InvalidCombination,
message: "prompt must not exceed 1024 characters".into(),
});
}
if request
if params
.negative_prompt
.as_deref()
.is_some_and(|value| value.chars().count() > 255)
@@ -35,20 +32,7 @@ pub(crate) fn validate_text_to_model_request(
});
}
validate_generation_options(&TripoGenerationOptions {
model: request.model,
texture: request.texture,
pbr: request.pbr,
texture_quality: request.texture_quality,
texture_version: request.texture_version,
geometry_quality: request.geometry_quality,
face_limit: request.face_limit,
auto_size: request.auto_size,
quad: request.quad,
smart_low_poly: request.smart_low_poly,
generate_parts: request.generate_parts,
compress: request.compress,
})?;
validate_generation_options(&params.into())?;
Ok(())
}