refactor: 移除 Python upstream 依赖,清理全部 legacy/Python 兼容层

- 移除 upstream_base_url 参数及 AETHER_GATEWAY_UPSTREAM 环境变量,gateway 不再需要指向 Python 宿主
- 删除所有 LEGACY_*/PYTHON_* 常量、路由组、header 定义及 sunset/phaseout 机制
- 将 legacy_gateway_bridge 重命名为 internal_gateway,executor 相关命名统一为 execution_runtime
- dev.sh 新增 Postgres/Redis 预检查,移除 upstream 相关启动参数和提示
- 新增 ai_public 路由处理器
- 全量适配 handler、test、state、control 等模块的命名和接口变更
This commit is contained in:
fawney19
2026-04-04 01:40:24 +08:00
parent 1d9c77522a
commit cbc811f6ce
484 changed files with 11046 additions and 3925 deletions

View File

@@ -1,4 +1,18 @@
use super::*;
use crate::gateway::constants::{
CONTROL_ENDPOINT_SIGNATURE_HEADER, CONTROL_EXECUTION_RUNTIME_HEADER,
CONTROL_ROUTE_CLASS_HEADER, CONTROL_ROUTE_FAMILY_HEADER, CONTROL_ROUTE_KIND_HEADER,
DEPENDENCY_REASON_HEADER, EXECUTION_PATH_HEADER, LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER,
TRACE_ID_HEADER,
};
use crate::gateway::{
record_shadow_result_non_blocking, AppState, GatewayControlDecision,
GatewayPublicRequestContext,
};
use aether_runtime::{maybe_hold_axum_response_permit, AdmissionPermit};
use axum::body::{Body, Bytes};
use axum::http::{self, header::HeaderName, header::HeaderValue, Response};
use std::time::Instant;
use tracing::info;
pub(super) fn request_wants_stream(
request_context: &GatewayPublicRequestContext,
@@ -37,25 +51,27 @@ pub(super) fn finalize_gateway_response(
started_at: &Instant,
request_permit: Option<AdmissionPermit>,
) -> Response<Body> {
attach_control_decision_headers(&mut response, control_decision);
if !response.headers().contains_key(TRACE_ID_HEADER) {
response.headers_mut().insert(
HeaderName::from_static(TRACE_ID_HEADER),
HeaderValue::from_str(trace_id).expect("trace id should be a valid header value"),
);
}
response.headers_mut().insert(
HeaderName::from_static(EXECUTION_PATH_HEADER),
HeaderValue::from_static(execution_path),
);
let elapsed_ms = started_at.elapsed().as_millis() as u64;
let python_dependency_reason = response
let dependency_reason = response
.headers()
.get(PYTHON_DEPENDENCY_REASON_HEADER)
.get(DEPENDENCY_REASON_HEADER)
.and_then(|value| value.to_str().ok())
.unwrap_or("none");
let local_execution_runtime_miss_reason = response
.headers()
.get(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER)
.or_else(|| {
response
.headers()
.get(LOCAL_LEGACY_EXECUTION_RUNTIME_MISS_REASON_HEADER)
})
.and_then(|value| value.to_str().ok())
.unwrap_or("none");
info!(
@@ -67,7 +83,7 @@ pub(super) fn finalize_gateway_response(
.and_then(|decision| decision.route_class.as_deref())
.unwrap_or("passthrough"),
execution_path,
python_dependency_reason,
dependency_reason,
local_execution_runtime_miss_reason,
status = response.status().as_u16(),
elapsed_ms,
@@ -87,6 +103,70 @@ pub(super) fn finalize_gateway_response(
maybe_hold_axum_response_permit(response, request_permit)
}
fn attach_control_decision_headers(
response: &mut Response<Body>,
control_decision: Option<&GatewayControlDecision>,
) {
let Some(control_decision) = control_decision else {
return;
};
if !response.headers().contains_key(CONTROL_ROUTE_CLASS_HEADER) {
response.headers_mut().insert(
HeaderName::from_static(CONTROL_ROUTE_CLASS_HEADER),
HeaderValue::from_str(
control_decision
.route_class
.as_deref()
.unwrap_or("passthrough"),
)
.expect("route class should be a valid header value"),
);
}
if !response
.headers()
.contains_key(CONTROL_EXECUTION_RUNTIME_HEADER)
{
response.headers_mut().insert(
HeaderName::from_static(CONTROL_EXECUTION_RUNTIME_HEADER),
HeaderValue::from_static(if control_decision.is_execution_runtime_candidate() {
"true"
} else {
"false"
}),
);
}
if let Some(route_family) = control_decision.route_family.as_deref() {
if !response.headers().contains_key(CONTROL_ROUTE_FAMILY_HEADER) {
response.headers_mut().insert(
HeaderName::from_static(CONTROL_ROUTE_FAMILY_HEADER),
HeaderValue::from_str(route_family)
.expect("route family should be a valid header value"),
);
}
}
if let Some(route_kind) = control_decision.route_kind.as_deref() {
if !response.headers().contains_key(CONTROL_ROUTE_KIND_HEADER) {
response.headers_mut().insert(
HeaderName::from_static(CONTROL_ROUTE_KIND_HEADER),
HeaderValue::from_str(route_kind)
.expect("route kind should be a valid header value"),
);
}
}
if let Some(endpoint_signature) = control_decision.auth_endpoint_signature.as_deref() {
if !response
.headers()
.contains_key(CONTROL_ENDPOINT_SIGNATURE_HEADER)
{
response.headers_mut().insert(
HeaderName::from_static(CONTROL_ENDPOINT_SIGNATURE_HEADER),
HeaderValue::from_str(endpoint_signature)
.expect("endpoint signature should be a valid header value"),
);
}
}
}
pub(super) fn finalize_gateway_response_with_context(
state: &AppState,
response: Response<Body>,

View File

@@ -1,38 +1,27 @@
use super::super::{admin, internal, public};
use super::*;
use crate::gateway::{AppState, GatewayError, GatewayPublicRequestContext};
use axum::body::{Body, Bytes};
use axum::http::Response;
pub(super) async fn maybe_build_local_internal_proxy_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
remote_addr: &std::net::SocketAddr,
request_body: Option<&axum::body::Bytes>,
legacy_internal_gateway_allowed: bool,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
let response = internal::maybe_build_local_internal_proxy_response_impl(
internal::maybe_build_local_internal_proxy_response_impl(
state,
request_context,
remote_addr,
request_body,
legacy_internal_gateway_allowed,
)
.await?;
if request_context
.control_decision
.as_ref()
.and_then(|decision| decision.route_family.as_deref())
== Some("gateway_legacy")
{
return Ok(response.map(internal::attach_legacy_internal_gateway_deprecation_headers));
}
Ok(response)
.await
}
pub(super) async fn maybe_build_local_admin_proxy_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
request_body: Option<&axum::body::Bytes>,
request_body: Option<&Bytes>,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(decision) = request_context.control_decision.as_ref() else {
return Ok(None);

View File

@@ -1,30 +1,64 @@
pub(crate) use super::*;
mod local;
use self::local::{
maybe_build_local_admin_proxy_response, maybe_build_local_internal_proxy_response,
};
use super::internal::{
attach_legacy_internal_gateway_deprecation_headers, resolve_local_proxy_execution_path,
};
use super::internal::resolve_local_proxy_execution_path;
pub(crate) use super::public::matches_model_mapping_for_models;
use crate::gateway::ai_pipeline::{finalize as ai_finalize, runtime as ai_runtime};
use crate::gateway::api::response::{
build_local_auth_rejection_response, build_local_http_error_response,
build_local_overloaded_response, build_local_user_rpm_limited_response,
};
use crate::gateway::constants::{
DEPENDENCY_REASON_HEADER, EXECUTION_PATH_CONTROL_EXECUTE_STREAM,
EXECUTION_PATH_CONTROL_EXECUTE_SYNC, EXECUTION_PATH_DISTRIBUTED_OVERLOADED,
EXECUTION_PATH_EXECUTION_RUNTIME_STREAM, EXECUTION_PATH_EXECUTION_RUNTIME_SYNC,
EXECUTION_PATH_LOCAL_AI_PUBLIC, EXECUTION_PATH_LOCAL_AUTH_DENIED,
EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS, EXECUTION_PATH_LOCAL_OVERLOADED,
EXECUTION_PATH_LOCAL_PROXY_PASSTHROUGH_REMOVED, EXECUTION_PATH_LOCAL_RATE_LIMITED,
EXECUTION_PATH_LOCAL_ROUTE_NOT_FOUND, EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH,
FORWARDED_FOR_HEADER, FORWARDED_HOST_HEADER, FORWARDED_PROTO_HEADER, GATEWAY_HEADER,
LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER, TRACE_ID_HEADER,
TRUSTED_AUTH_ACCESS_ALLOWED_HEADER, TRUSTED_AUTH_API_KEY_ID_HEADER,
TRUSTED_AUTH_BALANCE_HEADER, TRUSTED_AUTH_USER_ID_HEADER, TUNNEL_AFFINITY_FORWARDED_BY_HEADER,
TUNNEL_AFFINITY_OWNER_INSTANCE_HEADER,
};
use crate::gateway::handlers::{
allows_control_execute_emergency, local_proxy_route_requires_buffered_body,
request_enables_control_execute, request_model_local_rejection,
should_buffer_request_for_local_auth, should_strip_forwarded_provider_credential_header,
should_strip_forwarded_trusted_admin_header, trusted_auth_local_rejection,
};
use crate::gateway::headers::{extract_or_generate_trace_id, should_skip_request_header};
use crate::gateway::{
maybe_execute_via_control, AppState, FrontdoorUserRpmOutcome, GatewayControlDecision,
GatewayError, GatewayFallbackMetricKind, GatewayFallbackReason, GatewayPublicRequestContext,
};
use axum::body::{to_bytes, Body, Bytes};
use axum::extract::{ConnectInfo, Request, State};
use axum::http::{self, header::HeaderName, header::HeaderValue, Response};
use chrono::Utc;
use std::time::Instant;
use tracing::warn;
const OPENAI_CHAT_PYTHON_FALLBACK_REMOVED_DETAIL: &str =
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed";
const OPENAI_RESPONSES_PYTHON_FALLBACK_REMOVED_DETAIL: &str =
"OpenAI responses execution runtime miss did not match a Rust execution path, and Python fallback has been removed";
const OPENAI_COMPACT_PYTHON_FALLBACK_REMOVED_DETAIL: &str =
"OpenAI compact execution runtime miss did not match a Rust execution path, and Python fallback has been removed";
const OPENAI_VIDEO_PYTHON_FALLBACK_REMOVED_DETAIL: &str =
"OpenAI video execution runtime miss did not match a Rust execution path, and Python fallback has been removed";
const CLAUDE_MESSAGES_PYTHON_FALLBACK_REMOVED_DETAIL: &str =
"Claude messages execution runtime miss did not match a Rust execution path, and Python fallback has been removed";
const GEMINI_PUBLIC_PYTHON_FALLBACK_REMOVED_DETAIL: &str =
"Gemini public execution runtime miss did not match a Rust execution path, and Python fallback has been removed";
const GEMINI_FILES_PYTHON_FALLBACK_REMOVED_DETAIL: &str =
"Gemini files execution runtime miss did not match a Rust execution path, and Python fallback has been removed";
const OPENAI_CHAT_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL: &str =
"OpenAI chat execution runtime miss did not match a Rust execution path";
const OPENAI_RESPONSES_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL: &str =
"OpenAI responses execution runtime miss did not match a Rust execution path";
const OPENAI_COMPACT_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL: &str =
"OpenAI compact execution runtime miss did not match a Rust execution path";
const OPENAI_VIDEO_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL: &str =
"OpenAI video execution runtime miss did not match a Rust execution path";
const CLAUDE_MESSAGES_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL: &str =
"Claude messages execution runtime miss did not match a Rust execution path";
const GEMINI_PUBLIC_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL: &str =
"Gemini public execution runtime miss did not match a Rust execution path";
const GEMINI_FILES_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL: &str =
"Gemini files execution runtime miss did not match a Rust execution path";
const LOCAL_ROUTE_NOT_FOUND_DETAIL: &str = "Route not found";
const LOCAL_PROXY_PASSTHROUGH_REMOVED_DETAIL: &str =
"Route matched a removed compatibility passthrough; implement it in Rust or retire the route";
const EXECUTION_PATH_TUNNEL_AFFINITY_FORWARD: &str = "tunnel_affinity_forward";
fn execution_runtime_candidate_header_value(decision: &GatewayControlDecision) -> &'static str {
@@ -311,13 +345,11 @@ pub(crate) async fn proxy_request(
let request_path_and_query = request_context.request_path_and_query();
let path_and_query = request_path_and_query.as_str();
let control_decision = request_context.control_decision.as_ref();
let legacy_internal_gateway_allowed = request_enables_control_execute(&parts.headers);
if let Some(response) = maybe_build_local_internal_proxy_response(
&state,
&request_context,
&remote_addr,
local_proxy_body.as_ref(),
legacy_internal_gateway_allowed,
)
.await?
{
@@ -349,6 +381,56 @@ pub(crate) async fn proxy_request(
request_permit.take(),
));
}
if request_context
.control_decision
.as_ref()
.is_some_and(|decision| {
decision.route_class.as_deref() == Some("admin_proxy")
&& decision.admin_principal.is_none()
})
{
let response = super::admin::build_admin_proxy_auth_required_response(&request_context);
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH,
&started_at,
request_permit.take(),
));
}
if request_context
.control_decision
.as_ref()
.is_some_and(|decision| {
decision.route_class.as_deref() == Some("admin_proxy")
&& decision.admin_principal.is_some()
})
{
let response = super::admin::build_unhandled_admin_proxy_response(&request_context);
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH,
&started_at,
request_permit.take(),
));
}
if request_context.request_path.starts_with("/api/admin/") {
let response = super::admin::build_unhandled_admin_proxy_response(&request_context);
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH,
&started_at,
request_permit.take(),
));
}
if let Some(response) = super::public::maybe_build_local_public_support_response(
&state,
&request_context,
@@ -369,6 +451,23 @@ pub(crate) async fn proxy_request(
request_permit.take(),
));
}
if request_context
.control_decision
.as_ref()
.and_then(|decision| decision.route_class.as_deref())
== Some("public_support")
{
let response = super::public::build_unhandled_public_support_response(&request_context);
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH,
&started_at,
request_permit.take(),
));
}
if let Some(buffered_body) = local_proxy_body {
request_body = Some(Body::from(buffered_body));
}
@@ -378,9 +477,13 @@ pub(crate) async fn proxy_request(
&& decision.route_class.as_deref() == Some("ai_public")
})
.unwrap_or(false);
let should_buffer_for_local_ai_public =
super::public::ai_public_local_requires_buffered_body(&request_context);
let should_buffer_for_local_auth =
should_buffer_request_for_local_auth(control_decision, &parts.headers);
let should_buffer_body = should_try_control_execute || should_buffer_for_local_auth;
let should_buffer_body = should_try_control_execute
|| should_buffer_for_local_auth
|| should_buffer_for_local_ai_public;
let allow_control_execute_fallback = should_try_control_execute
&& control_decision.is_some_and(allows_control_execute_emergency)
@@ -474,107 +577,43 @@ pub(crate) async fn proxy_request(
));
}
let upstream_path_and_query =
sanitize_upstream_path_and_query(control_decision, path_and_query);
let target_url = format!("{}{}", state.upstream_base_url, upstream_path_and_query);
let mut upstream_request = state.client.request(method.clone(), &target_url);
for (name, value) in &parts.headers {
if should_skip_request_header(name.as_str()) {
continue;
}
// Once Rust has produced trusted auth headers, Python should not need the raw
// provider credential anymore. Keep the bridge boundary explicit.
if should_strip_forwarded_provider_credential_header(control_decision, name) {
continue;
}
if should_strip_forwarded_trusted_admin_header(control_decision, name) {
continue;
}
upstream_request = upstream_request.header(name, value);
if let Some(response) = super::public::maybe_build_local_ai_public_response(
&state,
&request_context,
buffered_body.as_ref(),
)
.await
{
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_LOCAL_AI_PUBLIC,
&started_at,
request_permit.take(),
));
}
if let Some(host) = request_context.host_header.as_deref() {
if !parts.headers.contains_key(FORWARDED_HOST_HEADER) {
upstream_request = upstream_request.header(FORWARDED_HOST_HEADER, host);
}
if control_decision.is_none() {
let response = build_local_http_error_response(
&trace_id,
None,
http::StatusCode::NOT_FOUND,
LOCAL_ROUTE_NOT_FOUND_DETAIL,
)?;
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_LOCAL_ROUTE_NOT_FOUND,
&started_at,
request_permit.take(),
));
}
if !parts.headers.contains_key(FORWARDED_FOR_HEADER) {
upstream_request =
upstream_request.header(FORWARDED_FOR_HEADER, remote_addr.ip().to_string());
}
if !parts.headers.contains_key(FORWARDED_PROTO_HEADER) {
upstream_request = upstream_request.header(FORWARDED_PROTO_HEADER, "http");
}
if !parts.headers.contains_key(TRACE_ID_HEADER) {
upstream_request = upstream_request.header(TRACE_ID_HEADER, &trace_id);
}
if let Some(decision) = control_decision {
let execution_runtime_candidate = execution_runtime_candidate_header_value(decision);
upstream_request = upstream_request
.header(
CONTROL_ROUTE_CLASS_HEADER,
decision.route_class.as_deref().unwrap_or("passthrough"),
)
.header(
CONTROL_LEGACY_EXECUTION_RUNTIME_HEADER,
execution_runtime_candidate,
)
.header(
CONTROL_EXECUTION_RUNTIME_HEADER,
execution_runtime_candidate,
);
if let Some(route_family) = decision.route_family.as_deref() {
upstream_request = upstream_request.header(CONTROL_ROUTE_FAMILY_HEADER, route_family);
}
if let Some(route_kind) = decision.route_kind.as_deref() {
upstream_request = upstream_request.header(CONTROL_ROUTE_KIND_HEADER, route_kind);
}
if let Some(endpoint_signature) = decision.auth_endpoint_signature.as_deref() {
upstream_request =
upstream_request.header(CONTROL_ENDPOINT_SIGNATURE_HEADER, endpoint_signature);
}
if let Some(auth_context) = decision.auth_context.as_ref() {
upstream_request = upstream_request
.header(TRUSTED_AUTH_USER_ID_HEADER, &auth_context.user_id)
.header(TRUSTED_AUTH_API_KEY_ID_HEADER, &auth_context.api_key_id)
.header(
TRUSTED_AUTH_ACCESS_ALLOWED_HEADER,
if auth_context.access_allowed {
"true"
} else {
"false"
},
);
if let Some(balance_remaining) = auth_context.balance_remaining {
upstream_request = upstream_request
.header(TRUSTED_AUTH_BALANCE_HEADER, balance_remaining.to_string());
}
}
if let Some(admin_principal) = decision.admin_principal.as_ref() {
upstream_request = upstream_request
.header(TRUSTED_ADMIN_USER_ID_HEADER, &admin_principal.user_id)
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, &admin_principal.user_role);
if let Some(session_id) = admin_principal.session_id.as_deref() {
upstream_request =
upstream_request.header(TRUSTED_ADMIN_SESSION_ID_HEADER, session_id);
}
if let Some(token_id) = admin_principal.management_token_id.as_deref() {
upstream_request =
upstream_request.header(TRUSTED_ADMIN_MANAGEMENT_TOKEN_ID_HEADER, token_id);
}
}
}
if matches!(rate_limit_outcome, FrontdoorUserRpmOutcome::Allowed) {
upstream_request = upstream_request.header(TRUSTED_RATE_LIMIT_PREFLIGHT_HEADER, "true");
}
upstream_request = upstream_request.header(GATEWAY_HEADER, "rust-phase3b");
let upstream_response = if should_try_control_execute {
if should_try_control_execute {
let buffered_body = buffered_body
.as_ref()
.expect("execution runtime/control auth gate should have buffered request body");
@@ -668,7 +707,7 @@ pub(crate) async fn proxy_request(
reason,
);
state.record_fallback_metric(
GatewayFallbackMetricKind::PythonExecuteEmergency,
GatewayFallbackMetricKind::RemoteExecuteEmergency,
control_decision,
None,
Some(control_execution_path),
@@ -677,7 +716,7 @@ pub(crate) async fn proxy_request(
let mut control_response = control_response;
state.clear_local_execution_runtime_miss_diagnostic(&trace_id);
control_response.headers_mut().insert(
HeaderName::from_static(PYTHON_DEPENDENCY_REASON_HEADER),
HeaderName::from_static(DEPENDENCY_REASON_HEADER),
HeaderValue::from_static(reason.as_label_value()),
);
return Ok(finalize_gateway_response_with_context(
@@ -692,158 +731,76 @@ pub(crate) async fn proxy_request(
}
}
let local_execution_runtime_miss_detail =
local_execution_runtime_miss_detail_after_python_fallback_removal(control_decision);
local_execution_runtime_miss_detail(control_decision)
.unwrap_or("AI public execution runtime miss did not match a Rust execution path");
state.record_fallback_metric(
if local_execution_runtime_miss_detail.is_some() {
GatewayFallbackMetricKind::LocalExecutionRuntimeMiss
} else {
GatewayFallbackMetricKind::PublicProxyAfterExecutionRuntimeMiss
},
GatewayFallbackMetricKind::LocalExecutionRuntimeMiss,
control_decision,
None,
Some(if local_execution_runtime_miss_detail.is_some() {
EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS
} else {
EXECUTION_PATH_PUBLIC_PROXY_AFTER_EXECUTION_RUNTIME_MISS
}),
if local_execution_runtime_miss_detail.is_some() {
GatewayFallbackReason::PythonFallbackRemoved
} else {
GatewayFallbackReason::ExecutionRuntimeMiss
},
Some(EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS),
GatewayFallbackReason::LocalExecutionPathRequired,
);
if let Some(local_execution_runtime_miss_detail) = local_execution_runtime_miss_detail {
let local_execution_runtime_miss_diagnostic =
state.take_local_execution_runtime_miss_diagnostic(&trace_id);
if let Some(diagnostic) = local_execution_runtime_miss_diagnostic.as_ref() {
warn!(
trace_id = %trace_id,
local_execution_runtime_miss_reason = %diagnostic.reason,
route_family = diagnostic.route_family.as_deref().unwrap_or_default(),
route_kind = diagnostic.route_kind.as_deref().unwrap_or_default(),
public_path = diagnostic.public_path.as_deref().unwrap_or_default(),
plan_kind = diagnostic.plan_kind.as_deref().unwrap_or_default(),
requested_model = diagnostic.requested_model.as_deref().unwrap_or_default(),
candidate_count = diagnostic.candidate_count.unwrap_or(0),
skipped_candidate_count = diagnostic.skipped_candidate_count.unwrap_or(0),
skip_reasons = diagnostic.skip_reasons_summary().unwrap_or_default(),
"gateway local execution runtime miss"
let local_execution_runtime_miss_diagnostic =
state.take_local_execution_runtime_miss_diagnostic(&trace_id);
if let Some(diagnostic) = local_execution_runtime_miss_diagnostic.as_ref() {
warn!(
trace_id = %trace_id,
local_execution_runtime_miss_reason = %diagnostic.reason,
route_family = diagnostic.route_family.as_deref().unwrap_or_default(),
route_kind = diagnostic.route_kind.as_deref().unwrap_or_default(),
public_path = diagnostic.public_path.as_deref().unwrap_or_default(),
plan_kind = diagnostic.plan_kind.as_deref().unwrap_or_default(),
requested_model = diagnostic.requested_model.as_deref().unwrap_or_default(),
candidate_count = diagnostic.candidate_count.unwrap_or(0),
skipped_candidate_count = diagnostic.skipped_candidate_count.unwrap_or(0),
skip_reasons = diagnostic.skip_reasons_summary().unwrap_or_default(),
"gateway local execution runtime miss"
);
}
let mut response = build_local_http_error_response(
&trace_id,
control_decision,
http::StatusCode::SERVICE_UNAVAILABLE,
local_execution_runtime_miss_detail,
)?;
if let Some(diagnostic) = local_execution_runtime_miss_diagnostic {
if !diagnostic.reason.trim().is_empty() {
response.headers_mut().insert(
HeaderName::from_static(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER),
HeaderValue::from_str(diagnostic.reason.as_str())
.map_err(|err| GatewayError::Internal(err.to_string()))?,
);
}
let mut response = build_local_http_error_response(
&trace_id,
control_decision,
http::StatusCode::SERVICE_UNAVAILABLE,
local_execution_runtime_miss_detail,
)?;
if let Some(diagnostic) = local_execution_runtime_miss_diagnostic {
if !diagnostic.reason.trim().is_empty() {
response.headers_mut().insert(
HeaderName::from_static(LOCAL_LEGACY_EXECUTION_RUNTIME_MISS_REASON_HEADER),
HeaderValue::from_str(diagnostic.reason.as_str())
.map_err(|err| GatewayError::Internal(err.to_string()))?,
);
response.headers_mut().insert(
HeaderName::from_static(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER),
HeaderValue::from_str(diagnostic.reason.as_str())
.map_err(|err| GatewayError::Internal(err.to_string()))?,
);
}
}
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS,
&started_at,
request_permit.take(),
));
}
state.clear_local_execution_runtime_miss_diagnostic(&trace_id);
upstream_request = upstream_request.header(
EXECUTION_PATH_HEADER,
EXECUTION_PATH_PUBLIC_PROXY_AFTER_EXECUTION_RUNTIME_MISS,
);
upstream_request
.body(buffered_body.clone())
.send()
.await
.map_err(|err| GatewayError::UpstreamUnavailable {
trace_id: trace_id.clone(),
message: err.to_string(),
})?
} else {
state.record_fallback_metric(
GatewayFallbackMetricKind::PublicProxyPassthrough,
control_decision,
None,
Some(EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH),
GatewayFallbackReason::ProxyPassthrough,
);
upstream_request = upstream_request.header(
EXECUTION_PATH_HEADER,
EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH,
);
if let Some(buffered_body) = buffered_body {
upstream_request
.body(buffered_body)
.send()
.await
.map_err(|err| GatewayError::UpstreamUnavailable {
trace_id: trace_id.clone(),
message: err.to_string(),
})?
} else {
let request_body_stream = request_body
.take()
.expect("streaming passthrough path should retain request body")
.into_data_stream()
.map_err(|err| std::io::Error::other(err.to_string()));
upstream_request
.body(reqwest::Body::wrap_stream(request_body_stream))
.send()
.await
.map_err(|err| GatewayError::UpstreamUnavailable {
trace_id: trace_id.clone(),
message: err.to_string(),
})?
}
};
state.clear_local_execution_runtime_miss_diagnostic(&trace_id);
let mut response =
ai_finalize::build_client_response(upstream_response, &trace_id, control_decision)?;
if control_decision.and_then(|decision| decision.route_family.as_deref())
== Some("gateway_legacy")
{
response = attach_legacy_internal_gateway_deprecation_headers(response);
return Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS,
&started_at,
request_permit.take(),
));
}
let python_dependency_reason = if should_try_control_execute {
GatewayFallbackReason::ExecutionRuntimeMiss
} else {
GatewayFallbackReason::ProxyPassthrough
};
response.headers_mut().insert(
HeaderName::from_static(PYTHON_DEPENDENCY_REASON_HEADER),
HeaderValue::from_static(python_dependency_reason.as_label_value()),
);
let response = build_local_http_error_response(
&trace_id,
control_decision,
http::StatusCode::NOT_IMPLEMENTED,
LOCAL_PROXY_PASSTHROUGH_REMOVED_DETAIL,
)?;
Ok(finalize_gateway_response_with_context(
&state,
response,
&remote_addr,
&request_context,
if should_try_control_execute {
EXECUTION_PATH_PUBLIC_PROXY_AFTER_EXECUTION_RUNTIME_MISS
} else {
EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH
},
EXECUTION_PATH_LOCAL_PROXY_PASSTHROUGH_REMOVED,
&started_at,
request_permit.take(),
))
}
fn local_execution_runtime_miss_detail_after_python_fallback_removal(
fn local_execution_runtime_miss_detail(
decision: Option<&GatewayControlDecision>,
) -> Option<&'static str> {
let decision = decision?;
@@ -852,18 +809,20 @@ fn local_execution_runtime_miss_detail_after_python_fallback_removal(
}
let public_path = decision.public_path.as_str();
match public_path {
"/v1/chat/completions" => Some(OPENAI_CHAT_PYTHON_FALLBACK_REMOVED_DETAIL),
"/v1/responses" => Some(OPENAI_RESPONSES_PYTHON_FALLBACK_REMOVED_DETAIL),
"/v1/responses/compact" => Some(OPENAI_COMPACT_PYTHON_FALLBACK_REMOVED_DETAIL),
"/v1/messages" => Some(CLAUDE_MESSAGES_PYTHON_FALLBACK_REMOVED_DETAIL),
path if path.starts_with("/v1/videos") => Some(OPENAI_VIDEO_PYTHON_FALLBACK_REMOVED_DETAIL),
"/v1/chat/completions" => Some(OPENAI_CHAT_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL),
"/v1/responses" => Some(OPENAI_RESPONSES_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL),
"/v1/responses/compact" => Some(OPENAI_COMPACT_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL),
"/v1/messages" => Some(CLAUDE_MESSAGES_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL),
path if path.starts_with("/v1/videos") => {
Some(OPENAI_VIDEO_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL)
}
path if path.starts_with("/upload/v1beta/files") || path.starts_with("/v1beta/files") => {
Some(GEMINI_FILES_PYTHON_FALLBACK_REMOVED_DETAIL)
Some(GEMINI_FILES_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL)
}
path if decision.route_family.as_deref() == Some("gemini")
&& (path.starts_with("/v1beta/models/") || path.starts_with("/v1/models/")) =>
{
Some(GEMINI_PUBLIC_PYTHON_FALLBACK_REMOVED_DETAIL)
Some(GEMINI_PUBLIC_LOCAL_EXECUTION_RUNTIME_MISS_DETAIL)
}
_ => None,
}
@@ -875,5 +834,3 @@ mod finalize;
use self::finalize::{
finalize_gateway_response, finalize_gateway_response_with_context, request_wants_stream,
};
pub(super) use self::finalize::*;