use std::collections::{BTreeMap, BTreeSet};
use std::sync::atomic::{AtomicBool, Ordering};
use aether_ai_serving::{
run_ai_attempt_loop, AiAttemptExecutionOutcome, AiAttemptLoopOutcome, AiAttemptLoopPort,
AiAttemptRetryScope, AiExecutionAttempt,
};
use aether_data_contracts::repository::candidates::RequestCandidateStatus;
use aether_runtime::{AdmissionPermit, ConcurrencyPermit};
use aether_scheduler_core::{
parse_request_candidate_report_context, SchedulerRequestCandidateStatusUpdate,
};
use async_trait::async_trait;
use axum::body::Body;
use axum::http::Response;
use futures_util::StreamExt;
use tokio::time::{timeout, Duration, Instant};
use tracing::{debug, warn, Instrument};
use crate::ai_serving::LocalExecutionAttemptSource;
use crate::clock::current_unix_ms;
use crate::control::GatewayControlDecision;
use crate::execution_runtime::{
acquire_upstream_execution_gate, build_transport_error_stop_response,
execute_execution_runtime_stream_with_retry_scope,
execute_execution_runtime_sync_with_retry_scope,
mark_stream_candidate_watchdog_terminal_started, StreamCandidateWatchdogProgress,
UpstreamExecutionGateProvider, UPSTREAM_EXECUTION_GATE_NAME,
};
use crate::executor::{
attach_deferred_usage_context, build_local_execution_exhaustion,
mark_deferred_upstream_response, LocalExecutionRequestOutcome,
};
use crate::handlers::shared::provider_pool::release_admin_provider_pool_key_lease;
use crate::log_ids::short_request_id;
use crate::orchestration::{
local_execution_candidate_metadata_from_report_context,
local_failover_policy_from_report_context, resolve_local_failover_policy,
resolve_local_transport_failover_analysis_for_attempt, LocalFailoverDecision,
LocalFailoverPolicy,
};
use crate::privacy::RedactionExecutionCandidateId;
use crate::request_candidate_runtime::{
record_local_request_candidate_status, RequestCandidateRuntimeWriter,
};
use crate::stage_metrics::observe_gateway_stage_ms;
use crate::{AppState, GatewayError};
const DEFAULT_STREAM_FIRST_BYTE_WATCHDOG_TIMEOUT_MS: u64 = 30_000;
const UPSTREAM_TARGET_GATE_NAME: &str = "gateway_upstream_target";
const UPSTREAM_EXECUTION_GATE_HOLD_STREAM_RESPONSE_ENV: &str =
"AETHER_GATEWAY_UPSTREAM_EXECUTION_GATE_HOLD_STREAM_RESPONSE";
const UPSTREAM_EXECUTION_GATE_STREAM_HOLD_MODE_ENV: &str =
"AETHER_GATEWAY_UPSTREAM_EXECUTION_GATE_STREAM_HOLD_MODE";
#[derive(Clone, Debug)]
pub(crate) struct BackgroundAdmissionPermit {
_permit: AdmissionPermit,
}
impl BackgroundAdmissionPermit {
pub(crate) fn new(permit: AdmissionPermit) -> Self {
Self { _permit: permit }
}
}
fn attach_redaction_execution_candidate(response: &mut Response
, candidate_id: Option<&str>) {
if let Some(candidate_id) = candidate_id
.map(str::trim)
.filter(|value| !value.is_empty())
{
response
.extensions_mut()
.insert(RedactionExecutionCandidateId::new(candidate_id));
}
}
pub(crate) async fn execute_sync_plan_and_reports(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
plan_and_reports: Vec,
) -> Result
where
T: AiExecutionAttempt + Send + Sync + 'static,
{
let transfer_tracker = ProviderTransferTracker::for_request(parts);
execute_sync_plan_and_reports_with_transfer_tracker(
state,
parts,
trace_id,
decision,
plan_kind,
plan_and_reports,
&transfer_tracker,
)
.await
}
pub(crate) async fn execute_sync_plan_and_reports_with_transfer_tracker(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
plan_and_reports: Vec,
transfer_tracker: &ProviderTransferTracker,
) -> Result
where
T: AiExecutionAttempt + Send + Sync + 'static,
{
let candidate_count = plan_and_reports.len();
let first_provider = plan_and_reports
.first()
.and_then(|item| item.execution_plan().provider_name.as_deref())
.unwrap_or("-")
.to_string();
let span = tracing::debug_span!(
"candidates",
trace_id = %trace_id,
plan_kind,
candidate_count,
);
async move {
tracing::debug!(
event_name = "candidate_loop_started",
log_type = "event",
trace_id = %trace_id,
plan_kind,
candidate_count,
first_provider = first_provider.as_str(),
"candidate loop started"
);
let port = SyncAttemptLoopPort {
state,
parts,
trace_id,
decision,
plan_kind,
transfer_tracker,
};
let loop_result = run_ai_attempt_loop(&port, plan_and_reports).await;
if loop_result.is_err() {
release_active_plan_usage_policy_cost_best_effort(
state,
decision,
transfer_tracker,
"candidate_loop_error",
)
.await;
}
match loop_result? {
AiAttemptLoopOutcome::Responded(response) => {
Ok(LocalExecutionRequestOutcome::responded(response))
}
AiAttemptLoopOutcome::Deferred(response) => Ok(
LocalExecutionRequestOutcome::responded(mark_deferred_upstream_response(response)),
),
AiAttemptLoopOutcome::Exhausted(exhaustion) => {
Ok(LocalExecutionRequestOutcome::Exhausted(exhaustion))
}
AiAttemptLoopOutcome::NoPath => Ok(LocalExecutionRequestOutcome::NoPath),
}
}
.instrument(span)
.await
}
pub(crate) async fn execute_sync_attempt_source(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
source: S,
) -> Result
where
T: AiExecutionAttempt + Send + Sync + 'static,
S: LocalExecutionAttemptSource,
{
let transfer_tracker = ProviderTransferTracker::for_request(parts);
execute_sync_attempt_source_with_transfer_tracker(
state,
parts,
trace_id,
decision,
plan_kind,
source,
&transfer_tracker,
)
.await
}
pub(crate) async fn execute_sync_attempt_source_with_transfer_tracker(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
mut source: S,
transfer_tracker: &ProviderTransferTracker,
) -> Result
where
T: AiExecutionAttempt + Send + Sync + 'static,
S: LocalExecutionAttemptSource,
{
let span = tracing::debug_span!("candidates", trace_id = %trace_id, plan_kind);
async move {
tracing::debug!(
event_name = "candidate_loop_started",
log_type = "event",
trace_id = %trace_id,
plan_kind,
"dynamic candidate loop started"
);
let port = SyncAttemptLoopPort {
state,
parts,
trace_id,
decision,
plan_kind,
transfer_tracker,
};
let loop_result = run_dynamic_attempt_loop(
&port,
&mut source,
trace_id,
plan_kind,
state
.frontdoor_runtime_guards
.local_execution_planning_timeout,
)
.await;
if loop_result.is_err() {
release_active_plan_usage_policy_cost_best_effort(
state,
decision,
transfer_tracker,
"dynamic_candidate_loop_error",
)
.await;
}
loop_result
}
.instrument(span)
.await
}
struct SyncAttemptLoopPort<'a> {
state: &'a AppState,
parts: &'a http::request::Parts,
trace_id: &'a str,
decision: &'a GatewayControlDecision,
plan_kind: &'a str,
transfer_tracker: &'a ProviderTransferTracker,
}
#[async_trait]
impl AiAttemptLoopPort for SyncAttemptLoopPort<'_>
where
T: AiExecutionAttempt + Send + Sync + 'static,
{
type Response = Response;
type Exhaustion = crate::executor::LocalExecutionExhaustion;
type Error = GatewayError;
async fn should_skip_attempt(&self, attempt: &T) -> Result {
Ok(should_skip_provider_transfer_attempt(
self.transfer_tracker,
self.trace_id,
self.plan_kind,
attempt,
)
.await)
}
async fn record_attempt_started(&self, attempt: &T) -> Result<(), Self::Error> {
record_provider_transfer_attempt_started(self.transfer_tracker, attempt).await;
Ok(())
}
async fn next_same_key_retry(&self, attempt: &T) -> Result