fix(usage): 修复含失败信号的 pending 请求被误判为活跃的问题

- pending/streaming 状态含 status_code >= 400 或 error_message 时视为 failed
- 候选状态派生改为 failed 优先于 stale pending/streaming
- 活跃请求轮询时保留失败信号字段的更新
- 图片流支持 partial_image 事件转发及从 response.completed 读取最终图片
This commit is contained in:
fawney19
2026-04-24 03:07:11 +08:00
parent 581bc03d4e
commit 5148370253
16 changed files with 394 additions and 40 deletions

View File

@@ -404,6 +404,8 @@ fn build_users_me_usage_active_payload(item: &StoredRequestUsageAudit) -> serde_
"rate_multiplier": item.settlement_rate_multiplier(),
"response_time_ms": item.response_time_ms,
"first_byte_time_ms": item.first_byte_time_ms,
"status_code": item.status_code,
"error_message": item.error_message,
"api_format": item.api_format,
"endpoint_api_format": item.endpoint_api_format,
"is_stream": item.is_stream,
@@ -435,6 +437,24 @@ fn build_users_me_usage_active_payload(item: &StoredRequestUsageAudit) -> serde_
payload
}
fn users_me_usage_is_failed(item: &StoredRequestUsageAudit) -> bool {
let has_failure_signal = item.status_code.is_some_and(|value| value >= 400)
|| item
.error_message
.as_deref()
.is_some_and(|value| !value.trim().is_empty());
let status = item.status.trim().to_ascii_lowercase();
if status.is_empty() {
return has_failure_signal;
}
match status.as_str() {
"completed" | "cancelled" => false,
"pending" | "streaming" => has_failure_signal,
"failed" => true,
_ => false,
}
}
fn build_users_me_usage_summary_by_model(
rows: &[StoredUsageBreakdownSummaryRow],
include_actual_cost: bool,
@@ -997,6 +1017,15 @@ pub(super) async fn handle_users_me_usage_active_get(
},
};
let items = if ids.is_some() {
items
} else {
items
.into_iter()
.filter(|item| !users_me_usage_is_failed(item))
.collect::<Vec<_>>()
};
Json(json!({
"requests": items
.iter()
@@ -1233,7 +1262,8 @@ mod tests {
use super::{
build_users_me_usage_active_payload, build_users_me_usage_record_payload,
users_me_usage_client_is_stream, users_me_usage_upstream_is_stream,
users_me_usage_client_is_stream, users_me_usage_is_failed,
users_me_usage_upstream_is_stream,
};
fn sample_usage(status: &str) -> StoredRequestUsageAudit {
@@ -1310,6 +1340,17 @@ mod tests {
assert_eq!(payload["cache_creation_ephemeral_1h_input_tokens"], 6);
}
#[test]
fn user_usage_active_pending_with_failure_signal_is_not_active() {
let item = StoredRequestUsageAudit {
status_code: Some(503),
error_message: Some("upstream failed".to_string()),
..sample_usage("pending")
};
assert!(users_me_usage_is_failed(&item));
}
#[test]
fn user_usage_payloads_include_symmetric_stream_fields() {
let item = StoredRequestUsageAudit {