mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 01:47:47 +08:00
Merge remote-tracking branch 'origin/main' into codex/fix-antigravity-quota
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
use aether_ai_serving::{AiExecutionAttempt, STICKY_KEY_ATTEMPTS_REPORT_FIELD};
|
||||
use aether_routing_core::DEFAULT_STICKY_KEY_ATTEMPTS;
|
||||
use aether_runtime_state::RuntimeLockLease;
|
||||
use aether_scheduler_core::parse_request_candidate_report_context;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::provider_transport::GatewayProviderTransportSnapshot;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct ExecutionAttemptIdentity {
|
||||
@@ -32,6 +33,9 @@ pub(crate) struct LocalExecutionCandidateMetadata {
|
||||
pub(crate) pool_key_index: Option<u32>,
|
||||
pub(crate) pool_key_lease: Option<RuntimeLockLease>,
|
||||
pub(crate) scheduler_affinity_epoch: Option<u64>,
|
||||
/// Routing-policy `sticky_key_attempts` in effect for this request. `None`
|
||||
/// means the policy default applies.
|
||||
pub(crate) sticky_key_attempts: Option<u32>,
|
||||
}
|
||||
|
||||
pub(crate) const SCHEDULER_AFFINITY_EPOCH_REPORT_FIELD: &str = "scheduler_affinity_epoch";
|
||||
@@ -42,6 +46,11 @@ pub(crate) const POOL_KEY_LEASE_TOKEN_REPORT_FIELD: &str = "pool_key_lease_token
|
||||
pub(crate) const POOL_KEY_LEASE_FENCING_REPORT_FIELD: &str = "pool_key_lease_fencing_token";
|
||||
pub(crate) const POOL_KEY_LEASE_TTL_MS_REPORT_FIELD: &str = "pool_key_lease_ttl_ms";
|
||||
|
||||
/// Pool-expanded keys encode `pool_key_index * STRIDE + retry_index` into the
|
||||
/// persisted `retry_index` so a pool group's keys stay ordered in one candidate
|
||||
/// slot. Same-key retries on a pool key are therefore bounded by the stride.
|
||||
pub(crate) const POOL_KEY_RETRY_INDEX_STRIDE: u32 = 100;
|
||||
|
||||
pub(crate) fn attempt_identity_from_report_context(
|
||||
report_context: Option<&Value>,
|
||||
) -> Option<ExecutionAttemptIdentity> {
|
||||
@@ -72,6 +81,10 @@ pub(crate) fn local_execution_candidate_metadata_from_report_context(
|
||||
scheduler_affinity_epoch: report_context
|
||||
.and_then(|value| value.get(SCHEDULER_AFFINITY_EPOCH_REPORT_FIELD))
|
||||
.and_then(Value::as_u64),
|
||||
sticky_key_attempts: report_context
|
||||
.and_then(|value| value.get(STICKY_KEY_ATTEMPTS_REPORT_FIELD))
|
||||
.and_then(Value::as_u64)
|
||||
.and_then(|value| u32::try_from(value).ok()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,58 +153,55 @@ fn pool_key_lease_from_report_context(report_context: Option<&Value>) -> Option<
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn build_local_attempt_identities(
|
||||
candidate_index: u32,
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> Vec<ExecutionAttemptIdentity> {
|
||||
let attempt_slots = local_attempt_slot_count(transport);
|
||||
(0..attempt_slots)
|
||||
.map(|retry_index| ExecutionAttemptIdentity::new(candidate_index, retry_index))
|
||||
.collect()
|
||||
/// Retry index of the next same-key attempt, or `None` when the sticky-key
|
||||
/// budget for this candidate is used up.
|
||||
///
|
||||
/// Only the first-ranked candidate (index `0`, the cache-affinity sticky key)
|
||||
/// is retried on the same key; every later candidate gets exactly one attempt
|
||||
/// so that once failover has started it keeps advancing. `sticky_key_attempts`
|
||||
/// is the *total* attempt count on that key: `2` means one retry, `0` and `1`
|
||||
/// mean none. There is no upper bound: attempts are derived one at a time
|
||||
/// after each failure, never materialized ahead of time.
|
||||
///
|
||||
/// Inside a pool group only the first key (`pool_key_index == 0`) is treated
|
||||
/// as sticky, and its retries stay below `POOL_KEY_RETRY_INDEX_STRIDE` so the
|
||||
/// encoded retry index never collides with the next pool key.
|
||||
pub(crate) fn next_same_key_retry_index(
|
||||
identity: ExecutionAttemptIdentity,
|
||||
sticky_key_attempts: Option<u32>,
|
||||
) -> Option<u32> {
|
||||
if identity.candidate_index != 0 {
|
||||
return None;
|
||||
}
|
||||
let pool_limit = match identity.pool_key_index {
|
||||
None => u32::MAX,
|
||||
Some(0) => POOL_KEY_RETRY_INDEX_STRIDE,
|
||||
Some(_) => return None,
|
||||
};
|
||||
let budget = sticky_key_attempts.unwrap_or(DEFAULT_STICKY_KEY_ATTEMPTS);
|
||||
let attempts_so_far = identity.retry_index.checked_add(1)?;
|
||||
if attempts_so_far >= budget || attempts_so_far >= pool_limit {
|
||||
return None;
|
||||
}
|
||||
Some(attempts_so_far)
|
||||
}
|
||||
|
||||
pub(crate) fn local_attempt_slot_count(transport: &GatewayProviderTransportSnapshot) -> u32 {
|
||||
local_attempt_slots_from_transport(transport).unwrap_or(1)
|
||||
}
|
||||
|
||||
/// For endpoint/provider table fields, `2` is the legacy admin default and is
|
||||
/// treated as "not explicitly configured" so existing local-execution behaviour
|
||||
/// (one attempt slot per candidate) stays unchanged. Values `0`, `1`, and `>2`
|
||||
/// are treated as explicit.
|
||||
const LEGACY_DEFAULT_MAX_RETRIES: u32 = 2;
|
||||
|
||||
/// Upper bound on local attempt slots. This is intentionally stricter than
|
||||
/// admin max_retries validation to prevent unbounded pre-materialization from
|
||||
/// arbitrarily large JSON config values.
|
||||
const MAX_LOCAL_ATTEMPT_SLOTS: u32 = 99;
|
||||
|
||||
fn local_attempt_slots_from_transport(transport: &GatewayProviderTransportSnapshot) -> Option<u32> {
|
||||
let rules = transport
|
||||
.provider
|
||||
.config
|
||||
.as_ref()
|
||||
.and_then(|config| config.get("failover_rules"))
|
||||
.and_then(Value::as_object);
|
||||
|
||||
rules
|
||||
.and_then(|value| value.get("max_retries"))
|
||||
.and_then(Value::as_u64)
|
||||
.and_then(|value| u32::try_from(value).ok())
|
||||
.or_else(|| {
|
||||
transport
|
||||
.endpoint
|
||||
.max_retries
|
||||
.and_then(|value| u32::try_from(value).ok())
|
||||
.filter(|&value| value != LEGACY_DEFAULT_MAX_RETRIES)
|
||||
})
|
||||
.or_else(|| {
|
||||
transport
|
||||
.provider
|
||||
.max_retries
|
||||
.and_then(|value| u32::try_from(value).ok())
|
||||
.filter(|&value| value != LEGACY_DEFAULT_MAX_RETRIES)
|
||||
})
|
||||
.map(|value| value.clamp(1, MAX_LOCAL_ATTEMPT_SLOTS))
|
||||
/// Derive the next same-key attempt for `attempt` after a candidate-scoped
|
||||
/// failure, reading the attempt identity and sticky budget from its report
|
||||
/// context. Returns `None` when no further same-key retry is allowed.
|
||||
pub(crate) fn next_same_key_retry_attempt<A: AiExecutionAttempt>(attempt: &A) -> Option<A> {
|
||||
let owned_report_context = attempt
|
||||
.report_context_ref()
|
||||
.is_none()
|
||||
.then(|| attempt.report_context())
|
||||
.flatten();
|
||||
let report_context = attempt
|
||||
.report_context_ref()
|
||||
.or(owned_report_context.as_ref());
|
||||
let identity = attempt_identity_from_report_context(report_context)?;
|
||||
let metadata = local_execution_candidate_metadata_from_report_context(report_context);
|
||||
let retry_index = next_same_key_retry_index(identity, metadata.sticky_key_attempts)?;
|
||||
attempt.with_same_key_retry(retry_index, Uuid::new_v4().to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -199,277 +209,143 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::{
|
||||
attempt_identity_from_report_context, build_local_attempt_identities,
|
||||
local_execution_candidate_metadata_from_report_context, ExecutionAttemptIdentity,
|
||||
LocalExecutionCandidateMetadata,
|
||||
};
|
||||
use crate::provider_transport::snapshot::{
|
||||
GatewayProviderTransportEndpoint, GatewayProviderTransportKey,
|
||||
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
|
||||
attempt_identity_from_report_context,
|
||||
local_execution_candidate_metadata_from_report_context, next_same_key_retry_attempt,
|
||||
next_same_key_retry_index, ExecutionAttemptIdentity, LocalExecutionCandidateMetadata,
|
||||
POOL_KEY_RETRY_INDEX_STRIDE,
|
||||
};
|
||||
use aether_ai_serving::{AiExecutionAttempt, AiSyncAttempt};
|
||||
use aether_runtime_state::RuntimeLockLease;
|
||||
|
||||
fn sample_transport(
|
||||
provider_max_retries: Option<i32>,
|
||||
endpoint_max_retries: Option<i32>,
|
||||
provider_config: Option<serde_json::Value>,
|
||||
) -> GatewayProviderTransportSnapshot {
|
||||
GatewayProviderTransportSnapshot {
|
||||
provider: GatewayProviderTransportProvider {
|
||||
id: "provider-1".to_string(),
|
||||
name: "OpenAI".to_string(),
|
||||
provider_type: "llm".to_string(),
|
||||
website: None,
|
||||
is_active: true,
|
||||
keep_priority_on_conversion: false,
|
||||
enable_format_conversion: true,
|
||||
concurrent_limit: None,
|
||||
max_retries: provider_max_retries,
|
||||
proxy: None,
|
||||
request_timeout_secs: None,
|
||||
stream_first_byte_timeout_secs: None,
|
||||
config: provider_config,
|
||||
},
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "openai:chat".to_string(),
|
||||
api_family: Some("openai".to_string()),
|
||||
endpoint_kind: Some("chat".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://example.com".to_string(),
|
||||
header_rules: None,
|
||||
body_rules: None,
|
||||
max_retries: endpoint_max_retries,
|
||||
custom_path: None,
|
||||
config: None,
|
||||
format_acceptance_config: None,
|
||||
proxy: None,
|
||||
},
|
||||
key: GatewayProviderTransportKey {
|
||||
id: "key-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
name: "primary".to_string(),
|
||||
auth_type: "bearer".to_string(),
|
||||
is_active: true,
|
||||
api_formats: None,
|
||||
auth_type_by_format: None,
|
||||
allow_auth_channel_mismatch_formats: None,
|
||||
#[test]
|
||||
fn first_candidate_defaults_to_one_same_key_retry() {
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(0, 0), None),
|
||||
Some(1)
|
||||
);
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(0, 1), None),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
global_priority_by_format: None,
|
||||
expires_at_unix_secs: None,
|
||||
proxy: None,
|
||||
fingerprint: None,
|
||||
upstream_metadata: None,
|
||||
decrypted_api_key: "secret".to_string(),
|
||||
decrypted_auth_config: None,
|
||||
},
|
||||
#[test]
|
||||
fn first_candidate_uses_policy_sticky_key_attempts_without_upper_bound() {
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(0, 2), Some(3)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(0, 1), Some(3)),
|
||||
Some(2)
|
||||
);
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(0, 4_999), Some(10_000)),
|
||||
Some(5_000)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_and_one_mean_single_attempt() {
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(0, 0), Some(0)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(0, 0), Some(1)),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failover_candidates_never_retry_on_the_same_key() {
|
||||
for candidate_index in 1..5 {
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(ExecutionAttemptIdentity::new(candidate_index, 0), None),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
next_same_key_retry_index(
|
||||
ExecutionAttemptIdentity::new(candidate_index, 0),
|
||||
Some(50)
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_defaults_to_single_attempt() {
|
||||
let identities = build_local_attempt_identities(3, &sample_transport(None, None, None));
|
||||
|
||||
assert_eq!(identities, vec![ExecutionAttemptIdentity::new(3, 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_prefer_failover_rules_over_endpoint_and_provider() {
|
||||
let identities = build_local_attempt_identities(
|
||||
1,
|
||||
&sample_transport(
|
||||
Some(5),
|
||||
Some(4),
|
||||
Some(json!({
|
||||
"failover_rules": {
|
||||
"max_retries": 2
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
fn pool_groups_only_retry_their_first_key_within_the_stride() {
|
||||
let first_pool_key = ExecutionAttemptIdentity::new(0, 0).with_pool_key_index(Some(0));
|
||||
assert_eq!(next_same_key_retry_index(first_pool_key, Some(3)), Some(1));
|
||||
|
||||
let at_stride_limit = ExecutionAttemptIdentity::new(0, POOL_KEY_RETRY_INDEX_STRIDE - 1)
|
||||
.with_pool_key_index(Some(0));
|
||||
assert_eq!(
|
||||
identities,
|
||||
vec![
|
||||
ExecutionAttemptIdentity::new(1, 0),
|
||||
ExecutionAttemptIdentity::new(1, 1),
|
||||
]
|
||||
next_same_key_retry_index(at_stride_limit, Some(10_000)),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_falls_back_to_endpoint_max_retries() {
|
||||
let identities =
|
||||
build_local_attempt_identities(2, &sample_transport(Some(5), Some(3), None));
|
||||
|
||||
let second_pool_key = ExecutionAttemptIdentity::new(0, POOL_KEY_RETRY_INDEX_STRIDE)
|
||||
.with_pool_key_index(Some(1));
|
||||
assert_eq!(
|
||||
identities,
|
||||
vec![
|
||||
ExecutionAttemptIdentity::new(2, 0),
|
||||
ExecutionAttemptIdentity::new(2, 1),
|
||||
ExecutionAttemptIdentity::new(2, 2),
|
||||
]
|
||||
next_same_key_retry_index(second_pool_key, Some(10_000)),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_falls_back_to_provider_max_retries() {
|
||||
let identities = build_local_attempt_identities(0, &sample_transport(Some(4), None, None));
|
||||
fn next_same_key_retry_attempt_rewrites_candidate_id_and_retry_index() {
|
||||
let attempt = AiSyncAttempt {
|
||||
plan: aether_contracts::ExecutionPlan {
|
||||
request_id: "trace-1".to_string(),
|
||||
candidate_id: Some("candidate-a".to_string()),
|
||||
provider_name: None,
|
||||
provider_id: "provider-1".to_string(),
|
||||
endpoint_id: "endpoint-1".to_string(),
|
||||
key_id: "key-1".to_string(),
|
||||
method: "POST".to_string(),
|
||||
url: "https://example.com".to_string(),
|
||||
headers: Default::default(),
|
||||
content_type: None,
|
||||
content_encoding: None,
|
||||
body: aether_contracts::RequestBody {
|
||||
json_body: None,
|
||||
body_bytes_b64: None,
|
||||
body_ref: None,
|
||||
},
|
||||
stream: false,
|
||||
client_api_format: "openai:chat".to_string(),
|
||||
provider_api_format: "openai:chat".to_string(),
|
||||
model_name: None,
|
||||
proxy: None,
|
||||
transport_profile: None,
|
||||
timeouts: None,
|
||||
},
|
||||
report_kind: None,
|
||||
report_context: Some(json!({
|
||||
"candidate_id": "candidate-a",
|
||||
"candidate_index": 0,
|
||||
"retry_index": 0,
|
||||
"sticky_key_attempts": 2,
|
||||
})),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
identities,
|
||||
vec![
|
||||
ExecutionAttemptIdentity::new(0, 0),
|
||||
ExecutionAttemptIdentity::new(0, 1),
|
||||
ExecutionAttemptIdentity::new(0, 2),
|
||||
ExecutionAttemptIdentity::new(0, 3),
|
||||
]
|
||||
let retry = next_same_key_retry_attempt(&attempt).expect("one same-key retry remains");
|
||||
let retry_candidate_id = retry.plan.candidate_id.clone().expect("fresh candidate id");
|
||||
assert_ne!(retry_candidate_id, "candidate-a");
|
||||
assert_eq!(retry.plan.key_id, "key-1");
|
||||
let context = retry.report_context_ref().expect("context retained");
|
||||
assert_eq!(context["candidate_id"], json!(retry_candidate_id));
|
||||
assert_eq!(context["retry_index"], json!(1));
|
||||
assert_eq!(context["candidate_index"], json!(0));
|
||||
|
||||
assert!(
|
||||
next_same_key_retry_attempt(&retry).is_none(),
|
||||
"budget of 2 attempts is exhausted after one retry"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_endpoint_overrides_provider() {
|
||||
let identities =
|
||||
build_local_attempt_identities(7, &sample_transport(Some(10), Some(3), None));
|
||||
|
||||
assert_eq!(
|
||||
identities,
|
||||
vec![
|
||||
ExecutionAttemptIdentity::new(7, 0),
|
||||
ExecutionAttemptIdentity::new(7, 1),
|
||||
ExecutionAttemptIdentity::new(7, 2),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_default_two_treated_as_unset() {
|
||||
let identities =
|
||||
build_local_attempt_identities(5, &sample_transport(Some(2), Some(2), None));
|
||||
|
||||
assert_eq!(identities, vec![ExecutionAttemptIdentity::new(5, 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_endpoint_two_falls_back_to_provider_ten() {
|
||||
let identities =
|
||||
build_local_attempt_identities(1, &sample_transport(Some(10), Some(2), None));
|
||||
|
||||
assert_eq!(
|
||||
identities,
|
||||
vec![
|
||||
ExecutionAttemptIdentity::new(1, 0),
|
||||
ExecutionAttemptIdentity::new(1, 1),
|
||||
ExecutionAttemptIdentity::new(1, 2),
|
||||
ExecutionAttemptIdentity::new(1, 3),
|
||||
ExecutionAttemptIdentity::new(1, 4),
|
||||
ExecutionAttemptIdentity::new(1, 5),
|
||||
ExecutionAttemptIdentity::new(1, 6),
|
||||
ExecutionAttemptIdentity::new(1, 7),
|
||||
ExecutionAttemptIdentity::new(1, 8),
|
||||
ExecutionAttemptIdentity::new(1, 9),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_failover_rules_zero_produces_one_slot() {
|
||||
let identities = build_local_attempt_identities(
|
||||
1,
|
||||
&sample_transport(
|
||||
Some(5),
|
||||
Some(4),
|
||||
Some(json!({
|
||||
"failover_rules": {
|
||||
"max_retries": 0
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(identities, vec![ExecutionAttemptIdentity::new(1, 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_endpoint_zero_produces_one_slot() {
|
||||
let identities =
|
||||
build_local_attempt_identities(3, &sample_transport(Some(5), Some(0), None));
|
||||
|
||||
assert_eq!(identities, vec![ExecutionAttemptIdentity::new(3, 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_provider_zero_produces_one_slot() {
|
||||
let identities = build_local_attempt_identities(3, &sample_transport(Some(0), None, None));
|
||||
|
||||
assert_eq!(identities, vec![ExecutionAttemptIdentity::new(3, 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_provider_ten_creates_ten_slots() {
|
||||
let identities = build_local_attempt_identities(2, &sample_transport(Some(10), None, None));
|
||||
|
||||
assert_eq!(identities.len(), 10);
|
||||
assert_eq!(identities[0], ExecutionAttemptIdentity::new(2, 0));
|
||||
assert_eq!(identities[9], ExecutionAttemptIdentity::new(2, 9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_failover_rules_over_limit_clamped_to_max() {
|
||||
let identities = build_local_attempt_identities(
|
||||
0,
|
||||
&sample_transport(
|
||||
Some(3),
|
||||
Some(5),
|
||||
Some(json!({
|
||||
"failover_rules": {
|
||||
"max_retries": 1000
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(identities.len(), 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_failover_rules_u32_max_clamped_to_max() {
|
||||
let identities = build_local_attempt_identities(
|
||||
0,
|
||||
&sample_transport(
|
||||
None,
|
||||
None,
|
||||
Some(json!({
|
||||
"failover_rules": {
|
||||
"max_retries": u32::MAX
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(identities.len(), 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_endpoint_over_limit_clamped_to_max() {
|
||||
let identities =
|
||||
build_local_attempt_identities(0, &sample_transport(None, Some(2000), None));
|
||||
|
||||
assert_eq!(identities.len(), 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_local_attempt_identities_provider_over_limit_clamped_to_max() {
|
||||
let identities =
|
||||
build_local_attempt_identities(0, &sample_transport(Some(5000), None, None));
|
||||
|
||||
assert_eq!(identities.len(), 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_attempt_identity_from_report_context_reads_candidate_and_retry_indices() {
|
||||
let identity = attempt_identity_from_report_context(Some(&json!({
|
||||
@@ -499,6 +375,7 @@ mod tests {
|
||||
"pool_key_lease_token": "gateway-1:token-1",
|
||||
"pool_key_lease_fencing_token": 7,
|
||||
"pool_key_lease_ttl_ms": 900000,
|
||||
"sticky_key_attempts": 3,
|
||||
})));
|
||||
|
||||
assert_eq!(
|
||||
@@ -514,6 +391,7 @@ mod tests {
|
||||
ttl_ms: 900000,
|
||||
}),
|
||||
scheduler_affinity_epoch: None,
|
||||
sticky_key_attempts: Some(3),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,11 +20,10 @@ pub(crate) use self::adaptive::{
|
||||
LocalAdaptiveRateLimitProjection, LocalAdaptiveSuccessProjection,
|
||||
};
|
||||
pub(crate) use self::attempt::{
|
||||
attempt_identity_from_report_context, build_local_attempt_identities,
|
||||
insert_pool_key_lease_report_context_fields, local_attempt_slot_count,
|
||||
local_execution_candidate_metadata_from_report_context, ExecutionAttemptIdentity,
|
||||
LocalExecutionCandidateMetadata, ROUTING_POOL_POLICY_OVERRIDE_REPORT_FIELD,
|
||||
SCHEDULER_AFFINITY_EPOCH_REPORT_FIELD,
|
||||
attempt_identity_from_report_context, insert_pool_key_lease_report_context_fields,
|
||||
local_execution_candidate_metadata_from_report_context, next_same_key_retry_attempt,
|
||||
ExecutionAttemptIdentity, LocalExecutionCandidateMetadata, POOL_KEY_RETRY_INDEX_STRIDE,
|
||||
ROUTING_POOL_POLICY_OVERRIDE_REPORT_FIELD, SCHEDULER_AFFINITY_EPOCH_REPORT_FIELD,
|
||||
};
|
||||
pub(crate) use self::classifier::{
|
||||
classify_anthropic_failure_disposition, classify_failure_disposition, classify_local_failover,
|
||||
|
||||
Reference in New Issue
Block a user