mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
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:
@@ -1,14 +1,14 @@
|
||||
use axum::body::Body;
|
||||
use axum::http::Response;
|
||||
|
||||
use aether_data_contracts::repository::candidates::RequestCandidateStatus;
|
||||
|
||||
use crate::ai_pipeline::planner::plan_builders::{
|
||||
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
|
||||
};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::execution_runtime::{
|
||||
execute_execution_runtime_stream, execute_execution_runtime_sync,
|
||||
};
|
||||
use crate::scheduler::record_local_request_candidate_status;
|
||||
use crate::execution_runtime::{execute_execution_runtime_stream, execute_execution_runtime_sync};
|
||||
use crate::request_candidate_runtime::record_local_request_candidate_status;
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
pub(crate) trait LocalPlanAndReport {
|
||||
@@ -120,7 +120,33 @@ where
|
||||
state,
|
||||
plan_and_report.plan(),
|
||||
plan_and_report.report_context().as_ref(),
|
||||
aether_data::repository::candidates::RequestCandidateStatus::Unused,
|
||||
RequestCandidateStatus::Unused,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn mark_unused_local_candidate_items<T, FPlan, FContext>(
|
||||
state: &AppState,
|
||||
remaining: Vec<T>,
|
||||
plan: FPlan,
|
||||
report_context: FContext,
|
||||
) where
|
||||
FPlan: Fn(&T) -> &aether_contracts::ExecutionPlan,
|
||||
FContext: Fn(&T) -> Option<&serde_json::Value>,
|
||||
{
|
||||
for item in remaining {
|
||||
record_local_request_candidate_status(
|
||||
state,
|
||||
plan(&item),
|
||||
report_context(&item),
|
||||
RequestCandidateStatus::Unused,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
pub(crate) mod candidate_loop;
|
||||
mod diagnostics;
|
||||
mod orchestration;
|
||||
mod plan_fallback;
|
||||
mod policy;
|
||||
mod remote;
|
||||
mod reports;
|
||||
mod retries;
|
||||
mod stream_path;
|
||||
mod sync_path;
|
||||
|
||||
pub(crate) use crate::request_candidate_runtime::{
|
||||
persist_available_local_candidate, persist_skipped_local_candidate,
|
||||
};
|
||||
pub(crate) use candidate_loop::mark_unused_local_candidate_items;
|
||||
pub(crate) use orchestration::*;
|
||||
pub(crate) use plan_fallback::{
|
||||
maybe_execute_stream_via_plan_fallback, maybe_execute_sync_via_plan_fallback,
|
||||
};
|
||||
pub(crate) use policy::{
|
||||
build_direct_plan_bypass_cache_key, mark_direct_plan_bypass,
|
||||
should_bypass_execution_runtime_decision, should_bypass_execution_runtime_plan,
|
||||
should_skip_direct_plan,
|
||||
};
|
||||
pub(crate) use remote::{
|
||||
maybe_execute_stream_via_remote_decision, maybe_execute_sync_via_remote_decision,
|
||||
};
|
||||
pub(crate) use stream_path::maybe_execute_via_stream_decision_path;
|
||||
pub(crate) use sync_path::maybe_execute_via_sync_decision_path;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use axum::body::Body;
|
||||
use axum::http::Response;
|
||||
|
||||
use crate::ai_pipeline::planner;
|
||||
use crate::ai_pipeline::planner::common::{
|
||||
parse_direct_request_body, EXECUTION_RUNTIME_STREAM_DECISION_ACTION,
|
||||
EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
|
||||
@@ -16,12 +17,10 @@ use crate::ai_pipeline::planner::standard::family::{
|
||||
build_local_sync_plan_and_reports as build_standard_sync_plan_and_reports, LocalStandardSpec,
|
||||
};
|
||||
use crate::ai_pipeline::planner::standard::{claude, gemini, openai};
|
||||
use crate::ai_pipeline::{planner, runtime};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::executor::candidate_loop::{
|
||||
execute_stream_plan_and_reports, execute_sync_plan_and_reports,
|
||||
};
|
||||
use crate::intent;
|
||||
use crate::{AppState, GatewayControlSyncDecisionResponse, GatewayError};
|
||||
|
||||
pub(crate) async fn maybe_execute_sync_local_path(
|
||||
@@ -31,7 +30,7 @@ pub(crate) async fn maybe_execute_sync_local_path(
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
intent::maybe_execute_via_sync_intent_path(state, parts, body_bytes, trace_id, decision).await
|
||||
super::maybe_execute_via_sync_decision_path(state, parts, body_bytes, trace_id, decision).await
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_execute_stream_local_path(
|
||||
@@ -41,7 +40,8 @@ pub(crate) async fn maybe_execute_stream_local_path(
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
intent::maybe_execute_via_stream_intent_path(state, parts, body_bytes, trace_id, decision).await
|
||||
super::maybe_execute_via_stream_decision_path(state, parts, body_bytes, trace_id, decision)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_execute_sync_via_local_decision(
|
||||
@@ -225,10 +225,7 @@ pub(crate) async fn maybe_execute_sync_via_local_standard_decision(
|
||||
decision,
|
||||
body_json,
|
||||
plan_kind,
|
||||
|plan_kind| {
|
||||
claude::chat::resolve_sync_spec(plan_kind)
|
||||
.or_else(|| claude::cli::resolve_sync_spec(plan_kind))
|
||||
},
|
||||
claude::resolve_sync_spec,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -242,10 +239,7 @@ pub(crate) async fn maybe_execute_sync_via_local_standard_decision(
|
||||
decision,
|
||||
body_json,
|
||||
plan_kind,
|
||||
|plan_kind| {
|
||||
gemini::chat::resolve_sync_spec(plan_kind)
|
||||
.or_else(|| gemini::cli::resolve_sync_spec(plan_kind))
|
||||
},
|
||||
gemini::resolve_sync_spec,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -265,10 +259,7 @@ pub(crate) async fn maybe_execute_stream_via_local_standard_decision(
|
||||
decision,
|
||||
body_json,
|
||||
plan_kind,
|
||||
|plan_kind| {
|
||||
claude::chat::resolve_stream_spec(plan_kind)
|
||||
.or_else(|| claude::cli::resolve_stream_spec(plan_kind))
|
||||
},
|
||||
claude::resolve_stream_spec,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -282,10 +273,7 @@ pub(crate) async fn maybe_execute_stream_via_local_standard_decision(
|
||||
decision,
|
||||
body_json,
|
||||
plan_kind,
|
||||
|plan_kind| {
|
||||
gemini::chat::resolve_stream_spec(plan_kind)
|
||||
.or_else(|| gemini::cli::resolve_stream_spec(plan_kind))
|
||||
},
|
||||
gemini::resolve_stream_spec,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -434,7 +422,28 @@ pub(crate) async fn maybe_execute_sync_request(
|
||||
trace_id: &str,
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
runtime::maybe_execute_sync_request(state, parts, body_bytes, trace_id, decision).await
|
||||
let Some(decision) = decision else {
|
||||
return Ok(None);
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if parts.method != http::Method::POST {
|
||||
return Ok(None);
|
||||
}
|
||||
return maybe_execute_sync_local_path(state, parts, body_bytes, trace_id, decision).await;
|
||||
}
|
||||
#[cfg(test)]
|
||||
{
|
||||
if state
|
||||
.execution_runtime_override_base_url()
|
||||
.unwrap_or_default()
|
||||
.is_empty()
|
||||
&& parts.method != http::Method::POST
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
maybe_execute_sync_local_path(state, parts, body_bytes, trace_id, decision).await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_execute_stream_request(
|
||||
@@ -444,7 +453,28 @@ pub(crate) async fn maybe_execute_stream_request(
|
||||
trace_id: &str,
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
runtime::maybe_execute_stream_request(state, parts, body_bytes, trace_id, decision).await
|
||||
let Some(decision) = decision else {
|
||||
return Ok(None);
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if parts.method != http::Method::POST {
|
||||
return Ok(None);
|
||||
}
|
||||
return maybe_execute_stream_local_path(state, parts, body_bytes, trace_id, decision).await;
|
||||
}
|
||||
#[cfg(test)]
|
||||
{
|
||||
if state
|
||||
.execution_runtime_override_base_url()
|
||||
.unwrap_or_default()
|
||||
.is_empty()
|
||||
&& parts.method != http::Method::POST
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
maybe_execute_stream_local_path(state, parts, body_bytes, trace_id, decision).await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn planner_decision_action(action: &str) -> bool {
|
||||
|
||||
102
apps/aether-gateway/src/executor/plan_fallback.rs
Normal file
102
apps/aether-gateway/src/executor/plan_fallback.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use axum::body::Body;
|
||||
use axum::http::Response;
|
||||
|
||||
use crate::ai_pipeline::planner::{maybe_build_stream_plan_payload, maybe_build_sync_plan_payload};
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::execution_runtime::{execute_execution_runtime_stream, execute_execution_runtime_sync};
|
||||
use crate::{AppState, GatewayControlPlanResponse, GatewayError, GatewayFallbackReason};
|
||||
|
||||
pub(crate) async fn maybe_execute_sync_via_plan_fallback(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
body_json: &serde_json::Value,
|
||||
body_base64: Option<String>,
|
||||
_plan_kind: &str,
|
||||
_bypass_cache_key: String,
|
||||
_fallback_reason: GatewayFallbackReason,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let body_is_empty =
|
||||
body_base64.is_none() && body_json.as_object().is_some_and(|value| value.is_empty());
|
||||
let Some(payload) = maybe_build_sync_plan_payload(
|
||||
state,
|
||||
parts,
|
||||
trace_id,
|
||||
decision,
|
||||
body_json,
|
||||
body_base64.as_deref(),
|
||||
body_is_empty,
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let GatewayControlPlanResponse {
|
||||
action: _,
|
||||
plan_kind,
|
||||
plan,
|
||||
report_kind,
|
||||
report_context,
|
||||
auth_context: _,
|
||||
} = payload;
|
||||
|
||||
let (Some(plan_kind), Some(plan)) = (plan_kind, plan) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
execute_execution_runtime_sync(
|
||||
state,
|
||||
parts.uri.path(),
|
||||
plan,
|
||||
trace_id,
|
||||
decision,
|
||||
plan_kind.as_str(),
|
||||
report_kind,
|
||||
report_context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_execute_stream_via_plan_fallback(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
body_json: &serde_json::Value,
|
||||
body_base64: Option<String>,
|
||||
_plan_kind: &str,
|
||||
_bypass_cache_key: String,
|
||||
_fallback_reason: GatewayFallbackReason,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(payload) =
|
||||
maybe_build_stream_plan_payload(state, parts, trace_id, decision, body_json).await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let GatewayControlPlanResponse {
|
||||
action: _,
|
||||
plan_kind,
|
||||
plan,
|
||||
report_kind,
|
||||
report_context,
|
||||
auth_context: _,
|
||||
} = payload;
|
||||
|
||||
let (Some(plan_kind), Some(plan)) = (plan_kind, plan) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
execute_execution_runtime_stream(
|
||||
state,
|
||||
plan,
|
||||
trace_id,
|
||||
decision,
|
||||
plan_kind.as_str(),
|
||||
report_kind,
|
||||
report_context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
107
apps/aether-gateway/src/executor/policy.rs
Normal file
107
apps/aether-gateway/src/executor/policy.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::time::Duration;
|
||||
|
||||
use aether_contracts::ExecutionPlan;
|
||||
use axum::body::Bytes;
|
||||
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::headers::header_value_str;
|
||||
use crate::provider_transport::provider_types::is_codex_cli_backend_url;
|
||||
use crate::{AppState, GatewayControlSyncDecisionResponse};
|
||||
|
||||
pub(crate) const DIRECT_PLAN_BYPASS_TTL: Duration = Duration::from_secs(30);
|
||||
pub(crate) const DIRECT_PLAN_BYPASS_MAX_ENTRIES: usize = 512;
|
||||
|
||||
pub(crate) fn should_bypass_execution_runtime_decision(
|
||||
payload: &GatewayControlSyncDecisionResponse,
|
||||
) -> bool {
|
||||
let provider_api_format = payload
|
||||
.provider_api_format
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
if !matches!(
|
||||
provider_api_format.as_str(),
|
||||
"openai:cli" | "openai:compact"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
payload
|
||||
.upstream_url
|
||||
.as_deref()
|
||||
.or(payload.upstream_base_url.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some_and(is_codex_cli_backend_url)
|
||||
}
|
||||
|
||||
pub(crate) fn should_bypass_execution_runtime_plan(plan: &ExecutionPlan) -> bool {
|
||||
let provider_api_format = plan.provider_api_format.trim().to_ascii_lowercase();
|
||||
if !matches!(
|
||||
provider_api_format.as_str(),
|
||||
"openai:cli" | "openai:compact"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
is_codex_cli_backend_url(&plan.url)
|
||||
}
|
||||
|
||||
pub(crate) fn build_direct_plan_bypass_cache_key(
|
||||
plan_kind: &str,
|
||||
parts: &http::request::Parts,
|
||||
body_bytes: &Bytes,
|
||||
decision: &GatewayControlDecision,
|
||||
) -> String {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
plan_kind.hash(&mut hasher);
|
||||
parts.method.as_str().hash(&mut hasher);
|
||||
parts.uri.path().hash(&mut hasher);
|
||||
parts.uri.query().unwrap_or_default().hash(&mut hasher);
|
||||
decision
|
||||
.route_family
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.hash(&mut hasher);
|
||||
decision
|
||||
.route_kind
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.hash(&mut hasher);
|
||||
decision
|
||||
.auth_endpoint_signature
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.hash(&mut hasher);
|
||||
header_value_str(&parts.headers, http::header::AUTHORIZATION.as_str())
|
||||
.unwrap_or_default()
|
||||
.hash(&mut hasher);
|
||||
header_value_str(&parts.headers, "x-api-key")
|
||||
.unwrap_or_default()
|
||||
.hash(&mut hasher);
|
||||
header_value_str(&parts.headers, "api-key")
|
||||
.unwrap_or_default()
|
||||
.hash(&mut hasher);
|
||||
header_value_str(&parts.headers, http::header::CONTENT_TYPE.as_str())
|
||||
.unwrap_or_default()
|
||||
.hash(&mut hasher);
|
||||
body_bytes.hash(&mut hasher);
|
||||
format!("{plan_kind}:{:x}", hasher.finish())
|
||||
}
|
||||
|
||||
pub(crate) fn should_skip_direct_plan(state: &AppState, cache_key: &str) -> bool {
|
||||
state
|
||||
.direct_plan_bypass_cache
|
||||
.should_skip(cache_key, DIRECT_PLAN_BYPASS_TTL)
|
||||
}
|
||||
|
||||
pub(crate) fn mark_direct_plan_bypass(state: &AppState, cache_key: String) {
|
||||
state.direct_plan_bypass_cache.mark(
|
||||
cache_key,
|
||||
DIRECT_PLAN_BYPASS_TTL,
|
||||
DIRECT_PLAN_BYPASS_MAX_ENTRIES,
|
||||
);
|
||||
}
|
||||
27
apps/aether-gateway/src/executor/remote.rs
Normal file
27
apps/aether-gateway/src/executor/remote.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use axum::body::Body;
|
||||
use axum::http::Response;
|
||||
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
pub(crate) async fn maybe_execute_sync_via_remote_decision(
|
||||
_state: &AppState,
|
||||
_parts: &http::request::Parts,
|
||||
_trace_id: &str,
|
||||
_decision: &GatewayControlDecision,
|
||||
_body_json: &serde_json::Value,
|
||||
_plan_kind: &str,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_execute_stream_via_remote_decision(
|
||||
_state: &AppState,
|
||||
_parts: &http::request::Parts,
|
||||
_trace_id: &str,
|
||||
_decision: &GatewayControlDecision,
|
||||
_body_json: &serde_json::Value,
|
||||
_plan_kind: &str,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
Ok(None)
|
||||
}
|
||||
197
apps/aether-gateway/src/executor/stream_path.rs
Normal file
197
apps/aether-gateway/src/executor/stream_path.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::Response;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::ai_pipeline::planner::common::OPENAI_VIDEO_CONTENT_PLAN_KIND;
|
||||
use crate::ai_pipeline::planner::{
|
||||
is_matching_stream_request, resolve_execution_runtime_stream_plan_kind,
|
||||
supports_stream_scheduler_decision_kind,
|
||||
};
|
||||
use crate::api::response::build_client_response_from_parts;
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::execution_runtime::execute_execution_runtime_stream;
|
||||
use crate::{AppState, GatewayError, GatewayFallbackReason};
|
||||
|
||||
use super::{
|
||||
build_direct_plan_bypass_cache_key, maybe_execute_stream_via_local_decision,
|
||||
maybe_execute_stream_via_local_gemini_files_decision,
|
||||
maybe_execute_stream_via_local_openai_cli_decision,
|
||||
maybe_execute_stream_via_local_same_format_provider_decision,
|
||||
maybe_execute_stream_via_local_standard_decision, maybe_execute_stream_via_plan_fallback,
|
||||
maybe_execute_stream_via_remote_decision, parse_local_request_body, should_skip_direct_plan,
|
||||
};
|
||||
|
||||
pub(crate) async fn maybe_execute_via_stream_decision_path(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
body_bytes: &Bytes,
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(plan_kind) = resolve_execution_runtime_stream_plan_kind(parts, decision) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some((body_json, body_base64)) = parse_local_request_body(parts, body_bytes) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if !is_matching_stream_request(plan_kind, parts, &body_json) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let bypass_cache_key =
|
||||
build_direct_plan_bypass_cache_key(plan_kind, parts, body_bytes, decision);
|
||||
if should_skip_direct_plan(state, &bypass_cache_key) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(response) =
|
||||
maybe_execute_local_video_task_content_stream(state, parts, trace_id, decision, plan_kind)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if supports_stream_scheduler_decision_kind(plan_kind) {
|
||||
if let Some(response) = maybe_execute_stream_via_local_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_stream_via_local_openai_cli_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_stream_via_local_standard_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_stream_via_local_same_format_provider_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_stream_via_local_gemini_files_decision(
|
||||
state, parts, trace_id, decision, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_stream_via_remote_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
}
|
||||
|
||||
maybe_execute_stream_via_plan_fallback(
|
||||
state,
|
||||
parts,
|
||||
trace_id,
|
||||
decision,
|
||||
&body_json,
|
||||
body_base64,
|
||||
plan_kind,
|
||||
bypass_cache_key,
|
||||
if supports_stream_scheduler_decision_kind(plan_kind) {
|
||||
GatewayFallbackReason::RemoteDecisionMiss
|
||||
} else {
|
||||
GatewayFallbackReason::SchedulerDecisionUnsupported
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn maybe_execute_local_video_task_content_stream(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
plan_kind: &str,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
if plan_kind != OPENAI_VIDEO_CONTENT_PLAN_KIND
|
||||
|| decision.route_family.as_deref() != Some("openai")
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let _ = state
|
||||
.hydrate_video_task_for_route(decision.route_family.as_deref(), parts.uri.path())
|
||||
.await?;
|
||||
|
||||
if let Some(task_id) =
|
||||
crate::video_tasks::extract_openai_task_id_from_content_path(parts.uri.path())
|
||||
{
|
||||
let refresh_path = format!("/v1/videos/{task_id}");
|
||||
if let Some(refresh_plan) = state.video_tasks.prepare_read_refresh_sync_plan(
|
||||
Some("openai"),
|
||||
&refresh_path,
|
||||
trace_id,
|
||||
) {
|
||||
state.execute_video_task_refresh_plan(&refresh_plan).await?;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(action) = state.video_tasks.prepare_openai_content_stream_action(
|
||||
parts.uri.path(),
|
||||
parts.uri.query(),
|
||||
trace_id,
|
||||
) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
match action {
|
||||
crate::video_tasks::LocalVideoTaskContentAction::Immediate {
|
||||
status_code,
|
||||
body_json,
|
||||
} => Ok(Some(build_json_response(
|
||||
trace_id,
|
||||
decision,
|
||||
status_code,
|
||||
&body_json,
|
||||
)?)),
|
||||
crate::video_tasks::LocalVideoTaskContentAction::StreamPlan(plan) => {
|
||||
execute_execution_runtime_stream(state, plan, trace_id, decision, plan_kind, None, None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_json_response(
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
status_code: u16,
|
||||
body_json: &serde_json::Value,
|
||||
) -> Result<Response<Body>, GatewayError> {
|
||||
let body_bytes =
|
||||
serde_json::to_vec(body_json).map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
let mut headers = BTreeMap::new();
|
||||
headers.insert("content-type".to_string(), "application/json".to_string());
|
||||
headers.insert("content-length".to_string(), body_bytes.len().to_string());
|
||||
build_client_response_from_parts(
|
||||
status_code,
|
||||
&headers,
|
||||
Body::from(body_bytes),
|
||||
trace_id,
|
||||
Some(decision),
|
||||
)
|
||||
}
|
||||
261
apps/aether-gateway/src/executor/sync_path.rs
Normal file
261
apps/aether-gateway/src/executor/sync_path.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::Response;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::ai_pipeline::planner::common::{
|
||||
GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND, OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND,
|
||||
OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND, OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
|
||||
};
|
||||
use crate::ai_pipeline::planner::{
|
||||
is_matching_stream_request, resolve_execution_runtime_stream_plan_kind,
|
||||
resolve_execution_runtime_sync_plan_kind, supports_sync_scheduler_decision_kind,
|
||||
};
|
||||
use crate::api::response::build_client_response_from_parts;
|
||||
use crate::control::resolve_execution_runtime_auth_context;
|
||||
use crate::control::GatewayControlDecision;
|
||||
use crate::execution_runtime::execute_execution_runtime_sync;
|
||||
use crate::{AppState, GatewayError, GatewayFallbackReason};
|
||||
|
||||
use super::{
|
||||
build_direct_plan_bypass_cache_key, maybe_execute_sync_via_local_decision,
|
||||
maybe_execute_sync_via_local_gemini_files_decision,
|
||||
maybe_execute_sync_via_local_openai_cli_decision,
|
||||
maybe_execute_sync_via_local_same_format_provider_decision,
|
||||
maybe_execute_sync_via_local_standard_decision, maybe_execute_sync_via_local_video_decision,
|
||||
maybe_execute_sync_via_plan_fallback, maybe_execute_sync_via_remote_decision,
|
||||
parse_local_request_body, should_skip_direct_plan,
|
||||
};
|
||||
|
||||
pub(crate) async fn maybe_execute_via_sync_decision_path(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
body_bytes: &Bytes,
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
if let Some(response) =
|
||||
maybe_build_local_video_task_read_response(state, parts, trace_id, decision).await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
let Some(plan_kind) = resolve_execution_runtime_sync_plan_kind(parts, decision) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some((body_json, body_base64)) = parse_local_request_body(parts, body_bytes) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if let Some(stream_plan_kind) = resolve_execution_runtime_stream_plan_kind(parts, decision) {
|
||||
if is_matching_stream_request(stream_plan_kind, parts, &body_json) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
let bypass_cache_key =
|
||||
build_direct_plan_bypass_cache_key(plan_kind, parts, body_bytes, decision);
|
||||
if should_skip_direct_plan(state, &bypass_cache_key) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_local_video_task_follow_up_sync(
|
||||
state, parts, &body_json, trace_id, decision, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if supports_sync_scheduler_decision_kind(plan_kind) {
|
||||
if let Some(response) = maybe_execute_sync_via_local_video_decision(
|
||||
state, parts, &body_json, trace_id, decision, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_sync_via_local_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_sync_via_local_openai_cli_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_sync_via_local_standard_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_sync_via_local_same_format_provider_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_sync_via_local_gemini_files_decision(
|
||||
state,
|
||||
parts,
|
||||
&body_json,
|
||||
body_base64.as_deref(),
|
||||
body_bytes.is_empty(),
|
||||
trace_id,
|
||||
decision,
|
||||
plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
if let Some(response) = maybe_execute_sync_via_remote_decision(
|
||||
state, parts, trace_id, decision, &body_json, plan_kind,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(response));
|
||||
}
|
||||
}
|
||||
|
||||
maybe_execute_sync_via_plan_fallback(
|
||||
state,
|
||||
parts,
|
||||
trace_id,
|
||||
decision,
|
||||
&body_json,
|
||||
body_base64,
|
||||
plan_kind,
|
||||
bypass_cache_key,
|
||||
if supports_sync_scheduler_decision_kind(plan_kind) {
|
||||
GatewayFallbackReason::RemoteDecisionMiss
|
||||
} else {
|
||||
GatewayFallbackReason::SchedulerDecisionUnsupported
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn maybe_build_local_video_task_read_response(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
if parts.method != http::Method::GET || decision.route_kind.as_deref() != Some("video") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let _ = state
|
||||
.hydrate_video_task_for_route(decision.route_family.as_deref(), parts.uri.path())
|
||||
.await?;
|
||||
|
||||
let refresh_plan = state.video_tasks.prepare_read_refresh_sync_plan(
|
||||
decision.route_family.as_deref(),
|
||||
parts.uri.path(),
|
||||
trace_id,
|
||||
);
|
||||
|
||||
if let Some(refresh_plan) = refresh_plan {
|
||||
state.execute_video_task_refresh_plan(&refresh_plan).await?;
|
||||
}
|
||||
|
||||
let read_response = state
|
||||
.video_tasks
|
||||
.read_response(decision.route_family.as_deref(), parts.uri.path());
|
||||
let read_response = match read_response {
|
||||
Some(read_response) => Some(read_response),
|
||||
None => {
|
||||
state
|
||||
.read_data_backed_video_task_response(
|
||||
decision.route_family.as_deref(),
|
||||
parts.uri.path(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
let Some(read_response) = read_response else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let body_bytes = serde_json::to_vec(&read_response.body_json)
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
let mut headers = BTreeMap::new();
|
||||
headers.insert("content-type".to_string(), "application/json".to_string());
|
||||
headers.insert("content-length".to_string(), body_bytes.len().to_string());
|
||||
|
||||
Ok(Some(build_client_response_from_parts(
|
||||
read_response.status_code,
|
||||
&headers,
|
||||
Body::from(body_bytes),
|
||||
trace_id,
|
||||
Some(decision),
|
||||
)?))
|
||||
}
|
||||
|
||||
async fn maybe_execute_local_video_task_follow_up_sync(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
body_json: &serde_json::Value,
|
||||
trace_id: &str,
|
||||
decision: &GatewayControlDecision,
|
||||
plan_kind: &str,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
if !matches!(
|
||||
plan_kind,
|
||||
OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND
|
||||
| OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND
|
||||
| OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND
|
||||
| GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND
|
||||
) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let _ = state
|
||||
.hydrate_video_task_for_route(decision.route_family.as_deref(), parts.uri.path())
|
||||
.await?;
|
||||
|
||||
let auth_context = resolve_execution_runtime_auth_context(
|
||||
state,
|
||||
decision,
|
||||
&parts.headers,
|
||||
&parts.uri,
|
||||
trace_id,
|
||||
)
|
||||
.await?;
|
||||
let Some(follow_up) = state.video_tasks.prepare_follow_up_sync_plan(
|
||||
plan_kind,
|
||||
parts.uri.path(),
|
||||
Some(body_json),
|
||||
auth_context.as_ref(),
|
||||
trace_id,
|
||||
) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
execute_execution_runtime_sync(
|
||||
state,
|
||||
parts.uri.path(),
|
||||
follow_up.plan,
|
||||
trace_id,
|
||||
decision,
|
||||
plan_kind,
|
||||
follow_up.report_kind,
|
||||
follow_up.report_context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Reference in New Issue
Block a user