mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor: extract provider pool abstractions
This commit is contained in:
@@ -92,180 +92,11 @@ fn admin_pool_reason_indicates_ban(reason: &str) -> bool {
|
||||
.any(|hint| normalized.contains(hint))
|
||||
}
|
||||
|
||||
fn admin_pool_metadata_bucket<'a>(
|
||||
upstream_metadata: Option<&'a Value>,
|
||||
provider_type: &str,
|
||||
) -> Option<&'a serde_json::Map<String, Value>> {
|
||||
upstream_metadata
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|metadata| metadata.get(&provider_type.trim().to_ascii_lowercase()))
|
||||
.and_then(Value::as_object)
|
||||
}
|
||||
|
||||
fn admin_pool_json_bool(value: Option<&Value>) -> Option<bool> {
|
||||
match value {
|
||||
Some(Value::Bool(value)) => Some(*value),
|
||||
Some(Value::String(value)) => match value.trim().to_ascii_lowercase().as_str() {
|
||||
"true" | "1" => Some(true),
|
||||
"false" | "0" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_pool_json_f64(value: Option<&Value>) -> Option<f64> {
|
||||
match value {
|
||||
Some(Value::Number(number)) => number.as_f64(),
|
||||
Some(Value::String(value)) => value.trim().parse::<f64>().ok(),
|
||||
_ => None,
|
||||
}
|
||||
.filter(|value| value.is_finite())
|
||||
}
|
||||
|
||||
fn admin_pool_quota_snapshot_matches_provider(
|
||||
quota_snapshot: &serde_json::Map<String, Value>,
|
||||
provider_type: &str,
|
||||
) -> bool {
|
||||
let normalized_provider_type = provider_type.trim().to_ascii_lowercase();
|
||||
match quota_snapshot
|
||||
.get("provider_type")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
Some(provider_type) => provider_type.eq_ignore_ascii_case(&normalized_provider_type),
|
||||
None => {
|
||||
admin_pool_json_bool(quota_snapshot.get("exhausted")) == Some(true)
|
||||
|| quota_snapshot
|
||||
.get("code")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|code| !code.trim().eq_ignore_ascii_case("unknown"))
|
||||
|| quota_snapshot
|
||||
.get("updated_at")
|
||||
.is_some_and(|value| !value.is_null())
|
||||
|| quota_snapshot
|
||||
.get("observed_at")
|
||||
.is_some_and(|value| !value.is_null())
|
||||
|| quota_snapshot
|
||||
.get("usage_ratio")
|
||||
.is_some_and(|value| !value.is_null())
|
||||
|| quota_snapshot
|
||||
.get("reset_seconds")
|
||||
.is_some_and(|value| !value.is_null())
|
||||
|| quota_snapshot
|
||||
.get("windows")
|
||||
.and_then(Value::as_array)
|
||||
.is_some_and(|windows| !windows.is_empty())
|
||||
|| quota_snapshot
|
||||
.get("credits")
|
||||
.and_then(Value::as_object)
|
||||
.is_some_and(|credits| !credits.is_empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn admin_pool_key_quota_snapshot<'a>(
|
||||
key: &'a StoredProviderCatalogKey,
|
||||
provider_type: &str,
|
||||
) -> Option<&'a serde_json::Map<String, Value>> {
|
||||
let quota_snapshot = key
|
||||
.status_snapshot
|
||||
.as_ref()
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|snapshot| snapshot.get("quota"))
|
||||
.and_then(Value::as_object)?;
|
||||
admin_pool_quota_snapshot_matches_provider(quota_snapshot, provider_type)
|
||||
.then_some(quota_snapshot)
|
||||
}
|
||||
|
||||
pub fn admin_pool_key_account_quota_exhausted(
|
||||
key: &StoredProviderCatalogKey,
|
||||
provider_type: &str,
|
||||
) -> bool {
|
||||
if let Some(exhausted) =
|
||||
admin_pool_key_quota_snapshot(key, provider_type).and_then(|quota_snapshot| {
|
||||
let exhausted = admin_pool_json_bool(quota_snapshot.get("exhausted"))?;
|
||||
if exhausted {
|
||||
let windows_max_ratio = quota_snapshot
|
||||
.get("windows")
|
||||
.and_then(Value::as_array)
|
||||
.filter(|w| !w.is_empty())
|
||||
.and_then(|windows| {
|
||||
windows
|
||||
.iter()
|
||||
.filter_map(Value::as_object)
|
||||
.filter_map(|w| w.get("used_ratio"))
|
||||
.filter_map(Value::as_f64)
|
||||
.max_by(f64::total_cmp)
|
||||
});
|
||||
if windows_max_ratio.is_some_and(|ratio| ratio < 1.0 - 1e-6) {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
Some(exhausted)
|
||||
})
|
||||
{
|
||||
return exhausted;
|
||||
}
|
||||
|
||||
let provider_type = provider_type.trim().to_ascii_lowercase();
|
||||
let Some(bucket) = admin_pool_metadata_bucket(key.upstream_metadata.as_ref(), &provider_type)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match provider_type.as_str() {
|
||||
"codex" => {
|
||||
if admin_pool_json_bool(bucket.get("credits_unlimited")) == Some(true) {
|
||||
return false;
|
||||
}
|
||||
let has_window_data = admin_pool_json_f64(bucket.get("primary_used_percent")).is_some()
|
||||
|| admin_pool_json_f64(bucket.get("secondary_used_percent")).is_some();
|
||||
if !has_window_data && admin_pool_json_bool(bucket.get("has_credits")) == Some(false) {
|
||||
return true;
|
||||
}
|
||||
admin_pool_json_f64(bucket.get("primary_used_percent"))
|
||||
.is_some_and(|value| value >= 100.0)
|
||||
|| admin_pool_json_f64(bucket.get("secondary_used_percent"))
|
||||
.is_some_and(|value| value >= 100.0)
|
||||
}
|
||||
"kiro" => {
|
||||
if admin_pool_json_f64(bucket.get("remaining")).is_some_and(|value| value <= 0.0) {
|
||||
return true;
|
||||
}
|
||||
if admin_pool_json_f64(bucket.get("usage_percentage"))
|
||||
.is_some_and(|value| value >= 100.0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
match (
|
||||
admin_pool_json_f64(bucket.get("usage_limit")),
|
||||
admin_pool_json_f64(bucket.get("current_usage")),
|
||||
) {
|
||||
(Some(limit), Some(current)) if limit > 0.0 => current >= limit,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
"chatgpt_web" => {
|
||||
if admin_pool_json_bool(bucket.get("image_quota_blocked")) == Some(true) {
|
||||
return true;
|
||||
}
|
||||
if admin_pool_json_f64(bucket.get("image_quota_remaining"))
|
||||
.is_some_and(|value| value <= 0.0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
match (
|
||||
admin_pool_json_f64(bucket.get("image_quota_total")),
|
||||
admin_pool_json_f64(bucket.get("image_quota_used")),
|
||||
) {
|
||||
(Some(limit), Some(used)) if limit > 0.0 => used >= limit,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
aether_provider_pool::provider_pool_key_account_quota_exhausted(key, provider_type)
|
||||
}
|
||||
|
||||
fn admin_pool_has_proxy(key: &StoredProviderCatalogKey) -> bool {
|
||||
@@ -937,148 +768,7 @@ pub fn build_admin_pool_key_payload(
|
||||
}
|
||||
|
||||
pub fn build_admin_pool_scheduling_presets_payload() -> Value {
|
||||
json!([
|
||||
{
|
||||
"name": "lru",
|
||||
"label": "LRU 轮转",
|
||||
"description": "最久未使用的 Key 优先",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": "distribution_mode",
|
||||
"evidence_hint": "依据 LRU 时间戳(最近未使用优先)",
|
||||
},
|
||||
{
|
||||
"name": "cache_affinity",
|
||||
"label": "缓存亲和",
|
||||
"description": "优先复用最近使用过的 Key,利用 Prompt Caching",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": "distribution_mode",
|
||||
"evidence_hint": "依据 LRU 时间戳(最近使用优先,与 LRU 轮转相反)",
|
||||
},
|
||||
{
|
||||
"name": "cost_first",
|
||||
"label": "成本优先",
|
||||
"description": "优先选择窗口消耗更低的账号",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据窗口成本/Token 用量,缺失时回退配额使用率",
|
||||
},
|
||||
{
|
||||
"name": "free_first",
|
||||
"label": "Free 优先",
|
||||
"description": "优先消耗 Free 账号(依赖 plan_type)",
|
||||
"providers": ["codex", "kiro"],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据 plan_type(Free 账号优先调度)",
|
||||
},
|
||||
{
|
||||
"name": "health_first",
|
||||
"label": "健康优先",
|
||||
"description": "优先选择健康分更高、失败更少的账号",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据 health_by_format 聚合分(含熔断/失败衰减)",
|
||||
},
|
||||
{
|
||||
"name": "latency_first",
|
||||
"label": "延迟优先",
|
||||
"description": "优先选择最近延迟更低的账号",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据号池延迟窗口均值(latency_window_seconds)",
|
||||
},
|
||||
{
|
||||
"name": "load_balance",
|
||||
"label": "负载均衡",
|
||||
"description": "随机分散 Key 使用,均匀分摊负载",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": "distribution_mode",
|
||||
"evidence_hint": "每次随机分值,实现完全均匀分散",
|
||||
},
|
||||
{
|
||||
"name": "plus_first",
|
||||
"label": "Plus 优先",
|
||||
"description": "优先消耗 Plus 账号(依赖 plan_type)",
|
||||
"providers": ["codex", "kiro"],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据 plan_type(Plus 账号优先调度)",
|
||||
},
|
||||
{
|
||||
"name": "pro_first",
|
||||
"label": "Pro 优先",
|
||||
"description": "优先消耗 Pro 账号(依赖 plan_type)",
|
||||
"providers": ["codex", "kiro"],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据 plan_type(Pro 账号优先调度)",
|
||||
},
|
||||
{
|
||||
"name": "priority_first",
|
||||
"label": "优先级优先",
|
||||
"description": "按账号优先级顺序调度(数字越小越优先)",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据 internal_priority(支持拖拽/手工编辑)",
|
||||
},
|
||||
{
|
||||
"name": "quota_balanced",
|
||||
"label": "额度平均",
|
||||
"description": "优先选额度消耗最少的账号",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据账号配额使用率;无配额时回退到窗口成本使用",
|
||||
},
|
||||
{
|
||||
"name": "recent_refresh",
|
||||
"label": "额度刷新优先",
|
||||
"description": "优先选即将刷新额度的账号",
|
||||
"providers": ["codex", "kiro"],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据账号额度重置倒计时(next_reset / reset_seconds)",
|
||||
},
|
||||
{
|
||||
"name": "single_account",
|
||||
"label": "单号优先",
|
||||
"description": "集中使用同一账号(反向 LRU)",
|
||||
"providers": [],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": "distribution_mode",
|
||||
"evidence_hint": "先按账号优先级(internal_priority),同级再按反向 LRU 集中",
|
||||
},
|
||||
{
|
||||
"name": "team_first",
|
||||
"label": "Team 优先",
|
||||
"description": "优先消耗 Team 账号(依赖 plan_type)",
|
||||
"providers": ["codex", "kiro"],
|
||||
"modes": Value::Null,
|
||||
"default_mode": Value::Null,
|
||||
"mutex_group": Value::Null,
|
||||
"evidence_hint": "依据 plan_type(Team 账号优先调度)",
|
||||
}
|
||||
])
|
||||
aether_provider_pool::build_admin_pool_scheduling_presets_payload()
|
||||
}
|
||||
|
||||
pub fn admin_pool_batch_delete_task_parts(request_path: &str) -> Option<(String, String)> {
|
||||
|
||||
Reference in New Issue
Block a user