mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 18:59:50 +08:00
fix(mapping): 对齐全局模型映射的正则匹配行为与范围 (#296)
- scheduler_core: `matches_model_mapping` 改为大小写不敏感且整串匹配,并补充单测 - global model routing 预览: - Key 过滤增加 `allowed_models + model_mappings` 校验 - `all_keys_whitelist` 改为收集全站活跃 Provider 的活跃 Key 白名单 - provider mapping-preview: - 优先使用 admin 全量 GlobalModel(含非激活)参与映射 - admin 数据为空时回退 public 模型,保持兼容 - public models 匹配逻辑统一复用 scheduler_core 实现,避免行为分叉 - 更新网关测试,覆盖未关联 Provider 的 Key 也进入 whitelist 的场景
This commit is contained in:
@@ -7,12 +7,21 @@ use crate::handlers::admin::shared::{decrypt_catalog_secret_with_fallbacks, json
|
||||
use crate::handlers::public::matches_model_mapping_for_models;
|
||||
use crate::{GatewayError, LocalProviderDeleteTaskState};
|
||||
use aether_data_contracts::repository::global_models::{
|
||||
AdminProviderModelListQuery, PublicGlobalModelQuery, StoredPublicGlobalModel,
|
||||
AdminGlobalModelListQuery, AdminProviderModelListQuery, PublicGlobalModelQuery,
|
||||
};
|
||||
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use serde_json::json;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct MappingPreviewGlobalModel {
|
||||
id: String,
|
||||
name: String,
|
||||
display_name: String,
|
||||
is_active: bool,
|
||||
mappings: Vec<String>,
|
||||
}
|
||||
|
||||
pub(crate) async fn run_admin_provider_delete_task(
|
||||
state: &AdminAppState<'_>,
|
||||
provider_id: &str,
|
||||
@@ -141,10 +150,10 @@ pub(crate) async fn run_admin_provider_delete_task(
|
||||
Ok(task)
|
||||
}
|
||||
|
||||
pub(crate) fn public_global_model_mapping_patterns(model: &StoredPublicGlobalModel) -> Vec<String> {
|
||||
model
|
||||
.config
|
||||
.as_ref()
|
||||
pub(crate) fn global_model_mapping_patterns_from_config(
|
||||
config: Option<&serde_json::Value>,
|
||||
) -> Vec<String> {
|
||||
config
|
||||
.and_then(serde_json::Value::as_object)
|
||||
.and_then(|config| config.get("model_mappings"))
|
||||
.and_then(serde_json::Value::as_array)
|
||||
@@ -211,8 +220,8 @@ pub(crate) async fn build_admin_provider_mapping_preview_payload(
|
||||
keys.truncate(ADMIN_PROVIDER_MAPPING_PREVIEW_MAX_KEYS);
|
||||
}
|
||||
|
||||
let public_models = app
|
||||
.list_public_global_models(&PublicGlobalModelQuery {
|
||||
let admin_models = state
|
||||
.list_admin_global_models(&AdminGlobalModelListQuery {
|
||||
offset: 0,
|
||||
limit: ADMIN_PROVIDER_MAPPING_PREVIEW_FETCH_LIMIT,
|
||||
is_active: None,
|
||||
@@ -221,19 +230,56 @@ pub(crate) async fn build_admin_provider_mapping_preview_payload(
|
||||
.await
|
||||
.ok()
|
||||
.unwrap_or_else(|| {
|
||||
aether_data_contracts::repository::global_models::StoredPublicGlobalModelPage {
|
||||
aether_data_contracts::repository::global_models::StoredAdminGlobalModelPage {
|
||||
items: Vec::new(),
|
||||
total: 0,
|
||||
}
|
||||
})
|
||||
.items;
|
||||
let mut models_with_mappings = public_models
|
||||
let mut models_with_mappings = admin_models
|
||||
.into_iter()
|
||||
.filter_map(|model| {
|
||||
let mappings = public_global_model_mapping_patterns(&model);
|
||||
(!mappings.is_empty()).then_some((model, mappings))
|
||||
let mappings = global_model_mapping_patterns_from_config(model.config.as_ref());
|
||||
(!mappings.is_empty()).then_some(MappingPreviewGlobalModel {
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
display_name: model.display_name,
|
||||
is_active: model.is_active,
|
||||
mappings,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if models_with_mappings.is_empty() {
|
||||
let public_models = app
|
||||
.list_public_global_models(&PublicGlobalModelQuery {
|
||||
offset: 0,
|
||||
limit: ADMIN_PROVIDER_MAPPING_PREVIEW_FETCH_LIMIT,
|
||||
is_active: None,
|
||||
search: None,
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
.unwrap_or_else(|| {
|
||||
aether_data_contracts::repository::global_models::StoredPublicGlobalModelPage {
|
||||
items: Vec::new(),
|
||||
total: 0,
|
||||
}
|
||||
})
|
||||
.items;
|
||||
models_with_mappings = public_models
|
||||
.into_iter()
|
||||
.filter_map(|model| {
|
||||
let mappings = global_model_mapping_patterns_from_config(model.config.as_ref());
|
||||
(!mappings.is_empty()).then_some(MappingPreviewGlobalModel {
|
||||
id: model.id,
|
||||
name: model.name.clone(),
|
||||
display_name: model.display_name.unwrap_or(model.name),
|
||||
is_active: model.is_active,
|
||||
mappings,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
let total_models_with_mappings = models_with_mappings.len();
|
||||
let truncated_models =
|
||||
total_models_with_mappings.saturating_sub(ADMIN_PROVIDER_MAPPING_PREVIEW_MAX_MODELS);
|
||||
@@ -263,10 +309,10 @@ pub(crate) async fn build_admin_provider_mapping_preview_payload(
|
||||
}
|
||||
|
||||
let mut matching_global_models = Vec::new();
|
||||
for (global_model, mappings) in &models_with_mappings {
|
||||
for global_model in &models_with_mappings {
|
||||
let mut matched_models = Vec::new();
|
||||
for allowed_model in &allowed_models {
|
||||
for mapping_pattern in mappings {
|
||||
for mapping_pattern in &global_model.mappings {
|
||||
if matches_model_mapping_for_models(mapping_pattern, allowed_model) {
|
||||
matched_models.push(json!({
|
||||
"allowed_model": allowed_model,
|
||||
@@ -281,10 +327,7 @@ pub(crate) async fn build_admin_provider_mapping_preview_payload(
|
||||
matching_global_models.push(json!({
|
||||
"global_model_id": global_model.id,
|
||||
"global_model_name": global_model.name,
|
||||
"display_name": global_model
|
||||
.display_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| global_model.name.clone()),
|
||||
"display_name": global_model.display_name,
|
||||
"is_active": global_model.is_active,
|
||||
"matched_models": matched_models,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user