feat: 新增 frontdoor 执行回环守卫与多项可观测性增强

- 新增 frontdoor_loop_guard 模块,检测并拒绝 execution runtime 回环到本地网关的请求(HTTP 508)
- candidate loop 引入 span tracking、执行尝试日志与流式看门狗超时
- 本地故障转移策略支持从 report_context 加载,新增 append_local_failover_policy_to_value
- runtime tracing 美化:移除 identity 前缀,按 span 深度树形缩进,target 固定宽度展示
- Codex OpenAI CLI 补齐 chatgpt-account-id/x-client-request-id/session_id/conversation_id 请求头
- OpenAI CLI same/cross-format 聚合规则放宽以支持 openai:compact 客户端格式,并过滤 error-like 响应体
- auth/proxy/finalize 日志补充 user_id/api_key_id/api_key_name/balance_remaining 等字段
- 启动日志拆分为 starting/ready/config 三段,新增 resolve_bind_http_base_url
- access_log middleware 将生成的 trace_id 回注到下游请求头
- Cargo.toml 启用 serde_json preserve_order 特性
This commit is contained in:
fawney19
2026-04-11 01:50:24 +08:00
parent 3f057628b7
commit 6144473ebe
38 changed files with 1957 additions and 421 deletions

View File

@@ -28,6 +28,7 @@ pub(crate) fn build_admin_global_model_response(
"config": global_model.config.clone(),
"provider_count": provider_count,
"active_provider_count": active_provider_count,
"usage_count": 0,
"created_at": timestamp_or_now(global_model.created_at_unix_ms, now_unix_secs),
"updated_at": timestamp_or_now(global_model.updated_at_unix_secs, now_unix_secs),
})

View File

@@ -2,6 +2,7 @@ use aether_admin::observability::usage::admin_usage_is_failed;
use aether_data_contracts::repository::usage::StoredRequestUsageAudit;
pub(super) fn admin_monitoring_usage_is_error(item: &StoredRequestUsageAudit) -> bool {
item.status.trim().eq_ignore_ascii_case("error") || admin_usage_is_failed(item)
item.status.trim().eq_ignore_ascii_case("error")
|| admin_usage_is_failed(item)
|| item.error_category.is_some()
}

View File

@@ -125,11 +125,11 @@ fn provider_query_selected_fetch_endpoints(
}
async fn provider_query_read_cached_models(
state: &AppState,
state: &AdminAppState<'_>,
provider_id: &str,
key_id: &str,
) -> Option<Vec<Value>> {
let runner = state.redis_kv_runner()?;
let runner = state.app().redis_kv_runner()?;
let cache_key = runner
.keyspace()
.key(&format!("upstream_models:{provider_id}:{key_id}"));
@@ -148,11 +148,11 @@ async fn provider_query_read_cached_models(
}
async fn provider_query_fetch_models_from_transport(
state: &AppState,
state: &AdminAppState<'_>,
transport: &crate::provider_transport::GatewayProviderTransportSnapshot,
) -> Result<Vec<Value>, String> {
let plan = build_models_fetch_execution_plan(state, transport).await?;
let result = execution_runtime::execute_execution_runtime_sync_plan(state, None, &plan)
let plan = build_models_fetch_execution_plan(state.app(), transport).await?;
let result = execution_runtime::execute_execution_runtime_sync_plan(state.app(), None, &plan)
.await
.map_err(|err| format!("{err:?}"))?;
@@ -182,7 +182,7 @@ async fn provider_query_fetch_models_from_transport(
}
async fn provider_query_fetch_models_for_key(
state: &AppState,
state: &AdminAppState<'_>,
provider: &StoredProviderCatalogProvider,
endpoints: &[StoredProviderCatalogEndpoint],
key: &StoredProviderCatalogKey,
@@ -213,6 +213,7 @@ async fn provider_query_fetch_models_for_key(
let mut all_errors = Vec::new();
for endpoint in selected_endpoints {
let Some(transport) = state
.app()
.read_provider_transport_snapshot(&provider.id, &endpoint.id, &key.id)
.await?
else {
@@ -231,7 +232,7 @@ async fn provider_query_fetch_models_for_key(
let unique_models = aggregate_models_for_cache(&all_models);
if !unique_models.is_empty() {
<AppState as ModelFetchRuntimeState>::write_upstream_models_cache(
state,
state.app(),
&provider.id,
&key.id,
&unique_models,
@@ -296,7 +297,7 @@ pub(crate) async fn build_admin_provider_query_models_response(
};
let result = provider_query_fetch_models_for_key(
state.app(),
state,
&provider,
&endpoints,
selected_key,
@@ -329,14 +330,9 @@ pub(crate) async fn build_admin_provider_query_models_response(
let mut cache_hit_count = 0usize;
let mut fetch_count = 0usize;
for key in active_keys {
let result = provider_query_fetch_models_for_key(
state.app(),
&provider,
&endpoints,
key,
force_refresh,
)
.await?;
let result =
provider_query_fetch_models_for_key(state, &provider, &endpoints, key, force_refresh)
.await?;
all_models.extend(result.models);
if let Some(error) = result.error {
all_errors.push(format!(