修复 BgFilter 熔断打开时绕过阿里云兜底直接本地扣色
circuit open 时直接 return 本地键色扣除,使同函数后面的阿里云兜底不可达; BgFilter 一旦连续失败触发 cooldown,整段冷却期从「BgFilter→阿里云→本地」 双供应商降级退化成只剩低质量本地扣色。 - 抽出统一兜底链 fallback_editor_screen_background_removal(阿里云→本地), 熔断打开与 BgFilter 调用失败两条路径共用:熔断只跳过 BgFilter HTTP 调用本身, 仍先试阿里云。阿里云失败审计逻辑原样搬入 helper,零行为改动。 - 日志 tag 改为 editor_bgfilter_circuit_open_fallback_to_aliyun_matting,如实反映。 - 结构测试升级为 in-order 断言,钉死「熔断→fallback→请求→fallback」与 helper 内「阿里云→本地」顺序,防止回退成直接本地扣色。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2227,14 +2227,16 @@ async fn remove_editor_generated_screen_background_with_bgfilter(
|
||||
audit: &crate::external_api_audit::ExternalApiAuditContext,
|
||||
) -> Result<DownloadedOpenAiImage, AppError> {
|
||||
if let Some(remaining) = editor_bgfilter_circuit_open_remaining(state) {
|
||||
// 熔断打开只跳过 BgFilter 调用本身,仍要走阿里云兜底;绝不能在 cooldown 内直接退化到
|
||||
// 低质量本地扣色,否则整段冷却期从「双供应商降级」退化成单一本地键色扣除。
|
||||
tracing::warn!(
|
||||
provider = "bgfilter",
|
||||
screen_color = screen_color.hex,
|
||||
seg_model,
|
||||
cooldown_ms = remaining.as_millis() as u64,
|
||||
"editor_bgfilter_circuit_open_fallback_to_local_screen_background_removal"
|
||||
"editor_bgfilter_circuit_open_fallback_to_aliyun_matting"
|
||||
);
|
||||
return remove_editor_generated_green_screen_background(image, screen_color);
|
||||
return fallback_editor_screen_background_removal(state, image, screen_color, audit).await;
|
||||
}
|
||||
|
||||
match request_editor_generated_screen_background_with_bgfilter(
|
||||
@@ -2273,39 +2275,50 @@ async fn remove_editor_generated_screen_background_with_bgfilter(
|
||||
crate::external_api_audit::matting_failure_audit_raw_excerpt(&error),
|
||||
)
|
||||
.await;
|
||||
match crate::aliyun_matting::segment_image_with_aliyun_matting(
|
||||
fallback_editor_screen_background_removal(state, image, screen_color, audit).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// BgFilter 不可用(熔断打开或调用失败)时的统一兜底链:先阿里云通用抠图,失败再退本地键色扣除。
|
||||
/// 熔断打开路径与 BgFilter 调用失败路径共用此链,确保 cooldown 内仍是「阿里云 → 本地」而非直接本地。
|
||||
async fn fallback_editor_screen_background_removal(
|
||||
state: &AppState,
|
||||
image: &DownloadedOpenAiImage,
|
||||
screen_color: EditorScreenBackgroundColor,
|
||||
audit: &crate::external_api_audit::ExternalApiAuditContext,
|
||||
) -> Result<DownloadedOpenAiImage, AppError> {
|
||||
match crate::aliyun_matting::segment_image_with_aliyun_matting(
|
||||
state,
|
||||
image,
|
||||
"editor-screen-background",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(image) => Ok(image),
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
provider = "aliyun-matting",
|
||||
screen_color = screen_color.hex,
|
||||
error = %error,
|
||||
error_details = ?error.details(),
|
||||
"editor_aliyun_matting_fallback_to_local_screen_background_removal"
|
||||
);
|
||||
crate::external_api_audit::record_matting_external_api_failure(
|
||||
state,
|
||||
image,
|
||||
"editor-screen-background",
|
||||
audit,
|
||||
"aliyun-matting",
|
||||
state.config.aliyun_matting_endpoint.clone(),
|
||||
"editor-screen-background-removal",
|
||||
"aliyun_segment",
|
||||
crate::external_api_audit::matting_failure_audit_status_code(&error),
|
||||
crate::external_api_audit::matting_failure_audit_timeout(&error),
|
||||
crate::external_api_audit::matting_failure_audit_latency_ms(&error),
|
||||
error.message().to_string(),
|
||||
crate::external_api_audit::matting_failure_audit_raw_excerpt(&error),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(image) => Ok(image),
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
provider = "aliyun-matting",
|
||||
screen_color = screen_color.hex,
|
||||
error = %error,
|
||||
error_details = ?error.details(),
|
||||
"editor_aliyun_matting_fallback_to_local_screen_background_removal"
|
||||
);
|
||||
crate::external_api_audit::record_matting_external_api_failure(
|
||||
state,
|
||||
audit,
|
||||
"aliyun-matting",
|
||||
state.config.aliyun_matting_endpoint.clone(),
|
||||
"editor-screen-background-removal",
|
||||
"aliyun_segment",
|
||||
crate::external_api_audit::matting_failure_audit_status_code(&error),
|
||||
crate::external_api_audit::matting_failure_audit_timeout(&error),
|
||||
crate::external_api_audit::matting_failure_audit_latency_ms(&error),
|
||||
error.message().to_string(),
|
||||
crate::external_api_audit::matting_failure_audit_raw_excerpt(&error),
|
||||
)
|
||||
.await;
|
||||
remove_editor_generated_green_screen_background(image, screen_color)
|
||||
}
|
||||
}
|
||||
.await;
|
||||
remove_editor_generated_green_screen_background(image, screen_color)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7573,12 +7586,25 @@ mod tests {
|
||||
"remove_editor_generated_screen_background_with_bgfilter",
|
||||
],
|
||||
);
|
||||
assert_function_contains(
|
||||
assert_function_contains_in_order(
|
||||
source,
|
||||
"async fn remove_editor_generated_screen_background_with_bgfilter",
|
||||
"async fn fallback_editor_screen_background_removal",
|
||||
&[
|
||||
// 熔断打开分支必须先委派统一兜底链(阿里云→本地),不能直接退化本地扣色;
|
||||
// BgFilter 调用失败分支同样走该兜底链。
|
||||
"editor_bgfilter_circuit_open_remaining",
|
||||
"fallback_editor_screen_background_removal",
|
||||
"request_editor_generated_screen_background_with_bgfilter",
|
||||
"fallback_editor_screen_background_removal",
|
||||
],
|
||||
);
|
||||
assert_function_contains_in_order(
|
||||
source,
|
||||
"async fn fallback_editor_screen_background_removal",
|
||||
"async fn request_editor_generated_screen_background_with_bgfilter",
|
||||
&[
|
||||
"request_editor_generated_screen_background_with_bgfilter",
|
||||
"segment_image_with_aliyun_matting",
|
||||
"remove_editor_generated_green_screen_background",
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user