feat(models): add external catalog proxy selection

This commit is contained in:
elky
2026-07-31 09:32:25 +08:00
parent 12057db476
commit beb003b7ad
34 changed files with 2286 additions and 176 deletions
@@ -11,6 +11,7 @@ use aether_admin::provider::{
use aether_data_contracts::repository::global_models::{
AdminProviderModelListQuery, StoredAdminProviderModel, UpsertAdminProviderModelRecord,
};
use axum::http;
use serde_json::json;
use std::collections::{BTreeMap, BTreeSet};
use uuid::Uuid;
@@ -488,8 +489,24 @@ impl<'a> AdminAppState<'a> {
pub(crate) async fn read_admin_external_models_cache(
&self,
request_id: &str,
) -> Result<Option<serde_json::Value>, GatewayError> {
crate::handlers::admin::model::read_admin_external_models_cache(self).await
crate::handlers::admin::model::read_admin_external_models_cache(self, request_id).await
}
pub(crate) async fn build_admin_external_models_config_payload(
&self,
) -> Result<serde_json::Value, GatewayError> {
crate::handlers::admin::model::build_admin_external_models_config_payload(self).await
}
pub(crate) async fn apply_admin_external_models_config_update(
&self,
request_body: &axum::body::Bytes,
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError>
{
crate::handlers::admin::model::apply_admin_external_models_config_update(self, request_body)
.await
}
pub(crate) async fn clear_admin_external_models_cache(
@@ -2,6 +2,7 @@ use super::{AdminAppState, ADMIN_SYSTEM_DATA_EXPORT_VERSION};
use crate::ai_serving::build_provider_key_pool_score_upsert;
use crate::api::ai::admin_endpoint_signature_parts;
use crate::handlers::admin::admin_provider_pool_config;
use crate::handlers::admin::model::ADMIN_EXTERNAL_MODELS_PROXY_NODE_CONFIG_KEY;
use crate::handlers::admin::provider::endpoints_admin::payloads::AdminProviderEndpointUpdatePatch;
use crate::handlers::admin::provider::shared::payloads::{
AdminProviderCreateRequest, AdminProviderKeyCreateRequest, AdminProviderKeyUpdatePatch,
@@ -61,6 +62,15 @@ fn invalid_request(detail: impl Into<String>) -> (http::StatusCode, Value) {
)
}
fn normalize_imported_system_config_key(key: &str) -> String {
let normalized = normalize_admin_system_config_key(key);
if normalized.eq_ignore_ascii_case(ADMIN_EXTERNAL_MODELS_PROXY_NODE_CONFIG_KEY) {
ADMIN_EXTERNAL_MODELS_PROXY_NODE_CONFIG_KEY.to_string()
} else {
normalized
}
}
fn build_admin_system_data_import_part_body(
root: &Map<String, Value>,
field_name: &str,
@@ -1299,6 +1309,82 @@ impl<'a> AdminAppState<'a> {
let mut stats = AdminSystemConfigImportStats::default();
// Proxy nodes are deployment-local resources and are intentionally not imported by the
// Rust admin backend. Apply the external catalog selector before importing any other
// object, and turn a non-empty exported node reference into direct mode. This keeps a
// clean-environment restore portable and prevents a late selector validation failure from
// leaving the rest of the document partially imported.
let (imported_external_models_configs, imported_system_configs): (Vec<_>, Vec<_>) =
imported_system_configs.into_iter().partition(|item| {
normalize_imported_system_config_key(&item.value.key)
== ADMIN_EXTERNAL_MODELS_PROXY_NODE_CONFIG_KEY
});
let mut existing_system_config_keys = self
.list_system_config_entries()
.await?
.into_iter()
.map(|entry| normalize_imported_system_config_key(&entry.key))
.collect::<BTreeSet<_>>();
for imported_config_item in imported_external_models_configs {
let (_, system_config) = imported_config_item.into_parts();
let exists =
existing_system_config_keys.contains(ADMIN_EXTERNAL_MODELS_PROXY_NODE_CONFIG_KEY);
match (exists, merge_mode) {
(true, AdminImportMergeMode::Skip) => {
stats.system_configs.skipped += 1;
continue;
}
(true, AdminImportMergeMode::Error) => {
return Ok(Err(invalid_request(format!(
"SystemConfig '{ADMIN_EXTERNAL_MODELS_PROXY_NODE_CONFIG_KEY}' 已存在"
))));
}
_ => {}
}
let imported_proxy_node_id = match system_config.value {
Value::Null => None,
Value::String(value) => {
let value = value.trim();
if value.is_empty() {
return Ok(Err(invalid_request(
"external_models_proxy_node_id 不能为空",
)));
}
Some(value.to_string())
}
_ => {
return Ok(Err(invalid_request(
"external_models_proxy_node_id 必须是字符串或 null",
)))
}
};
let request_bytes = Bytes::from(
serde_json::to_vec(&json!({ "proxy_node_id": null }))
.map_err(|err| GatewayError::Internal(err.to_string()))?,
);
match self
.apply_admin_external_models_config_update(&request_bytes)
.await?
{
Ok(_) => {
if exists {
stats.system_configs.updated += 1;
} else {
stats.system_configs.created += 1;
existing_system_config_keys
.insert(ADMIN_EXTERNAL_MODELS_PROXY_NODE_CONFIG_KEY.to_string());
}
if let Some(node_id) = imported_proxy_node_id {
stats.errors.push(format!(
"外部模型目录代理节点 '{node_id}' 是当前部署的本地引用;代理节点未导入,已切换为直连"
));
}
}
Err((status, payload)) => return Ok(Err((status, payload))),
}
}
let mut global_models_by_name = self
.list_admin_global_models(&AdminGlobalModelListQuery {
offset: 0,
@@ -2231,15 +2317,14 @@ impl<'a> AdminAppState<'a> {
}
}
let mut existing_system_config_keys = self
.list_system_config_entries()
.await?
.into_iter()
.map(|entry| normalize_admin_system_config_key(&entry.key))
.collect::<BTreeSet<_>>();
for imported_config_item in imported_system_configs {
let (_, system_config) = imported_config_item.into_parts();
let normalized_key = normalize_admin_system_config_key(&system_config.key);
let ImportedSystemConfig {
key,
value,
description,
} = system_config;
let normalized_key = normalize_imported_system_config_key(&key);
let exists = existing_system_config_keys.contains(&normalized_key);
match (exists, merge_mode) {
(true, AdminImportMergeMode::Skip) => {
@@ -2256,13 +2341,14 @@ impl<'a> AdminAppState<'a> {
let request_bytes = Bytes::from(
serde_json::to_vec(&json!({
"value": system_config.value,
"description": system_config.description,
"value": value,
"description": description,
}))
.map_err(|err| GatewayError::Internal(err.to_string()))?,
);
match apply_admin_system_config_update(self, &system_config.key, &request_bytes).await?
{
let update_result =
apply_admin_system_config_update(self, &key, &request_bytes).await?;
match update_result {
Ok(_) => {
if exists {
stats.system_configs.updated += 1;
@@ -29,6 +29,13 @@ impl<'a> AdminAppState<'a> {
self.app.read_system_config_json_value(key).await
}
pub(crate) async fn read_system_config_json_value_strong(
&self,
key: &str,
) -> Result<Option<serde_json::Value>, GatewayError> {
self.app.read_system_config_json_value_strong(key).await
}
pub(crate) async fn list_system_config_entries(
&self,
) -> Result<Vec<crate::data::state::StoredSystemConfigEntry>, GatewayError> {