feat(routing): make routing profiles the sole scheduler policy source

Bootstrap an enabled system-default routing group from the legacy
scheduler config keys on startup, resolve the default ordering config
from that group before falling back to the legacy keys, and stop merging
keep_priority_on_conversion with the legacy flag when a policy is
resolved. Thread the policy-derived ordering config into candidate
preselection so it no longer reads system config independently.

Add per-API-format key priority overrides so a key serving several
formats keeps independent ordering, matching the legacy
global_priority_by_format semantics. Expose keep_priority_on_conversion
in the routing profile editor and read the effective policy in the
model routing preview, monitoring metrics and provider page badge.
This commit is contained in:
elky
2026-09-02 17:04:04 +08:00
parent 166236c2ee
commit 415b2da81b
32 changed files with 1001 additions and 128 deletions
@@ -4,11 +4,89 @@ use aether_data_contracts::repository::routing_profiles::{
StoredRoutingGroup, StoredRoutingGroupBinding, StoredRoutingGroupVersion,
UpdateRoutingGroupBindingRecord, UpdateRoutingGroupRecord,
};
use aether_routing_core::RoutingGroupConfig;
use std::sync::Arc;
use tracing::warn;
use super::{AppState, GatewayError};
const BOOTSTRAP_SYSTEM_DEFAULT_ROUTING_GROUP_NAME: &str = "system-default";
impl AppState {
/// Make sure an enabled system-default routing group exists.
///
/// When none exists, one is created from the legacy scheduler system-config
/// keys so that removing those keys later does not change behaviour. Returns
/// the created group, or `None` when nothing had to be created (no routing
/// storage, no writer, or a system default already exists).
pub async fn ensure_system_default_routing_group(
&self,
) -> Result<Option<StoredRoutingGroup>, std::io::Error> {
self.ensure_system_default_routing_group_inner()
.await
.map_err(|err| std::io::Error::other(format!("{err:?}")))
}
pub(crate) async fn ensure_system_default_routing_group_inner(
&self,
) -> Result<Option<StoredRoutingGroup>, GatewayError> {
if !self.has_routing_group_data_reader() {
return Ok(None);
}
if self
.find_routing_group(RoutingGroupLookupKey::SystemDefault)
.await?
.is_some()
{
return Ok(None);
}
if !self.has_routing_group_data_writer() {
warn!(
event_name = "routing_system_default_bootstrap_skipped",
log_type = "event",
"no system default routing group exists and routing storage is read-only; scheduler falls back to legacy system config"
);
return Ok(None);
}
let legacy = crate::scheduler::config::read_legacy_scheduler_ordering_config(self).await?;
let config = RoutingGroupConfig {
default_policy: legacy.to_routing_default_policy(),
..RoutingGroupConfig::default()
};
let config_json = serde_json::to_value(config)
.map_err(|err| GatewayError::Internal(format!("serialize routing config: {err}")))?;
let name = if self
.find_routing_group(RoutingGroupLookupKey::Name(
BOOTSTRAP_SYSTEM_DEFAULT_ROUTING_GROUP_NAME,
))
.await?
.is_some()
{
format!(
"{BOOTSTRAP_SYSTEM_DEFAULT_ROUTING_GROUP_NAME}-{}",
&uuid::Uuid::new_v4().simple().to_string()[..8]
)
} else {
BOOTSTRAP_SYSTEM_DEFAULT_ROUTING_GROUP_NAME.to_string()
};
let now = crate::clock::current_unix_secs() as i64;
self.create_routing_group(CreateRoutingGroupRecord {
id: uuid::Uuid::new_v4().to_string(),
name,
description: Some("自动从旧版调度配置迁移生成的系统默认策略".to_string()),
enabled: true,
is_system_default: true,
config_json,
version: 1,
created_at: now,
updated_at: now,
published_at: Some(now),
})
.await
}
pub(crate) fn has_routing_group_data_reader(&self) -> bool {
self.data.has_routing_group_reader()
}