收紧 Raw 成功响应字段类型

要求 data 与 b64_json 必填并移除无用 provider id

prompt 上限调整为 4 KiB 原始字节并同步文档
This commit is contained in:
2026-09-09 12:03:03 +08:00
parent acfcf0c158
commit c049d68b8e
4 changed files with 14 additions and 15 deletions
@@ -35,7 +35,7 @@ POST /api/raw/v1/images/edit
}
```
`image` 是必填的单图结构 `{ data, mimeType }``mask` 可选并使用相同结构。`image``mask``mimeType` 必须为 `image/png`,base64 解码后必须是可完整解码的有效 PNG 文件,提供 mask 时其宽高必须与 image 完全一致;空数据、非 PNG 字节、MIME 不匹配或尺寸不一致均在扣费前返回 400。服务端不把输入格式另建成请求参数。`prompt` 必填,UTF-8 原始字节长度不得超过 `16 KiB`;超限在扣费前返回 400。`quality``background``output_format` 采用 GPT Image 模型支持的值。
`image` 是必填的单图结构 `{ data, mimeType }``mask` 可选并使用相同结构。`image``mask``mimeType` 必须为 `image/png`,base64 解码后必须是可完整解码的有效 PNG 文件,提供 mask 时其宽高必须与 image 完全一致;空数据、非 PNG 字节、MIME 不匹配或尺寸不一致均在扣费前返回 400。服务端不把输入格式另建成请求参数。`prompt` 必填,UTF-8 原始字节长度不得超过 `4 KiB`;超限在扣费前返回 400。`quality``background``output_format` 采用 GPT Image 模型支持的值。
`width``height` 使用严格输出尺寸规则,均在扣费前校验:
+3 -4
View File
@@ -59,7 +59,7 @@ pub(crate) struct RawImageEditResponse {
pub(crate) data: Vec<RawImageEditItem>,
}
const RAW_IMAGE_MAX_PROMPT_BYTES: usize = 16 * 1024;
const RAW_IMAGE_MAX_PROMPT_BYTES: usize = 4 * 1024;
pub(crate) async fn edit_raw_image(
State(state): State<AppState>,
@@ -109,7 +109,6 @@ pub(crate) async fn edit_raw_image(
return Err(map_platform_image_error(error));
}
};
let task_id = generated.task_id.clone();
let data: Vec<RawImageEditItem> = generated
.b64_images
.into_iter()
@@ -125,7 +124,7 @@ pub(crate) async fn edit_raw_image(
started_at_micros,
true,
None,
Some(task_id),
Some("raw-image-edit".to_string()),
Some(json!({ "imageCount": data.len() })),
)
.await;
@@ -452,6 +451,6 @@ mod tests {
Err(error) => error,
};
assert!(format!("{error:?}").contains("prompt 不能超过 16384 字节"));
assert!(format!("{error:?}").contains("prompt 不能超过 4096 字节"));
}
}
@@ -28,13 +28,12 @@ pub struct RawImageEditOptions {
#[derive(Debug, Deserialize)]
struct RawImageEditResponsePayload {
id: Option<String>,
data: Option<Vec<RawImageEditResponseEntry>>,
data: Vec<RawImageEditResponseEntry>,
}
#[derive(Debug, Deserialize)]
struct RawImageEditResponseEntry {
b64_json: Option<String>,
b64_json: String,
}
pub const RAW_IMAGE_MAX_EDGE: u32 = GPT_IMAGE_2_MAX_EDGE;
@@ -262,9 +261,8 @@ pub async fn create_vector_engine_raw_image_edit(
};
let b64_images = payload
.data
.unwrap_or_default()
.into_iter()
.filter_map(|entry| entry.b64_json)
.map(|entry| entry.b64_json)
.collect::<Vec<_>>();
if b64_images.is_empty() {
let message = format!("{failure_context}:上游未返回 b64_json 图片");
@@ -291,7 +289,6 @@ pub async fn create_vector_engine_raw_image_edit(
});
}
Ok(RawImageEditResult {
task_id: payload.id.unwrap_or_else(|| "raw-image-edit".to_string()),
b64_images,
recovered_failure_audits: Vec::new(),
})
@@ -387,7 +384,6 @@ fn status_class(status: u16) -> &'static str {
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn raw_result_forwards_b64_without_decoding_or_using_output_format() {
@@ -401,14 +397,19 @@ mod tests {
assert_eq!(
payload
.data
.unwrap_or_default()
.into_iter()
.filter_map(|entry| entry.b64_json)
.map(|entry| entry.b64_json)
.collect::<Vec<_>>(),
vec!["not-base64-but-forwarded".to_string()]
);
}
#[test]
fn raw_success_response_requires_typed_data_and_b64_json_fields() {
assert!(serde_json::from_str::<RawImageEditResponsePayload>(r#"{}"#).is_err());
assert!(serde_json::from_str::<RawImageEditResponsePayload>(r#"{"data":[{}]}"#).is_err());
}
#[test]
fn raw_image_edit_dimensions_enforce_strict_contract() {
assert!(validate_raw_image_edit_dimensions(1024, 640).is_ok());
@@ -18,7 +18,6 @@ pub struct GeneratedImages {
#[derive(Clone, Debug)]
pub struct RawImageEditResult {
pub task_id: String,
pub b64_images: Vec<String>,
pub recovered_failure_audits: Vec<PlatformImageFailureAudit>,
}