mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
Remove ranked minimal selection compatibility helper
This commit is contained in:
@@ -4,8 +4,8 @@ use aether_scheduler_core::{
|
|||||||
auth_constraints_allow_api_format, collect_global_model_names_for_required_capability,
|
auth_constraints_allow_api_format, collect_global_model_names_for_required_capability,
|
||||||
enumerate_minimal_candidate_selection, normalize_api_format,
|
enumerate_minimal_candidate_selection, normalize_api_format,
|
||||||
resolve_requested_global_model_name, row_supports_requested_model,
|
resolve_requested_global_model_name, row_supports_requested_model,
|
||||||
BuildMinimalCandidateSelectionInput, SchedulerAuthConstraints,
|
EnumerateMinimalCandidateSelectionInput, SchedulerAuthConstraints,
|
||||||
SchedulerMinimalCandidateSelectionCandidate, SchedulerPriorityMode,
|
SchedulerMinimalCandidateSelectionCandidate,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
@@ -77,7 +77,7 @@ pub(crate) async fn enumerate_minimal_candidate_selection_with_required_capabili
|
|||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
};
|
};
|
||||||
let auth_constraints = auth_snapshot.map(auth_snapshot_constraints);
|
let auth_constraints = auth_snapshot.map(auth_snapshot_constraints);
|
||||||
enumerate_minimal_candidate_selection(BuildMinimalCandidateSelectionInput {
|
enumerate_minimal_candidate_selection(EnumerateMinimalCandidateSelectionInput {
|
||||||
rows,
|
rows,
|
||||||
normalized_api_format: &normalized_api_format,
|
normalized_api_format: &normalized_api_format,
|
||||||
requested_model_name,
|
requested_model_name,
|
||||||
@@ -85,8 +85,6 @@ pub(crate) async fn enumerate_minimal_candidate_selection_with_required_capabili
|
|||||||
require_streaming,
|
require_streaming,
|
||||||
required_capabilities,
|
required_capabilities,
|
||||||
auth_constraints: auth_constraints.as_ref(),
|
auth_constraints: auth_constraints.as_ref(),
|
||||||
affinity_key: None,
|
|
||||||
priority_mode: SchedulerPriorityMode::Provider,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,12 +170,6 @@ pub(crate) async fn read_global_model_names_for_api_format(
|
|||||||
Ok(model_names.into_iter().collect())
|
Ok(model_names.into_iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_snapshot_affinity_key(auth_snapshot: Option<&GatewayAuthApiKeySnapshot>) -> Option<&str> {
|
|
||||||
auth_snapshot
|
|
||||||
.map(|snapshot| snapshot.api_key_id.trim())
|
|
||||||
.filter(|value| !value.is_empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn auth_snapshot_constraints(snapshot: &GatewayAuthApiKeySnapshot) -> SchedulerAuthConstraints {
|
fn auth_snapshot_constraints(snapshot: &GatewayAuthApiKeySnapshot) -> SchedulerAuthConstraints {
|
||||||
SchedulerAuthConstraints {
|
SchedulerAuthConstraints {
|
||||||
allowed_providers: snapshot
|
allowed_providers: snapshot
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ use aether_data_contracts::repository::video_tasks::{
|
|||||||
UpsertVideoTask, VideoTaskLookupKey, VideoTaskStatus, VideoTaskWriteRepository,
|
UpsertVideoTask, VideoTaskLookupKey, VideoTaskStatus, VideoTaskWriteRepository,
|
||||||
};
|
};
|
||||||
use aether_scheduler_core::{
|
use aether_scheduler_core::{
|
||||||
build_ranked_minimal_candidate_selection, BuildMinimalCandidateSelectionInput,
|
enumerate_minimal_candidate_selection, EnumerateMinimalCandidateSelectionInput,
|
||||||
SchedulerAuthConstraints, SchedulerPriorityMode,
|
SchedulerAuthConstraints,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
@@ -622,18 +622,17 @@ async fn data_state_reads_minimal_candidate_selection_with_auth_filters() {
|
|||||||
.map(|items| items.to_vec()),
|
.map(|items| items.to_vec()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let selection = build_ranked_minimal_candidate_selection(BuildMinimalCandidateSelectionInput {
|
let selection =
|
||||||
rows,
|
enumerate_minimal_candidate_selection(EnumerateMinimalCandidateSelectionInput {
|
||||||
normalized_api_format: "openai:chat",
|
rows,
|
||||||
requested_model_name: "gpt-4.1",
|
normalized_api_format: "openai:chat",
|
||||||
resolved_global_model_name: "gpt-4.1",
|
requested_model_name: "gpt-4.1",
|
||||||
require_streaming: false,
|
resolved_global_model_name: "gpt-4.1",
|
||||||
required_capabilities: None,
|
require_streaming: false,
|
||||||
auth_constraints: Some(&auth_constraints),
|
required_capabilities: None,
|
||||||
affinity_key: Some(auth_snapshot.api_key_id.as_str()),
|
auth_constraints: Some(&auth_constraints),
|
||||||
priority_mode: SchedulerPriorityMode::Provider,
|
})
|
||||||
})
|
.expect("selection should read");
|
||||||
.expect("selection should read");
|
|
||||||
|
|
||||||
assert_eq!(selection.len(), 2);
|
assert_eq!(selection.len(), 2);
|
||||||
assert_eq!(selection[0].provider_id, "provider-1");
|
assert_eq!(selection[0].provider_id, "provider-1");
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ use aether_data_contracts::repository::candidates::{
|
|||||||
RequestCandidateStatus, StoredRequestCandidate,
|
RequestCandidateStatus, StoredRequestCandidate,
|
||||||
};
|
};
|
||||||
use aether_scheduler_core::{
|
use aether_scheduler_core::{
|
||||||
build_ranked_minimal_candidate_selection, BuildMinimalCandidateSelectionInput,
|
apply_scheduler_candidate_ranking, enumerate_minimal_candidate_selection,
|
||||||
SchedulerMinimalCandidateSelectionCandidate, SchedulerPriorityMode,
|
EnumerateMinimalCandidateSelectionInput, SchedulerMinimalCandidateSelectionCandidate,
|
||||||
|
SchedulerPriorityMode, SchedulerRankableCandidate, SchedulerRankingContext,
|
||||||
|
SchedulerRankingMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::cache::SchedulerAffinityTarget;
|
use crate::cache::SchedulerAffinityTarget;
|
||||||
@@ -94,18 +96,36 @@ async fn same_priority_candidates_are_distributed_by_affinity_key() {
|
|||||||
.await
|
.await
|
||||||
.expect("selection rows should read")
|
.expect("selection rows should read")
|
||||||
.expect("selection rows should match requested model");
|
.expect("selection rows should match requested model");
|
||||||
let selection = build_ranked_minimal_candidate_selection(BuildMinimalCandidateSelectionInput {
|
let mut selection =
|
||||||
rows,
|
enumerate_minimal_candidate_selection(EnumerateMinimalCandidateSelectionInput {
|
||||||
normalized_api_format: "openai:chat",
|
rows,
|
||||||
requested_model_name: "gpt-4.1",
|
normalized_api_format: "openai:chat",
|
||||||
resolved_global_model_name: "gpt-4.1",
|
requested_model_name: "gpt-4.1",
|
||||||
require_streaming: false,
|
resolved_global_model_name: "gpt-4.1",
|
||||||
required_capabilities: None,
|
require_streaming: false,
|
||||||
auth_constraints: None,
|
required_capabilities: None,
|
||||||
affinity_key: Some(auth_snapshot.api_key_id.as_str()),
|
auth_constraints: None,
|
||||||
priority_mode: SchedulerPriorityMode::Provider,
|
})
|
||||||
})
|
.expect("selection should succeed");
|
||||||
.expect("selection should succeed");
|
let rankables = selection
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, candidate)| {
|
||||||
|
SchedulerRankableCandidate::from_candidate(candidate, index).with_affinity_hash(Some(
|
||||||
|
candidate_affinity_hash(auth_snapshot.api_key_id.as_str(), candidate),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
apply_scheduler_candidate_ranking(
|
||||||
|
&mut selection,
|
||||||
|
&rankables,
|
||||||
|
SchedulerRankingContext {
|
||||||
|
priority_mode: SchedulerPriorityMode::Provider,
|
||||||
|
ranking_mode: SchedulerRankingMode::CacheAffinity,
|
||||||
|
include_health: false,
|
||||||
|
load_balance_seed: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(selection.len(), 2);
|
assert_eq!(selection.len(), 2);
|
||||||
|
|
||||||
|
|||||||
@@ -481,30 +481,9 @@ fn scheduler_candidate_runtime_paths_depend_on_scheduler_core_and_state_trait()
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
workspace_file_exists("crates/aether-scheduler-core/src/candidate/selection.rs"),
|
!workspace_file_exists("crates/aether-scheduler-core/src/candidate/selection.rs"),
|
||||||
"core candidate/selection.rs should host legacy minimal selection compatibility helper"
|
"core candidate/selection.rs compatibility helper should be removed"
|
||||||
);
|
);
|
||||||
let core_candidate_selection =
|
|
||||||
read_workspace_file("crates/aether-scheduler-core/src/candidate/selection.rs");
|
|
||||||
for expected in [
|
|
||||||
"build_ranked_minimal_candidate_selection",
|
|
||||||
"apply_scheduler_candidate_ranking",
|
|
||||||
] {
|
|
||||||
assert!(
|
|
||||||
core_candidate_selection.contains(expected),
|
|
||||||
"core candidate/selection.rs should host compatibility helper {expected}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for forbidden in [
|
|
||||||
"collect_selectable_candidates_from_keys",
|
|
||||||
"reorder_candidates_by_scheduler_health",
|
|
||||||
"compare_candidates_by_priority_mode",
|
|
||||||
] {
|
|
||||||
assert!(
|
|
||||||
!core_candidate_selection.contains(forbidden),
|
|
||||||
"core candidate/selection.rs should not keep removed legacy helper {forbidden}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let affinity_cache = read_workspace_file("apps/aether-gateway/src/cache/scheduler_affinity.rs");
|
let affinity_cache = read_workspace_file("apps/aether-gateway/src/cache/scheduler_affinity.rs");
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -65,26 +65,7 @@ pub fn requested_capability_priority_for_candidate(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn enabled_required_capabilities(
|
fn requested_capability_priority_for_candidate_descriptors<'a, I>(
|
||||||
required_capabilities: Option<&serde_json::Value>,
|
|
||||||
) -> Vec<RequiredCapabilityDescriptor<'_>> {
|
|
||||||
let Some(required_capabilities) = required_capabilities.and_then(serde_json::Value::as_object)
|
|
||||||
else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
|
|
||||||
required_capabilities
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(capability, value)| {
|
|
||||||
requested_capability_is_enabled(value).then_some(RequiredCapabilityDescriptor {
|
|
||||||
name: capability.as_str(),
|
|
||||||
compatible: requested_capability_is_compatible(capability),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn requested_capability_priority_for_candidate_descriptors<'a, I>(
|
|
||||||
required_capabilities: I,
|
required_capabilities: I,
|
||||||
candidate: &SchedulerMinimalCandidateSelectionCandidate,
|
candidate: &SchedulerMinimalCandidateSelectionCandidate,
|
||||||
) -> (u32, u32)
|
) -> (u32, u32)
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use aether_data_contracts::repository::candidate_selection::StoredMinimalCandida
|
|||||||
use aether_data_contracts::DataLayerError;
|
use aether_data_contracts::DataLayerError;
|
||||||
|
|
||||||
use super::types::{
|
use super::types::{
|
||||||
BuildMinimalCandidateSelectionInput, SchedulerMinimalCandidateSelectionCandidate,
|
EnumerateMinimalCandidateSelectionInput, SchedulerMinimalCandidateSelectionCandidate,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn enumerate_minimal_candidate_selection(
|
pub fn enumerate_minimal_candidate_selection(
|
||||||
input: BuildMinimalCandidateSelectionInput<'_>,
|
input: EnumerateMinimalCandidateSelectionInput<'_>,
|
||||||
) -> Result<Vec<SchedulerMinimalCandidateSelectionCandidate>, DataLayerError> {
|
) -> Result<Vec<SchedulerMinimalCandidateSelectionCandidate>, DataLayerError> {
|
||||||
let BuildMinimalCandidateSelectionInput {
|
let EnumerateMinimalCandidateSelectionInput {
|
||||||
rows,
|
rows,
|
||||||
normalized_api_format,
|
normalized_api_format,
|
||||||
requested_model_name,
|
requested_model_name,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
pub mod capability;
|
pub mod capability;
|
||||||
pub mod enumeration;
|
pub mod enumeration;
|
||||||
pub mod selectability;
|
pub mod selectability;
|
||||||
pub mod selection;
|
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use capability::{
|
pub use capability::{
|
||||||
@@ -14,9 +13,8 @@ pub use selectability::{
|
|||||||
auth_api_key_concurrency_limit_reached, candidate_is_selectable_with_runtime_state,
|
auth_api_key_concurrency_limit_reached, candidate_is_selectable_with_runtime_state,
|
||||||
candidate_runtime_skip_reason_with_state, CandidateRuntimeSelectabilityInput,
|
candidate_runtime_skip_reason_with_state, CandidateRuntimeSelectabilityInput,
|
||||||
};
|
};
|
||||||
pub use selection::build_ranked_minimal_candidate_selection;
|
|
||||||
pub use types::{
|
pub use types::{
|
||||||
BuildMinimalCandidateSelectionInput, SchedulerMinimalCandidateSelectionCandidate,
|
EnumerateMinimalCandidateSelectionInput, SchedulerMinimalCandidateSelectionCandidate,
|
||||||
SchedulerPriorityMode,
|
SchedulerPriorityMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,11 +31,10 @@ mod tests {
|
|||||||
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
|
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
auth_api_key_concurrency_limit_reached, build_ranked_minimal_candidate_selection,
|
auth_api_key_concurrency_limit_reached, candidate_is_selectable_with_runtime_state,
|
||||||
candidate_is_selectable_with_runtime_state, candidate_supports_required_capability,
|
candidate_supports_required_capability, collect_global_model_names_for_required_capability,
|
||||||
collect_global_model_names_for_required_capability, BuildMinimalCandidateSelectionInput,
|
CandidateRuntimeSelectabilityInput, EnumerateMinimalCandidateSelectionInput,
|
||||||
CandidateRuntimeSelectabilityInput, SchedulerMinimalCandidateSelectionCandidate,
|
SchedulerMinimalCandidateSelectionCandidate,
|
||||||
SchedulerPriorityMode,
|
|
||||||
};
|
};
|
||||||
use crate::SchedulerAuthConstraints;
|
use crate::SchedulerAuthConstraints;
|
||||||
|
|
||||||
@@ -176,7 +173,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn builds_ranked_minimal_candidate_selection_with_auth_constraints() {
|
fn enumerates_minimal_candidate_selection_with_auth_constraints() {
|
||||||
let mut disallowed = sample_row("2");
|
let mut disallowed = sample_row("2");
|
||||||
disallowed.provider_id = "provider-blocked".to_string();
|
disallowed.provider_id = "provider-blocked".to_string();
|
||||||
disallowed.provider_name = "Blocked".to_string();
|
disallowed.provider_name = "Blocked".to_string();
|
||||||
@@ -187,7 +184,7 @@ mod tests {
|
|||||||
allowed_models: Some(vec!["gpt-5".to_string()]),
|
allowed_models: Some(vec!["gpt-5".to_string()]),
|
||||||
};
|
};
|
||||||
let candidates =
|
let candidates =
|
||||||
build_ranked_minimal_candidate_selection(BuildMinimalCandidateSelectionInput {
|
super::enumerate_minimal_candidate_selection(EnumerateMinimalCandidateSelectionInput {
|
||||||
rows: vec![sample_row("1"), disallowed],
|
rows: vec![sample_row("1"), disallowed],
|
||||||
normalized_api_format: "openai:chat",
|
normalized_api_format: "openai:chat",
|
||||||
requested_model_name: "gpt-5",
|
requested_model_name: "gpt-5",
|
||||||
@@ -195,8 +192,6 @@ mod tests {
|
|||||||
require_streaming: false,
|
require_streaming: false,
|
||||||
required_capabilities: None,
|
required_capabilities: None,
|
||||||
auth_constraints: Some(&constraints),
|
auth_constraints: Some(&constraints),
|
||||||
affinity_key: None,
|
|
||||||
priority_mode: SchedulerPriorityMode::Provider,
|
|
||||||
})
|
})
|
||||||
.expect("candidate selection should build");
|
.expect("candidate selection should build");
|
||||||
|
|
||||||
@@ -213,7 +208,7 @@ mod tests {
|
|||||||
earlier_priority.provider_priority = 0;
|
earlier_priority.provider_priority = 0;
|
||||||
|
|
||||||
let candidates =
|
let candidates =
|
||||||
super::enumerate_minimal_candidate_selection(BuildMinimalCandidateSelectionInput {
|
super::enumerate_minimal_candidate_selection(EnumerateMinimalCandidateSelectionInput {
|
||||||
rows: vec![later_priority, earlier_priority],
|
rows: vec![later_priority, earlier_priority],
|
||||||
normalized_api_format: "openai:chat",
|
normalized_api_format: "openai:chat",
|
||||||
requested_model_name: "gpt-5",
|
requested_model_name: "gpt-5",
|
||||||
@@ -221,8 +216,6 @@ mod tests {
|
|||||||
require_streaming: false,
|
require_streaming: false,
|
||||||
required_capabilities: None,
|
required_capabilities: None,
|
||||||
auth_constraints: None,
|
auth_constraints: None,
|
||||||
affinity_key: None,
|
|
||||||
priority_mode: SchedulerPriorityMode::Provider,
|
|
||||||
})
|
})
|
||||||
.expect("candidate enumeration should build");
|
.expect("candidate enumeration should build");
|
||||||
|
|
||||||
@@ -255,8 +248,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ranked_minimal_candidate_selection_prefers_matching_requested_capabilities_before_priority()
|
fn requested_capability_priority_counts_missing_compatible_capabilities() {
|
||||||
{
|
|
||||||
let mut missing_capability = sample_row("1");
|
let mut missing_capability = sample_row("1");
|
||||||
missing_capability.key_capabilities = Some(serde_json::json!({"cache_1h": false}));
|
missing_capability.key_capabilities = Some(serde_json::json!({"cache_1h": false}));
|
||||||
missing_capability.provider_priority = 0;
|
missing_capability.provider_priority = 0;
|
||||||
@@ -267,7 +259,7 @@ mod tests {
|
|||||||
|
|
||||||
let required_capabilities = serde_json::json!({"cache_1h": true});
|
let required_capabilities = serde_json::json!({"cache_1h": true});
|
||||||
let candidates =
|
let candidates =
|
||||||
build_ranked_minimal_candidate_selection(BuildMinimalCandidateSelectionInput {
|
super::enumerate_minimal_candidate_selection(EnumerateMinimalCandidateSelectionInput {
|
||||||
rows: vec![missing_capability, matching_capability],
|
rows: vec![missing_capability, matching_capability],
|
||||||
normalized_api_format: "openai:chat",
|
normalized_api_format: "openai:chat",
|
||||||
requested_model_name: "gpt-5",
|
requested_model_name: "gpt-5",
|
||||||
@@ -275,14 +267,20 @@ mod tests {
|
|||||||
require_streaming: false,
|
require_streaming: false,
|
||||||
required_capabilities: Some(&required_capabilities),
|
required_capabilities: Some(&required_capabilities),
|
||||||
auth_constraints: None,
|
auth_constraints: None,
|
||||||
affinity_key: None,
|
|
||||||
priority_mode: SchedulerPriorityMode::Provider,
|
|
||||||
})
|
})
|
||||||
.expect("candidate selection should build");
|
.expect("candidate selection should build");
|
||||||
|
let priority = candidates
|
||||||
|
.iter()
|
||||||
|
.map(|candidate| {
|
||||||
|
super::requested_capability_priority_for_candidate(
|
||||||
|
Some(&required_capabilities),
|
||||||
|
candidate,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(candidates.len(), 2);
|
assert_eq!(candidates.len(), 2);
|
||||||
assert_eq!(candidates[0].key_id, "key-2");
|
assert_eq!(priority, vec![(0, 1), (0, 0)]);
|
||||||
assert_eq!(candidates[1].key_id, "key-1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
use aether_data_contracts::DataLayerError;
|
|
||||||
|
|
||||||
use super::capability::{
|
|
||||||
enabled_required_capabilities, requested_capability_priority_for_candidate_descriptors,
|
|
||||||
};
|
|
||||||
use super::enumeration::enumerate_minimal_candidate_selection;
|
|
||||||
use super::types::{
|
|
||||||
BuildMinimalCandidateSelectionInput, SchedulerMinimalCandidateSelectionCandidate,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn build_ranked_minimal_candidate_selection(
|
|
||||||
input: BuildMinimalCandidateSelectionInput<'_>,
|
|
||||||
) -> Result<Vec<SchedulerMinimalCandidateSelectionCandidate>, DataLayerError> {
|
|
||||||
let priority_mode = input.priority_mode;
|
|
||||||
let affinity_key = input.affinity_key.map(str::to_string);
|
|
||||||
let required_capabilities = enabled_required_capabilities(input.required_capabilities);
|
|
||||||
let mut candidates = enumerate_minimal_candidate_selection(input)?;
|
|
||||||
let rankables = candidates
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, candidate)| {
|
|
||||||
crate::SchedulerRankableCandidate::from_candidate(candidate, index)
|
|
||||||
.with_capability_priority(requested_capability_priority_for_candidate_descriptors(
|
|
||||||
required_capabilities.iter().copied(),
|
|
||||||
candidate,
|
|
||||||
))
|
|
||||||
.with_affinity_hash(
|
|
||||||
affinity_key
|
|
||||||
.as_deref()
|
|
||||||
.map(|key| crate::candidate_affinity_hash(key, candidate)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
crate::apply_scheduler_candidate_ranking(
|
|
||||||
&mut candidates,
|
|
||||||
&rankables,
|
|
||||||
crate::SchedulerRankingContext {
|
|
||||||
priority_mode,
|
|
||||||
ranking_mode: crate::SchedulerRankingMode::CacheAffinity,
|
|
||||||
include_health: false,
|
|
||||||
load_balance_seed: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(candidates)
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,7 @@ pub struct SchedulerMinimalCandidateSelectionCandidate {
|
|||||||
pub mapping_matched_model: Option<String>,
|
pub mapping_matched_model: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BuildMinimalCandidateSelectionInput<'a> {
|
pub struct EnumerateMinimalCandidateSelectionInput<'a> {
|
||||||
pub rows: Vec<StoredMinimalCandidateSelectionRow>,
|
pub rows: Vec<StoredMinimalCandidateSelectionRow>,
|
||||||
pub normalized_api_format: &'a str,
|
pub normalized_api_format: &'a str,
|
||||||
pub requested_model_name: &'a str,
|
pub requested_model_name: &'a str,
|
||||||
@@ -36,6 +36,4 @@ pub struct BuildMinimalCandidateSelectionInput<'a> {
|
|||||||
pub require_streaming: bool,
|
pub require_streaming: bool,
|
||||||
pub required_capabilities: Option<&'a serde_json::Value>,
|
pub required_capabilities: Option<&'a serde_json::Value>,
|
||||||
pub auth_constraints: Option<&'a crate::SchedulerAuthConstraints>,
|
pub auth_constraints: Option<&'a crate::SchedulerAuthConstraints>,
|
||||||
pub affinity_key: Option<&'a str>,
|
|
||||||
pub priority_mode: SchedulerPriorityMode,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ pub use auth::{
|
|||||||
auth_constraints_allow_provider, provider_matches_allowed_value, SchedulerAuthConstraints,
|
auth_constraints_allow_provider, provider_matches_allowed_value, SchedulerAuthConstraints,
|
||||||
};
|
};
|
||||||
pub use candidate::{
|
pub use candidate::{
|
||||||
auth_api_key_concurrency_limit_reached, build_ranked_minimal_candidate_selection,
|
auth_api_key_concurrency_limit_reached, candidate_is_selectable_with_runtime_state,
|
||||||
candidate_is_selectable_with_runtime_state, candidate_runtime_skip_reason_with_state,
|
candidate_runtime_skip_reason_with_state, candidate_supports_required_capability,
|
||||||
candidate_supports_required_capability, collect_global_model_names_for_required_capability,
|
collect_global_model_names_for_required_capability, enumerate_minimal_candidate_selection,
|
||||||
enumerate_minimal_candidate_selection, requested_capability_priority_for_candidate,
|
requested_capability_priority_for_candidate, CandidateRuntimeSelectabilityInput,
|
||||||
BuildMinimalCandidateSelectionInput, CandidateRuntimeSelectabilityInput,
|
EnumerateMinimalCandidateSelectionInput, SchedulerMinimalCandidateSelectionCandidate,
|
||||||
SchedulerMinimalCandidateSelectionCandidate, SchedulerPriorityMode,
|
SchedulerPriorityMode,
|
||||||
};
|
};
|
||||||
pub use health::{
|
pub use health::{
|
||||||
aggregate_provider_key_health_score, count_recent_active_requests_for_api_key,
|
aggregate_provider_key_health_score, count_recent_active_requests_for_api_key,
|
||||||
|
|||||||
@@ -209,6 +209,26 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn capability_priority_precedes_provider_priority() {
|
||||||
|
let matching_capability = candidate("matching", 10, 0, None);
|
||||||
|
let mut missing_compatible_capability = candidate("missing", 0, 0, None);
|
||||||
|
missing_compatible_capability.capability_priority = (0, 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ranked_ids(
|
||||||
|
&[missing_compatible_capability, matching_capability],
|
||||||
|
SchedulerRankingContext {
|
||||||
|
priority_mode: SchedulerPriorityMode::Provider,
|
||||||
|
ranking_mode: SchedulerRankingMode::FixedOrder,
|
||||||
|
include_health: false,
|
||||||
|
load_balance_seed: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
vec!["provider-matching", "provider-missing"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cache_affinity_can_promote_cached_candidate_and_reports_reason() {
|
fn cache_affinity_can_promote_cached_candidate_and_reports_reason() {
|
||||||
let high_priority = candidate("high", 0, 0, Some(0));
|
let high_priority = candidate("high", 0, 0, Some(0));
|
||||||
|
|||||||
Reference in New Issue
Block a user