mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 02:47:45 +08:00
feat: improve failover rules and request timeline
This commit is contained in:
@@ -61,11 +61,8 @@ pub(crate) fn classify_local_failover(
|
||||
}
|
||||
|
||||
if input.status_code >= 400
|
||||
&& input.response_text.is_some_and(|text| {
|
||||
policy
|
||||
.error_stop_patterns
|
||||
.iter()
|
||||
.any(|rule| local_failover_regex_rule_matches(rule, text, input.status_code))
|
||||
&& policy.error_stop_patterns.iter().any(|rule| {
|
||||
local_failover_regex_rule_matches(rule, input.response_text, input.status_code)
|
||||
})
|
||||
{
|
||||
return LocalFailoverClassification::StopErrorPattern;
|
||||
@@ -76,7 +73,7 @@ pub(crate) fn classify_local_failover(
|
||||
policy
|
||||
.success_failover_patterns
|
||||
.iter()
|
||||
.any(|rule| local_failover_regex_rule_matches(rule, text, input.status_code))
|
||||
.any(|rule| local_failover_regex_rule_matches(rule, Some(text), input.status_code))
|
||||
})
|
||||
{
|
||||
return LocalFailoverClassification::RetrySuccessPattern;
|
||||
@@ -190,14 +187,23 @@ fn first_non_empty_json_text(
|
||||
|
||||
fn local_failover_regex_rule_matches(
|
||||
rule: &LocalFailoverRegexRule,
|
||||
response_text: &str,
|
||||
response_text: Option<&str>,
|
||||
status_code: u16,
|
||||
) -> bool {
|
||||
if !rule.status_codes.is_empty() && !rule.status_codes.contains(&status_code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Regex::new(&rule.pattern)
|
||||
let pattern = rule.pattern.trim();
|
||||
if pattern.is_empty() {
|
||||
return !rule.status_codes.is_empty();
|
||||
}
|
||||
|
||||
let Some(response_text) = response_text else {
|
||||
return false;
|
||||
};
|
||||
|
||||
Regex::new(pattern)
|
||||
.ok()
|
||||
.is_some_and(|regex| regex.is_match(response_text))
|
||||
}
|
||||
@@ -260,6 +266,50 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifier_detects_error_stop_pattern_without_status_codes_on_any_error_status() {
|
||||
let policy = LocalFailoverPolicy {
|
||||
error_stop_patterns: vec![LocalFailoverRegexRule {
|
||||
pattern: "content_policy_violation".to_string(),
|
||||
status_codes: BTreeSet::new(),
|
||||
}],
|
||||
..LocalFailoverPolicy::default()
|
||||
};
|
||||
|
||||
for status_code in [400, 429, 503] {
|
||||
assert_eq!(
|
||||
classify_local_failover(
|
||||
&policy,
|
||||
LocalFailoverInput::new(
|
||||
status_code,
|
||||
Some("{\"error\":\"content_policy_violation\"}")
|
||||
)
|
||||
),
|
||||
LocalFailoverClassification::StopErrorPattern
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifier_detects_status_only_error_stop_rule_without_response_text() {
|
||||
let policy = LocalFailoverPolicy {
|
||||
error_stop_patterns: vec![LocalFailoverRegexRule {
|
||||
pattern: String::new(),
|
||||
status_codes: [429].into_iter().collect(),
|
||||
}],
|
||||
..LocalFailoverPolicy::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
classify_local_failover(&policy, LocalFailoverInput::new(429, None)),
|
||||
LocalFailoverClassification::StopErrorPattern
|
||||
);
|
||||
assert_eq!(
|
||||
classify_local_failover(&policy, LocalFailoverInput::new(503, None)),
|
||||
LocalFailoverClassification::RetryUpstreamFailure
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifier_detects_success_continue_status_code() {
|
||||
let policy = LocalFailoverPolicy {
|
||||
|
||||
@@ -25,27 +25,8 @@ pub(crate) struct LocalFailoverRegexRule {
|
||||
pub(crate) async fn resolve_local_failover_policy(
|
||||
state: &AppState,
|
||||
plan: &ExecutionPlan,
|
||||
report_context: Option<&serde_json::Value>,
|
||||
_report_context: Option<&serde_json::Value>,
|
||||
) -> LocalFailoverPolicy {
|
||||
if let Some(policy) = local_failover_policy_from_report_context(report_context) {
|
||||
debug!(
|
||||
event_name = "local_failover_policy_loaded",
|
||||
log_type = "debug",
|
||||
request_id = %plan.request_id,
|
||||
provider_id = %plan.provider_id,
|
||||
endpoint_id = %plan.endpoint_id,
|
||||
key_id = %plan.key_id,
|
||||
source = "report_context",
|
||||
max_retries = ?policy.max_retries,
|
||||
stop_status_code_count = policy.stop_status_codes.len(),
|
||||
continue_status_code_count = policy.continue_status_codes.len(),
|
||||
success_failover_pattern_count = policy.success_failover_patterns.len(),
|
||||
error_stop_pattern_count = policy.error_stop_patterns.len(),
|
||||
"gateway loaded local failover policy from report context"
|
||||
);
|
||||
return policy;
|
||||
}
|
||||
|
||||
let transport = match state
|
||||
.read_provider_transport_snapshot(&plan.provider_id, &plan.endpoint_id, &plan.key_id)
|
||||
.await
|
||||
@@ -201,31 +182,39 @@ fn parse_regex_rules(
|
||||
rules: &serde_json::Map<String, serde_json::Value>,
|
||||
key: &str,
|
||||
) -> Vec<LocalFailoverRegexRule> {
|
||||
let allow_status_only = key == "error_stop_patterns";
|
||||
rules
|
||||
.get(key)
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flat_map(|items| items.iter())
|
||||
.filter_map(parse_regex_rule)
|
||||
.filter_map(|value| parse_regex_rule(value, allow_status_only))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_regex_rule(value: &serde_json::Value) -> Option<LocalFailoverRegexRule> {
|
||||
fn parse_regex_rule(
|
||||
value: &serde_json::Value,
|
||||
allow_status_only: bool,
|
||||
) -> Option<LocalFailoverRegexRule> {
|
||||
let object = value.as_object()?;
|
||||
let pattern = object
|
||||
.get("pattern")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?;
|
||||
.unwrap_or_default();
|
||||
let status_codes: BTreeSet<u16> = object
|
||||
.get("status_codes")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flat_map(|values| values.iter())
|
||||
.filter_map(|value| parse_u64_value(value).and_then(|value| u16::try_from(value).ok()))
|
||||
.collect();
|
||||
if pattern.is_empty() && (!allow_status_only || status_codes.is_empty()) {
|
||||
return None;
|
||||
}
|
||||
Some(LocalFailoverRegexRule {
|
||||
pattern: pattern.to_string(),
|
||||
status_codes: object
|
||||
.get("status_codes")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flat_map(|values| values.iter())
|
||||
.filter_map(|value| parse_u64_value(value).and_then(|value| u16::try_from(value).ok()))
|
||||
.collect(),
|
||||
status_codes,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user