mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 03:09:50 +08:00
refactor gateway orchestration and failover effects
This commit is contained in:
@@ -4,6 +4,7 @@ use aether_scheduler_core::SchedulerMinimalCandidateSelectionCandidate;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::ai_pipeline::{GatewayProviderTransportSnapshot, PlannerAppState};
|
||||
use crate::orchestration::LocalExecutionCandidateMetadata;
|
||||
|
||||
use super::candidate_affinity::rank_eligible_local_execution_candidates;
|
||||
use super::pool_scheduler::apply_local_execution_pool_scheduler;
|
||||
@@ -13,6 +14,7 @@ pub(crate) struct EligibleLocalExecutionCandidate {
|
||||
pub(crate) candidate: SchedulerMinimalCandidateSelectionCandidate,
|
||||
pub(crate) transport: GatewayProviderTransportSnapshot,
|
||||
pub(crate) provider_api_format: String,
|
||||
pub(crate) orchestration: LocalExecutionCandidateMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -122,6 +124,7 @@ where
|
||||
provider_api_format: transport.endpoint.api_format.trim().to_ascii_lowercase(),
|
||||
candidate,
|
||||
transport,
|
||||
orchestration: LocalExecutionCandidateMetadata::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,26 @@ use crate::ai_pipeline::planner::candidate_eligibility::{
|
||||
use crate::ai_pipeline::planner::runtime_miss::record_local_runtime_candidate_skip_reason;
|
||||
use crate::ai_pipeline::{GatewayAuthApiKeySnapshot, PlannerAppState};
|
||||
use crate::clock::current_unix_ms;
|
||||
use crate::orchestration::{build_local_attempt_identities, ExecutionAttemptIdentity};
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LocalExecutionCandidateAttempt {
|
||||
pub(crate) eligible: EligibleLocalExecutionCandidate,
|
||||
pub(crate) candidate_index: u32,
|
||||
pub(crate) retry_index: u32,
|
||||
pub(crate) pool_key_index: Option<u32>,
|
||||
pub(crate) candidate_group_id: Option<String>,
|
||||
pub(crate) candidate_id: String,
|
||||
}
|
||||
|
||||
impl LocalExecutionCandidateAttempt {
|
||||
pub(crate) fn attempt_identity(&self) -> ExecutionAttemptIdentity {
|
||||
ExecutionAttemptIdentity::new(self.candidate_index, self.retry_index)
|
||||
.with_pool_key_index(self.pool_key_index)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct LocalAvailableCandidatePersistenceContext<'a> {
|
||||
pub(crate) user_id: &'a str,
|
||||
@@ -73,30 +84,43 @@ where
|
||||
F: Fn(&EligibleLocalExecutionCandidate) -> Option<Value>,
|
||||
{
|
||||
let created_at_unix_ms = current_unix_ms();
|
||||
let mut materialized = Vec::with_capacity(candidates.len());
|
||||
let mut materialized = Vec::new();
|
||||
|
||||
for (candidate_index, eligible) in candidates.into_iter().enumerate() {
|
||||
let generated_candidate_id = Uuid::new_v4().to_string();
|
||||
let candidate_id = state
|
||||
.persist_available_local_candidate(
|
||||
trace_id,
|
||||
user_id,
|
||||
api_key_id,
|
||||
&eligible.candidate,
|
||||
candidate_index as u32,
|
||||
&generated_candidate_id,
|
||||
required_capabilities,
|
||||
build_extra_data(&eligible),
|
||||
created_at_unix_ms,
|
||||
error_context,
|
||||
)
|
||||
.await;
|
||||
let candidate_index = candidate_index as u32;
|
||||
let attempt_identities =
|
||||
build_local_attempt_identities(candidate_index, &eligible.transport)
|
||||
.into_iter()
|
||||
.map(|identity| identity.with_pool_key_index(eligible.orchestration.pool_key_index))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
materialized.push(LocalExecutionCandidateAttempt {
|
||||
eligible,
|
||||
candidate_index: candidate_index as u32,
|
||||
candidate_id,
|
||||
});
|
||||
for attempt_identity in attempt_identities {
|
||||
let generated_candidate_id = Uuid::new_v4().to_string();
|
||||
let candidate_id = state
|
||||
.persist_available_local_candidate(
|
||||
trace_id,
|
||||
user_id,
|
||||
api_key_id,
|
||||
&eligible.candidate,
|
||||
attempt_identity.candidate_index,
|
||||
attempt_identity.retry_index,
|
||||
&generated_candidate_id,
|
||||
required_capabilities,
|
||||
build_extra_data(&eligible),
|
||||
created_at_unix_ms,
|
||||
error_context,
|
||||
)
|
||||
.await;
|
||||
|
||||
materialized.push(LocalExecutionCandidateAttempt {
|
||||
eligible: eligible.clone(),
|
||||
candidate_index: attempt_identity.candidate_index,
|
||||
retry_index: attempt_identity.retry_index,
|
||||
pool_key_index: attempt_identity.pool_key_index,
|
||||
candidate_group_id: eligible.orchestration.candidate_group_id.clone(),
|
||||
candidate_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
materialized
|
||||
@@ -151,6 +175,7 @@ pub(crate) async fn persist_skipped_local_execution_candidate(
|
||||
api_key_id,
|
||||
candidate,
|
||||
candidate_index,
|
||||
0,
|
||||
candidate_id,
|
||||
required_capabilities,
|
||||
skip_reason,
|
||||
|
||||
+2
-1
@@ -155,6 +155,7 @@ pub(crate) async fn materialize_local_same_format_provider_candidate_attempts(
|
||||
Some(&input.requested_model),
|
||||
&candidates,
|
||||
);
|
||||
let available_candidate_count = candidates.len() as u32;
|
||||
let attempts = persist_available_local_execution_candidates_with_context(
|
||||
planner_state,
|
||||
trace_id,
|
||||
@@ -180,7 +181,7 @@ pub(crate) async fn materialize_local_same_format_provider_candidate_attempts(
|
||||
state,
|
||||
trace_id,
|
||||
persistence_policy.skipped,
|
||||
attempts.len() as u32,
|
||||
available_candidate_count,
|
||||
skipped_candidates,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -41,7 +41,9 @@ pub(crate) async fn maybe_build_local_same_format_provider_decision_payload_for_
|
||||
let LocalSameFormatProviderCandidateAttempt {
|
||||
eligible,
|
||||
candidate_index,
|
||||
candidate_group_id,
|
||||
candidate_id,
|
||||
..
|
||||
} = &attempt;
|
||||
let candidate = &eligible.candidate;
|
||||
let resolved = resolve_local_same_format_provider_candidate_payload_parts(
|
||||
@@ -83,8 +85,7 @@ pub(crate) async fn maybe_build_local_same_format_provider_decision_payload_for_
|
||||
auth_context: &input.auth_context,
|
||||
request_id: trace_id,
|
||||
candidate_id,
|
||||
candidate_index: *candidate_index,
|
||||
retry_index: 0,
|
||||
attempt_identity: attempt.attempt_identity(),
|
||||
model: &input.requested_model,
|
||||
provider_name: &resolved.transport.provider.name,
|
||||
provider_id: &candidate.provider_id,
|
||||
@@ -94,6 +95,7 @@ pub(crate) async fn maybe_build_local_same_format_provider_decision_payload_for_
|
||||
provider_api_format: spec_metadata.api_format,
|
||||
client_api_format: spec_metadata.api_format,
|
||||
mapped_model: Some(&resolved.mapped_model),
|
||||
candidate_group_id: candidate_group_id.as_deref(),
|
||||
upstream_url: Some(&resolved.upstream_url),
|
||||
provider_request_method: Some(serde_json::Value::Null),
|
||||
provider_request_headers: Some(&resolved.provider_request_headers),
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::handlers::shared::{
|
||||
parse_catalog_auth_config_json, provider_key_health_summary,
|
||||
provider_key_status_snapshot_payload,
|
||||
};
|
||||
use crate::orchestration::LocalExecutionCandidateMetadata;
|
||||
use crate::provider_key_auth::provider_key_auth_semantics;
|
||||
|
||||
const POOL_ACCOUNT_BLOCKED_SKIP_REASON: &str = "pool_account_blocked";
|
||||
@@ -337,17 +338,27 @@ fn apply_local_execution_pool_scheduler_with_runtime_map(
|
||||
let Some(group) = groups.remove(&group_key) else {
|
||||
continue;
|
||||
};
|
||||
let candidate_group_id = local_execution_candidate_group_id(&group_key);
|
||||
let Some(pool_config) =
|
||||
pool_config_for_candidate(group.first().expect("group should exist"))
|
||||
else {
|
||||
reordered.extend(group);
|
||||
reordered.extend(annotate_local_execution_group_candidates(
|
||||
group,
|
||||
candidate_group_id.as_str(),
|
||||
false,
|
||||
));
|
||||
continue;
|
||||
};
|
||||
let runtime = runtime_by_provider
|
||||
.get(&group_key.provider_id)
|
||||
.unwrap_or(&default_runtime);
|
||||
let (group_candidates, group_skipped) =
|
||||
schedule_pool_group(group, pool_config, runtime, key_context_by_id);
|
||||
let (group_candidates, group_skipped) = schedule_pool_group(
|
||||
group,
|
||||
pool_config,
|
||||
runtime,
|
||||
key_context_by_id,
|
||||
candidate_group_id.as_str(),
|
||||
);
|
||||
reordered.extend(group_candidates);
|
||||
skipped.extend(group_skipped);
|
||||
}
|
||||
@@ -366,6 +377,18 @@ fn pool_group_key(candidate: &EligibleLocalExecutionCandidate, pool_enabled: boo
|
||||
}
|
||||
}
|
||||
|
||||
fn local_execution_candidate_group_id(group_key: &PoolGroupKey) -> String {
|
||||
format!(
|
||||
"provider={}|endpoint={}|model={}|selected_model={}|api_format={}|singleton_key={}",
|
||||
group_key.provider_id,
|
||||
group_key.endpoint_id,
|
||||
group_key.model_id,
|
||||
group_key.selected_provider_model_name,
|
||||
group_key.provider_api_format,
|
||||
group_key.singleton_key_id.as_deref().unwrap_or("*"),
|
||||
)
|
||||
}
|
||||
|
||||
fn pool_config_for_candidate(
|
||||
candidate: &EligibleLocalExecutionCandidate,
|
||||
) -> Option<AdminProviderPoolConfig> {
|
||||
@@ -377,6 +400,7 @@ fn schedule_pool_group(
|
||||
pool_config: AdminProviderPoolConfig,
|
||||
runtime: &AdminProviderPoolRuntimeState,
|
||||
key_context_by_id: &BTreeMap<String, PoolCatalogKeyContext>,
|
||||
candidate_group_id: &str,
|
||||
) -> (
|
||||
Vec<EligibleLocalExecutionCandidate>,
|
||||
Vec<SkippedLocalExecutionCandidate>,
|
||||
@@ -403,6 +427,7 @@ fn schedule_pool_group(
|
||||
candidate,
|
||||
transport,
|
||||
provider_api_format,
|
||||
orchestration,
|
||||
} = eligible;
|
||||
let key_id = candidate.key_id.clone();
|
||||
let mut key_context = key_context_by_id.get(&key_id).cloned().unwrap_or_default();
|
||||
@@ -460,6 +485,7 @@ fn schedule_pool_group(
|
||||
candidate,
|
||||
transport,
|
||||
provider_api_format,
|
||||
orchestration,
|
||||
},
|
||||
key_context,
|
||||
original_index,
|
||||
@@ -516,7 +542,28 @@ fn schedule_pool_group(
|
||||
}
|
||||
ordered.extend(available.into_iter().map(|item| item.eligible));
|
||||
|
||||
(ordered, skipped)
|
||||
(
|
||||
annotate_local_execution_group_candidates(ordered, candidate_group_id, true),
|
||||
skipped,
|
||||
)
|
||||
}
|
||||
|
||||
fn annotate_local_execution_group_candidates(
|
||||
candidates: Vec<EligibleLocalExecutionCandidate>,
|
||||
candidate_group_id: &str,
|
||||
pool_enabled: bool,
|
||||
) -> Vec<EligibleLocalExecutionCandidate> {
|
||||
candidates
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, mut candidate)| {
|
||||
candidate.orchestration = LocalExecutionCandidateMetadata {
|
||||
candidate_group_id: Some(candidate_group_id.to_string()),
|
||||
pool_key_index: pool_enabled.then_some(index as u32),
|
||||
};
|
||||
candidate
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -980,6 +1027,7 @@ mod tests {
|
||||
use crate::handlers::shared::provider_pool::{
|
||||
AdminProviderPoolRuntimeState, AdminProviderPoolSchedulingPreset,
|
||||
};
|
||||
use crate::orchestration::LocalExecutionCandidateMetadata;
|
||||
use crate::AppState;
|
||||
use aether_data::repository::provider_catalog::InMemoryProviderCatalogReadRepository;
|
||||
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
@@ -1039,6 +1087,72 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_scheduler_attaches_group_and_pool_metadata_to_ranked_candidates() {
|
||||
let pool_first = sample_eligible_candidate(
|
||||
"provider-pool",
|
||||
"endpoint-1",
|
||||
"key-pool-a",
|
||||
10,
|
||||
Some(json!({ "pool_advanced": { "lru_enabled": true } })),
|
||||
);
|
||||
let other =
|
||||
sample_eligible_candidate("provider-other", "endpoint-2", "key-other", 10, None);
|
||||
let pool_second = sample_eligible_candidate(
|
||||
"provider-pool",
|
||||
"endpoint-1",
|
||||
"key-pool-b",
|
||||
10,
|
||||
Some(json!({ "pool_advanced": { "lru_enabled": true } })),
|
||||
);
|
||||
|
||||
let mut runtime_by_provider = BTreeMap::new();
|
||||
runtime_by_provider.insert(
|
||||
"provider-pool".to_string(),
|
||||
AdminProviderPoolRuntimeState {
|
||||
lru_score_by_key: BTreeMap::from([
|
||||
("key-pool-a".to_string(), 20.0),
|
||||
("key-pool-b".to_string(), 10.0),
|
||||
]),
|
||||
..AdminProviderPoolRuntimeState::default()
|
||||
},
|
||||
);
|
||||
|
||||
let (reordered, skipped) = apply_local_execution_pool_scheduler_with_runtime_map(
|
||||
vec![pool_first, other, pool_second],
|
||||
&runtime_by_provider,
|
||||
&BTreeMap::new(),
|
||||
);
|
||||
|
||||
assert!(skipped.is_empty());
|
||||
assert_eq!(reordered.len(), 3);
|
||||
assert_eq!(
|
||||
reordered[0].orchestration,
|
||||
LocalExecutionCandidateMetadata {
|
||||
candidate_group_id: Some(
|
||||
"provider=provider-pool|endpoint=endpoint-1|model=model-1|selected_model=gpt-5|api_format=openai:chat|singleton_key=*"
|
||||
.to_string(),
|
||||
),
|
||||
pool_key_index: Some(0),
|
||||
}
|
||||
);
|
||||
assert_eq!(reordered[1].orchestration.pool_key_index, Some(1));
|
||||
assert_eq!(
|
||||
reordered[1].orchestration.candidate_group_id,
|
||||
reordered[0].orchestration.candidate_group_id
|
||||
);
|
||||
assert_eq!(
|
||||
reordered[2].orchestration,
|
||||
LocalExecutionCandidateMetadata {
|
||||
candidate_group_id: Some(
|
||||
"provider=provider-other|endpoint=endpoint-2|model=model-1|selected_model=gpt-5|api_format=openai:chat|singleton_key=key-other"
|
||||
.to_string(),
|
||||
),
|
||||
pool_key_index: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_scheduler_promotes_sticky_hit_before_other_sorted_keys() {
|
||||
let key_a = sample_eligible_candidate(
|
||||
@@ -1394,6 +1508,7 @@ mod tests {
|
||||
mapping_matched_model: None,
|
||||
},
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
orchestration: LocalExecutionCandidateMetadata::default(),
|
||||
transport: crate::ai_pipeline::GatewayProviderTransportSnapshot {
|
||||
provider: GatewayProviderTransportProvider {
|
||||
id: provider_id.to_string(),
|
||||
|
||||
@@ -3,13 +3,13 @@ use std::collections::BTreeMap;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::ai_pipeline::contracts::ExecutionRuntimeAuthContext;
|
||||
use crate::orchestration::ExecutionAttemptIdentity;
|
||||
|
||||
pub(crate) struct LocalExecutionReportContextParts<'a> {
|
||||
pub(crate) auth_context: &'a ExecutionRuntimeAuthContext,
|
||||
pub(crate) request_id: &'a str,
|
||||
pub(crate) candidate_id: &'a str,
|
||||
pub(crate) candidate_index: u32,
|
||||
pub(crate) retry_index: u32,
|
||||
pub(crate) attempt_identity: ExecutionAttemptIdentity,
|
||||
pub(crate) model: &'a str,
|
||||
pub(crate) provider_name: &'a str,
|
||||
pub(crate) provider_id: &'a str,
|
||||
@@ -19,6 +19,7 @@ pub(crate) struct LocalExecutionReportContextParts<'a> {
|
||||
pub(crate) provider_api_format: &'a str,
|
||||
pub(crate) client_api_format: &'a str,
|
||||
pub(crate) mapped_model: Option<&'a str>,
|
||||
pub(crate) candidate_group_id: Option<&'a str>,
|
||||
pub(crate) upstream_url: Option<&'a str>,
|
||||
pub(crate) provider_request_method: Option<Value>,
|
||||
pub(crate) provider_request_headers: Option<&'a BTreeMap<String, String>>,
|
||||
@@ -69,11 +70,11 @@ pub(crate) fn build_local_execution_report_context(
|
||||
);
|
||||
object.insert(
|
||||
"candidate_index".to_string(),
|
||||
Value::Number(parts.candidate_index.into()),
|
||||
Value::Number(parts.attempt_identity.candidate_index.into()),
|
||||
);
|
||||
object.insert(
|
||||
"retry_index".to_string(),
|
||||
Value::Number(parts.retry_index.into()),
|
||||
Value::Number(parts.attempt_identity.retry_index.into()),
|
||||
);
|
||||
object.insert("model".to_string(), Value::String(parts.model.to_string()));
|
||||
object.insert(
|
||||
@@ -127,6 +128,12 @@ pub(crate) fn build_local_execution_report_context(
|
||||
Value::String(mapped_model.to_string()),
|
||||
);
|
||||
}
|
||||
if let Some(candidate_group_id) = parts.candidate_group_id {
|
||||
object.insert(
|
||||
"candidate_group_id".to_string(),
|
||||
Value::String(candidate_group_id.to_string()),
|
||||
);
|
||||
}
|
||||
if let Some(upstream_url) = parts.upstream_url {
|
||||
object.insert(
|
||||
"upstream_url".to_string(),
|
||||
@@ -146,6 +153,12 @@ pub(crate) fn build_local_execution_report_context(
|
||||
.expect("provider request headers should serialize"),
|
||||
);
|
||||
}
|
||||
if let Some(pool_key_index) = parts.attempt_identity.pool_key_index {
|
||||
object.insert(
|
||||
"pool_key_index".to_string(),
|
||||
Value::Number(pool_key_index.into()),
|
||||
);
|
||||
}
|
||||
|
||||
object.extend(parts.extra_fields);
|
||||
Value::Object(object)
|
||||
|
||||
@@ -34,6 +34,7 @@ pub(super) async fn maybe_build_local_gemini_files_decision_payload_for_candidat
|
||||
) -> Option<GatewayControlSyncDecisionResponse> {
|
||||
let spec_metadata = local_gemini_files_spec_metadata(spec);
|
||||
let planner_state = PlannerAppState::new(state);
|
||||
let attempt_identity = attempt.attempt_identity();
|
||||
let resolved = resolve_local_gemini_files_candidate_payload_parts(
|
||||
state,
|
||||
parts,
|
||||
@@ -48,8 +49,9 @@ pub(super) async fn maybe_build_local_gemini_files_decision_payload_for_candidat
|
||||
.await?;
|
||||
let LocalGeminiFilesCandidateAttempt {
|
||||
eligible,
|
||||
candidate_index,
|
||||
candidate_group_id,
|
||||
candidate_id,
|
||||
..
|
||||
} = attempt;
|
||||
let candidate = eligible.candidate;
|
||||
let transport = resolved.transport;
|
||||
@@ -107,8 +109,7 @@ pub(super) async fn maybe_build_local_gemini_files_decision_payload_for_candidat
|
||||
auth_context: &input.auth_context,
|
||||
request_id: trace_id,
|
||||
candidate_id: &candidate_id,
|
||||
candidate_index,
|
||||
retry_index: 0,
|
||||
attempt_identity,
|
||||
model: "gemini-files",
|
||||
provider_name: &transport.provider.name,
|
||||
provider_id: &candidate.provider_id,
|
||||
@@ -118,6 +119,7 @@ pub(super) async fn maybe_build_local_gemini_files_decision_payload_for_candidat
|
||||
provider_api_format: GEMINI_FILES_CLIENT_API_FORMAT,
|
||||
client_api_format: GEMINI_FILES_CLIENT_API_FORMAT,
|
||||
mapped_model: None,
|
||||
candidate_group_id: candidate_group_id.as_deref(),
|
||||
upstream_url: None,
|
||||
provider_request_method: None,
|
||||
provider_request_headers: None,
|
||||
|
||||
@@ -105,6 +105,7 @@ pub(super) async fn materialize_local_gemini_files_candidate_attempts(
|
||||
None,
|
||||
&candidates,
|
||||
);
|
||||
let available_candidate_count = candidates.len() as u32;
|
||||
let attempts = persist_available_local_execution_candidates_with_context(
|
||||
planner_state,
|
||||
trace_id,
|
||||
@@ -132,7 +133,7 @@ pub(super) async fn materialize_local_gemini_files_candidate_attempts(
|
||||
state,
|
||||
trace_id,
|
||||
persistence_policy.skipped,
|
||||
attempts.len() as u32,
|
||||
available_candidate_count,
|
||||
skipped_candidates
|
||||
.into_iter()
|
||||
.map(|mut skipped_candidate| {
|
||||
|
||||
@@ -27,14 +27,16 @@ pub(super) async fn maybe_build_local_video_create_decision_payload_for_candidat
|
||||
) -> Option<GatewayControlSyncDecisionResponse> {
|
||||
let spec_metadata = local_video_create_spec_metadata(spec);
|
||||
let planner_state = PlannerAppState::new(state);
|
||||
let attempt_identity = attempt.attempt_identity();
|
||||
let resolved = resolve_local_video_create_candidate_payload_parts(
|
||||
state, parts, body_json, trace_id, input, &attempt, spec,
|
||||
)
|
||||
.await?;
|
||||
let LocalVideoCreateCandidateAttempt {
|
||||
eligible,
|
||||
candidate_index,
|
||||
candidate_group_id,
|
||||
candidate_id,
|
||||
..
|
||||
} = attempt;
|
||||
let candidate = eligible.candidate;
|
||||
let transport = resolved.transport;
|
||||
@@ -90,8 +92,7 @@ pub(super) async fn maybe_build_local_video_create_decision_payload_for_candidat
|
||||
auth_context: &input.auth_context,
|
||||
request_id: trace_id,
|
||||
candidate_id: &candidate_id,
|
||||
candidate_index,
|
||||
retry_index: 0,
|
||||
attempt_identity,
|
||||
model: &input.requested_model,
|
||||
provider_name: &transport.provider.name,
|
||||
provider_id: &candidate.provider_id,
|
||||
@@ -101,6 +102,7 @@ pub(super) async fn maybe_build_local_video_create_decision_payload_for_candidat
|
||||
provider_api_format: spec_metadata.api_format,
|
||||
client_api_format: spec_metadata.api_format,
|
||||
mapped_model: Some(&resolved.mapped_model),
|
||||
candidate_group_id: candidate_group_id.as_deref(),
|
||||
upstream_url: None,
|
||||
provider_request_method: None,
|
||||
provider_request_headers: None,
|
||||
|
||||
@@ -182,6 +182,7 @@ async fn materialize_local_video_create_candidate_attempts(
|
||||
Some(&input.requested_model),
|
||||
&candidates,
|
||||
);
|
||||
let available_candidate_count = candidates.len() as u32;
|
||||
let attempts = persist_available_local_execution_candidates_with_context(
|
||||
state,
|
||||
trace_id,
|
||||
@@ -204,7 +205,7 @@ async fn materialize_local_video_create_candidate_attempts(
|
||||
state.app(),
|
||||
trace_id,
|
||||
persistence_policy.skipped,
|
||||
attempts.len() as u32,
|
||||
available_candidate_count,
|
||||
skipped_candidates
|
||||
.into_iter()
|
||||
.map(|mut skipped_candidate| {
|
||||
|
||||
@@ -234,6 +234,7 @@ pub(super) async fn materialize_local_standard_candidate_attempts(
|
||||
Some(&input.requested_model),
|
||||
&candidates,
|
||||
);
|
||||
let available_candidate_count = candidates.len() as u32;
|
||||
let attempts = persist_available_local_execution_candidates_with_context(
|
||||
planner_state,
|
||||
trace_id,
|
||||
@@ -273,7 +274,7 @@ pub(super) async fn materialize_local_standard_candidate_attempts(
|
||||
state,
|
||||
trace_id,
|
||||
persistence_policy.skipped,
|
||||
attempts.len() as u32,
|
||||
available_candidate_count,
|
||||
skipped_candidates,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -35,7 +35,9 @@ pub(super) async fn maybe_build_local_standard_decision_payload_for_candidate(
|
||||
let LocalStandardCandidateAttempt {
|
||||
eligible,
|
||||
candidate_index,
|
||||
candidate_group_id,
|
||||
candidate_id,
|
||||
..
|
||||
} = &attempt;
|
||||
let candidate = &eligible.candidate;
|
||||
let resolved = resolve_local_standard_candidate_payload_parts(
|
||||
@@ -89,8 +91,7 @@ pub(super) async fn maybe_build_local_standard_decision_payload_for_candidate(
|
||||
auth_context: &input.auth_context,
|
||||
request_id: trace_id,
|
||||
candidate_id,
|
||||
candidate_index: *candidate_index,
|
||||
retry_index: 0,
|
||||
attempt_identity: attempt.attempt_identity(),
|
||||
model: &input.requested_model,
|
||||
provider_name: &candidate.provider_name,
|
||||
provider_id: &candidate.provider_id,
|
||||
@@ -100,6 +101,7 @@ pub(super) async fn maybe_build_local_standard_decision_payload_for_candidate(
|
||||
provider_api_format: &resolved.provider_api_format,
|
||||
client_api_format: spec_metadata.api_format,
|
||||
mapped_model: Some(&resolved.mapped_model),
|
||||
candidate_group_id: candidate_group_id.as_deref(),
|
||||
upstream_url: Some(&resolved.upstream_url),
|
||||
provider_request_method: Some(serde_json::Value::Null),
|
||||
provider_request_headers: Some(&resolved.provider_request_headers),
|
||||
|
||||
+5
-2
@@ -28,10 +28,13 @@ pub(crate) async fn maybe_build_local_openai_chat_decision_payload_for_candidate
|
||||
report_kind: &str,
|
||||
upstream_is_stream: bool,
|
||||
) -> Option<GatewayControlSyncDecisionResponse> {
|
||||
let attempt_identity = attempt.attempt_identity();
|
||||
let LocalOpenAiChatCandidateAttempt {
|
||||
eligible,
|
||||
candidate_index,
|
||||
candidate_group_id,
|
||||
candidate_id,
|
||||
..
|
||||
} = attempt;
|
||||
let resolved = resolve_local_openai_chat_candidate_payload_parts(
|
||||
state,
|
||||
@@ -105,8 +108,7 @@ pub(crate) async fn maybe_build_local_openai_chat_decision_payload_for_candidate
|
||||
auth_context: &input.auth_context,
|
||||
request_id: trace_id,
|
||||
candidate_id: &candidate_id,
|
||||
candidate_index,
|
||||
retry_index: 0,
|
||||
attempt_identity,
|
||||
model: &input.requested_model,
|
||||
provider_name: &resolved.transport.provider.name,
|
||||
provider_id: &candidate.provider_id,
|
||||
@@ -116,6 +118,7 @@ pub(crate) async fn maybe_build_local_openai_chat_decision_payload_for_candidate
|
||||
provider_api_format: &resolved.provider_api_format,
|
||||
client_api_format: "openai:chat",
|
||||
mapped_model: Some(&resolved.mapped_model),
|
||||
candidate_group_id: candidate_group_id.as_deref(),
|
||||
upstream_url: Some(&resolved.upstream_url),
|
||||
provider_request_method: Some(serde_json::Value::Null),
|
||||
provider_request_headers: Some(&resolved.provider_request_headers),
|
||||
|
||||
+2
-1
@@ -122,6 +122,7 @@ pub(crate) async fn materialize_local_openai_chat_candidate_attempts(
|
||||
Some(&input.requested_model),
|
||||
&candidates,
|
||||
);
|
||||
let available_candidate_count = candidates.len() as u32;
|
||||
let attempts = persist_available_local_execution_candidates_with_context(
|
||||
planner_state,
|
||||
trace_id,
|
||||
@@ -156,7 +157,7 @@ pub(crate) async fn materialize_local_openai_chat_candidate_attempts(
|
||||
state,
|
||||
trace_id,
|
||||
persistence_policy.skipped,
|
||||
attempts.len() as u32,
|
||||
available_candidate_count,
|
||||
skipped_candidates,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -31,10 +31,13 @@ pub(crate) async fn maybe_build_local_openai_cli_decision_payload_for_candidate(
|
||||
spec: LocalOpenAiCliSpec,
|
||||
) -> Option<GatewayControlSyncDecisionResponse> {
|
||||
let spec_metadata = local_openai_cli_spec_metadata(spec);
|
||||
let attempt_identity = attempt.attempt_identity();
|
||||
let LocalOpenAiCliCandidateAttempt {
|
||||
eligible,
|
||||
candidate_index,
|
||||
candidate_group_id,
|
||||
candidate_id,
|
||||
..
|
||||
} = attempt;
|
||||
let resolved = resolve_local_openai_cli_candidate_payload_parts(
|
||||
state,
|
||||
@@ -133,8 +136,7 @@ pub(crate) async fn maybe_build_local_openai_cli_decision_payload_for_candidate(
|
||||
auth_context: &input.auth_context,
|
||||
request_id: trace_id,
|
||||
candidate_id: &candidate_id,
|
||||
candidate_index,
|
||||
retry_index: 0,
|
||||
attempt_identity,
|
||||
model: &input.requested_model,
|
||||
provider_name: &resolved.transport.provider.name,
|
||||
provider_id: &candidate.provider_id,
|
||||
@@ -144,6 +146,7 @@ pub(crate) async fn maybe_build_local_openai_cli_decision_payload_for_candidate(
|
||||
provider_api_format: &resolved.provider_api_format,
|
||||
client_api_format: spec_metadata.api_format,
|
||||
mapped_model: Some(&resolved.mapped_model),
|
||||
candidate_group_id: candidate_group_id.as_deref(),
|
||||
upstream_url: Some(&resolved.upstream_url),
|
||||
provider_request_method: Some(serde_json::Value::Null),
|
||||
provider_request_headers: Some(&resolved.provider_request_headers),
|
||||
|
||||
@@ -230,6 +230,7 @@ pub(crate) async fn materialize_local_openai_cli_candidate_attempts(
|
||||
Some(&input.requested_model),
|
||||
&candidates,
|
||||
);
|
||||
let available_candidate_count = candidates.len() as u32;
|
||||
let attempts = persist_available_local_execution_candidates_with_context(
|
||||
planner_state,
|
||||
trace_id,
|
||||
@@ -269,7 +270,7 @@ pub(crate) async fn materialize_local_openai_cli_candidate_attempts(
|
||||
state,
|
||||
trace_id,
|
||||
persistence_policy.skipped,
|
||||
attempts.len() as u32,
|
||||
available_candidate_count,
|
||||
skipped_candidates,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -29,6 +29,7 @@ impl<'a> PlannerAppState<'a> {
|
||||
api_key_id: &str,
|
||||
candidate: &SchedulerMinimalCandidateSelectionCandidate,
|
||||
candidate_index: u32,
|
||||
retry_index: u32,
|
||||
candidate_id: &str,
|
||||
required_capabilities: Option<&Value>,
|
||||
extra_data: Option<Value>,
|
||||
@@ -42,6 +43,7 @@ impl<'a> PlannerAppState<'a> {
|
||||
api_key_id,
|
||||
candidate,
|
||||
candidate_index,
|
||||
retry_index,
|
||||
candidate_id,
|
||||
required_capabilities,
|
||||
extra_data,
|
||||
@@ -59,6 +61,7 @@ impl<'a> PlannerAppState<'a> {
|
||||
api_key_id: &str,
|
||||
candidate: &SchedulerMinimalCandidateSelectionCandidate,
|
||||
candidate_index: u32,
|
||||
retry_index: u32,
|
||||
candidate_id: &str,
|
||||
required_capabilities: Option<&Value>,
|
||||
skip_reason: &str,
|
||||
@@ -73,6 +76,7 @@ impl<'a> PlannerAppState<'a> {
|
||||
api_key_id,
|
||||
candidate,
|
||||
candidate_index,
|
||||
retry_index,
|
||||
candidate_id,
|
||||
required_capabilities,
|
||||
skip_reason,
|
||||
|
||||
Reference in New Issue
Block a user