优化原始图片请求缓冲
将 ReferenceImage 与 multipart 输入改用 bytes::Bytes。 通过流式 multipart part 避免 reqwest 路径的额外整图复制。
This commit is contained in:
@@ -3,6 +3,7 @@ use axum::{
|
||||
extract::{Extension, Multipart, State},
|
||||
http::StatusCode,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use image::{GenericImageView, ImageFormat, ImageReader};
|
||||
use platform_image::{
|
||||
RAW_IMAGE_MAX_EDGE, RAW_IMAGE_MAX_PIXELS, RawImageEditOptions, ReferenceImage,
|
||||
@@ -30,7 +31,7 @@ use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RawImageData {
|
||||
pub(crate) bytes: Vec<u8>,
|
||||
pub(crate) bytes: Bytes,
|
||||
pub(crate) mime_type: String,
|
||||
pub(crate) file_name: String,
|
||||
}
|
||||
@@ -229,7 +230,7 @@ async fn read_multipart_image(
|
||||
return Err(bad_request(format!("{name} 文件不能为空")));
|
||||
}
|
||||
Ok(RawImageData {
|
||||
bytes: bytes.to_vec(),
|
||||
bytes,
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: format!("{name}.png"),
|
||||
})
|
||||
@@ -425,12 +426,12 @@ mod tests {
|
||||
fn request(image: Vec<u8>, mask: Option<Vec<u8>>) -> RawImageEditRequest {
|
||||
RawImageEditRequest {
|
||||
image: RawImageData {
|
||||
bytes: image,
|
||||
bytes: Bytes::from(image),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: "image.png".to_string(),
|
||||
},
|
||||
mask: mask.map(|bytes| RawImageData {
|
||||
bytes,
|
||||
bytes: Bytes::from(bytes),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: "mask.png".to_string(),
|
||||
}),
|
||||
@@ -520,7 +521,7 @@ mod tests {
|
||||
|
||||
let invalid_bytes = RawImageEditRequest {
|
||||
image: RawImageData {
|
||||
bytes: b"hello".to_vec(),
|
||||
bytes: Bytes::from_static(b"hello"),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: "image.png".to_string(),
|
||||
},
|
||||
@@ -530,7 +531,7 @@ mod tests {
|
||||
|
||||
let invalid_mime = RawImageEditRequest {
|
||||
image: RawImageData {
|
||||
bytes: png_bytes(1, 1),
|
||||
bytes: Bytes::from(png_bytes(1, 1)),
|
||||
mime_type: "image/jpeg".to_string(),
|
||||
file_name: "image.jpg".to_string(),
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
curl = { workspace = true }
|
||||
image = { workspace = true, features = ["jpeg", "png", "webp"] }
|
||||
reqwest = { workspace = true, features = ["json", "multipart", "rustls-tls"] }
|
||||
|
||||
@@ -1107,7 +1107,7 @@ mod tests {
|
||||
|
||||
fn reference_image(index: usize) -> ReferenceImage {
|
||||
ReferenceImage {
|
||||
bytes: vec![index as u8],
|
||||
bytes: bytes::Bytes::from(vec![index as u8]),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: format!("reference-{index}.png"),
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ fn send_multipart_edit_request_with_curl_blocking(
|
||||
form.part("image")
|
||||
.buffer(
|
||||
reference_image.file_name.as_str(),
|
||||
reference_image.bytes.clone(),
|
||||
reference_image.bytes.to_vec(),
|
||||
)
|
||||
.content_type(reference_image.mime_type.as_str())
|
||||
.add()?;
|
||||
@@ -338,7 +338,7 @@ mod tests {
|
||||
"1024x1024",
|
||||
1,
|
||||
&[ReferenceImage {
|
||||
bytes: b"reference".to_vec(),
|
||||
bytes: bytes::Bytes::from_static(b"reference"),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: "reference.png".to_string(),
|
||||
}],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
|
||||
use bytes::Bytes;
|
||||
use reqwest::header;
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -161,7 +162,7 @@ pub(crate) async fn resolve_reference_images(
|
||||
.await
|
||||
.map_err(|error| contextualize_reference_download_error(error, failure_context))?;
|
||||
resolved.push(ReferenceImage {
|
||||
bytes: downloaded.bytes,
|
||||
bytes: Bytes::from(downloaded.bytes),
|
||||
mime_type: downloaded.mime_type.clone(),
|
||||
file_name: format!(
|
||||
"reference-{index}.{}",
|
||||
@@ -213,7 +214,7 @@ pub(crate) fn parse_reference_image_data_url(
|
||||
})?;
|
||||
let mime_type = normalize_downloaded_image_mime_type(mime_type);
|
||||
Ok(Some(ReferenceImage {
|
||||
bytes,
|
||||
bytes: Bytes::from(bytes),
|
||||
file_name: format!(
|
||||
"reference-{index}.{}",
|
||||
mime_to_extension(mime_type.as_str())
|
||||
|
||||
@@ -138,7 +138,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
.text("size", format!("{}x{}", options.width, options.height))
|
||||
.part(
|
||||
"image",
|
||||
Part::bytes(image_bytes)
|
||||
Part::stream_with_length(image_bytes.clone(), image_bytes.len() as u64)
|
||||
.file_name(image_file_name)
|
||||
.mime_str(image_mime_type.as_str())
|
||||
.map_err(|error| invalid_request(failure_context, error.to_string()))?,
|
||||
@@ -155,7 +155,7 @@ pub async fn create_vector_engine_raw_image_edit(
|
||||
if let Some(mask) = options.mask {
|
||||
form = form.part(
|
||||
"mask",
|
||||
Part::bytes(mask.bytes)
|
||||
Part::stream_with_length(mask.bytes.clone(), mask.bytes.len() as u64)
|
||||
.file_name(mask.file_name)
|
||||
.mime_str(mask.mime_type.as_str())
|
||||
.map_err(|error| invalid_request(failure_context, error.to_string()))?,
|
||||
|
||||
@@ -65,7 +65,7 @@ pub fn build_vector_engine_nanobanana_generate_content_request_body(
|
||||
"mime_type": reference_image.mime_type,
|
||||
"data": base64::Engine::encode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
reference_image.bytes.as_slice()
|
||||
reference_image.bytes.as_ref()
|
||||
),
|
||||
},
|
||||
}));
|
||||
@@ -346,12 +346,12 @@ mod tests {
|
||||
9,
|
||||
&[
|
||||
ReferenceImage {
|
||||
bytes: vec![1, 2, 3, 4, 5],
|
||||
bytes: bytes::Bytes::from_static(&[1, 2, 3, 4, 5]),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: "reference-a.png".to_string(),
|
||||
},
|
||||
ReferenceImage {
|
||||
bytes: vec![8; 7],
|
||||
bytes: bytes::Bytes::from(vec![8; 7]),
|
||||
mime_type: "image/jpeg".to_string(),
|
||||
file_name: "reference-b.jpg".to_string(),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::audit::PlatformImageFailureAudit;
|
||||
use bytes::Bytes;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VectorEngineImageSettings {
|
||||
@@ -31,7 +32,7 @@ pub struct DownloadedImage {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ReferenceImage {
|
||||
pub bytes: Vec<u8>,
|
||||
pub bytes: Bytes,
|
||||
pub mime_type: String,
|
||||
pub file_name: String,
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ async fn vector_engine_image_edit_retries_send_timeout_once_and_succeeds() {
|
||||
let http_client =
|
||||
build_vector_engine_image_http_client(&settings).expect("client should build");
|
||||
let reference_image = ReferenceImage {
|
||||
bytes: b"reference".to_vec(),
|
||||
bytes: bytes::Bytes::from_static(b"reference"),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: "reference.png".to_string(),
|
||||
};
|
||||
@@ -598,7 +598,7 @@ async fn vector_engine_image_edit_falls_back_when_preferred_model_is_unsupported
|
||||
let http_client =
|
||||
build_vector_engine_image_http_client(&settings).expect("client should build");
|
||||
let reference = ReferenceImage {
|
||||
bytes: b"reference".to_vec(),
|
||||
bytes: bytes::Bytes::from_static(b"reference"),
|
||||
mime_type: "image/png".to_string(),
|
||||
file_name: "reference.png".to_string(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user