diff --git a/docs/technical/【技术方案】Raw GPT Image 2图片编辑代理-2026-09-07.md b/docs/technical/【技术方案】Raw GPT Image 2图片编辑代理-2026-09-07.md index 81f44f5e7..ef702e821 100644 --- a/docs/technical/【技术方案】Raw GPT Image 2图片编辑代理-2026-09-07.md +++ b/docs/technical/【技术方案】Raw GPT Image 2图片编辑代理-2026-09-07.md @@ -90,7 +90,7 @@ provider 响应只提取并透传 `data[].b64_json` 字符串,不在服务端 ## 代码拆分 - `server-rs/crates/api-server/src/raw_image.rs`:独立路由 handler、multipart 字段解析、请求/响应 DTO、PNG 输入校验、预检查和 raw billing 编排。 -- `server-rs/crates/platform-image/src/vector_engine/raw_edit.rs`:raw 编辑选项、严格尺寸校验、独立 provider 请求映射和 `b64_json` 响应透传;不复用现有 editor 图片编辑 client 或其 multipart transport。 +- `server-rs/crates/platform-image/src/vector_engine/raw_edit.rs`:raw 编辑选项、严格尺寸校验、独立 provider 请求映射和 `b64_json` 响应透传;由 api-server 按现有图片 API 传统构造并传入共享的 VectorEngine `reqwest::Client`,不在每个 raw 调用内部重复构造 client。每次请求仍用 `effective_request_timeout_ms` 通过 request builder 设置剩余 deadline;不修改统一 transport builder 的连接池策略。 raw-edit 的图片输入使用独立的 `RawImageEditImage`(`bytes::Bytes`),由 reqwest `Part::stream(Body::from(Bytes))` 直接接管 multipart 请求体,避免整图和掩码在 `Part::bytes` 的 `Cow<[u8]>` 转换中再次复制。既有 `ReferenceImage`、`DownloadedImage` 及 curl/编辑器链路继续保持 `Vec` 契约,不因 raw-edit 引入全局字节类型迁移。 diff --git a/server-rs/crates/api-server/src/raw_image.rs b/server-rs/crates/api-server/src/raw_image.rs index 30932c3a1..e7bb2ff53 100644 --- a/server-rs/crates/api-server/src/raw_image.rs +++ b/server-rs/crates/api-server/src/raw_image.rs @@ -22,8 +22,8 @@ use crate::{ auth::AuthenticatedAccessToken, http_error::AppError, openai_image_generation::{ - map_platform_image_error, record_openai_image_failure_if_configured, - require_openai_image_settings, + build_openai_image_http_client, map_platform_image_error, + record_openai_image_failure_if_configured, require_openai_image_settings, }, request_context::RequestContext, state::{AppState, RAW_IMAGE_DECODE_MAX_CONCURRENCY}, @@ -119,6 +119,7 @@ pub(crate) async fn edit_raw_image( None, ); let provider_settings = settings.provider_settings(); + let http_client = build_openai_image_http_client(&settings)?; let user_id = authenticated.claims().user_id().to_string(); let request_id = request_context.request_id().to_string(); let points_cost = raw_image_edit_price(&state, prepared.width, prepared.height).await?; @@ -136,6 +137,7 @@ pub(crate) async fn edit_raw_image( let started_at_micros = (OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000) as i64; let operation = async move { let generated = match create_vector_engine_raw_image_edit( + &http_client, &provider_settings, prepared.prompt.as_str(), prepared.image, diff --git a/server-rs/crates/platform-image/src/vector_engine/raw_edit.rs b/server-rs/crates/platform-image/src/vector_engine/raw_edit.rs index 6731432d9..a383ecdce 100644 --- a/server-rs/crates/platform-image/src/vector_engine/raw_edit.rs +++ b/server-rs/crates/platform-image/src/vector_engine/raw_edit.rs @@ -118,6 +118,7 @@ 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( + http_client: &reqwest::Client, settings: &VectorEngineImageSettings, prompt: &str, image: RawImageEditImage, @@ -181,13 +182,9 @@ pub async fn create_vector_engine_raw_image_edit( ); } - let client = reqwest::Client::builder() - .timeout(Duration::from_millis(request_timeout_ms)) - .http1_only() - .build() - .map_err(|error| invalid_config(failure_context, error.to_string()))?; - let response = client + let response = http_client .post(url.as_str()) + .timeout(Duration::from_millis(request_timeout_ms)) .bearer_auth(settings.api_key.as_str()) .multipart(form) .send() @@ -333,13 +330,6 @@ fn invalid_input(context: &str, message: String) -> PlatformImageError { } } -fn invalid_config(context: &str, message: String) -> PlatformImageError { - PlatformImageError::InvalidConfig { - provider: VECTOR_ENGINE_PROVIDER, - message: format!("{context}:构造请求客户端失败:{message}"), - } -} - fn request_error( url: &str, context: &str, @@ -477,16 +467,6 @@ mod tests { assert_eq!(error.to_string(), "raw_image_edit:请求参数无效:尺寸无效"); } - #[test] - fn invalid_config_error_keeps_operation_context() { - let error = invalid_config("raw_image_edit", "builder failed".to_string()); - - assert_eq!( - error.to_string(), - "raw_image_edit:构造请求客户端失败:builder failed" - ); - } - #[test] fn raw_edit_image_owns_shared_bytes_without_conversion() { let bytes = bytes::Bytes::from_static(b"raw-image");