修正预算与图片下载错误的 provider 归属
request_budget_exhausted_error 接收实际 provider,审计、日志与 Request 错误不再写死 vector-engine download_remote_image / download_images_from_urls / resolve_reference_images / parse_reference_image_data_url 逐层透传 provider executor 与 raw image edit 调用处传入各自 settings.provider,nanobanana 下载路径传入 VectorEngine
This commit is contained in:
@@ -109,6 +109,7 @@ pub(crate) async fn handle_image_response(
|
||||
task_id,
|
||||
image_urls,
|
||||
candidate_count,
|
||||
provider,
|
||||
request_deadline,
|
||||
failure_context,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::image_provider::{
|
||||
audit::build_failure_audit, constants::VECTOR_ENGINE_PROVIDER, error::PlatformImageError,
|
||||
audit::build_failure_audit, error::PlatformImageError, types::ImageProvider,
|
||||
};
|
||||
|
||||
pub(crate) fn effective_request_timeout_ms(
|
||||
@@ -51,6 +51,7 @@ fn retry_delay_fits_request_deadline_at(
|
||||
}
|
||||
|
||||
pub(crate) fn request_budget_exhausted_error(
|
||||
provider: ImageProvider,
|
||||
request_url: &str,
|
||||
operation: &str,
|
||||
image_model: Option<&'static str>,
|
||||
@@ -59,9 +60,10 @@ pub(crate) fn request_budget_exhausted_error(
|
||||
reference_image_count: Option<usize>,
|
||||
) -> PlatformImageError {
|
||||
const ERROR_SOURCE: &str = "external request deadline elapsed";
|
||||
let provider_label = provider.as_str();
|
||||
let message = format!("{operation}:外部图片请求执行预算已耗尽");
|
||||
let audit = build_failure_audit(
|
||||
VECTOR_ENGINE_PROVIDER,
|
||||
provider_label,
|
||||
request_url,
|
||||
operation,
|
||||
"request_budget",
|
||||
@@ -78,7 +80,7 @@ pub(crate) fn request_budget_exhausted_error(
|
||||
image_model,
|
||||
);
|
||||
tracing::warn!(
|
||||
provider = VECTOR_ENGINE_PROVIDER,
|
||||
provider = provider_label,
|
||||
endpoint = %request_url,
|
||||
failure_stage = "request_budget",
|
||||
timeout = true,
|
||||
@@ -90,7 +92,7 @@ pub(crate) fn request_budget_exhausted_error(
|
||||
"ImageProvider 图片请求执行预算已耗尽"
|
||||
);
|
||||
PlatformImageError::Request {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider_label,
|
||||
message,
|
||||
endpoint: Some(request_url.to_string()),
|
||||
timeout: true,
|
||||
@@ -164,6 +166,7 @@ mod tests {
|
||||
#[test]
|
||||
fn exhausted_budget_maps_to_timeout_request_error_and_audit() {
|
||||
let error = request_budget_exhausted_error(
|
||||
ImageProvider::VectorEngine,
|
||||
"https://vector.example/v1/images/generations",
|
||||
"生成图片失败",
|
||||
None,
|
||||
|
||||
@@ -78,6 +78,7 @@ pub async fn create_image_generation_with_model(
|
||||
let resolved_references = resolve_reference_images(
|
||||
http_client,
|
||||
reference_images,
|
||||
settings.provider,
|
||||
failure_context,
|
||||
settings.request_deadline,
|
||||
)
|
||||
@@ -116,6 +117,7 @@ pub async fn create_image_generation_with_model(
|
||||
else {
|
||||
return Err(finish_image_model_fallback_error(
|
||||
request_budget_exhausted_error(
|
||||
settings.provider,
|
||||
request_url.as_str(),
|
||||
failure_context,
|
||||
auditable_image_model(upstream_model),
|
||||
@@ -307,6 +309,7 @@ pub async fn create_nanobanana_generate_content(
|
||||
effective_request_timeout_ms(settings.request_timeout_ms, settings.request_deadline)
|
||||
else {
|
||||
return Err(request_budget_exhausted_error(
|
||||
ImageProvider::VectorEngine,
|
||||
request_url.as_str(),
|
||||
failure_context,
|
||||
None,
|
||||
@@ -550,6 +553,7 @@ pub async fn create_image_edit_with_references_and_model(
|
||||
else {
|
||||
return Err(finish_image_model_fallback_error(
|
||||
request_budget_exhausted_error(
|
||||
settings.provider,
|
||||
request_url.as_str(),
|
||||
failure_context,
|
||||
auditable_image_model(upstream_model),
|
||||
|
||||
@@ -4,19 +4,21 @@ use std::time::Instant;
|
||||
|
||||
use crate::image_provider::{
|
||||
budget::request_budget_exhausted_error,
|
||||
constants::{VECTOR_ENGINE_IMAGE_EDIT_MAX_REFERENCE_IMAGES, VECTOR_ENGINE_PROVIDER},
|
||||
constants::VECTOR_ENGINE_IMAGE_EDIT_MAX_REFERENCE_IMAGES,
|
||||
error::PlatformImageError,
|
||||
types::{DownloadedImage, GeneratedImages, ReferenceImage},
|
||||
types::{DownloadedImage, GeneratedImages, ImageProvider, ReferenceImage},
|
||||
};
|
||||
|
||||
pub async fn download_remote_image(
|
||||
http_client: &reqwest::Client,
|
||||
image_url: &str,
|
||||
provider: ImageProvider,
|
||||
) -> Result<DownloadedImage, PlatformImageError> {
|
||||
let response = http_client.get(image_url).send().await.map_err(|error| {
|
||||
map_simple_request_error(
|
||||
format!("下载生成图片失败:{error}"),
|
||||
Some(image_url.to_string()),
|
||||
provider,
|
||||
)
|
||||
})?;
|
||||
let status = response.status();
|
||||
@@ -30,11 +32,12 @@ pub async fn download_remote_image(
|
||||
map_simple_request_error(
|
||||
format!("读取生成图片内容失败:{error}"),
|
||||
Some(image_url.to_string()),
|
||||
provider,
|
||||
)
|
||||
})?;
|
||||
if !status.is_success() {
|
||||
return Err(PlatformImageError::Request {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message: "下载生成图片失败".to_string(),
|
||||
endpoint: Some(image_url.to_string()),
|
||||
timeout: false,
|
||||
@@ -58,15 +61,17 @@ pub async fn download_remote_image(
|
||||
async fn download_remote_image_with_deadline(
|
||||
http_client: &reqwest::Client,
|
||||
image_url: &str,
|
||||
provider: ImageProvider,
|
||||
request_deadline: Option<Instant>,
|
||||
operation: &str,
|
||||
) -> Result<DownloadedImage, PlatformImageError> {
|
||||
let Some(request_deadline) = request_deadline else {
|
||||
return download_remote_image(http_client, image_url).await;
|
||||
return download_remote_image(http_client, image_url, provider).await;
|
||||
};
|
||||
let started_at = Instant::now();
|
||||
if request_deadline <= started_at {
|
||||
return Err(request_budget_exhausted_error(
|
||||
provider,
|
||||
image_url,
|
||||
operation,
|
||||
None,
|
||||
@@ -77,11 +82,12 @@ async fn download_remote_image_with_deadline(
|
||||
}
|
||||
tokio::time::timeout_at(
|
||||
tokio::time::Instant::from_std(request_deadline),
|
||||
download_remote_image(http_client, image_url),
|
||||
download_remote_image(http_client, image_url, provider),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
request_budget_exhausted_error(
|
||||
provider,
|
||||
image_url,
|
||||
operation,
|
||||
None,
|
||||
@@ -97,6 +103,7 @@ pub(crate) async fn download_images_from_urls(
|
||||
task_id: String,
|
||||
image_urls: Vec<String>,
|
||||
candidate_count: u32,
|
||||
provider: ImageProvider,
|
||||
request_deadline: Option<Instant>,
|
||||
operation: &str,
|
||||
) -> Result<GeneratedImages, PlatformImageError> {
|
||||
@@ -109,6 +116,7 @@ pub(crate) async fn download_images_from_urls(
|
||||
download_remote_image_with_deadline(
|
||||
http_client,
|
||||
image_url.as_str(),
|
||||
provider,
|
||||
request_deadline,
|
||||
operation,
|
||||
)
|
||||
@@ -126,6 +134,7 @@ pub(crate) async fn download_images_from_urls(
|
||||
pub(crate) async fn resolve_reference_images(
|
||||
http_client: &reqwest::Client,
|
||||
reference_images: &[String],
|
||||
provider: ImageProvider,
|
||||
failure_context: &str,
|
||||
request_deadline: Option<Instant>,
|
||||
) -> Result<Vec<ReferenceImage>, PlatformImageError> {
|
||||
@@ -135,7 +144,7 @@ pub(crate) async fn resolve_reference_images(
|
||||
.count();
|
||||
if reference_count > VECTOR_ENGINE_IMAGE_EDIT_MAX_REFERENCE_IMAGES {
|
||||
return Err(PlatformImageError::InvalidRequest {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message: format!(
|
||||
"{failure_context}:参考图最多允许 {VECTOR_ENGINE_IMAGE_EDIT_MAX_REFERENCE_IMAGES} 张,当前提交 {reference_count} 张。"
|
||||
),
|
||||
@@ -147,7 +156,7 @@ pub(crate) async fn resolve_reference_images(
|
||||
if source.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some(reference_image) = parse_reference_image_data_url(source, index)? {
|
||||
if let Some(reference_image) = parse_reference_image_data_url(source, index, provider)? {
|
||||
resolved.push(reference_image);
|
||||
continue;
|
||||
}
|
||||
@@ -155,6 +164,7 @@ pub(crate) async fn resolve_reference_images(
|
||||
let downloaded = download_remote_image_with_deadline(
|
||||
http_client,
|
||||
source,
|
||||
provider,
|
||||
request_deadline,
|
||||
failure_context,
|
||||
)
|
||||
@@ -171,14 +181,14 @@ pub(crate) async fn resolve_reference_images(
|
||||
continue;
|
||||
}
|
||||
return Err(PlatformImageError::InvalidRequest {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message: format!("{failure_context}:参考图必须是图片 Data URL 或 HTTP(S) URL。"),
|
||||
});
|
||||
}
|
||||
|
||||
if resolved.is_empty() {
|
||||
return Err(PlatformImageError::InvalidRequest {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message: format!("{failure_context}:图片编辑需要至少一张参考图。"),
|
||||
});
|
||||
}
|
||||
@@ -189,25 +199,26 @@ pub(crate) async fn resolve_reference_images(
|
||||
pub(crate) fn parse_reference_image_data_url(
|
||||
source: &str,
|
||||
index: usize,
|
||||
provider: ImageProvider,
|
||||
) -> Result<Option<ReferenceImage>, PlatformImageError> {
|
||||
let Some(body) = source.strip_prefix("data:") else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some((mime_type, data)) = body.split_once(";base64,") else {
|
||||
return Err(PlatformImageError::InvalidRequest {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message: "参考图 Data URL 必须是 base64 图片。".to_string(),
|
||||
});
|
||||
};
|
||||
if !mime_type.starts_with("image/") {
|
||||
return Err(PlatformImageError::InvalidRequest {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message: "参考图 Data URL 必须是图片类型。".to_string(),
|
||||
});
|
||||
}
|
||||
let bytes = BASE64_STANDARD.decode(data.trim()).map_err(|error| {
|
||||
PlatformImageError::InvalidRequest {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message: format!("参考图 Data URL 解码失败:{error}"),
|
||||
}
|
||||
})?;
|
||||
@@ -290,9 +301,13 @@ pub(crate) fn infer_image_mime_type(bytes: &[u8]) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn map_simple_request_error(message: String, endpoint: Option<String>) -> PlatformImageError {
|
||||
fn map_simple_request_error(
|
||||
message: String,
|
||||
endpoint: Option<String>,
|
||||
provider: ImageProvider,
|
||||
) -> PlatformImageError {
|
||||
PlatformImageError::Request {
|
||||
provider: VECTOR_ENGINE_PROVIDER,
|
||||
provider: provider.as_str(),
|
||||
message,
|
||||
endpoint,
|
||||
timeout: false,
|
||||
@@ -349,6 +364,7 @@ mod tests {
|
||||
let error = download_remote_image_with_deadline(
|
||||
&http_client,
|
||||
"http://127.0.0.1:9/not-called.png",
|
||||
ImageProvider::VectorEngine,
|
||||
Some(Instant::now()),
|
||||
"下载测试图片失败",
|
||||
)
|
||||
@@ -387,6 +403,7 @@ mod tests {
|
||||
let error = download_remote_image_with_deadline(
|
||||
&reqwest::Client::new(),
|
||||
format!("http://{address}/pending.png").as_str(),
|
||||
ImageProvider::VectorEngine,
|
||||
Some(Instant::now() + Duration::from_millis(100)),
|
||||
"下载测试图片失败",
|
||||
)
|
||||
|
||||
@@ -134,6 +134,7 @@ pub async fn create_raw_image_edit(
|
||||
effective_request_timeout_ms(settings.request_timeout_ms, settings.request_deadline)
|
||||
else {
|
||||
return Err(request_budget_exhausted_error(
|
||||
settings.provider,
|
||||
url.as_str(),
|
||||
failure_context,
|
||||
Some(GPT_IMAGE_2_5_EDIT_MODEL),
|
||||
|
||||
Reference in New Issue
Block a user