2026-05-03 20:14:29 +08:00
|
|
|
use async_trait::async_trait;
|
2026-04-03 14:59:58 +08:00
|
|
|
use tracing::warn;
|
|
|
|
|
|
2026-05-03 20:14:29 +08:00
|
|
|
use crate::ai_serving::planner::candidate_materialization::LocalExecutionAttemptSource;
|
|
|
|
|
use crate::ai_serving::planner::common::{
|
|
|
|
|
extract_requested_model_from_request, RequestedModelFamily,
|
|
|
|
|
};
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::planner::runtime_miss::{
|
2026-04-14 01:27:04 +08:00
|
|
|
apply_local_runtime_candidate_evaluation_progress_preserving_candidate_signal,
|
|
|
|
|
apply_local_runtime_candidate_terminal_reason, set_local_runtime_miss_diagnostic_reason,
|
|
|
|
|
};
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::planner::spec_metadata::{
|
2026-04-14 01:27:04 +08:00
|
|
|
build_stream_plan_from_requested_model_family, build_sync_plan_from_requested_model_family,
|
|
|
|
|
local_same_format_provider_spec_metadata,
|
|
|
|
|
};
|
2026-05-02 13:23:54 +08:00
|
|
|
pub(crate) use crate::ai_serving::{
|
2026-04-09 00:10:38 +08:00
|
|
|
resolve_local_same_format_stream_spec as resolve_stream_spec,
|
|
|
|
|
resolve_local_same_format_sync_spec as resolve_sync_spec,
|
2026-04-07 02:50:19 +08:00
|
|
|
};
|
|
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
use super::{
|
2026-05-03 20:14:29 +08:00
|
|
|
build_local_same_format_provider_candidate_attempt_source,
|
2026-04-03 14:59:58 +08:00
|
|
|
maybe_build_local_same_format_provider_decision_payload_for_candidate,
|
2026-05-02 13:23:54 +08:00
|
|
|
resolve_local_same_format_provider_decision_input, AiStreamAttempt, AiSyncAttempt, AppState,
|
2026-05-03 20:14:29 +08:00
|
|
|
GatewayControlDecision, GatewayError, LocalSameFormatProviderCandidateAttempt,
|
|
|
|
|
LocalSameFormatProviderCandidateAttemptSource, LocalSameFormatProviderDecisionInput,
|
|
|
|
|
LocalSameFormatProviderSpec,
|
2026-04-03 14:59:58 +08:00
|
|
|
};
|
|
|
|
|
|
2026-05-03 20:14:29 +08:00
|
|
|
pub(crate) struct LocalSameFormatProviderSyncAttemptSource<'a> {
|
|
|
|
|
state: &'a AppState,
|
|
|
|
|
parts: &'a http::request::Parts,
|
|
|
|
|
trace_id: &'a str,
|
2026-05-18 11:03:49 +08:00
|
|
|
body_json: serde_json::Value,
|
2026-05-03 20:14:29 +08:00
|
|
|
input: LocalSameFormatProviderDecisionInput,
|
|
|
|
|
spec: LocalSameFormatProviderSpec,
|
|
|
|
|
requested_model_family: RequestedModelFamily,
|
|
|
|
|
candidates: LocalSameFormatProviderCandidateAttemptSource<'a>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) struct LocalSameFormatProviderStreamAttemptSource<'a> {
|
|
|
|
|
state: &'a AppState,
|
|
|
|
|
parts: &'a http::request::Parts,
|
|
|
|
|
trace_id: &'a str,
|
2026-05-18 11:03:49 +08:00
|
|
|
body_json: serde_json::Value,
|
2026-05-03 20:14:29 +08:00
|
|
|
input: LocalSameFormatProviderDecisionInput,
|
|
|
|
|
spec: LocalSameFormatProviderSpec,
|
|
|
|
|
requested_model_family: RequestedModelFamily,
|
|
|
|
|
candidates: LocalSameFormatProviderCandidateAttemptSource<'a>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) async fn build_local_sync_attempt_source<'a>(
|
|
|
|
|
state: &'a AppState,
|
|
|
|
|
parts: &'a http::request::Parts,
|
|
|
|
|
trace_id: &'a str,
|
|
|
|
|
decision: &'a GatewayControlDecision,
|
|
|
|
|
body_json: &'a serde_json::Value,
|
|
|
|
|
spec: LocalSameFormatProviderSpec,
|
|
|
|
|
) -> Result<Option<(LocalSameFormatProviderSyncAttemptSource<'a>, usize)>, GatewayError> {
|
|
|
|
|
let spec_metadata = local_same_format_provider_spec_metadata(spec);
|
|
|
|
|
let requested_model_family = spec_metadata
|
|
|
|
|
.requested_model_family
|
|
|
|
|
.expect("same-format provider spec metadata should include requested-model family");
|
|
|
|
|
let Some(input) = resolve_local_same_format_provider_decision_input(
|
|
|
|
|
state, parts, trace_id, decision, body_json, spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-05-03 20:14:29 +08:00
|
|
|
else {
|
|
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
extract_requested_model_from_request(parts, body_json, requested_model_family)
|
|
|
|
|
.as_deref(),
|
|
|
|
|
"decision_input_unavailable",
|
|
|
|
|
);
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
Some(input.requested_model.as_str()),
|
|
|
|
|
"candidate_evaluation_incomplete",
|
|
|
|
|
);
|
2026-05-18 11:03:49 +08:00
|
|
|
let effective_body_json = input.effective_body_json(body_json).clone();
|
2026-05-03 20:14:29 +08:00
|
|
|
let (candidates, candidate_count) = build_local_same_format_provider_candidate_attempt_source(
|
2026-05-18 11:03:49 +08:00
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
&input,
|
|
|
|
|
&effective_body_json,
|
|
|
|
|
spec,
|
2026-05-03 20:14:29 +08:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
apply_local_runtime_candidate_evaluation_progress_preserving_candidate_signal(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
candidate_count,
|
|
|
|
|
);
|
|
|
|
|
if candidate_count == 0 {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Some((
|
|
|
|
|
LocalSameFormatProviderSyncAttemptSource {
|
|
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
trace_id,
|
2026-05-18 11:03:49 +08:00
|
|
|
body_json: effective_body_json,
|
2026-05-03 20:14:29 +08:00
|
|
|
input,
|
|
|
|
|
spec,
|
|
|
|
|
requested_model_family,
|
|
|
|
|
candidates,
|
|
|
|
|
},
|
|
|
|
|
candidate_count,
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) async fn build_local_stream_attempt_source<'a>(
|
|
|
|
|
state: &'a AppState,
|
|
|
|
|
parts: &'a http::request::Parts,
|
|
|
|
|
trace_id: &'a str,
|
|
|
|
|
decision: &'a GatewayControlDecision,
|
|
|
|
|
body_json: &'a serde_json::Value,
|
|
|
|
|
spec: LocalSameFormatProviderSpec,
|
|
|
|
|
) -> Result<Option<(LocalSameFormatProviderStreamAttemptSource<'a>, usize)>, GatewayError> {
|
|
|
|
|
let spec_metadata = local_same_format_provider_spec_metadata(spec);
|
|
|
|
|
let requested_model_family = spec_metadata
|
|
|
|
|
.requested_model_family
|
|
|
|
|
.expect("same-format provider spec metadata should include requested-model family");
|
|
|
|
|
let Some(input) = resolve_local_same_format_provider_decision_input(
|
|
|
|
|
state, parts, trace_id, decision, body_json, spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-05-03 20:14:29 +08:00
|
|
|
else {
|
|
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
extract_requested_model_from_request(parts, body_json, requested_model_family)
|
|
|
|
|
.as_deref(),
|
|
|
|
|
"decision_input_unavailable",
|
|
|
|
|
);
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
Some(input.requested_model.as_str()),
|
|
|
|
|
"candidate_evaluation_incomplete",
|
|
|
|
|
);
|
2026-05-18 11:03:49 +08:00
|
|
|
let effective_body_json = input.effective_body_json(body_json).clone();
|
2026-05-03 20:14:29 +08:00
|
|
|
let (candidates, candidate_count) = build_local_same_format_provider_candidate_attempt_source(
|
2026-05-18 11:03:49 +08:00
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
&input,
|
|
|
|
|
&effective_body_json,
|
|
|
|
|
spec,
|
2026-05-03 20:14:29 +08:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
apply_local_runtime_candidate_evaluation_progress_preserving_candidate_signal(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
candidate_count,
|
|
|
|
|
);
|
|
|
|
|
if candidate_count == 0 {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Some((
|
|
|
|
|
LocalSameFormatProviderStreamAttemptSource {
|
|
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
trace_id,
|
2026-05-18 11:03:49 +08:00
|
|
|
body_json: effective_body_json,
|
2026-05-03 20:14:29 +08:00
|
|
|
input,
|
|
|
|
|
spec,
|
|
|
|
|
requested_model_family,
|
|
|
|
|
candidates,
|
|
|
|
|
},
|
|
|
|
|
candidate_count,
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl LocalExecutionAttemptSource<AiSyncAttempt> for LocalSameFormatProviderSyncAttemptSource<'_> {
|
|
|
|
|
async fn next_execution_attempt(&mut self) -> Result<Option<AiSyncAttempt>, GatewayError> {
|
|
|
|
|
while let Some(attempt) = self.candidates.next_attempt().await {
|
|
|
|
|
match self.build_sync_attempt(attempt).await? {
|
|
|
|
|
Some(attempt) => return Ok(Some(attempt)),
|
|
|
|
|
None => continue,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
apply_local_runtime_candidate_terminal_reason(
|
|
|
|
|
self.state,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
"no_local_sync_plans",
|
|
|
|
|
);
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn drain_execution_attempts(&mut self) -> Result<Vec<AiSyncAttempt>, GatewayError> {
|
|
|
|
|
let mut drained = Vec::new();
|
|
|
|
|
for attempt in self.candidates.drain_static_attempts() {
|
|
|
|
|
if let Some(attempt) = self.build_sync_attempt(attempt).await? {
|
|
|
|
|
drained.push(attempt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(drained)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl LocalExecutionAttemptSource<AiStreamAttempt>
|
|
|
|
|
for LocalSameFormatProviderStreamAttemptSource<'_>
|
|
|
|
|
{
|
|
|
|
|
async fn next_execution_attempt(&mut self) -> Result<Option<AiStreamAttempt>, GatewayError> {
|
|
|
|
|
while let Some(attempt) = self.candidates.next_attempt().await {
|
|
|
|
|
match self.build_stream_attempt(attempt).await? {
|
|
|
|
|
Some(attempt) => return Ok(Some(attempt)),
|
|
|
|
|
None => continue,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
apply_local_runtime_candidate_terminal_reason(
|
|
|
|
|
self.state,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
"no_local_stream_plans",
|
|
|
|
|
);
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn drain_execution_attempts(&mut self) -> Result<Vec<AiStreamAttempt>, GatewayError> {
|
|
|
|
|
let mut drained = Vec::new();
|
|
|
|
|
for attempt in self.candidates.drain_static_attempts() {
|
|
|
|
|
if let Some(attempt) = self.build_stream_attempt(attempt).await? {
|
|
|
|
|
drained.push(attempt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(drained)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LocalSameFormatProviderSyncAttemptSource<'_> {
|
|
|
|
|
async fn build_sync_attempt(
|
|
|
|
|
&self,
|
|
|
|
|
attempt: LocalSameFormatProviderCandidateAttempt,
|
|
|
|
|
) -> Result<Option<AiSyncAttempt>, GatewayError> {
|
|
|
|
|
let Some(payload) = maybe_build_local_same_format_provider_decision_payload_for_candidate(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.trace_id,
|
2026-05-18 11:03:49 +08:00
|
|
|
&self.body_json,
|
2026-05-03 20:14:29 +08:00
|
|
|
&self.input,
|
|
|
|
|
attempt,
|
|
|
|
|
self.spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-05-03 20:14:29 +08:00
|
|
|
else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match build_sync_plan_from_requested_model_family(
|
|
|
|
|
self.requested_model_family,
|
|
|
|
|
self.parts,
|
2026-05-18 11:03:49 +08:00
|
|
|
&self.body_json,
|
2026-05-03 20:14:29 +08:00
|
|
|
payload,
|
|
|
|
|
) {
|
|
|
|
|
Ok(value) => Ok(value),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!(
|
|
|
|
|
trace_id = %self.trace_id,
|
|
|
|
|
error = ?err,
|
|
|
|
|
"gateway local same-format sync decision plan build failed"
|
|
|
|
|
);
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LocalSameFormatProviderStreamAttemptSource<'_> {
|
|
|
|
|
async fn build_stream_attempt(
|
|
|
|
|
&self,
|
|
|
|
|
attempt: LocalSameFormatProviderCandidateAttempt,
|
|
|
|
|
) -> Result<Option<AiStreamAttempt>, GatewayError> {
|
|
|
|
|
let Some(payload) = maybe_build_local_same_format_provider_decision_payload_for_candidate(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.trace_id,
|
2026-05-18 11:03:49 +08:00
|
|
|
&self.body_json,
|
2026-05-03 20:14:29 +08:00
|
|
|
&self.input,
|
|
|
|
|
attempt,
|
|
|
|
|
self.spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-05-03 20:14:29 +08:00
|
|
|
else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match build_stream_plan_from_requested_model_family(
|
|
|
|
|
self.requested_model_family,
|
|
|
|
|
self.parts,
|
2026-05-18 11:03:49 +08:00
|
|
|
&self.body_json,
|
2026-05-03 20:14:29 +08:00
|
|
|
payload,
|
|
|
|
|
) {
|
|
|
|
|
Ok(value) => Ok(value),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!(
|
|
|
|
|
trace_id = %self.trace_id,
|
|
|
|
|
error = ?err,
|
|
|
|
|
"gateway local same-format stream decision plan build failed"
|
|
|
|
|
);
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:26:16 +08:00
|
|
|
pub(crate) async fn build_local_sync_plan_and_reports(
|
2026-04-03 14:59:58 +08:00
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
spec: LocalSameFormatProviderSpec,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Vec<AiSyncAttempt>, GatewayError> {
|
2026-04-14 01:27:04 +08:00
|
|
|
let spec_metadata = local_same_format_provider_spec_metadata(spec);
|
|
|
|
|
let requested_model_family = spec_metadata
|
|
|
|
|
.requested_model_family
|
|
|
|
|
.expect("same-format provider spec metadata should include requested-model family");
|
2026-04-03 14:59:58 +08:00
|
|
|
let Some(input) = resolve_local_same_format_provider_decision_input(
|
|
|
|
|
state, parts, trace_id, decision, body_json, spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-04-03 14:59:58 +08:00
|
|
|
else {
|
2026-04-14 01:27:04 +08:00
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
2026-04-13 14:01:22 +08:00
|
|
|
trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
extract_requested_model_from_request(parts, body_json, requested_model_family)
|
|
|
|
|
.as_deref(),
|
|
|
|
|
"decision_input_unavailable",
|
2026-04-13 14:01:22 +08:00
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
return Ok(Vec::new());
|
|
|
|
|
};
|
2026-04-14 01:27:04 +08:00
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
2026-04-13 14:01:22 +08:00
|
|
|
trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
Some(input.requested_model.as_str()),
|
|
|
|
|
"candidate_evaluation_incomplete",
|
2026-04-13 14:01:22 +08:00
|
|
|
);
|
2026-05-18 11:03:49 +08:00
|
|
|
let body_json = input.effective_body_json(body_json);
|
2026-05-12 13:15:41 +08:00
|
|
|
let (mut source, candidate_count) = build_local_same_format_provider_candidate_attempt_source(
|
2026-04-17 23:05:49 +08:00
|
|
|
state, trace_id, &input, body_json, spec,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
2026-04-14 01:27:04 +08:00
|
|
|
apply_local_runtime_candidate_evaluation_progress_preserving_candidate_signal(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
candidate_count,
|
|
|
|
|
);
|
2026-04-13 14:01:22 +08:00
|
|
|
if candidate_count == 0 {
|
|
|
|
|
return Ok(Vec::new());
|
|
|
|
|
}
|
2026-04-03 14:59:58 +08:00
|
|
|
|
|
|
|
|
let mut plans = Vec::new();
|
2026-05-12 13:15:41 +08:00
|
|
|
while let Some(attempt) = source.next_attempt().await {
|
2026-04-03 14:59:58 +08:00
|
|
|
let Some(payload) = maybe_build_local_same_format_provider_decision_payload_for_candidate(
|
|
|
|
|
state, parts, trace_id, body_json, &input, attempt, spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-04-03 14:59:58 +08:00
|
|
|
else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-14 01:27:04 +08:00
|
|
|
let built = build_sync_plan_from_requested_model_family(
|
|
|
|
|
requested_model_family,
|
|
|
|
|
parts,
|
|
|
|
|
body_json,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
|
|
|
|
|
match built {
|
|
|
|
|
Ok(Some(value)) => plans.push(value),
|
|
|
|
|
Ok(None) => {}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!(
|
|
|
|
|
trace_id = %trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
api_format = spec_metadata.api_format,
|
2026-04-03 14:59:58 +08:00
|
|
|
error = ?err,
|
|
|
|
|
"gateway local same-format sync decision plan build failed"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:27:04 +08:00
|
|
|
apply_local_runtime_candidate_terminal_reason(state, trace_id, "no_local_sync_plans");
|
2026-04-13 14:01:22 +08:00
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
Ok(plans)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:26:16 +08:00
|
|
|
pub(crate) async fn build_local_stream_plan_and_reports(
|
2026-04-03 14:59:58 +08:00
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
spec: LocalSameFormatProviderSpec,
|
2026-05-02 13:23:54 +08:00
|
|
|
) -> Result<Vec<AiStreamAttempt>, GatewayError> {
|
2026-04-14 01:27:04 +08:00
|
|
|
let spec_metadata = local_same_format_provider_spec_metadata(spec);
|
|
|
|
|
let requested_model_family = spec_metadata
|
|
|
|
|
.requested_model_family
|
|
|
|
|
.expect("same-format provider spec metadata should include requested-model family");
|
2026-04-03 14:59:58 +08:00
|
|
|
let Some(input) = resolve_local_same_format_provider_decision_input(
|
|
|
|
|
state, parts, trace_id, decision, body_json, spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-04-03 14:59:58 +08:00
|
|
|
else {
|
2026-04-14 01:27:04 +08:00
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
2026-04-13 14:01:22 +08:00
|
|
|
trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
extract_requested_model_from_request(parts, body_json, requested_model_family)
|
|
|
|
|
.as_deref(),
|
|
|
|
|
"decision_input_unavailable",
|
2026-04-13 14:01:22 +08:00
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
return Ok(Vec::new());
|
|
|
|
|
};
|
2026-04-14 01:27:04 +08:00
|
|
|
set_local_runtime_miss_diagnostic_reason(
|
|
|
|
|
state,
|
2026-04-13 14:01:22 +08:00
|
|
|
trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
decision,
|
|
|
|
|
spec_metadata.decision_kind,
|
|
|
|
|
Some(input.requested_model.as_str()),
|
|
|
|
|
"candidate_evaluation_incomplete",
|
2026-04-13 14:01:22 +08:00
|
|
|
);
|
2026-05-18 11:03:49 +08:00
|
|
|
let body_json = input.effective_body_json(body_json);
|
2026-05-12 13:15:41 +08:00
|
|
|
let (mut source, candidate_count) = build_local_same_format_provider_candidate_attempt_source(
|
2026-04-17 23:05:49 +08:00
|
|
|
state, trace_id, &input, body_json, spec,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
2026-04-14 01:27:04 +08:00
|
|
|
apply_local_runtime_candidate_evaluation_progress_preserving_candidate_signal(
|
|
|
|
|
state,
|
|
|
|
|
trace_id,
|
|
|
|
|
candidate_count,
|
|
|
|
|
);
|
2026-04-13 14:01:22 +08:00
|
|
|
if candidate_count == 0 {
|
|
|
|
|
return Ok(Vec::new());
|
|
|
|
|
}
|
2026-04-03 14:59:58 +08:00
|
|
|
|
|
|
|
|
let mut plans = Vec::new();
|
2026-05-12 13:15:41 +08:00
|
|
|
while let Some(attempt) = source.next_attempt().await {
|
2026-04-03 14:59:58 +08:00
|
|
|
let Some(payload) = maybe_build_local_same_format_provider_decision_payload_for_candidate(
|
|
|
|
|
state, parts, trace_id, body_json, &input, attempt, spec,
|
|
|
|
|
)
|
2026-05-18 11:03:49 +08:00
|
|
|
.await?
|
2026-04-03 14:59:58 +08:00
|
|
|
else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-14 01:27:04 +08:00
|
|
|
let built = build_stream_plan_from_requested_model_family(
|
|
|
|
|
requested_model_family,
|
|
|
|
|
parts,
|
|
|
|
|
body_json,
|
|
|
|
|
payload,
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
|
|
|
|
|
match built {
|
|
|
|
|
Ok(Some(value)) => plans.push(value),
|
|
|
|
|
Ok(None) => {}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
warn!(
|
|
|
|
|
trace_id = %trace_id,
|
2026-04-14 01:27:04 +08:00
|
|
|
api_format = spec_metadata.api_format,
|
2026-04-03 14:59:58 +08:00
|
|
|
error = ?err,
|
|
|
|
|
"gateway local same-format stream decision plan build failed"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:27:04 +08:00
|
|
|
apply_local_runtime_candidate_terminal_reason(state, trace_id, "no_local_stream_plans");
|
2026-04-13 14:01:22 +08:00
|
|
|
|
2026-04-03 14:59:58 +08:00
|
|
|
Ok(plans)
|
|
|
|
|
}
|