mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
refactor: 抽离 AI pipeline 与调度共享能力逻辑
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use crate::api::ai::public_api_format_local_path;
|
||||
use crate::handlers::shared::{query_param_optional_bool, query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::handlers::shared::{
|
||||
query_param_optional_bool, query_param_value, unix_ms_to_rfc3339, unix_secs_to_rfc3339,
|
||||
};
|
||||
use crate::AppState;
|
||||
use aether_data_contracts::repository::candidates::{
|
||||
PublicHealthTimelineBucket, RequestCandidateStatus, StoredRequestCandidate,
|
||||
@@ -25,11 +27,11 @@ pub(crate) fn request_candidate_status_label(status: RequestCandidateStatus) ->
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn request_candidate_event_unix_secs(candidate: &StoredRequestCandidate) -> u64 {
|
||||
pub(crate) fn request_candidate_event_unix_ms(candidate: &StoredRequestCandidate) -> u64 {
|
||||
candidate
|
||||
.finished_at_unix_secs
|
||||
.or(candidate.started_at_unix_secs)
|
||||
.unwrap_or(candidate.created_at_unix_secs)
|
||||
.finished_at_unix_ms
|
||||
.or(candidate.started_at_unix_ms)
|
||||
.unwrap_or(candidate.created_at_unix_ms)
|
||||
}
|
||||
|
||||
pub(crate) fn normalize_admin_base_url(base_url: &str) -> Result<String, String> {
|
||||
@@ -410,28 +412,24 @@ pub(crate) async fn build_api_format_health_monitor_payload(
|
||||
total_count: 0,
|
||||
success_count: 0,
|
||||
failed_count: 0,
|
||||
min_created_at_unix_secs: None,
|
||||
max_created_at_unix_secs: None,
|
||||
min_created_at_unix_ms: None,
|
||||
max_created_at_unix_ms: None,
|
||||
});
|
||||
bucket.total_count += row.total_count;
|
||||
bucket.success_count += row.success_count;
|
||||
bucket.failed_count += row.failed_count;
|
||||
bucket.min_created_at_unix_secs = match (
|
||||
bucket.min_created_at_unix_secs,
|
||||
row.min_created_at_unix_secs,
|
||||
) {
|
||||
(Some(left), Some(right)) => Some(left.min(right)),
|
||||
(None, Some(right)) => Some(right),
|
||||
(left, None) => left,
|
||||
};
|
||||
bucket.max_created_at_unix_secs = match (
|
||||
bucket.max_created_at_unix_secs,
|
||||
row.max_created_at_unix_secs,
|
||||
) {
|
||||
(Some(left), Some(right)) => Some(left.max(right)),
|
||||
(None, Some(right)) => Some(right),
|
||||
(left, None) => left,
|
||||
};
|
||||
bucket.min_created_at_unix_ms =
|
||||
match (bucket.min_created_at_unix_ms, row.min_created_at_unix_ms) {
|
||||
(Some(left), Some(right)) => Some(left.min(right)),
|
||||
(None, Some(right)) => Some(right),
|
||||
(left, None) => left,
|
||||
};
|
||||
bucket.max_created_at_unix_ms =
|
||||
match (bucket.max_created_at_unix_ms, row.max_created_at_unix_ms) {
|
||||
(Some(left), Some(right)) => Some(left.max(right)),
|
||||
(None, Some(right)) => Some(right),
|
||||
(left, None) => left,
|
||||
};
|
||||
}
|
||||
|
||||
let mut formats = Vec::new();
|
||||
@@ -456,19 +454,19 @@ pub(crate) async fn build_api_format_health_monitor_payload(
|
||||
};
|
||||
let last_event_at = attempts.first().and_then(|candidate| {
|
||||
candidate
|
||||
.finished_at_unix_secs
|
||||
.or(candidate.started_at_unix_secs)
|
||||
.or(Some(candidate.created_at_unix_secs))
|
||||
.finished_at_unix_ms
|
||||
.or(candidate.started_at_unix_ms)
|
||||
.or(Some(candidate.created_at_unix_ms))
|
||||
});
|
||||
let events = attempts
|
||||
.into_iter()
|
||||
.filter_map(|candidate| {
|
||||
let timestamp = candidate
|
||||
.finished_at_unix_secs
|
||||
.or(candidate.started_at_unix_secs)
|
||||
.unwrap_or(candidate.created_at_unix_secs);
|
||||
.finished_at_unix_ms
|
||||
.or(candidate.started_at_unix_ms)
|
||||
.unwrap_or(candidate.created_at_unix_ms);
|
||||
Some(json!({
|
||||
"timestamp": unix_secs_to_rfc3339(timestamp)?,
|
||||
"timestamp": unix_ms_to_rfc3339(timestamp)?,
|
||||
"status": request_candidate_status_label(candidate.status),
|
||||
"status_code": candidate.status_code,
|
||||
"latency_ms": candidate.latency_ms,
|
||||
@@ -490,11 +488,11 @@ pub(crate) async fn build_api_format_health_monitor_payload(
|
||||
"failed_count": failed_count,
|
||||
"skipped_count": skipped_count,
|
||||
"success_rate": success_rate,
|
||||
"last_event_at": last_event_at.and_then(unix_secs_to_rfc3339),
|
||||
"last_event_at": last_event_at.and_then(unix_ms_to_rfc3339),
|
||||
"events": events,
|
||||
"timeline": timeline,
|
||||
"time_range_start": time_range_start.and_then(unix_secs_to_rfc3339),
|
||||
"time_range_end": time_range_end.or(Some(now_unix_secs)).and_then(unix_secs_to_rfc3339),
|
||||
"time_range_start": time_range_start.and_then(unix_ms_to_rfc3339),
|
||||
"time_range_end": time_range_end.map(|ms| unix_ms_to_rfc3339(ms)).unwrap_or_else(|| unix_secs_to_rfc3339(now_unix_secs)),
|
||||
});
|
||||
if options.include_api_path {
|
||||
format_payload["api_path"] = json!(public_api_format_local_path(&api_format));
|
||||
@@ -536,12 +534,12 @@ pub(crate) fn build_public_health_timeline(
|
||||
continue;
|
||||
}
|
||||
|
||||
earliest_time = match (earliest_time, bucket.min_created_at_unix_secs) {
|
||||
earliest_time = match (earliest_time, bucket.min_created_at_unix_ms) {
|
||||
(Some(left), Some(right)) => Some(left.min(right)),
|
||||
(None, Some(right)) => Some(right),
|
||||
(left, None) => left,
|
||||
};
|
||||
latest_time = match (latest_time, bucket.max_created_at_unix_secs) {
|
||||
latest_time = match (latest_time, bucket.max_created_at_unix_ms) {
|
||||
(Some(left), Some(right)) => Some(left.max(right)),
|
||||
(None, Some(right)) => Some(right),
|
||||
(left, None) => left,
|
||||
@@ -592,3 +590,50 @@ pub(crate) fn api_format_display_name(api_format: &str) -> String {
|
||||
};
|
||||
format!("{family_label} {kind_label}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::request_candidate_event_unix_ms;
|
||||
use crate::handlers::shared::unix_ms_to_rfc3339;
|
||||
use aether_data_contracts::repository::candidates::{
|
||||
RequestCandidateStatus, StoredRequestCandidate,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn request_candidate_event_timestamp_uses_millisecond_precision() {
|
||||
let candidate = StoredRequestCandidate::new(
|
||||
"cand-1".to_string(),
|
||||
"req-1".to_string(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
Some("provider-1".to_string()),
|
||||
Some("endpoint-1".to_string()),
|
||||
Some("key-1".to_string()),
|
||||
RequestCandidateStatus::Success,
|
||||
None,
|
||||
false,
|
||||
Some(200),
|
||||
None,
|
||||
None,
|
||||
Some(42),
|
||||
Some(1),
|
||||
None,
|
||||
None,
|
||||
1_700_000_000_000,
|
||||
Some(1_700_000_000_111),
|
||||
Some(1_700_000_000_123),
|
||||
)
|
||||
.expect("candidate should build");
|
||||
|
||||
let event_unix_ms = request_candidate_event_unix_ms(&candidate);
|
||||
assert_eq!(event_unix_ms, 1_700_000_000_123);
|
||||
assert_eq!(
|
||||
unix_ms_to_rfc3339(event_unix_ms).as_deref(),
|
||||
Some("2023-11-14T22:13:20.123Z")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub(crate) use self::catalog_helpers::{
|
||||
admin_requested_force_stream, api_format_display_name, build_api_format_health_monitor_payload,
|
||||
build_public_catalog_models_payload, build_public_catalog_search_models_payload,
|
||||
build_public_health_timeline, build_public_providers_payload, normalize_admin_base_url,
|
||||
provider_key_api_formats, request_candidate_event_unix_secs, request_candidate_status_label,
|
||||
provider_key_api_formats, request_candidate_event_unix_ms, request_candidate_status_label,
|
||||
ApiFormatHealthMonitorOptions,
|
||||
};
|
||||
pub(crate) use self::system_modules_helpers::{
|
||||
|
||||
@@ -84,7 +84,7 @@ pub(super) fn build_public_announcement_payload(
|
||||
},
|
||||
"start_time": format_optional_unix_datetime(announcement.start_time_unix_secs),
|
||||
"end_time": format_optional_unix_datetime(announcement.end_time_unix_secs),
|
||||
"created_at": format_required_unix_datetime(announcement.created_at_unix_secs),
|
||||
"created_at": format_required_unix_datetime(announcement.created_at_unix_ms),
|
||||
"updated_at": format_required_unix_datetime(announcement.updated_at_unix_secs),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ fn dashboard_usage_local_date(
|
||||
item: &StoredRequestUsageAudit,
|
||||
tz_offset_minutes: i32,
|
||||
) -> Option<chrono::NaiveDate> {
|
||||
let timestamp = i64::try_from(item.created_at_unix_secs).ok()?;
|
||||
let timestamp = i64::try_from(item.created_at_unix_ms).ok()?;
|
||||
let datetime = chrono::DateTime::<chrono::Utc>::from_timestamp(timestamp, 0)?;
|
||||
Some((datetime + chrono::Duration::minutes(i64::from(tz_offset_minutes))).date_naive())
|
||||
}
|
||||
@@ -1008,7 +1008,7 @@ pub(super) async fn handle_dashboard_recent_requests_get(
|
||||
"user": username,
|
||||
"model": dashboard_non_empty_value(&item.model, "N/A"),
|
||||
"tokens": item.total_tokens,
|
||||
"time": dashboard_format_time_hhmm(item.created_at_unix_secs),
|
||||
"time": dashboard_format_time_hhmm(item.created_at_unix_ms),
|
||||
"is_stream": item.is_stream,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -95,7 +95,7 @@ fn users_me_usage_total_input_context(item: &StoredRequestUsageAudit) -> u64 {
|
||||
|
||||
fn users_me_usage_effective_unix_secs(item: &StoredRequestUsageAudit) -> u64 {
|
||||
item.finalized_at_unix_secs
|
||||
.unwrap_or(item.created_at_unix_secs)
|
||||
.unwrap_or(item.created_at_unix_ms)
|
||||
}
|
||||
|
||||
fn users_me_usage_cache_hit_rate(total_input_context: u64, cache_read_tokens: u64) -> f64 {
|
||||
@@ -159,7 +159,7 @@ fn build_users_me_usage_record_payload(
|
||||
"first_byte_time_ms": item.first_byte_time_ms,
|
||||
"is_stream": item.is_stream,
|
||||
"status": item.status,
|
||||
"created_at": unix_secs_to_rfc3339(item.created_at_unix_secs),
|
||||
"created_at": unix_secs_to_rfc3339(item.created_at_unix_ms),
|
||||
"cache_creation_input_tokens": item.cache_creation_input_tokens,
|
||||
"cache_read_input_tokens": item.cache_read_input_tokens,
|
||||
"status_code": item.status_code,
|
||||
@@ -649,8 +649,8 @@ pub(super) async fn handle_users_me_usage_get(
|
||||
.collect::<Vec<_>>();
|
||||
records.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_unix_secs
|
||||
.cmp(&left.created_at_unix_secs)
|
||||
.created_at_unix_ms
|
||||
.cmp(&left.created_at_unix_ms)
|
||||
.then_with(|| left.id.cmp(&right.id))
|
||||
});
|
||||
let total_record_count = records.len();
|
||||
@@ -735,8 +735,8 @@ pub(super) async fn handle_users_me_usage_active_get(
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_unix_secs
|
||||
.cmp(&left.created_at_unix_secs)
|
||||
.created_at_unix_ms
|
||||
.cmp(&left.created_at_unix_ms)
|
||||
.then_with(|| left.id.cmp(&right.id))
|
||||
});
|
||||
if ids.is_none() && items.len() > 50 {
|
||||
@@ -798,20 +798,19 @@ pub(super) async fn handle_users_me_usage_interval_timeline_get(
|
||||
};
|
||||
items.retain(|item| item.status == "completed");
|
||||
items.sort_by(|left, right| {
|
||||
left.created_at_unix_secs
|
||||
.cmp(&right.created_at_unix_secs)
|
||||
left.created_at_unix_ms
|
||||
.cmp(&right.created_at_unix_ms)
|
||||
.then_with(|| left.id.cmp(&right.id))
|
||||
});
|
||||
|
||||
let mut points = Vec::new();
|
||||
let mut previous_created_at_unix_secs = None;
|
||||
let mut previous_created_at_unix_ms = None;
|
||||
for item in items {
|
||||
if let Some(previous) = previous_created_at_unix_secs {
|
||||
let interval_minutes =
|
||||
(item.created_at_unix_secs.saturating_sub(previous) as f64) / 60.0;
|
||||
if let Some(previous) = previous_created_at_unix_ms {
|
||||
let interval_minutes = (item.created_at_unix_ms.saturating_sub(previous) as f64) / 60.0;
|
||||
if interval_minutes <= 120.0 {
|
||||
points.push(json!({
|
||||
"x": unix_secs_to_rfc3339(item.created_at_unix_secs),
|
||||
"x": unix_secs_to_rfc3339(item.created_at_unix_ms),
|
||||
"y": round_to(interval_minutes, 2),
|
||||
"model": item.model,
|
||||
}));
|
||||
@@ -820,7 +819,7 @@ pub(super) async fn handle_users_me_usage_interval_timeline_get(
|
||||
}
|
||||
}
|
||||
}
|
||||
previous_created_at_unix_secs = Some(item.created_at_unix_secs);
|
||||
previous_created_at_unix_ms = Some(item.created_at_unix_ms);
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
|
||||
@@ -153,7 +153,7 @@ pub(super) fn wallet_transaction_payload_from_record(
|
||||
"link_id": record.link_id.clone(),
|
||||
"operator_id": record.operator_id.clone(),
|
||||
"description": record.description.clone(),
|
||||
"created_at": record.created_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
"created_at": record.created_at_unix_ms.and_then(unix_secs_to_rfc3339),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ pub(crate) fn wallet_payment_order_payload_from_row(
|
||||
row: &sqlx::postgres::PgRow,
|
||||
) -> Result<serde_json::Value, GatewayError> {
|
||||
let created_at = row
|
||||
.try_get::<Option<i64>, _>("created_at_unix_secs")
|
||||
.try_get::<Option<i64>, _>("created_at_unix_ms")
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?
|
||||
.and_then(|value| u64::try_from(value).ok())
|
||||
.and_then(unix_secs_to_rfc3339);
|
||||
@@ -280,7 +280,7 @@ fn wallet_payment_order_payload_from_record(
|
||||
record.gateway_order_id.clone(),
|
||||
record.gateway_response.clone(),
|
||||
record.status.clone(),
|
||||
Some(unix_secs_to_rfc3339(record.created_at_unix_secs)).flatten(),
|
||||
Some(unix_secs_to_rfc3339(record.created_at_unix_ms)).flatten(),
|
||||
record.paid_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
record.credited_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
record.expires_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
|
||||
@@ -98,7 +98,7 @@ fn wallet_refund_payload_from_row(
|
||||
row: &sqlx::postgres::PgRow,
|
||||
) -> Result<serde_json::Value, GatewayError> {
|
||||
let created_at = row
|
||||
.try_get::<Option<i64>, _>("created_at_unix_secs")
|
||||
.try_get::<Option<i64>, _>("created_at_unix_ms")
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?
|
||||
.and_then(|value| u64::try_from(value).ok())
|
||||
.and_then(unix_secs_to_rfc3339);
|
||||
@@ -157,7 +157,7 @@ fn wallet_refund_payload_from_record(
|
||||
"payout_method": record.payout_method,
|
||||
"payout_reference": record.payout_reference,
|
||||
"payout_proof": record.payout_proof,
|
||||
"created_at": unix_secs_to_rfc3339(record.created_at_unix_secs),
|
||||
"created_at": unix_secs_to_rfc3339(record.created_at_unix_ms),
|
||||
"updated_at": unix_secs_to_rfc3339(record.updated_at_unix_secs),
|
||||
"processed_at": record.processed_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
"completed_at": record.completed_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
@@ -247,7 +247,7 @@ pub(super) async fn handle_wallet_refunds_list(
|
||||
"payout_method": record.payout_method,
|
||||
"payout_reference": record.payout_reference,
|
||||
"payout_proof": record.payout_proof,
|
||||
"created_at": unix_secs_to_rfc3339(record.created_at_unix_secs),
|
||||
"created_at": unix_secs_to_rfc3339(record.created_at_unix_ms),
|
||||
"updated_at": unix_secs_to_rfc3339(record.updated_at_unix_secs),
|
||||
"processed_at": record.processed_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
"completed_at": record.completed_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
|
||||
@@ -156,7 +156,7 @@ pub(crate) async fn build_admin_keys_grouped_by_format_payload(
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false),
|
||||
"last_used_at": key.last_used_at_unix_secs.and_then(unix_secs_to_rfc3339),
|
||||
"created_at": unix_secs_to_rfc3339(key.created_at_unix_secs.unwrap_or(now_unix_secs)),
|
||||
"created_at": unix_secs_to_rfc3339(key.created_at_unix_ms.unwrap_or(now_unix_secs)),
|
||||
"updated_at": unix_secs_to_rfc3339(key.updated_at_unix_secs.unwrap_or(now_unix_secs)),
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user