mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
fix(gateway): preserve openai image 200 responses and sync success reporting
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
#[cfg(test)]
|
|
||||||
use crate::ai_serving::api::core_success_background_report_kind;
|
|
||||||
use crate::ai_serving::api::{
|
use crate::ai_serving::api::{
|
||||||
build_core_error_body_for_client_format, core_error_background_report_kind,
|
build_core_error_body_for_client_format, core_error_background_report_kind,
|
||||||
core_error_default_client_api_format, is_core_error_finalize_kind,
|
core_error_default_client_api_format, core_success_background_report_kind,
|
||||||
maybe_compile_sync_finalize_response,
|
is_core_error_finalize_kind, maybe_compile_sync_finalize_response,
|
||||||
normalize_provider_private_response_value as unwrap_local_finalize_response_value,
|
normalize_provider_private_response_value as unwrap_local_finalize_response_value,
|
||||||
LocalCoreSyncErrorKind,
|
LocalCoreSyncErrorKind,
|
||||||
};
|
};
|
||||||
@@ -36,6 +34,12 @@ pub(super) fn maybe_build_local_core_error_response(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let status_source_json = resolve_local_sync_source_body_json(payload)?;
|
let status_source_json = resolve_local_sync_source_body_json(payload)?;
|
||||||
|
if payload.status_code < 400
|
||||||
|
&& !has_nested_error(&response_body_json)
|
||||||
|
&& !status_source_json.as_ref().is_some_and(has_nested_error)
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
let mut response_headers = payload.headers.clone();
|
let mut response_headers = payload.headers.clone();
|
||||||
response_headers.remove("content-encoding");
|
response_headers.remove("content-encoding");
|
||||||
@@ -81,8 +85,9 @@ fn build_local_sync_response_from_json(
|
|||||||
payload: &GatewaySyncReportRequest,
|
payload: &GatewaySyncReportRequest,
|
||||||
body_json: serde_json::Value,
|
body_json: serde_json::Value,
|
||||||
) -> Result<Response<Body>, GatewayError> {
|
) -> Result<Response<Body>, GatewayError> {
|
||||||
let status_code = if is_core_error_finalize_kind(payload.report_kind.as_str())
|
let body_has_error = has_nested_error(&body_json);
|
||||||
|| has_nested_error(&body_json)
|
let status_code = if body_has_error
|
||||||
|
|| (payload.status_code >= 400 && is_core_error_finalize_kind(payload.report_kind.as_str()))
|
||||||
{
|
{
|
||||||
resolve_local_sync_error_status_code(payload.status_code, &body_json)
|
resolve_local_sync_error_status_code(payload.status_code, &body_json)
|
||||||
} else {
|
} else {
|
||||||
@@ -621,11 +626,30 @@ pub(crate) async fn submit_local_core_error_or_sync_finalize(
|
|||||||
build_local_core_sync_finalize_fallback_response(trace_id, decision, &payload)?
|
build_local_core_sync_finalize_fallback_response(trace_id, decision, &payload)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(error_report_kind) =
|
let response_status = response.status();
|
||||||
|
if response_status.is_success() {
|
||||||
|
if let Some(success_report_kind) =
|
||||||
|
core_success_background_report_kind(payload.report_kind.as_str())
|
||||||
|
{
|
||||||
|
let mut report_payload = payload.clone();
|
||||||
|
report_payload.report_kind = success_report_kind.to_string();
|
||||||
|
report_payload.status_code = response_status.as_u16();
|
||||||
|
spawn_sync_report(state.clone(), report_payload);
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
event_name = "local_core_finalize_missing_success_report_mapping",
|
||||||
|
log_type = "event",
|
||||||
|
trace_id = %trace_id,
|
||||||
|
report_kind = %payload.report_kind,
|
||||||
|
"gateway built local core finalize success response without background success report mapping"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some(error_report_kind) =
|
||||||
resolve_core_error_background_report_kind(payload.report_kind.as_str())
|
resolve_core_error_background_report_kind(payload.report_kind.as_str())
|
||||||
{
|
{
|
||||||
let mut report_payload = payload.clone();
|
let mut report_payload = payload.clone();
|
||||||
report_payload.report_kind = error_report_kind;
|
report_payload.report_kind = error_report_kind;
|
||||||
|
report_payload.status_code = response_status.as_u16();
|
||||||
spawn_sync_report(state.clone(), report_payload);
|
spawn_sync_report(state.clone(), report_payload);
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -819,4 +843,39 @@ mod tests {
|
|||||||
"unexpected message: {message}"
|
"unexpected message: {message}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn submit_local_core_finalize_keeps_http_200_for_success_image_body() {
|
||||||
|
let payload = core_finalize_payload(
|
||||||
|
"openai_image_sync_finalize",
|
||||||
|
"openai:image",
|
||||||
|
"openai:image",
|
||||||
|
200,
|
||||||
|
json!({
|
||||||
|
"created": 1779273523,
|
||||||
|
"data": [{
|
||||||
|
"b64_json": "aGVsbG8="
|
||||||
|
}]
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = AppState::new().expect("state should build");
|
||||||
|
let response = submit_local_core_error_or_sync_finalize(
|
||||||
|
&state,
|
||||||
|
"trace-image-success-200",
|
||||||
|
&test_decision(),
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("response should build");
|
||||||
|
|
||||||
|
assert_eq!(response.status(), http::StatusCode::OK);
|
||||||
|
let body: serde_json::Value = serde_json::from_slice(
|
||||||
|
&to_bytes(response.into_body(), usize::MAX)
|
||||||
|
.await
|
||||||
|
.expect("body should read"),
|
||||||
|
)
|
||||||
|
.expect("body should decode");
|
||||||
|
assert_eq!(body["data"][0]["b64_json"], "aGVsbG8=");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1146,6 +1146,12 @@ pub fn maybe_build_openai_image_sync_finalize_product(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
if let Some(provider_body_json) = body_json {
|
if let Some(provider_body_json) = body_json {
|
||||||
|
if openai_image_response_has_standard_data(provider_body_json) {
|
||||||
|
return Ok(Some(OpenAiImageSyncFinalizeProduct {
|
||||||
|
client_body_json: provider_body_json.clone(),
|
||||||
|
provider_body_json: provider_body_json.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
if provider_body_json.get("output").is_some() && provider_body_json.get("data").is_none() {
|
if provider_body_json.get("output").is_some() && provider_body_json.get("data").is_none() {
|
||||||
let Some(client_body_json) = crate::formats::shared::image_bridge::build_openai_image_response_from_response_stream_sync_body(
|
let Some(client_body_json) = crate::formats::shared::image_bridge::build_openai_image_response_from_response_stream_sync_body(
|
||||||
provider_body_json,
|
provider_body_json,
|
||||||
@@ -1279,6 +1285,25 @@ pub fn maybe_build_openai_image_sync_finalize_product(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn openai_image_response_has_standard_data(body_json: &Value) -> bool {
|
||||||
|
body_json
|
||||||
|
.get("data")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.is_some_and(|items| {
|
||||||
|
items.iter().any(|item| {
|
||||||
|
item.as_object().is_some_and(|object| {
|
||||||
|
["b64_json", "url"].iter().any(|field| {
|
||||||
|
object
|
||||||
|
.get(*field)
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.map(str::trim)
|
||||||
|
.is_some_and(|value| !value.is_empty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use base64::Engine as _;
|
use base64::Engine as _;
|
||||||
@@ -1487,4 +1512,35 @@ mod tests {
|
|||||||
"revised history prompt"
|
"revised history prompt"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sync_finalize_accepts_standard_openai_image_response() {
|
||||||
|
let provider_body = json!({
|
||||||
|
"created": 1779273523,
|
||||||
|
"data": [{
|
||||||
|
"b64_json": "aGVsbG8=",
|
||||||
|
"revised_prompt": "draw a small cat"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
let product = maybe_build_openai_image_sync_finalize_product(
|
||||||
|
"openai_image_sync_finalize",
|
||||||
|
200,
|
||||||
|
Some(&json!({
|
||||||
|
"client_api_format": "openai:image",
|
||||||
|
"provider_api_format": "openai:image",
|
||||||
|
"image_request": {
|
||||||
|
"operation": "generate",
|
||||||
|
"response_format": "b64_json"
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
Some(&provider_body),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("standard image response should finalize")
|
||||||
|
.expect("standard image response should match");
|
||||||
|
|
||||||
|
assert_eq!(product.client_body_json["created"], 1779273523);
|
||||||
|
assert_eq!(product.client_body_json["data"][0]["b64_json"], "aGVsbG8=");
|
||||||
|
assert_eq!(product.provider_body_json, provider_body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user