mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Hide format_conversion_disabled skips when exact-format candidate shares the same key
Also switch failover test to surface an auth failure instead of a 502 upstream error.
This commit is contained in:
@@ -1196,6 +1196,62 @@ mod tests {
|
||||
assert_eq!(skipped[0].skip_reason, "format_conversion_disabled");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn realtime_gate_hides_cross_format_disablement_when_same_key_has_exact_endpoint() {
|
||||
let provider_catalog = InMemoryProviderCatalogReadRepository::seed(
|
||||
vec![sample_provider_with_options("provider-shared", false, 0)],
|
||||
vec![
|
||||
sample_endpoint_for_provider("provider-shared", "endpoint-exact", "openai:chat"),
|
||||
sample_endpoint_for_provider("provider-shared", "endpoint-cross", "claude:chat"),
|
||||
],
|
||||
vec![sample_key_for_provider_with_options(
|
||||
"provider-shared",
|
||||
"key-shared",
|
||||
"",
|
||||
true,
|
||||
Some(json!(["openai:chat", "claude:chat"])),
|
||||
None,
|
||||
)],
|
||||
);
|
||||
let data_state = GatewayDataState::with_provider_transport_reader_for_tests(
|
||||
std::sync::Arc::new(provider_catalog),
|
||||
"development-key",
|
||||
);
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(data_state);
|
||||
|
||||
let (ranked, skipped) = filter_and_rank_local_execution_candidates(
|
||||
PlannerAppState::new(&state),
|
||||
vec![
|
||||
sample_priority_candidate(
|
||||
"provider-shared",
|
||||
"endpoint-exact",
|
||||
"key-shared",
|
||||
"openai:chat",
|
||||
Some(0),
|
||||
0,
|
||||
),
|
||||
sample_priority_candidate(
|
||||
"provider-shared",
|
||||
"endpoint-cross",
|
||||
"key-shared",
|
||||
"claude:chat",
|
||||
Some(0),
|
||||
0,
|
||||
),
|
||||
],
|
||||
"openai:chat",
|
||||
"gpt-4.1",
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(ranked.len(), 1);
|
||||
assert_eq!(ranked[0].candidate.endpoint_id, "endpoint-exact");
|
||||
assert!(skipped.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn realtime_gate_allows_cross_format_candidates_when_endpoint_acceptance_is_enabled() {
|
||||
let mut endpoint_cross =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use tracing::warn;
|
||||
|
||||
use aether_scheduler_core::SchedulerMinimalCandidateSelectionCandidate;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::ai_pipeline::{GatewayProviderTransportSnapshot, PlannerAppState};
|
||||
|
||||
@@ -92,6 +93,7 @@ where
|
||||
{
|
||||
let mut selectable = Vec::with_capacity(candidates.len());
|
||||
let mut skipped = Vec::new();
|
||||
let normalized_client_api_format = client_api_format.trim().to_ascii_lowercase();
|
||||
|
||||
for candidate in candidates {
|
||||
let Some(transport) = read_candidate_transport_snapshot(state, &candidate).await else {
|
||||
@@ -118,6 +120,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let exact_selectable_keys = selectable
|
||||
.iter()
|
||||
.filter(|candidate| {
|
||||
candidate
|
||||
.provider_api_format
|
||||
.eq_ignore_ascii_case(normalized_client_api_format.as_str())
|
||||
})
|
||||
.map(|candidate| {
|
||||
(
|
||||
candidate.candidate.provider_id.clone(),
|
||||
candidate.candidate.key_id.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
skipped.retain(|candidate| {
|
||||
candidate.skip_reason != "format_conversion_disabled"
|
||||
|| !exact_selectable_keys.contains(&(
|
||||
candidate.candidate.provider_id.clone(),
|
||||
candidate.candidate.key_id.clone(),
|
||||
))
|
||||
});
|
||||
|
||||
let ranked = rank_eligible_local_execution_candidates(
|
||||
state,
|
||||
selectable,
|
||||
|
||||
@@ -675,7 +675,7 @@ async fn gateway_surfaces_local_execution_runtime_miss_reason_when_all_openai_ch
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_retries_next_local_openai_chat_sync_candidate_with_local_failover_only() {
|
||||
async fn gateway_retries_next_local_openai_chat_sync_candidate_after_auth_failure() {
|
||||
#[derive(Debug, Clone)]
|
||||
struct SeenExecutionRuntimeSyncRequest {
|
||||
trace_id: String,
|
||||
@@ -951,14 +951,14 @@ async fn gateway_retries_next_local_openai_chat_sync_candidate_with_local_failov
|
||||
if attempt == 1 {
|
||||
return Json(json!({
|
||||
"request_id": "trace-openai-chat-local-failover-123",
|
||||
"status_code": 502,
|
||||
"status_code": 401,
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"json_body": {
|
||||
"error": {
|
||||
"message": "primary unavailable"
|
||||
"message": "invalid auth token"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1135,10 +1135,10 @@ async fn gateway_retries_next_local_openai_chat_sync_candidate_with_local_failov
|
||||
assert_eq!(stored_candidates.len(), 2);
|
||||
assert_eq!(stored_candidates[0].candidate_index, 0);
|
||||
assert_eq!(stored_candidates[0].status, RequestCandidateStatus::Failed);
|
||||
assert_eq!(stored_candidates[0].status_code, Some(502));
|
||||
assert_eq!(stored_candidates[0].status_code, Some(401));
|
||||
assert_eq!(
|
||||
stored_candidates[0].error_message.as_deref(),
|
||||
Some("primary unavailable")
|
||||
Some("invalid auth token")
|
||||
);
|
||||
assert_eq!(stored_candidates[1].candidate_index, 1);
|
||||
assert_eq!(stored_candidates[1].status, RequestCandidateStatus::Success);
|
||||
|
||||
Reference in New Issue
Block a user