重构图片 provider 中立执行层
将 platform-image 图片协议从 vector_engine 目录迁移到 image_provider 引入 ImageProviderClient 与 VectorEngine/Tiantoken provider 类型 按 concrete model 白名单拒绝未知模型并移除跨模型回退
This commit is contained in:
+1
-1
@@ -86,7 +86,7 @@ pub(crate) fn request_budget_exhausted_error(
|
||||
reference_image_count,
|
||||
image_model,
|
||||
operation,
|
||||
"VectorEngine 图片请求执行预算已耗尽"
|
||||
"ImageProvider 图片请求执行预算已耗尽"
|
||||
);
|
||||
PlatformImageError::Request {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
+168
-263
File diff suppressed because it is too large
Load Diff
+7
-1
@@ -1,8 +1,14 @@
|
||||
pub const GPT_IMAGE_2_MODEL: &str = "gpt-image-2";
|
||||
pub const GPT_IMAGE_2_C_MODEL: &str = "gpt-image-2-c";
|
||||
/// Current business model exposed to callers for new image tasks.
|
||||
pub const GPT_IMAGE_2_5_BUSINESS_NAME: &str = "gpt-image-2.5";
|
||||
/// Provider/pricing key for new generation tasks.
|
||||
pub const GPT_IMAGE_2_5_GENERATION_MODEL: &str = "gpt-image-2.5-flare-c";
|
||||
/// Provider/pricing key for explicit image-edit tasks.
|
||||
pub const GPT_IMAGE_2_5_EDIT_MODEL: &str = "gpt-image-2.5-sunburst-c";
|
||||
pub const NANOBANANA_2_MODEL: &str = "gemini-3.1-flash-image-preview";
|
||||
pub const VECTOR_ENGINE_GPT_IMAGE_2_MODEL: &str = GPT_IMAGE_2_MODEL;
|
||||
pub const VECTOR_ENGINE_PROVIDER: &str = "vector-engine";
|
||||
pub const TIANTOKEN_PROVIDER: &str = "tiantoken";
|
||||
pub const VECTOR_ENGINE_IMAGE_EDIT_MAX_REFERENCE_IMAGES: usize = 5;
|
||||
pub const VECTOR_ENGINE_NANOBANANA_MAX_REFERENCE_IMAGES: usize = 14;
|
||||
pub const GPT_IMAGE_2_MIN_PIXELS: u64 = 655_360;
|
||||
+27
-27
@@ -12,19 +12,19 @@ use super::{
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct VectorEngineCurlResponse {
|
||||
pub(crate) struct ImageProviderCurlResponse {
|
||||
pub(crate) status: u16,
|
||||
pub(crate) body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum VectorEngineCurlError {
|
||||
pub(crate) enum ImageProviderCurlError {
|
||||
Curl(curl::Error),
|
||||
Form(FormError),
|
||||
WorkerJoin(tokio::task::JoinError),
|
||||
}
|
||||
|
||||
impl VectorEngineCurlError {
|
||||
impl ImageProviderCurlError {
|
||||
pub(crate) fn is_timeout(&self) -> bool {
|
||||
match self {
|
||||
Self::Curl(error) => error.is_operation_timedout(),
|
||||
@@ -63,7 +63,7 @@ impl VectorEngineCurlError {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VectorEngineCurlError {
|
||||
impl fmt::Display for ImageProviderCurlError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Curl(error) => write!(formatter, "{error}"),
|
||||
@@ -73,26 +73,26 @@ impl fmt::Display for VectorEngineCurlError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for VectorEngineCurlError {}
|
||||
impl Error for ImageProviderCurlError {}
|
||||
|
||||
impl From<curl::Error> for VectorEngineCurlError {
|
||||
impl From<curl::Error> for ImageProviderCurlError {
|
||||
fn from(error: curl::Error) -> Self {
|
||||
Self::Curl(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FormError> for VectorEngineCurlError {
|
||||
impl From<FormError> for ImageProviderCurlError {
|
||||
fn from(error: FormError) -> Self {
|
||||
Self::Form(error)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_vector_engine_json_request_with_curl(
|
||||
pub(crate) async fn send_image_json_request_with_curl(
|
||||
request_url: &str,
|
||||
api_key: &str,
|
||||
request_body: &Value,
|
||||
timeout_ms: u64,
|
||||
) -> Result<VectorEngineCurlResponse, VectorEngineCurlError> {
|
||||
) -> Result<ImageProviderCurlResponse, ImageProviderCurlError> {
|
||||
let request_url = request_url.to_string();
|
||||
let api_key = api_key.to_string();
|
||||
let request_body = request_body.to_string();
|
||||
@@ -105,11 +105,11 @@ pub(crate) async fn send_vector_engine_json_request_with_curl(
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(VectorEngineCurlError::WorkerJoin)?
|
||||
.map_err(ImageProviderCurlError::WorkerJoin)?
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn send_vector_engine_multipart_edit_request_with_curl(
|
||||
pub(crate) async fn send_image_multipart_edit_request_with_curl(
|
||||
request_url: &str,
|
||||
api_key: &str,
|
||||
model: &str,
|
||||
@@ -119,7 +119,7 @@ pub(crate) async fn send_vector_engine_multipart_edit_request_with_curl(
|
||||
candidate_count: u32,
|
||||
reference_images: &[ReferenceImage],
|
||||
timeout_ms: u64,
|
||||
) -> Result<VectorEngineCurlResponse, VectorEngineCurlError> {
|
||||
) -> Result<ImageProviderCurlResponse, ImageProviderCurlError> {
|
||||
let request_url = request_url.to_string();
|
||||
let api_key = api_key.to_string();
|
||||
let model = model.to_string();
|
||||
@@ -141,7 +141,7 @@ pub(crate) async fn send_vector_engine_multipart_edit_request_with_curl(
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(VectorEngineCurlError::WorkerJoin)?
|
||||
.map_err(ImageProviderCurlError::WorkerJoin)?
|
||||
}
|
||||
|
||||
pub(crate) fn map_curl_error(
|
||||
@@ -149,7 +149,7 @@ pub(crate) fn map_curl_error(
|
||||
request_url: &str,
|
||||
failure_stage: &'static str,
|
||||
image_model: Option<&'static str>,
|
||||
error: VectorEngineCurlError,
|
||||
error: ImageProviderCurlError,
|
||||
latency_ms: u64,
|
||||
prompt_chars: Option<usize>,
|
||||
reference_image_count: Option<usize>,
|
||||
@@ -195,7 +195,7 @@ pub(crate) fn map_curl_error(
|
||||
request_params = %request_params
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_default(),
|
||||
"VectorEngine 图片 libcurl 请求失败"
|
||||
"ImageProvider 图片 libcurl 请求失败"
|
||||
);
|
||||
|
||||
PlatformImageError::Request {
|
||||
@@ -217,8 +217,8 @@ fn send_json_request_with_curl_blocking(
|
||||
api_key: &str,
|
||||
request_body: &str,
|
||||
timeout_ms: u64,
|
||||
) -> Result<VectorEngineCurlResponse, VectorEngineCurlError> {
|
||||
let mut headers = vector_engine_curl_headers(api_key)?;
|
||||
) -> Result<ImageProviderCurlResponse, ImageProviderCurlError> {
|
||||
let mut headers = image_curl_headers(api_key)?;
|
||||
headers.append("Content-Type: application/json")?;
|
||||
let mut easy = Easy::new();
|
||||
easy.url(request_url)?;
|
||||
@@ -240,7 +240,7 @@ fn send_multipart_edit_request_with_curl_blocking(
|
||||
candidate_count: u32,
|
||||
reference_images: &[ReferenceImage],
|
||||
timeout_ms: u64,
|
||||
) -> Result<VectorEngineCurlResponse, VectorEngineCurlError> {
|
||||
) -> Result<ImageProviderCurlResponse, ImageProviderCurlError> {
|
||||
let mut form = Form::new();
|
||||
form.part("model").contents(model.as_bytes()).add()?;
|
||||
form.part("prompt")
|
||||
@@ -263,7 +263,7 @@ fn send_multipart_edit_request_with_curl_blocking(
|
||||
.add()?;
|
||||
}
|
||||
|
||||
let headers = vector_engine_curl_headers(api_key)?;
|
||||
let headers = image_curl_headers(api_key)?;
|
||||
let mut easy = Easy::new();
|
||||
easy.url(request_url)?;
|
||||
easy.httppost(form)?;
|
||||
@@ -272,14 +272,14 @@ fn send_multipart_edit_request_with_curl_blocking(
|
||||
Ok(perform_curl_request(easy)?)
|
||||
}
|
||||
|
||||
fn vector_engine_curl_headers(api_key: &str) -> Result<List, curl::Error> {
|
||||
fn image_curl_headers(api_key: &str) -> Result<List, curl::Error> {
|
||||
let mut headers = List::new();
|
||||
headers.append(format!("Authorization: Bearer {api_key}").as_str())?;
|
||||
headers.append("Accept: application/json")?;
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn perform_curl_request(mut easy: Easy) -> Result<VectorEngineCurlResponse, curl::Error> {
|
||||
fn perform_curl_request(mut easy: Easy) -> Result<ImageProviderCurlResponse, curl::Error> {
|
||||
let mut body = Vec::new();
|
||||
{
|
||||
let mut transfer = easy.transfer();
|
||||
@@ -291,13 +291,13 @@ fn perform_curl_request(mut easy: Easy) -> Result<VectorEngineCurlResponse, curl
|
||||
}
|
||||
let status = easy.response_code()? as u16;
|
||||
let body = String::from_utf8_lossy(body.as_slice()).into_owned();
|
||||
Ok(VectorEngineCurlResponse { status, body })
|
||||
Ok(ImageProviderCurlResponse { status, body })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::vector_engine::{constants::GPT_IMAGE_2_MODEL, types::ReferenceImage};
|
||||
use crate::image_provider::{constants::GPT_IMAGE_2_MODEL, types::ReferenceImage};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpListener,
|
||||
@@ -305,9 +305,9 @@ mod tests {
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn vector_engine_curl_transport_posts_json_request() {
|
||||
async fn image_curl_transport_posts_json_request() {
|
||||
let (base_url, server, request_rx) = start_single_response_server().await;
|
||||
let response = send_vector_engine_json_request_with_curl(
|
||||
let response = send_image_json_request_with_curl(
|
||||
format!("{base_url}/v1/images/generations").as_str(),
|
||||
"test-key",
|
||||
&serde_json::json!({"model":"gpt-image-2","prompt":"测试"}),
|
||||
@@ -327,9 +327,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn vector_engine_curl_transport_posts_multipart_request() {
|
||||
async fn image_curl_transport_posts_multipart_request() {
|
||||
let (base_url, server, request_rx) = start_single_response_server().await;
|
||||
let response = send_vector_engine_multipart_edit_request_with_curl(
|
||||
let response = send_image_multipart_edit_request_with_curl(
|
||||
format!("{base_url}/v1/images/edits").as_str(),
|
||||
"test-key",
|
||||
GPT_IMAGE_2_MODEL,
|
||||
@@ -0,0 +1,44 @@
|
||||
mod audit;
|
||||
mod budget;
|
||||
mod client;
|
||||
mod constants;
|
||||
mod curl_transport;
|
||||
mod error;
|
||||
mod image_source;
|
||||
mod payload;
|
||||
mod raw_edit;
|
||||
mod request;
|
||||
mod response;
|
||||
mod transport;
|
||||
mod types;
|
||||
mod util;
|
||||
|
||||
pub use audit::PlatformImageFailureAudit;
|
||||
pub use client::{
|
||||
create_image_edit, create_image_edit_with_references,
|
||||
create_image_edit_with_references_and_model, create_image_generation,
|
||||
create_image_generation_with_model, create_nanobanana_generate_content,
|
||||
};
|
||||
pub use constants::TIANTOKEN_PROVIDER;
|
||||
pub use constants::{
|
||||
GPT_IMAGE_2_5_BUSINESS_NAME, GPT_IMAGE_2_5_EDIT_MODEL, GPT_IMAGE_2_5_GENERATION_MODEL,
|
||||
GPT_IMAGE_2_C_MODEL, GPT_IMAGE_2_MODEL, NANOBANANA_2_MODEL, VECTOR_ENGINE_PROVIDER,
|
||||
};
|
||||
pub use error::{PlatformImageError, PlatformImageStatusHint};
|
||||
pub use image_source::download_remote_image;
|
||||
pub use raw_edit::{
|
||||
GPT_IMAGE_2_2K_LONG_EDGE_THRESHOLD, RAW_IMAGE_DIMENSION_ALIGNMENT, RAW_IMAGE_MAX_EDGE,
|
||||
RAW_IMAGE_MAX_PIXELS, RAW_IMAGE_MIN_PIXELS, RawImageEditDimensionError, RawImageEditImage,
|
||||
RawImageEditOptions, RawImageEditResult, create_raw_image_edit,
|
||||
validate_raw_image_edit_dimensions,
|
||||
};
|
||||
pub use request::{
|
||||
build_image_request_body, build_image_request_body_with_model,
|
||||
build_nanobanana_generate_content_request_body, images_edit_url, images_generation_url,
|
||||
nanobanana_generate_content_url, normalize_image_size_for_model, resolve_image_provider,
|
||||
};
|
||||
pub use transport::{build_image_http_client, build_image_provider_client};
|
||||
pub use types::{
|
||||
DownloadedImage, GeneratedImages, ImageProvider, ImageProviderClient, ImageProviderSettings,
|
||||
ReferenceImage,
|
||||
};
|
||||
+15
-15
@@ -8,12 +8,12 @@ use super::{
|
||||
audit::build_failure_audit,
|
||||
budget::{effective_request_timeout_ms, request_budget_exhausted_error},
|
||||
constants::{
|
||||
GPT_IMAGE_2_DIMENSION_ALIGNMENT, GPT_IMAGE_2_MAX_EDGE, GPT_IMAGE_2_MAX_PIXELS,
|
||||
GPT_IMAGE_2_MIN_PIXELS, GPT_IMAGE_2_MODEL, VECTOR_ENGINE_PROVIDER,
|
||||
GPT_IMAGE_2_5_EDIT_MODEL, GPT_IMAGE_2_DIMENSION_ALIGNMENT, GPT_IMAGE_2_MAX_EDGE,
|
||||
GPT_IMAGE_2_MAX_PIXELS, GPT_IMAGE_2_MIN_PIXELS, VECTOR_ENGINE_PROVIDER,
|
||||
},
|
||||
error::PlatformImageError,
|
||||
request::vector_engine_images_edit_url,
|
||||
types::VectorEngineImageSettings,
|
||||
request::images_edit_url,
|
||||
types::ImageProviderSettings,
|
||||
util::truncate_raw,
|
||||
};
|
||||
|
||||
@@ -117,9 +117,9 @@ pub fn validate_raw_image_edit_dimensions(
|
||||
}
|
||||
|
||||
/// Independent raw GPT Image 2 proxy; it does not call the editor image-edit client.
|
||||
pub async fn create_vector_engine_raw_image_edit(
|
||||
pub async fn create_raw_image_edit(
|
||||
http_client: &reqwest::Client,
|
||||
settings: &VectorEngineImageSettings,
|
||||
settings: &ImageProviderSettings,
|
||||
prompt: &str,
|
||||
image: RawImageEditImage,
|
||||
options: RawImageEditOptions,
|
||||
@@ -127,7 +127,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
) -> Result<RawImageEditResult, PlatformImageError> {
|
||||
validate_raw_image_edit_dimensions(options.width, options.height)
|
||||
.map_err(|error| invalid_input(failure_context, error.to_string()))?;
|
||||
let url = vector_engine_images_edit_url(settings);
|
||||
let url = images_edit_url(settings);
|
||||
let started_at = Instant::now();
|
||||
let prompt_chars = Some(prompt.chars().count());
|
||||
let reference_image_count = Some(1_usize + usize::from(options.mask.is_some()));
|
||||
@@ -137,7 +137,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
return Err(request_budget_exhausted_error(
|
||||
url.as_str(),
|
||||
failure_context,
|
||||
Some(GPT_IMAGE_2_MODEL),
|
||||
Some(GPT_IMAGE_2_5_EDIT_MODEL),
|
||||
Some(started_at.elapsed().as_millis() as u64),
|
||||
prompt_chars,
|
||||
reference_image_count,
|
||||
@@ -149,7 +149,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
mime_type: image_mime_type,
|
||||
} = image;
|
||||
let mut form = Form::new()
|
||||
.text("model", GPT_IMAGE_2_MODEL.to_string())
|
||||
.text("model", GPT_IMAGE_2_5_EDIT_MODEL.to_string())
|
||||
.text("n", "1".to_string())
|
||||
.text("prompt", prompt.to_string())
|
||||
.text("size", format!("{}x{}", options.width, options.height))
|
||||
@@ -209,7 +209,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
reference_image_count,
|
||||
elapsed_ms = started_at.elapsed().as_millis() as u64,
|
||||
failure_context,
|
||||
"VectorEngine Raw 图片编辑 HTTP 返回"
|
||||
"ImageProvider Raw 图片编辑 HTTP 返回"
|
||||
);
|
||||
let body = response.text().await.map_err(|error| {
|
||||
request_error(
|
||||
@@ -242,7 +242,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
Some(started_at.elapsed().as_millis() as u64),
|
||||
prompt_chars,
|
||||
reference_image_count,
|
||||
Some(GPT_IMAGE_2_MODEL),
|
||||
Some(GPT_IMAGE_2_5_EDIT_MODEL),
|
||||
);
|
||||
return Err(PlatformImageError::Upstream {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
@@ -271,7 +271,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
Some(started_at.elapsed().as_millis() as u64),
|
||||
prompt_chars,
|
||||
reference_image_count,
|
||||
Some(GPT_IMAGE_2_MODEL),
|
||||
Some(GPT_IMAGE_2_5_EDIT_MODEL),
|
||||
);
|
||||
return Err(PlatformImageError::ResponseParse {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
@@ -298,7 +298,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
Some(started_at.elapsed().as_millis() as u64),
|
||||
prompt_chars,
|
||||
reference_image_count,
|
||||
Some(GPT_IMAGE_2_MODEL),
|
||||
Some(GPT_IMAGE_2_5_EDIT_MODEL),
|
||||
);
|
||||
return Err(PlatformImageError::MissingImage {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
@@ -354,7 +354,7 @@ fn request_error(
|
||||
elapsed_ms = started_at.elapsed().as_millis() as u64,
|
||||
failure_context = context,
|
||||
error = %source,
|
||||
"VectorEngine Raw 图片编辑请求失败"
|
||||
"ImageProvider Raw 图片编辑请求失败"
|
||||
);
|
||||
let audit = build_failure_audit(
|
||||
url,
|
||||
@@ -370,7 +370,7 @@ fn request_error(
|
||||
Some(started_at.elapsed().as_millis() as u64),
|
||||
prompt_chars,
|
||||
reference_image_count,
|
||||
Some(GPT_IMAGE_2_MODEL),
|
||||
Some(GPT_IMAGE_2_5_EDIT_MODEL),
|
||||
);
|
||||
PlatformImageError::Request {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
+41
-24
@@ -2,21 +2,22 @@ use serde_json::{Map, Value, json};
|
||||
|
||||
use super::{
|
||||
constants::{
|
||||
GPT_IMAGE_2_5_BUSINESS_NAME, GPT_IMAGE_2_5_EDIT_MODEL, GPT_IMAGE_2_5_GENERATION_MODEL,
|
||||
GPT_IMAGE_2_C_MODEL, GPT_IMAGE_2_DIMENSION_ALIGNMENT, GPT_IMAGE_2_MAX_EDGE,
|
||||
GPT_IMAGE_2_MAX_PIXELS, GPT_IMAGE_2_MIN_PIXELS, GPT_IMAGE_2_MODEL,
|
||||
},
|
||||
types::{ReferenceImage, VectorEngineImageSettings},
|
||||
types::{ImageProvider, ImageProviderSettings, ReferenceImage},
|
||||
};
|
||||
|
||||
pub fn build_vector_engine_image_request_body(
|
||||
pub fn build_image_request_body(
|
||||
prompt: &str,
|
||||
negative_prompt: Option<&str>,
|
||||
size: &str,
|
||||
candidate_count: u32,
|
||||
_reference_images: &[String],
|
||||
) -> Value {
|
||||
build_vector_engine_image_request_body_with_model(
|
||||
GPT_IMAGE_2_MODEL,
|
||||
build_image_request_body_with_model(
|
||||
GPT_IMAGE_2_5_GENERATION_MODEL,
|
||||
prompt,
|
||||
negative_prompt,
|
||||
size,
|
||||
@@ -25,7 +26,7 @@ pub fn build_vector_engine_image_request_body(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_vector_engine_image_request_body_with_model(
|
||||
pub fn build_image_request_body_with_model(
|
||||
model: &str,
|
||||
prompt: &str,
|
||||
negative_prompt: Option<&str>,
|
||||
@@ -33,7 +34,7 @@ pub fn build_vector_engine_image_request_body_with_model(
|
||||
candidate_count: u32,
|
||||
_reference_images: &[String],
|
||||
) -> Value {
|
||||
let model = normalize_vector_engine_image_model(model);
|
||||
let model = normalize_image_model(model);
|
||||
let body = Map::from_iter([
|
||||
("model".to_string(), Value::String(model.to_string())),
|
||||
(
|
||||
@@ -50,7 +51,7 @@ pub fn build_vector_engine_image_request_body_with_model(
|
||||
Value::Object(body)
|
||||
}
|
||||
|
||||
pub fn build_vector_engine_nanobanana_generate_content_request_body(
|
||||
pub fn build_nanobanana_generate_content_request_body(
|
||||
prompt: &str,
|
||||
negative_prompt: Option<&str>,
|
||||
aspect_ratio: &str,
|
||||
@@ -86,17 +87,36 @@ pub fn build_vector_engine_nanobanana_generate_content_request_body(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn normalize_vector_engine_image_model(model: &str) -> &str {
|
||||
pub fn normalize_image_model(model: &str) -> &str {
|
||||
match model.trim() {
|
||||
"" => GPT_IMAGE_2_MODEL,
|
||||
value => value,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves a persisted/concrete model to its owning provider without rewriting
|
||||
/// the model string. Legacy GPT Image 2 values remain readable, but are routed
|
||||
/// to the current Tiantoken task boundary instead of VectorEngine.
|
||||
pub fn resolve_image_provider(model: &str) -> Result<ImageProvider, &'static str> {
|
||||
match normalize_image_model(model) {
|
||||
GPT_IMAGE_2_MODEL
|
||||
| GPT_IMAGE_2_C_MODEL
|
||||
| GPT_IMAGE_2_5_BUSINESS_NAME
|
||||
| GPT_IMAGE_2_5_GENERATION_MODEL
|
||||
| GPT_IMAGE_2_5_EDIT_MODEL => Ok(ImageProvider::Tiantoken),
|
||||
"gemini-3.1-flash-image-preview" => Ok(ImageProvider::VectorEngine),
|
||||
_ => Err("不支持的图片模型"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_gpt_image_2_family_model(model: &str) -> bool {
|
||||
matches!(
|
||||
normalize_vector_engine_image_model(model),
|
||||
GPT_IMAGE_2_MODEL | GPT_IMAGE_2_C_MODEL
|
||||
normalize_image_model(model),
|
||||
GPT_IMAGE_2_MODEL
|
||||
| GPT_IMAGE_2_C_MODEL
|
||||
| GPT_IMAGE_2_5_BUSINESS_NAME
|
||||
| GPT_IMAGE_2_5_GENERATION_MODEL
|
||||
| GPT_IMAGE_2_5_EDIT_MODEL
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,7 +154,7 @@ fn normalize_explicit_pixel_size(value: &str) -> String {
|
||||
fn clamp_gpt_image_2_pixel_size(size: &str) -> String {
|
||||
const MAX_ASPECT_RATIO: f64 = 3.0;
|
||||
|
||||
// 中文注释:这里是 VectorEngine 的共享发送边界,只处理 gpt-image-2 的显式像素尺寸。
|
||||
// 中文注释:这里是 ImageProvider 的共享发送边界,只处理 gpt-image-2 的显式像素尺寸。
|
||||
let Some((width, height)) = parse_explicit_pixel_size(size) else {
|
||||
return size.to_string();
|
||||
};
|
||||
@@ -232,7 +252,7 @@ fn normalize_nanobanana_aspect_ratio(aspect_ratio: &str) -> &str {
|
||||
|
||||
fn normalize_nanobanana_image_size(image_size: &str) -> &str {
|
||||
match image_size.trim() {
|
||||
// 中文注释:nanobanana / Gemini 3.1 的 0.5K 在 VectorEngine 文档中要求传 512。
|
||||
// 中文注释:nanobanana / Gemini 3.1 的 0.5K 在 ImageProvider 文档中要求传 512。
|
||||
"512" | "0.5K" => "512",
|
||||
"2K" => "2K",
|
||||
"4K" => "4K",
|
||||
@@ -240,7 +260,7 @@ fn normalize_nanobanana_image_size(image_size: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vector_engine_images_generation_url(settings: &VectorEngineImageSettings) -> String {
|
||||
pub fn images_generation_url(settings: &ImageProviderSettings) -> String {
|
||||
if settings.base_url.ends_with("/v1") {
|
||||
format!("{}/images/generations", settings.base_url)
|
||||
} else {
|
||||
@@ -248,7 +268,7 @@ pub fn vector_engine_images_generation_url(settings: &VectorEngineImageSettings)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vector_engine_images_edit_url(settings: &VectorEngineImageSettings) -> String {
|
||||
pub fn images_edit_url(settings: &ImageProviderSettings) -> String {
|
||||
if settings.base_url.ends_with("/v1") {
|
||||
format!("{}/images/edits", settings.base_url)
|
||||
} else {
|
||||
@@ -256,10 +276,7 @@ pub fn vector_engine_images_edit_url(settings: &VectorEngineImageSettings) -> St
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vector_engine_nanobanana_generate_content_url(
|
||||
settings: &VectorEngineImageSettings,
|
||||
model: &str,
|
||||
) -> String {
|
||||
pub fn nanobanana_generate_content_url(settings: &ImageProviderSettings, model: &str) -> String {
|
||||
let base_url = settings
|
||||
.base_url
|
||||
.trim_end_matches("/v1")
|
||||
@@ -267,11 +284,11 @@ pub fn vector_engine_nanobanana_generate_content_url(
|
||||
format!(
|
||||
"{}/v1beta/models/{}:generateContent",
|
||||
base_url,
|
||||
normalize_vector_engine_image_model(model)
|
||||
normalize_image_model(model)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn build_vector_engine_image_edit_request_log_params(
|
||||
pub(crate) fn build_image_edit_request_log_params(
|
||||
model: &str,
|
||||
prompt: &str,
|
||||
negative_prompt: Option<&str>,
|
||||
@@ -279,7 +296,7 @@ pub(crate) fn build_vector_engine_image_edit_request_log_params(
|
||||
candidate_count: u32,
|
||||
reference_images: &[ReferenceImage],
|
||||
) -> Value {
|
||||
let model = normalize_vector_engine_image_model(model);
|
||||
let model = normalize_image_model(model);
|
||||
let prompt = prompt.trim();
|
||||
let negative_prompt = negative_prompt
|
||||
.map(str::trim)
|
||||
@@ -330,11 +347,11 @@ pub(crate) fn build_prompt_with_negative(prompt: &str, negative_prompt: Option<&
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::vector_engine::types::ReferenceImage;
|
||||
use crate::image_provider::types::ReferenceImage;
|
||||
|
||||
#[test]
|
||||
fn edit_request_log_params_include_reference_image_sizes_without_secrets_or_bytes() {
|
||||
let params = build_vector_engine_image_edit_request_log_params(
|
||||
let params = build_image_edit_request_log_params(
|
||||
GPT_IMAGE_2_MODEL,
|
||||
" 拼图参考图重绘 ",
|
||||
Some(" 文字,水印 "),
|
||||
@@ -389,7 +406,7 @@ mod tests {
|
||||
"1152x2048",
|
||||
"2048x1152",
|
||||
] {
|
||||
let body = build_vector_engine_image_request_body_with_model(
|
||||
let body = build_image_request_body_with_model(
|
||||
GPT_IMAGE_2_MODEL,
|
||||
"测试",
|
||||
None,
|
||||
+9
-9
@@ -12,7 +12,7 @@ use super::{
|
||||
};
|
||||
use std::time::Instant;
|
||||
|
||||
pub(crate) async fn handle_vector_engine_response(
|
||||
pub(crate) async fn handle_image_response(
|
||||
http_client: &reqwest::Client,
|
||||
request_url: &str,
|
||||
response_status: u16,
|
||||
@@ -53,7 +53,7 @@ pub(crate) async fn handle_vector_engine_response(
|
||||
retryable = audit.retryable,
|
||||
message = %message,
|
||||
raw_excerpt = %raw_excerpt,
|
||||
"VectorEngine 图片生成上游错误"
|
||||
"ImageProvider 图片生成上游错误"
|
||||
);
|
||||
return Err(PlatformImageError::Upstream {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
@@ -89,7 +89,7 @@ pub(crate) async fn handle_vector_engine_response(
|
||||
status = response_status,
|
||||
raw_excerpt = %truncate_raw(response_text),
|
||||
message = %error.message(),
|
||||
"VectorEngine 图片响应解析失败"
|
||||
"ImageProvider 图片响应解析失败"
|
||||
);
|
||||
return Err(error.with_audit(audit));
|
||||
}
|
||||
@@ -151,7 +151,7 @@ pub(crate) async fn handle_vector_engine_response(
|
||||
image_count = generated.images.len(),
|
||||
elapsed_ms = download_started_at.elapsed().as_millis() as u64,
|
||||
failure_context,
|
||||
"VectorEngine 图片下载完成"
|
||||
"ImageProvider 图片下载完成"
|
||||
);
|
||||
return Ok(generated);
|
||||
}
|
||||
@@ -159,7 +159,7 @@ pub(crate) async fn handle_vector_engine_response(
|
||||
if !b64_images.is_empty() {
|
||||
let mut generated = images_from_base64(task_id, b64_images, candidate_count);
|
||||
if generated.images.is_empty() {
|
||||
let message = format!("{failure_context}:VectorEngine 返回的 base64 图片无法解码");
|
||||
let message = format!("{failure_context}:ImageProvider 返回的 base64 图片无法解码");
|
||||
let raw_excerpt = truncate_raw(response_text);
|
||||
let audit = build_failure_audit(
|
||||
request_url,
|
||||
@@ -183,7 +183,7 @@ pub(crate) async fn handle_vector_engine_response(
|
||||
status = response_status,
|
||||
image_model,
|
||||
raw_excerpt = %raw_excerpt,
|
||||
"VectorEngine 图片 base64 解码失败"
|
||||
"ImageProvider 图片 base64 解码失败"
|
||||
);
|
||||
return Err(PlatformImageError::ResponseParse {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
@@ -198,12 +198,12 @@ pub(crate) async fn handle_vector_engine_response(
|
||||
endpoint = %request_url,
|
||||
image_count = generated.images.len(),
|
||||
failure_context,
|
||||
"VectorEngine 图片 base64 解码完成"
|
||||
"ImageProvider 图片 base64 解码完成"
|
||||
);
|
||||
return Ok(generated);
|
||||
}
|
||||
|
||||
let message = format!("{failure_context}:VectorEngine 未返回图片地址");
|
||||
let message = format!("{failure_context}:ImageProvider 未返回图片地址");
|
||||
let audit = build_failure_audit(
|
||||
request_url,
|
||||
failure_context,
|
||||
@@ -225,7 +225,7 @@ pub(crate) async fn handle_vector_engine_response(
|
||||
endpoint = %request_url,
|
||||
status = response_status,
|
||||
raw_excerpt = %truncate_raw(response_text),
|
||||
"VectorEngine 图片响应未返回图片"
|
||||
"ImageProvider 图片响应未返回图片"
|
||||
);
|
||||
Err(PlatformImageError::MissingImage {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
+12
-10
@@ -6,7 +6,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn request_body_normalizes_size_prompt_and_candidate_count() {
|
||||
let body = build_vector_engine_image_request_body(
|
||||
let body = build_image_request_body(
|
||||
" 风雨夜里的街道 ",
|
||||
Some(" 低清,水印 "),
|
||||
" 1:1 ",
|
||||
@@ -14,7 +14,7 @@ mod tests {
|
||||
&["data:image/png;base64,AAAA".to_string()],
|
||||
);
|
||||
|
||||
assert_eq!(body["model"], GPT_IMAGE_2_MODEL);
|
||||
assert_eq!(body["model"], GPT_IMAGE_2_5_GENERATION_MODEL);
|
||||
assert_eq!(body["size"], "1024x1024");
|
||||
assert_eq!(body["n"], 4);
|
||||
assert_eq!(body["prompt"], "风雨夜里的街道\n避免:低清,水印");
|
||||
@@ -23,13 +23,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn provider_urls_normalize_root_and_v1_base_urls() {
|
||||
let root_settings = VectorEngineImageSettings {
|
||||
let root_settings = ImageProviderSettings {
|
||||
provider: ImageProvider::VectorEngine,
|
||||
base_url: "https://vector.example".to_string(),
|
||||
api_key: "test-key".to_string(),
|
||||
request_timeout_ms: 1_000,
|
||||
request_deadline: None,
|
||||
};
|
||||
let v1_settings = VectorEngineImageSettings {
|
||||
let v1_settings = ImageProviderSettings {
|
||||
provider: ImageProvider::VectorEngine,
|
||||
base_url: "https://vector.example/v1".to_string(),
|
||||
api_key: "test-key".to_string(),
|
||||
request_timeout_ms: 1_000,
|
||||
@@ -37,19 +39,19 @@ mod tests {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
vector_engine_images_generation_url(&root_settings),
|
||||
images_generation_url(&root_settings),
|
||||
"https://vector.example/v1/images/generations"
|
||||
);
|
||||
assert_eq!(
|
||||
vector_engine_images_generation_url(&v1_settings),
|
||||
images_generation_url(&v1_settings),
|
||||
"https://vector.example/v1/images/generations"
|
||||
);
|
||||
assert_eq!(
|
||||
vector_engine_images_edit_url(&root_settings),
|
||||
images_edit_url(&root_settings),
|
||||
"https://vector.example/v1/images/edits"
|
||||
);
|
||||
assert_eq!(
|
||||
vector_engine_images_edit_url(&v1_settings),
|
||||
images_edit_url(&v1_settings),
|
||||
"https://vector.example/v1/images/edits"
|
||||
);
|
||||
}
|
||||
@@ -94,7 +96,7 @@ mod tests {
|
||||
latency_ms: Some(987),
|
||||
prompt_chars: Some(64),
|
||||
reference_image_count: Some(2),
|
||||
image_model: Some(VECTOR_ENGINE_GPT_IMAGE_2_MODEL),
|
||||
image_model: Some(GPT_IMAGE_2_MODEL),
|
||||
};
|
||||
|
||||
let request_error = PlatformImageError::Request {
|
||||
@@ -162,7 +164,7 @@ mod tests {
|
||||
assert!(audit_ref.timeout);
|
||||
assert!(audit_ref.retryable);
|
||||
assert_eq!(audit_ref.reference_image_count, Some(2));
|
||||
assert_eq!(audit_ref.image_model, Some(VECTOR_ENGINE_GPT_IMAGE_2_MODEL));
|
||||
assert_eq!(audit_ref.image_model, Some(GPT_IMAGE_2_MODEL));
|
||||
assert!(invalid_config.audit().is_none());
|
||||
assert!(invalid_request.audit().is_none());
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{
|
||||
error::PlatformImageError,
|
||||
types::{ImageProviderClient, ImageProviderSettings},
|
||||
};
|
||||
|
||||
pub fn build_image_http_client(
|
||||
settings: &ImageProviderSettings,
|
||||
) -> Result<reqwest::Client, PlatformImageError> {
|
||||
reqwest::Client::builder()
|
||||
.timeout(Duration::from_millis(settings.request_timeout_ms.max(1)))
|
||||
.http1_only()
|
||||
.pool_max_idle_per_host(0)
|
||||
.build()
|
||||
.map_err(|error| PlatformImageError::InvalidConfig {
|
||||
provider: settings.provider.as_str(),
|
||||
message: format!(
|
||||
"构造 {} 图片生成 HTTP 客户端失败:{error}",
|
||||
settings.provider.as_str()
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_image_provider_client(
|
||||
settings: ImageProviderSettings,
|
||||
) -> Result<ImageProviderClient, PlatformImageError> {
|
||||
let http_client = build_image_http_client(&settings)?;
|
||||
Ok(ImageProviderClient::new(settings, http_client))
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use super::audit::PlatformImageFailureAudit;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ImageProvider {
|
||||
VectorEngine,
|
||||
Tiantoken,
|
||||
}
|
||||
|
||||
impl ImageProvider {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::VectorEngine => super::constants::VECTOR_ENGINE_PROVIDER,
|
||||
Self::Tiantoken => super::constants::TIANTOKEN_PROVIDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageProviderSettings {
|
||||
pub provider: ImageProvider,
|
||||
pub base_url: String,
|
||||
pub api_key: String,
|
||||
pub request_timeout_ms: u64,
|
||||
pub request_deadline: Option<std::time::Instant>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImageProviderClient {
|
||||
settings: ImageProviderSettings,
|
||||
http_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ImageProviderClient {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter
|
||||
.debug_struct("ImageProviderClient")
|
||||
.field("provider", &self.settings.provider)
|
||||
.field("base_url", &self.settings.base_url)
|
||||
.field("api_key", &"<redacted>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageProviderClient {
|
||||
pub(crate) fn new(settings: ImageProviderSettings, http_client: reqwest::Client) -> Self {
|
||||
Self {
|
||||
settings,
|
||||
http_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn provider(&self) -> ImageProvider {
|
||||
self.settings.provider
|
||||
}
|
||||
|
||||
pub fn settings(&self) -> &ImageProviderSettings {
|
||||
&self.settings
|
||||
}
|
||||
|
||||
pub fn http_client(&self) -> &reqwest::Client {
|
||||
&self.http_client
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GeneratedImages {
|
||||
pub task_id: String,
|
||||
pub actual_prompt: Option<String>,
|
||||
pub images: Vec<DownloadedImage>,
|
||||
pub recovered_failure_audits: Vec<PlatformImageFailureAudit>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DownloadedImage {
|
||||
pub bytes: Vec<u8>,
|
||||
pub mime_type: String,
|
||||
pub extension: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ReferenceImage {
|
||||
pub bytes: Vec<u8>,
|
||||
pub mime_type: String,
|
||||
pub file_name: String,
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
pub mod generated_asset_sheets;
|
||||
pub mod generated_assets;
|
||||
pub mod image_provider;
|
||||
pub mod pixel_art_snapper;
|
||||
pub mod vector_engine;
|
||||
|
||||
pub use image_provider::{
|
||||
DownloadedImage, GPT_IMAGE_2_2K_LONG_EDGE_THRESHOLD, GPT_IMAGE_2_5_BUSINESS_NAME,
|
||||
GPT_IMAGE_2_5_EDIT_MODEL, GPT_IMAGE_2_5_GENERATION_MODEL, GPT_IMAGE_2_C_MODEL,
|
||||
GPT_IMAGE_2_MODEL, GeneratedImages, ImageProvider, ImageProviderClient, ImageProviderSettings,
|
||||
NANOBANANA_2_MODEL, PlatformImageError, PlatformImageFailureAudit, PlatformImageStatusHint,
|
||||
RAW_IMAGE_DIMENSION_ALIGNMENT, RAW_IMAGE_MAX_EDGE, RAW_IMAGE_MAX_PIXELS, RAW_IMAGE_MIN_PIXELS,
|
||||
RawImageEditDimensionError, RawImageEditImage, RawImageEditOptions, RawImageEditResult,
|
||||
ReferenceImage, TIANTOKEN_PROVIDER, VECTOR_ENGINE_PROVIDER, build_image_http_client,
|
||||
build_image_provider_client, build_image_request_body,
|
||||
build_nanobanana_generate_content_request_body, create_image_edit,
|
||||
create_image_edit_with_references, create_image_edit_with_references_and_model,
|
||||
create_image_generation, create_image_generation_with_model,
|
||||
create_nanobanana_generate_content, create_raw_image_edit, download_remote_image,
|
||||
images_edit_url, images_generation_url, nanobanana_generate_content_url,
|
||||
resolve_image_provider, validate_raw_image_edit_dimensions,
|
||||
};
|
||||
pub use pixel_art_snapper::{
|
||||
PIXEL_ART_ALPHA_COVERAGE_THRESHOLD, PIXEL_ART_ANALYSIS_COLORS, PIXEL_ART_KMEANS_SAMPLE_LIMIT,
|
||||
PIXEL_ART_MAX_IMAGE_PIXELS, PixelArtSnapError, snap_pixel_art_strict_with_deadline,
|
||||
snap_pixel_art_with_deadline,
|
||||
};
|
||||
pub use vector_engine::{
|
||||
DownloadedImage, GPT_IMAGE_2_2K_LONG_EDGE_THRESHOLD, GPT_IMAGE_2_C_MODEL, GPT_IMAGE_2_MODEL,
|
||||
GeneratedImages, NANOBANANA_2_MODEL, PlatformImageError, PlatformImageFailureAudit,
|
||||
PlatformImageStatusHint, RAW_IMAGE_DIMENSION_ALIGNMENT, RAW_IMAGE_MAX_EDGE,
|
||||
RAW_IMAGE_MAX_PIXELS, RAW_IMAGE_MIN_PIXELS, RawImageEditDimensionError, RawImageEditImage,
|
||||
RawImageEditOptions, RawImageEditResult, ReferenceImage, VECTOR_ENGINE_GPT_IMAGE_2_MODEL,
|
||||
VECTOR_ENGINE_PROVIDER, VectorEngineImageSettings, build_vector_engine_image_http_client,
|
||||
build_vector_engine_image_request_body,
|
||||
build_vector_engine_nanobanana_generate_content_request_body, create_vector_engine_image_edit,
|
||||
create_vector_engine_image_edit_with_references,
|
||||
create_vector_engine_image_edit_with_references_and_model,
|
||||
create_vector_engine_image_generation, create_vector_engine_image_generation_with_model,
|
||||
create_vector_engine_nanobanana_generate_content, create_vector_engine_raw_image_edit,
|
||||
download_remote_image, validate_raw_image_edit_dimensions, vector_engine_images_edit_url,
|
||||
vector_engine_images_generation_url, vector_engine_nanobanana_generate_content_url,
|
||||
};
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
mod audit;
|
||||
mod budget;
|
||||
mod client;
|
||||
mod constants;
|
||||
mod curl_transport;
|
||||
mod error;
|
||||
mod image_source;
|
||||
mod payload;
|
||||
mod raw_edit;
|
||||
mod request;
|
||||
mod response;
|
||||
mod transport;
|
||||
mod types;
|
||||
mod util;
|
||||
|
||||
pub use audit::PlatformImageFailureAudit;
|
||||
pub use client::{
|
||||
create_vector_engine_image_edit, create_vector_engine_image_edit_with_references,
|
||||
create_vector_engine_image_edit_with_references_and_model,
|
||||
create_vector_engine_image_generation, create_vector_engine_image_generation_with_model,
|
||||
create_vector_engine_nanobanana_generate_content,
|
||||
};
|
||||
pub use constants::{
|
||||
GPT_IMAGE_2_C_MODEL, GPT_IMAGE_2_MODEL, NANOBANANA_2_MODEL, VECTOR_ENGINE_GPT_IMAGE_2_MODEL,
|
||||
VECTOR_ENGINE_PROVIDER,
|
||||
};
|
||||
pub use error::{PlatformImageError, PlatformImageStatusHint};
|
||||
pub use image_source::download_remote_image;
|
||||
pub use raw_edit::{
|
||||
GPT_IMAGE_2_2K_LONG_EDGE_THRESHOLD, RAW_IMAGE_DIMENSION_ALIGNMENT, RAW_IMAGE_MAX_EDGE,
|
||||
RAW_IMAGE_MAX_PIXELS, RAW_IMAGE_MIN_PIXELS, RawImageEditDimensionError, RawImageEditImage,
|
||||
RawImageEditOptions, RawImageEditResult, create_vector_engine_raw_image_edit,
|
||||
validate_raw_image_edit_dimensions,
|
||||
};
|
||||
pub use request::{
|
||||
build_vector_engine_image_request_body, build_vector_engine_image_request_body_with_model,
|
||||
build_vector_engine_nanobanana_generate_content_request_body, normalize_image_size_for_model,
|
||||
vector_engine_images_edit_url, vector_engine_images_generation_url,
|
||||
vector_engine_nanobanana_generate_content_url,
|
||||
};
|
||||
pub use transport::build_vector_engine_image_http_client;
|
||||
pub use types::{DownloadedImage, GeneratedImages, ReferenceImage, VectorEngineImageSettings};
|
||||
@@ -1,19 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{
|
||||
constants::VECTOR_ENGINE_PROVIDER, error::PlatformImageError, types::VectorEngineImageSettings,
|
||||
};
|
||||
|
||||
pub fn build_vector_engine_image_http_client(
|
||||
settings: &VectorEngineImageSettings,
|
||||
) -> Result<reqwest::Client, PlatformImageError> {
|
||||
reqwest::Client::builder()
|
||||
.timeout(Duration::from_millis(settings.request_timeout_ms.max(1)))
|
||||
.http1_only()
|
||||
.pool_max_idle_per_host(0)
|
||||
.build()
|
||||
.map_err(|error| PlatformImageError::InvalidConfig {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
message: format!("构造 VectorEngine 图片生成 HTTP 客户端失败:{error}"),
|
||||
})
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use super::audit::PlatformImageFailureAudit;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VectorEngineImageSettings {
|
||||
pub base_url: String,
|
||||
pub api_key: String,
|
||||
pub request_timeout_ms: u64,
|
||||
pub request_deadline: Option<std::time::Instant>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GeneratedImages {
|
||||
pub task_id: String,
|
||||
pub actual_prompt: Option<String>,
|
||||
pub images: Vec<DownloadedImage>,
|
||||
pub recovered_failure_audits: Vec<PlatformImageFailureAudit>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DownloadedImage {
|
||||
pub bytes: Vec<u8>,
|
||||
pub mime_type: String,
|
||||
pub extension: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ReferenceImage {
|
||||
pub bytes: Vec<u8>,
|
||||
pub mime_type: String,
|
||||
pub file_name: String,
|
||||
}
|
||||
+107
-143
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user