限制 Raw prompt 原始字节大小

将 prompt UTF-8 原始字节限制为 16 KiB

补充超限回归测试并同步请求合同
This commit is contained in:
2026-09-09 11:50:30 +08:00
parent 40316d82c4
commit 5bbedd9a5c
3 changed files with 26 additions and 1 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` 必填。`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 原始字节长度不得超过 `16 KiB`;超限在扣费前返回 400`quality``background``output_format` 采用 GPT Image 模型支持的值。
`width``height` 使用严格输出尺寸规则,均在扣费前校验:
@@ -59,6 +59,8 @@ pub(crate) struct RawImageEditResponse {
pub(crate) data: Vec<RawImageEditItem>,
}
const RAW_IMAGE_MAX_PROMPT_BYTES: usize = 16 * 1024;
pub(crate) async fn edit_raw_image(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
@@ -156,6 +158,11 @@ fn prepare_request(payload: RawImageEditRequest) -> Result<PreparedRawImageEdit,
if payload.prompt.trim().is_empty() {
return Err(bad_request("prompt 不能为空"));
}
if payload.prompt.len() > RAW_IMAGE_MAX_PROMPT_BYTES {
return Err(bad_request(format!(
"prompt 不能超过 {RAW_IMAGE_MAX_PROMPT_BYTES} 字节"
)));
}
validate_raw_image_edit_dimensions(payload.width, payload.height)
.map_err(|error| bad_request(error.to_string()))?;
validate_optional_value(
@@ -430,4 +437,21 @@ mod tests {
assert!(format!("{error:?}").contains("mask 尺寸必须与 image 一致"));
}
#[test]
fn prompt_uses_raw_utf8_byte_limit() {
let payload = serde_json::json!({
"image": {"data": encoded_png(1, 1), "mimeType": "image/png"},
"prompt": "a".repeat(RAW_IMAGE_MAX_PROMPT_BYTES + 1),
"width": 1024,
"height": 1024
});
let parsed: RawImageEditRequest = serde_json::from_value(payload).expect("request");
let error = match prepare_request(parsed) {
Ok(_) => panic!("oversized prompt should fail before image decode and billing"),
Err(error) => error,
};
assert!(format!("{error:?}").contains("prompt 不能超过 16384 字节"));
}
}
@@ -9,6 +9,7 @@ base64 = { workspace = true }
curl = { workspace = true }
image = { workspace = true, features = ["jpeg", "png", "webp"] }
reqwest = { workspace = true, features = ["json", "multipart", "rustls-tls"] }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["io-util", "macros", "net", "time"] }
tracing = { workspace = true }