refactor: 大规模模块拆分与代码精简,新增 ai-pipeline/data-contracts 独立 crate

- 新增 aether-ai-pipeline 和 aether-data-contracts crate,将 pipeline 逻辑与数据契约从 gateway 中解耦
- 重构 admin handlers:拆分单体模块为 auth/billing/endpoint/features/model/observability/provider/system 等独立子模块
- 合并 chat/cli 重复代码路径:精简 conversion、finalize、planner 中的 sync/chat/cli 分支
- 重构 scheduler/executor/data 层,引入 facade 模式降低模块间耦合
- 移除冗余的 intent 模块,将 plan_fallback/policy/stream_path/sync_path 迁移至 executor
- 前端适配:调整 admin API 调用和 provider 模型测试对话框
This commit is contained in:
fawney19
2026-04-07 02:50:19 +08:00
parent 763ff03a7b
commit 5d96d6673b
732 changed files with 28589 additions and 20662 deletions
@@ -0,0 +1,17 @@
use crate::control::GatewayPublicRequestContext;
use crate::{AppState, GatewayError};
use axum::body::{Body, Bytes};
use axum::http::Response;
mod models;
mod routes;
mod shared;
pub(crate) async fn maybe_build_local_admin_provider_query_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
routes::maybe_build_local_admin_provider_query_response(state, request_context, request_body)
.await
}
@@ -0,0 +1,228 @@
use super::shared::{
build_admin_provider_query_bad_request_response, build_admin_provider_query_not_found_response,
provider_query_extract_api_key_id, provider_query_extract_provider_id,
ADMIN_PROVIDER_QUERY_API_KEY_NOT_FOUND_DETAIL, ADMIN_PROVIDER_QUERY_NO_ACTIVE_API_KEY_DETAIL,
ADMIN_PROVIDER_QUERY_NO_LOCAL_MODELS_DETAIL, ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL,
ADMIN_PROVIDER_QUERY_PROVIDER_NOT_FOUND_DETAIL,
};
use crate::{AppState, GatewayError};
use aether_data_contracts::repository::provider_catalog::{
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
};
use axum::{body::Body, http::Response, response::IntoResponse, Json};
use serde_json::json;
use std::collections::{BTreeMap, BTreeSet};
pub(super) const ADMIN_PROVIDER_QUERY_LOCAL_TEST_MODEL_MESSAGE: &str =
"Rust local provider-query model test is not configured";
pub(super) const ADMIN_PROVIDER_QUERY_LOCAL_TEST_MODEL_FAILOVER_MESSAGE: &str =
"Rust local provider-query failover simulation is not configured";
fn provider_query_string_list(value: Option<&serde_json::Value>) -> Vec<String> {
value
.and_then(serde_json::Value::as_array)
.map(|items| {
items
.iter()
.filter_map(serde_json::Value::as_str)
.map(str::trim)
.filter(|item| !item.is_empty())
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
})
.unwrap_or_default()
}
fn provider_query_resolved_api_formats(
endpoints: &[StoredProviderCatalogEndpoint],
selected_key: Option<&StoredProviderCatalogKey>,
) -> Vec<String> {
let mut seen = BTreeSet::new();
let key_formats = selected_key
.map(|key| provider_query_string_list(key.api_formats.as_ref()))
.unwrap_or_default();
let mut formats = Vec::new();
for endpoint in endpoints.iter().filter(|endpoint| endpoint.is_active) {
let api_format = endpoint.api_format.trim();
if api_format.is_empty() {
continue;
}
if !key_formats.is_empty() && !key_formats.iter().any(|value| value == api_format) {
continue;
}
if seen.insert(api_format.to_string()) {
formats.push(api_format.to_string());
}
}
if formats.is_empty() {
for api_format in key_formats {
if seen.insert(api_format.clone()) {
formats.push(api_format);
}
}
}
formats
}
pub(super) async fn build_admin_provider_query_models_response(
state: &AppState,
payload: &serde_json::Value,
) -> Result<Response<Body>, GatewayError> {
let Some(provider_id) = provider_query_extract_provider_id(payload) else {
return Ok(build_admin_provider_query_bad_request_response(
ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL,
));
};
let Some(provider) = state
.read_provider_catalog_providers_by_ids(std::slice::from_ref(&provider_id))
.await?
.into_iter()
.find(|item| item.id == provider_id)
else {
return Ok(build_admin_provider_query_not_found_response(
ADMIN_PROVIDER_QUERY_PROVIDER_NOT_FOUND_DETAIL,
));
};
let provider_ids = vec![provider.id.clone()];
let endpoints = state
.list_provider_catalog_endpoints_by_provider_ids(&provider_ids)
.await?;
let keys = state
.list_provider_catalog_keys_by_provider_ids(&provider_ids)
.await?;
let selected_key = if let Some(api_key_id) = provider_query_extract_api_key_id(payload) {
let Some(key) = keys.iter().find(|key| key.id == api_key_id) else {
return Ok(build_admin_provider_query_not_found_response(
ADMIN_PROVIDER_QUERY_API_KEY_NOT_FOUND_DETAIL,
));
};
Some(key)
} else {
None
};
let active_keys = keys.iter().filter(|key| key.is_active).count();
if selected_key.is_none() && active_keys == 0 {
return Ok(build_admin_provider_query_bad_request_response(
ADMIN_PROVIDER_QUERY_NO_ACTIVE_API_KEY_DETAIL,
));
}
let resolved_api_formats = provider_query_resolved_api_formats(&endpoints, selected_key);
let provider_models = state
.list_admin_provider_available_source_models(&provider.id)
.await?;
let mut grouped: BTreeMap<
String,
(
aether_data_contracts::repository::global_models::StoredAdminProviderModel,
BTreeSet<String>,
),
> = BTreeMap::new();
for model in provider_models {
let entry = grouped
.entry(model.provider_model_name.clone())
.or_insert_with(|| (model.clone(), BTreeSet::new()));
for api_format in &resolved_api_formats {
entry.1.insert(api_format.clone());
}
}
let models: Vec<_> = grouped
.into_iter()
.map(|(model_id, (model, api_formats))| {
let display_name = model
.global_model_display_name
.clone()
.or(model.global_model_name.clone())
.unwrap_or_else(|| model_id.clone());
let api_formats: Vec<_> = api_formats.into_iter().collect();
json!({
"id": model_id,
"object": "model",
"created": model.created_at_unix_secs,
"owned_by": provider.name,
"display_name": display_name,
"api_format": api_formats.first().cloned(),
"api_formats": api_formats,
"provider_model_name": model.provider_model_name,
"global_model_id": model.global_model_id,
"global_model_name": model.global_model_name,
"supports_streaming": model.supports_streaming,
"supports_function_calling": model.supports_function_calling,
"supports_vision": model.supports_vision,
"supports_extended_thinking": model.supports_extended_thinking,
"supports_image_generation": model.supports_image_generation,
"is_available": model.is_available,
})
})
.collect();
let success = !models.is_empty();
let error = if success {
None
} else {
Some(ADMIN_PROVIDER_QUERY_NO_LOCAL_MODELS_DETAIL)
};
Ok(Json(json!({
"success": success,
"data": {
"models": models,
"error": error,
"from_cache": true,
"keys_total": active_keys,
"keys_cached": 0,
"keys_fetched": 0,
},
"provider": {
"id": provider.id,
"name": provider.name,
"display_name": provider.name,
},
}))
.into_response())
}
pub(super) fn build_admin_provider_query_test_model_response(
provider_id: String,
model: String,
) -> Response<Body> {
Json(json!({
"success": false,
"tested": false,
"provider_id": provider_id,
"model": model,
"attempts": [],
"total_candidates": 0,
"total_attempts": 0,
"error": ADMIN_PROVIDER_QUERY_LOCAL_TEST_MODEL_MESSAGE,
"source": "local",
"message": ADMIN_PROVIDER_QUERY_LOCAL_TEST_MODEL_MESSAGE,
}))
.into_response()
}
pub(super) fn build_admin_provider_query_test_model_failover_response(
provider_id: String,
failover_models: Vec<String>,
) -> Response<Body> {
Json(json!({
"success": false,
"tested": false,
"provider_id": provider_id,
"model": failover_models.first().cloned(),
"failover_models": failover_models,
"attempts": [],
"total_candidates": 0,
"total_attempts": 0,
"error": ADMIN_PROVIDER_QUERY_LOCAL_TEST_MODEL_FAILOVER_MESSAGE,
"source": "local",
"message": ADMIN_PROVIDER_QUERY_LOCAL_TEST_MODEL_FAILOVER_MESSAGE,
}))
.into_response()
}
@@ -0,0 +1,140 @@
use super::models::{
build_admin_provider_query_models_response,
build_admin_provider_query_test_model_failover_response,
build_admin_provider_query_test_model_response,
};
use super::shared::{
build_admin_provider_query_bad_request_response, parse_admin_provider_query_body,
provider_query_extract_failover_models, provider_query_extract_model,
provider_query_extract_provider_id, provider_query_extract_request_id,
provider_query_payload_keys, ADMIN_PROVIDER_QUERY_FAILOVER_MODELS_REQUIRED_DETAIL,
ADMIN_PROVIDER_QUERY_MODEL_REQUIRED_DETAIL, ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL,
};
use crate::control::GatewayPublicRequestContext;
use crate::log_ids::short_request_id;
use crate::{AppState, GatewayError};
use axum::{
body::{Body, Bytes},
http,
http::Response,
};
use tracing::warn;
fn log_admin_provider_query_validation_failure(
request_context: &GatewayPublicRequestContext,
route_kind: &str,
detail: &'static str,
payload: &serde_json::Value,
) {
let provider_id =
provider_query_extract_provider_id(payload).unwrap_or_else(|| "-".to_string());
let model = provider_query_extract_model(payload).unwrap_or_else(|| "-".to_string());
let request_id = provider_query_extract_request_id(payload).unwrap_or_else(|| "-".to_string());
let request_id_for_log = short_request_id(request_id.as_str());
let payload_keys = provider_query_payload_keys(payload);
warn!(
event_name = "admin_provider_query_request_rejected",
log_type = "validation",
route_kind,
path = %request_context.request_path,
request_id = %request_id_for_log,
provider_id = %provider_id,
model = %model,
payload_keys = ?payload_keys,
detail,
"admin provider query request rejected"
);
}
pub(super) async fn maybe_build_local_admin_provider_query_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(decision) = request_context.control_decision.as_ref() else {
return Ok(None);
};
if decision.route_family.as_deref() != Some("provider_query_manage") {
return Ok(None);
}
if request_context.request_method != http::Method::POST {
return Ok(None);
}
let payload = match parse_admin_provider_query_body(request_body) {
Ok(value) => value,
Err(response) => return Ok(Some(response)),
};
let route_kind = decision.route_kind.as_deref().unwrap_or("query_models");
match route_kind {
"query_models" => Ok(Some(
build_admin_provider_query_models_response(state, &payload).await?,
)),
"test_model" => {
let Some(provider_id) = provider_query_extract_provider_id(&payload) else {
log_admin_provider_query_validation_failure(
request_context,
route_kind,
ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL,
&payload,
);
return Ok(Some(build_admin_provider_query_bad_request_response(
ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL,
)));
};
let Some(model) = provider_query_extract_model(&payload) else {
log_admin_provider_query_validation_failure(
request_context,
route_kind,
ADMIN_PROVIDER_QUERY_MODEL_REQUIRED_DETAIL,
&payload,
);
return Ok(Some(build_admin_provider_query_bad_request_response(
ADMIN_PROVIDER_QUERY_MODEL_REQUIRED_DETAIL,
)));
};
Ok(Some(build_admin_provider_query_test_model_response(
provider_id,
model,
)))
}
"test_model_failover" => {
let Some(provider_id) = provider_query_extract_provider_id(&payload) else {
log_admin_provider_query_validation_failure(
request_context,
route_kind,
ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL,
&payload,
);
return Ok(Some(build_admin_provider_query_bad_request_response(
ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL,
)));
};
let failover_models = provider_query_extract_failover_models(&payload);
if failover_models.is_empty() {
log_admin_provider_query_validation_failure(
request_context,
route_kind,
ADMIN_PROVIDER_QUERY_FAILOVER_MODELS_REQUIRED_DETAIL,
&payload,
);
return Ok(Some(build_admin_provider_query_bad_request_response(
ADMIN_PROVIDER_QUERY_FAILOVER_MODELS_REQUIRED_DETAIL,
)));
}
Ok(Some(
build_admin_provider_query_test_model_failover_response(
provider_id,
failover_models,
),
))
}
_ => Ok(Some(
build_admin_provider_query_models_response(state, &payload).await?,
)),
}
}
@@ -0,0 +1,119 @@
use axum::{
body::{Body, Bytes},
http,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
pub(super) const ADMIN_PROVIDER_QUERY_INVALID_JSON_DETAIL: &str = "Invalid JSON request body";
pub(super) const ADMIN_PROVIDER_QUERY_PROVIDER_ID_REQUIRED_DETAIL: &str = "provider_id is required";
pub(super) const ADMIN_PROVIDER_QUERY_MODEL_REQUIRED_DETAIL: &str = "model is required";
pub(super) const ADMIN_PROVIDER_QUERY_FAILOVER_MODELS_REQUIRED_DETAIL: &str =
"failover_models should not be empty";
pub(super) const ADMIN_PROVIDER_QUERY_PROVIDER_NOT_FOUND_DETAIL: &str = "Provider not found";
pub(super) const ADMIN_PROVIDER_QUERY_API_KEY_NOT_FOUND_DETAIL: &str = "API Key not found";
pub(super) const ADMIN_PROVIDER_QUERY_NO_ACTIVE_API_KEY_DETAIL: &str =
"No active API Key found for this provider";
pub(super) const ADMIN_PROVIDER_QUERY_NO_LOCAL_MODELS_DETAIL: &str =
"No models available from local provider catalog";
pub(super) fn build_admin_provider_query_bad_request_response(
detail: &'static str,
) -> Response<Body> {
(
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response()
}
pub(super) fn build_admin_provider_query_not_found_response(
detail: &'static str,
) -> Response<Body> {
(
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": detail })),
)
.into_response()
}
pub(super) fn parse_admin_provider_query_body(
request_body: Option<&Bytes>,
) -> Result<serde_json::Value, Response<Body>> {
let Some(raw_body) = request_body else {
return Ok(json!({}));
};
if raw_body.is_empty() {
return Ok(json!({}));
}
serde_json::from_slice::<serde_json::Value>(raw_body).map_err(|_| {
build_admin_provider_query_bad_request_response(ADMIN_PROVIDER_QUERY_INVALID_JSON_DETAIL)
})
}
pub(super) fn provider_query_extract_provider_id(payload: &serde_json::Value) -> Option<String> {
payload
.get("provider_id")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
pub(super) fn provider_query_extract_api_key_id(payload: &serde_json::Value) -> Option<String> {
payload
.get("api_key_id")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
pub(super) fn provider_query_extract_model(payload: &serde_json::Value) -> Option<String> {
payload
.get("model")
.or_else(|| payload.get("model_name"))
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
pub(super) fn provider_query_extract_failover_models(payload: &serde_json::Value) -> Vec<String> {
if let Some(items) = payload
.get("failover_models")
.or_else(|| payload.get("models"))
.and_then(serde_json::Value::as_array)
{
return items
.iter()
.filter_map(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
}
provider_query_extract_model(payload)
.into_iter()
.collect::<Vec<_>>()
}
pub(super) fn provider_query_extract_request_id(payload: &serde_json::Value) -> Option<String> {
payload
.get("request_id")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
pub(super) fn provider_query_payload_keys(payload: &serde_json::Value) -> Vec<String> {
let Some(object) = payload.as_object() else {
return Vec::new();
};
let mut keys = object.keys().cloned().collect::<Vec<_>>();
keys.sort();
keys
}