2026-04-09 00:10:38 +08:00
|
|
|
mod decision;
|
2026-04-14 01:27:04 +08:00
|
|
|
mod request;
|
2026-04-09 00:10:38 +08:00
|
|
|
mod support;
|
2026-03-31 19:19:04 +08:00
|
|
|
|
|
|
|
|
use tracing::warn;
|
|
|
|
|
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::planner::plan_builders::{
|
2026-04-05 20:23:16 +08:00
|
|
|
build_passthrough_stream_plan_from_decision, build_passthrough_sync_plan_from_decision,
|
2026-05-02 13:23:54 +08:00
|
|
|
AiStreamAttempt, AiSyncAttempt,
|
2026-03-31 19:19:04 +08:00
|
|
|
};
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::planner::spec_metadata::local_gemini_files_spec_metadata;
|
|
|
|
|
use crate::ai_serving::GatewayControlDecision;
|
|
|
|
|
use crate::ai_serving::{
|
2026-04-09 00:10:38 +08:00
|
|
|
resolve_gemini_files_stream_spec as resolve_stream_spec,
|
|
|
|
|
resolve_gemini_files_sync_spec as resolve_sync_spec, LocalGeminiFilesSpec,
|
2026-04-05 20:23:16 +08:00
|
|
|
};
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::{AiExecutionDecision, AppState, GatewayError};
|
2026-04-09 00:10:38 +08:00
|
|
|
|
|
|
|
|
use self::decision::maybe_build_local_gemini_files_decision_payload_for_candidate;
|
|
|
|
|
use self::support::{
|
|
|
|
|
materialize_local_gemini_files_candidate_attempts, resolve_local_gemini_files_decision_input,
|
2026-04-07 02:50:19 +08:00
|
|
|
};
|
2026-03-31 19:19:04 +08:00
|
|
|
|
2026-04-03 16:26:16 +08:00
|
|
|
pub(crate) async fn build_local_gemini_files_sync_plan_and_reports_for_kind(
|
2026-03-31 19:19:04 +08:00
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
body_base64: Option<&str>,
|
|
|
|
|
body_is_empty: bool,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
plan_kind: &str,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Vec<AiSyncAttempt>, GatewayError> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some(spec) = resolve_sync_spec(plan_kind) else {
|
2026-04-03 16:26:16 +08:00
|
|
|
return Ok(Vec::new());
|
2026-03-31 19:19:04 +08:00
|
|
|
};
|
|
|
|
|
|
2026-04-03 16:26:16 +08:00
|
|
|
build_local_sync_plan_and_reports(
|
2026-03-31 19:19:04 +08:00
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
body_json,
|
|
|
|
|
body_base64,
|
|
|
|
|
body_is_empty,
|
|
|
|
|
trace_id,
|
|
|
|
|
decision,
|
|
|
|
|
spec,
|
|
|
|
|
)
|
2026-04-03 16:26:16 +08:00
|
|
|
.await
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:26:16 +08:00
|
|
|
pub(crate) async fn build_local_gemini_files_stream_plan_and_reports_for_kind(
|
2026-03-31 19:19:04 +08:00
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
plan_kind: &str,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Vec<AiStreamAttempt>, GatewayError> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some(spec) = resolve_stream_spec(plan_kind) else {
|
2026-04-03 16:26:16 +08:00
|
|
|
return Ok(Vec::new());
|
2026-03-31 19:19:04 +08:00
|
|
|
};
|
|
|
|
|
|
2026-04-03 16:26:16 +08:00
|
|
|
build_local_stream_plan_and_reports(state, parts, trace_id, decision, spec).await
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) async fn maybe_build_sync_local_gemini_files_decision_payload(
|
2026-03-31 19:19:04 +08:00
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
body_base64: Option<&str>,
|
|
|
|
|
body_is_empty: bool,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
plan_kind: &str,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Option<AiExecutionDecision>, GatewayError> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some(spec) = resolve_sync_spec(plan_kind) else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(input) = resolve_local_gemini_files_decision_input(state, trace_id, decision).await
|
|
|
|
|
else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let attempts =
|
|
|
|
|
materialize_local_gemini_files_candidate_attempts(state, trace_id, &input).await?;
|
|
|
|
|
|
|
|
|
|
for attempt in attempts {
|
|
|
|
|
if let Some(payload) = maybe_build_local_gemini_files_decision_payload_for_candidate(
|
|
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
body_json,
|
|
|
|
|
body_base64,
|
|
|
|
|
body_is_empty,
|
|
|
|
|
trace_id,
|
|
|
|
|
&input,
|
|
|
|
|
attempt,
|
|
|
|
|
spec,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
return Ok(Some(payload));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
pub(crate) async fn maybe_build_stream_local_gemini_files_decision_payload(
|
2026-03-31 19:19:04 +08:00
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
plan_kind: &str,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Option<AiExecutionDecision>, GatewayError> {
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some(spec) = resolve_stream_spec(plan_kind) else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(input) = resolve_local_gemini_files_decision_input(state, trace_id, decision).await
|
|
|
|
|
else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let attempts =
|
|
|
|
|
materialize_local_gemini_files_candidate_attempts(state, trace_id, &input).await?;
|
|
|
|
|
|
|
|
|
|
let empty_body_json = serde_json::Value::Null;
|
|
|
|
|
for attempt in attempts {
|
|
|
|
|
if let Some(payload) = maybe_build_local_gemini_files_decision_payload_for_candidate(
|
|
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
&empty_body_json,
|
|
|
|
|
None,
|
|
|
|
|
true,
|
|
|
|
|
trace_id,
|
|
|
|
|
&input,
|
|
|
|
|
attempt,
|
|
|
|
|
spec,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
return Ok(Some(payload));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn build_local_sync_plan_and_reports(
|
|
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
body_base64: Option<&str>,
|
|
|
|
|
body_is_empty: bool,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
spec: LocalGeminiFilesSpec,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Vec<AiSyncAttempt>, GatewayError> {
|
2026-04-14 01:27:04 +08:00
|
|
|
let spec_metadata = local_gemini_files_spec_metadata(spec);
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some(input) = resolve_local_gemini_files_decision_input(state, trace_id, decision).await
|
|
|
|
|
else {
|
|
|
|
|
return Ok(Vec::new());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let attempts =
|
|
|
|
|
materialize_local_gemini_files_candidate_attempts(state, trace_id, &input).await?;
|
|
|
|
|
|
|
|
|
|
let mut plans = Vec::new();
|
|
|
|
|
for attempt in attempts {
|
|
|
|
|
let Some(payload) = maybe_build_local_gemini_files_decision_payload_for_candidate(
|
|
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
body_json,
|
|
|
|
|
body_base64,
|
|
|
|
|
body_is_empty,
|
|
|
|
|
trace_id,
|
|
|
|
|
&input,
|
|
|
|
|
attempt,
|
|
|
|
|
spec,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match build_passthrough_sync_plan_from_decision(parts, payload) {
|
|
|
|
|
Ok(Some(value)) => plans.push(value),
|
|
|
|
|
Ok(None) => {}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!(
|
|
|
|
|
trace_id = %trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
decision_kind = spec_metadata.decision_kind,
|
2026-03-31 19:19:04 +08:00
|
|
|
error = ?err,
|
|
|
|
|
"gateway local gemini files sync decision plan build failed"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(plans)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn build_local_stream_plan_and_reports(
|
|
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
spec: LocalGeminiFilesSpec,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Vec<AiStreamAttempt>, GatewayError> {
|
2026-04-14 01:27:04 +08:00
|
|
|
let spec_metadata = local_gemini_files_spec_metadata(spec);
|
2026-03-31 19:19:04 +08:00
|
|
|
let Some(input) = resolve_local_gemini_files_decision_input(state, trace_id, decision).await
|
|
|
|
|
else {
|
|
|
|
|
return Ok(Vec::new());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let attempts =
|
|
|
|
|
materialize_local_gemini_files_candidate_attempts(state, trace_id, &input).await?;
|
|
|
|
|
|
|
|
|
|
let mut plans = Vec::new();
|
|
|
|
|
let empty_body_json = serde_json::Value::Null;
|
|
|
|
|
for attempt in attempts {
|
|
|
|
|
let Some(payload) = maybe_build_local_gemini_files_decision_payload_for_candidate(
|
|
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
&empty_body_json,
|
|
|
|
|
None,
|
|
|
|
|
true,
|
|
|
|
|
trace_id,
|
|
|
|
|
&input,
|
|
|
|
|
attempt,
|
|
|
|
|
spec,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match build_passthrough_stream_plan_from_decision(parts, payload) {
|
|
|
|
|
Ok(Some(value)) => plans.push(value),
|
|
|
|
|
Ok(None) => {}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!(
|
|
|
|
|
trace_id = %trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
decision_kind = spec_metadata.decision_kind,
|
2026-03-31 19:19:04 +08:00
|
|
|
error = ?err,
|
|
|
|
|
"gateway local gemini files stream decision plan build failed"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(plans)
|
|
|
|
|
}
|