fix(providers): restore legacy endpoint health defaults

This commit is contained in:
elky
2026-09-09 15:51:51 +08:00
parent 8260a87215
commit d28dd89039
11 changed files with 460 additions and 63 deletions
@@ -103,10 +103,11 @@ pub(crate) async fn maybe_build_local_admin_provider_reads_response(
.build_admin_provider_summary_payload(&provider_id)
.await
{
Some(payload) => Json(payload).into_response(),
None => build_admin_provider_not_found_response(format!(
Ok(Some(payload)) => Json(payload).into_response(),
Ok(None) => build_admin_provider_not_found_response(format!(
"Provider {provider_id} 不存在"
)),
Err(_) => build_admin_providers_data_unavailable_response(),
},
));
}
@@ -173,16 +173,17 @@ pub(crate) async fn maybe_build_local_admin_provider_writes_response(
.build_admin_provider_summary_payload(&provider_id)
.await
{
Some(payload) => attach_admin_audit_response(
Ok(Some(payload)) => attach_admin_audit_response(
Json(payload).into_response(),
"admin_provider_updated",
"update_provider",
"provider",
&provider_id,
),
None => build_admin_provider_not_found_response(format!(
Ok(None) => build_admin_provider_not_found_response(format!(
"Provider {provider_id} 不存在"
)),
Err(_) => build_admin_providers_data_unavailable_response(),
},
));
}
@@ -1,5 +1,6 @@
use super::value::build_admin_provider_summary_value;
use crate::handlers::admin::request::AdminAppState;
use crate::GatewayError;
use aether_data_contracts::repository::provider_catalog::{
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
};
@@ -10,19 +11,23 @@ use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) async fn build_admin_provider_summary_payload(
state: &AdminAppState<'_>,
provider_id: &str,
) -> Option<serde_json::Value> {
) -> Result<Option<serde_json::Value>, GatewayError> {
let state = state.as_ref();
if !state.has_provider_catalog_data_reader() {
return None;
return Err(GatewayError::Internal(
"Admin provider catalog data unavailable".to_string(),
));
}
let provider_ids = vec![provider_id.to_string()];
let provider = state
let Some(provider) = state
.read_provider_catalog_providers_by_ids(&provider_ids)
.await
.ok()?
.await?
.into_iter()
.next()?;
.next()
else {
return Ok(None);
};
let (
endpoints_result,
keys_result,
@@ -36,8 +41,8 @@ pub(crate) async fn build_admin_provider_summary_payload(
state.list_provider_model_stats(&provider_ids),
state.list_active_global_model_ids_by_provider_ids(&provider_ids),
);
let endpoints = endpoints_result.ok().unwrap_or_default();
let keys = keys_result.ok().unwrap_or_default();
let endpoints = endpoints_result?;
let keys = keys_result?;
let quota_snapshot = quota_snapshot_result.ok().flatten();
let model_stats = model_stats_result
.ok()
@@ -57,7 +62,7 @@ pub(crate) async fn build_admin_provider_summary_payload(
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Some(build_admin_provider_summary_value(
Ok(Some(build_admin_provider_summary_value(
&provider,
&endpoints,
&keys,
@@ -65,7 +70,7 @@ pub(crate) async fn build_admin_provider_summary_payload(
model_stats.as_ref(),
active_global_model_ids,
now_unix_secs,
))
)))
}
pub(crate) async fn build_admin_providers_summary_payload(
@@ -94,11 +99,7 @@ pub(crate) async fn build_admin_providers_summary_payload(
normalized_api_format != "all" && !normalized_api_format.is_empty();
let requires_model_filter = normalized_model_id != "all" && !normalized_model_id.is_empty();
let mut providers = state
.list_provider_catalog_providers(false)
.await
.ok()
.unwrap_or_default();
let mut providers = state.list_provider_catalog_providers(false).await.ok()?;
let all_provider_ids = providers
.iter()
.map(|provider| provider.id.clone())
@@ -109,8 +110,7 @@ pub(crate) async fn build_admin_providers_summary_payload(
state
.list_provider_catalog_endpoints_by_provider_ids(&all_provider_ids)
.await
.ok()
.unwrap_or_default()
.ok()?
};
let active_global_model_refs = if !requires_model_filter || all_provider_ids.is_empty() {
Vec::new()
@@ -202,8 +202,8 @@ pub(crate) async fn build_admin_providers_summary_payload(
state.list_active_global_model_ids_by_provider_ids(&provider_ids),
);
(
endpoints_result.ok().unwrap_or_default(),
keys_result.ok().unwrap_or_default(),
endpoints_result.ok()?,
keys_result.ok()?,
model_stats_result.ok().unwrap_or_default(),
active_global_model_refs_result.ok().unwrap_or_default(),
)
@@ -95,8 +95,11 @@ pub(crate) fn build_admin_provider_summary_value(
let scores = endpoint_keys
.iter()
.filter(|key| endpoint.is_active && key.is_active)
.filter_map(|key| provider_key_health_score(key, &endpoint.api_format))
.filter(|score| score.is_finite())
.map(|key| {
provider_key_health_score(key, &endpoint.api_format)
.filter(|score| score.is_finite())
.unwrap_or(1.0)
})
.collect::<Vec<_>>();
let health_score =
(!scores.is_empty()).then(|| scores.iter().sum::<f64>() / scores.len() as f64);
@@ -141,7 +141,7 @@ impl<'a> AdminAppState<'a> {
pub(crate) async fn build_admin_provider_summary_payload(
&self,
provider_id: &str,
) -> Option<serde_json::Value> {
) -> Result<Option<serde_json::Value>, GatewayError> {
crate::handlers::admin::provider::summary::build_admin_provider_summary_payload(
self,
provider_id,
@@ -3074,6 +3074,45 @@ mod tests {
.expect("key transport should build")
}
#[test]
fn admin_provider_key_health_response_preserves_v0_7_13_defaults() {
let state = AppState::new().expect("gateway should build");
for (health, expected_score) in [
(None, json!(1.0)),
(Some(json!({})), json!(1.0)),
(
Some(json!({"openai:chat": {"consecutive_failures": 0}})),
json!(1.0),
),
(
Some(json!({"openai:chat": {"health_score": 0.0}})),
json!(0.0),
),
(
Some(json!({"openai:chat": {"health_score": 1.0}})),
json!(1.0),
),
(
Some(json!({
"openai:chat": {"health_score": 0.25},
"openai:responses": {"health_score": 0.75},
})),
json!(0.25),
),
] {
let mut key = sample_catalog_key();
key.health_by_format = health;
let payload = build_admin_provider_key_response(
&state,
&key,
"openai",
&["openai:chat".to_string()],
1_000,
);
assert_eq!(payload["health_score"], expected_score);
}
}
#[test]
fn responses_key_scope_covers_search_in_one_direction() {
let mut responses_key = sample_catalog_key();