mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
Expose ranking metadata in request trace
This commit is contained in:
@@ -358,6 +358,66 @@ async fn admin_monitoring_trace_request_enriches_proxy_timing_from_usage_audit()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn admin_monitoring_trace_request_exposes_structured_ranking_metadata() {
|
||||||
|
let mut candidate = sample_candidate(
|
||||||
|
"cand-used",
|
||||||
|
"request-1",
|
||||||
|
0,
|
||||||
|
RequestCandidateStatus::Success,
|
||||||
|
Some(101),
|
||||||
|
Some(33),
|
||||||
|
Some(200),
|
||||||
|
);
|
||||||
|
candidate.extra_data = Some(json!({
|
||||||
|
"ranking_mode": "CacheAffinity",
|
||||||
|
"priority_mode": "Provider",
|
||||||
|
"ranking_index": 0,
|
||||||
|
"priority_slot": 7,
|
||||||
|
"promoted_by": "cached_affinity",
|
||||||
|
"demoted_by": "cross_format",
|
||||||
|
"client_api_format": "openai:responses"
|
||||||
|
}));
|
||||||
|
|
||||||
|
let request_candidates = Arc::new(InMemoryRequestCandidateRepository::seed(vec![candidate]));
|
||||||
|
let provider_catalog = Arc::new(InMemoryProviderCatalogReadRepository::seed(
|
||||||
|
vec![sample_provider()],
|
||||||
|
vec![sample_endpoint()],
|
||||||
|
vec![sample_key()],
|
||||||
|
));
|
||||||
|
let state = AppState::new()
|
||||||
|
.expect("state should build")
|
||||||
|
.with_decision_trace_data_readers_for_tests(request_candidates, provider_catalog);
|
||||||
|
let context = request_context(http::Method::GET, "/api/admin/monitoring/trace/request-1");
|
||||||
|
|
||||||
|
let response = local_monitoring_response(&state, &context)
|
||||||
|
.await
|
||||||
|
.expect("handler should not error")
|
||||||
|
.expect("route should be handled locally");
|
||||||
|
|
||||||
|
assert_eq!(response.status(), http::StatusCode::OK);
|
||||||
|
let body = to_bytes(response.into_body(), usize::MAX)
|
||||||
|
.await
|
||||||
|
.expect("body should read");
|
||||||
|
let payload: serde_json::Value = serde_json::from_slice(&body).expect("json body should parse");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
payload["candidates"][0]["ranking"],
|
||||||
|
json!({
|
||||||
|
"mode": "CacheAffinity",
|
||||||
|
"priority_mode": "Provider",
|
||||||
|
"index": 0,
|
||||||
|
"priority_slot": 7,
|
||||||
|
"promoted_by": "cached_affinity",
|
||||||
|
"demoted_by": "cross_format"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
payload["candidates"][0]["extra_data"]["ranking_mode"],
|
||||||
|
json!("CacheAffinity")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn admin_monitoring_trace_provider_stats_returns_local_payload() {
|
async fn admin_monitoring_trace_provider_stats_returns_local_payload() {
|
||||||
let request_candidates = Arc::new(InMemoryRequestCandidateRepository::seed(vec![
|
let request_candidates = Arc::new(InMemoryRequestCandidateRepository::seed(vec![
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ pub fn build_admin_monitoring_trace_request_candidate_payload_with_key_accounts(
|
|||||||
"error_message": candidate.error_message,
|
"error_message": candidate.error_message,
|
||||||
"latency_ms": candidate.latency_ms,
|
"latency_ms": candidate.latency_ms,
|
||||||
"concurrent_requests": candidate.concurrent_requests,
|
"concurrent_requests": candidate.concurrent_requests,
|
||||||
|
"ranking": build_admin_monitoring_trace_candidate_ranking(candidate.extra_data.as_ref()),
|
||||||
"extra_data": build_admin_monitoring_trace_candidate_extra_data(candidate.extra_data.as_ref(), usage),
|
"extra_data": build_admin_monitoring_trace_candidate_extra_data(candidate.extra_data.as_ref(), usage),
|
||||||
"created_at": unix_ms_to_rfc3339(candidate.created_at_unix_ms),
|
"created_at": unix_ms_to_rfc3339(candidate.created_at_unix_ms),
|
||||||
"started_at": candidate.started_at_unix_ms.and_then(unix_ms_to_rfc3339),
|
"started_at": candidate.started_at_unix_ms.and_then(unix_ms_to_rfc3339),
|
||||||
@@ -417,6 +418,41 @@ fn admin_monitoring_candidate_status_rank(status: RequestCandidateStatus) -> u8
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_admin_monitoring_trace_candidate_ranking(existing: Option<&Value>) -> Value {
|
||||||
|
let Some(object) = existing.and_then(Value::as_object) else {
|
||||||
|
return Value::Null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ranking = serde_json::Map::new();
|
||||||
|
if let Some(ranking_mode) = json_string_field(object, "ranking_mode") {
|
||||||
|
ranking.insert("mode".to_string(), Value::String(ranking_mode));
|
||||||
|
}
|
||||||
|
if let Some(priority_mode) = json_string_field(object, "priority_mode") {
|
||||||
|
ranking.insert("priority_mode".to_string(), Value::String(priority_mode));
|
||||||
|
}
|
||||||
|
if let Some(ranking_index) = json_u64_field(object, "ranking_index") {
|
||||||
|
ranking.insert("index".to_string(), Value::Number(ranking_index.into()));
|
||||||
|
}
|
||||||
|
if let Some(priority_slot) = json_i64_field(object, "priority_slot") {
|
||||||
|
ranking.insert(
|
||||||
|
"priority_slot".to_string(),
|
||||||
|
Value::Number(priority_slot.into()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(promoted_by) = json_string_field(object, "promoted_by") {
|
||||||
|
ranking.insert("promoted_by".to_string(), Value::String(promoted_by));
|
||||||
|
}
|
||||||
|
if let Some(demoted_by) = json_string_field(object, "demoted_by") {
|
||||||
|
ranking.insert("demoted_by".to_string(), Value::String(demoted_by));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ranking.is_empty() {
|
||||||
|
Value::Null
|
||||||
|
} else {
|
||||||
|
Value::Object(ranking)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_admin_monitoring_trace_candidate_extra_data(
|
fn build_admin_monitoring_trace_candidate_extra_data(
|
||||||
existing: Option<&Value>,
|
existing: Option<&Value>,
|
||||||
usage: Option<&StoredRequestUsageAudit>,
|
usage: Option<&StoredRequestUsageAudit>,
|
||||||
@@ -461,6 +497,31 @@ fn build_admin_monitoring_trace_candidate_extra_data(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn json_string_field(object: &serde_json::Map<String, Value>, key: &str) -> Option<String> {
|
||||||
|
object
|
||||||
|
.get(key)
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_u64_field(object: &serde_json::Map<String, Value>, key: &str) -> Option<u64> {
|
||||||
|
match object.get(key)? {
|
||||||
|
Value::Number(value) => value.as_u64(),
|
||||||
|
Value::String(value) => value.trim().parse::<u64>().ok(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_i64_field(object: &serde_json::Map<String, Value>, key: &str) -> Option<i64> {
|
||||||
|
match object.get(key)? {
|
||||||
|
Value::Number(value) => value.as_i64().or_else(|| value.as_u64()?.try_into().ok()),
|
||||||
|
Value::String(value) => value.trim().parse::<i64>().ok(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_admin_monitoring_usage_proxy_timing(usage: &StoredRequestUsageAudit) -> Option<Value> {
|
fn parse_admin_monitoring_usage_proxy_timing(usage: &StoredRequestUsageAudit) -> Option<Value> {
|
||||||
admin_monitoring_header_value(usage.response_headers.as_ref(), "x-proxy-timing")
|
admin_monitoring_header_value(usage.response_headers.as_ref(), "x-proxy-timing")
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import apiClient from './client'
|
import apiClient from './client'
|
||||||
|
|
||||||
|
export interface CandidateRankingMetadata {
|
||||||
|
mode?: string
|
||||||
|
priority_mode?: string
|
||||||
|
index?: number
|
||||||
|
priority_slot?: number
|
||||||
|
promoted_by?: string
|
||||||
|
demoted_by?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface CandidateRecord {
|
export interface CandidateRecord {
|
||||||
id: string
|
id: string
|
||||||
request_id: string
|
request_id: string
|
||||||
@@ -36,6 +45,7 @@ export interface CandidateRecord {
|
|||||||
error_message?: string
|
error_message?: string
|
||||||
latency_ms?: number
|
latency_ms?: number
|
||||||
concurrent_requests?: number
|
concurrent_requests?: number
|
||||||
|
ranking?: CandidateRankingMetadata | null
|
||||||
extra_data?: Record<string, unknown>
|
extra_data?: Record<string, unknown>
|
||||||
created_at: string
|
created_at: string
|
||||||
started_at?: string
|
started_at?: string
|
||||||
|
|||||||
@@ -1342,15 +1342,16 @@ const currentAttemptRankingInfo = computed<{
|
|||||||
} | null>(() => {
|
} | null>(() => {
|
||||||
const attempt = currentAttempt.value
|
const attempt = currentAttempt.value
|
||||||
if (!attempt) return null
|
if (!attempt) return null
|
||||||
|
const ranking = extractObject(attempt.ranking)
|
||||||
const extra = extractObject(attempt.extra_data)
|
const extra = extractObject(attempt.extra_data)
|
||||||
if (!extra) return null
|
if (!ranking && !extra) return null
|
||||||
|
|
||||||
const rankingMode = normalizeMetadataText(extra.ranking_mode)
|
const rankingMode = normalizeMetadataText(ranking?.mode ?? extra?.ranking_mode)
|
||||||
const priorityMode = normalizeMetadataText(extra.priority_mode)
|
const priorityMode = normalizeMetadataText(ranking?.priority_mode ?? extra?.priority_mode)
|
||||||
const promotedBy = normalizeMetadataText(extra.promoted_by)
|
const promotedBy = normalizeMetadataText(ranking?.promoted_by ?? extra?.promoted_by)
|
||||||
const demotedBy = normalizeMetadataText(extra.demoted_by)
|
const demotedBy = normalizeMetadataText(ranking?.demoted_by ?? extra?.demoted_by)
|
||||||
const rankingIndex = normalizePriorityNumber(extra.ranking_index)
|
const rankingIndex = normalizePriorityNumber(ranking?.index ?? extra?.ranking_index)
|
||||||
const prioritySlot = normalizePriorityNumber(extra.priority_slot)
|
const prioritySlot = normalizePriorityNumber(ranking?.priority_slot ?? extra?.priority_slot)
|
||||||
if (!rankingMode && !priorityMode && !promotedBy && !demotedBy && rankingIndex === null && prioritySlot === null) {
|
if (!rankingMode && !priorityMode && !promotedBy && !demotedBy && rankingIndex === null && prioritySlot === null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2461,6 +2461,20 @@ registerDynamicRoute('GET', '/api/admin/monitoring/trace/:requestId', async (_co
|
|||||||
status: 'skipped',
|
status: 'skipped',
|
||||||
skip_reason: ['并发限制已满', '健康分数过低', '倍率不匹配'][i % 3],
|
skip_reason: ['并发限制已满', '健康分数过低', '倍率不匹配'][i % 3],
|
||||||
is_cached: false,
|
is_cached: false,
|
||||||
|
ranking: {
|
||||||
|
mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
index: i,
|
||||||
|
priority_slot: i + 1,
|
||||||
|
demoted_by: i > 0 ? 'cross_format' : undefined
|
||||||
|
},
|
||||||
|
extra_data: {
|
||||||
|
ranking_mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
ranking_index: i,
|
||||||
|
priority_slot: i + 1,
|
||||||
|
demoted_by: i > 0 ? 'cross_format' : undefined
|
||||||
|
},
|
||||||
latency_ms: 10 + Math.floor(Math.random() * 20),
|
latency_ms: 10 + Math.floor(Math.random() * 20),
|
||||||
created_at: skipStarted.toISOString(),
|
created_at: skipStarted.toISOString(),
|
||||||
started_at: skipStarted.toISOString(),
|
started_at: skipStarted.toISOString(),
|
||||||
@@ -2492,6 +2506,20 @@ registerDynamicRoute('GET', '/api/admin/monitoring/trace/:requestId', async (_co
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
is_cached: record.cache_read_input_tokens > 0,
|
is_cached: record.cache_read_input_tokens > 0,
|
||||||
status_code: 200,
|
status_code: 200,
|
||||||
|
ranking: {
|
||||||
|
mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
index: skipCount,
|
||||||
|
priority_slot: record.cache_read_input_tokens > 0 ? 7 : skipCount + 1,
|
||||||
|
promoted_by: record.cache_read_input_tokens > 0 ? 'cached_affinity' : undefined
|
||||||
|
},
|
||||||
|
extra_data: {
|
||||||
|
ranking_mode: record.cache_read_input_tokens > 0 ? 'CacheAffinity' : 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
ranking_index: skipCount,
|
||||||
|
priority_slot: record.cache_read_input_tokens > 0 ? 7 : skipCount + 1,
|
||||||
|
promoted_by: record.cache_read_input_tokens > 0 ? 'cached_affinity' : undefined
|
||||||
|
},
|
||||||
latency_ms: baseLatency,
|
latency_ms: baseLatency,
|
||||||
created_at: successStarted.toISOString(),
|
created_at: successStarted.toISOString(),
|
||||||
started_at: successStarted.toISOString(),
|
started_at: successStarted.toISOString(),
|
||||||
@@ -2524,6 +2552,18 @@ registerDynamicRoute('GET', '/api/admin/monitoring/trace/:requestId', async (_co
|
|||||||
status_code: record.status_code,
|
status_code: record.status_code,
|
||||||
error_type: ['rate_limit_error', 'api_error', 'timeout_error'][i % 3],
|
error_type: ['rate_limit_error', 'api_error', 'timeout_error'][i % 3],
|
||||||
error_message: record.error_message || 'Request failed',
|
error_message: record.error_message || 'Request failed',
|
||||||
|
ranking: {
|
||||||
|
mode: 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
index: i,
|
||||||
|
priority_slot: i + 1
|
||||||
|
},
|
||||||
|
extra_data: {
|
||||||
|
ranking_mode: 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
ranking_index: i,
|
||||||
|
priority_slot: i + 1
|
||||||
|
},
|
||||||
latency_ms: attemptLatency,
|
latency_ms: attemptLatency,
|
||||||
created_at: attemptStarted.toISOString(),
|
created_at: attemptStarted.toISOString(),
|
||||||
started_at: attemptStarted.toISOString(),
|
started_at: attemptStarted.toISOString(),
|
||||||
@@ -2549,6 +2589,18 @@ registerDynamicRoute('GET', '/api/admin/monitoring/trace/:requestId', async (_co
|
|||||||
required_capabilities: {},
|
required_capabilities: {},
|
||||||
status: 'streaming',
|
status: 'streaming',
|
||||||
is_cached: false,
|
is_cached: false,
|
||||||
|
ranking: {
|
||||||
|
mode: 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
index: 0,
|
||||||
|
priority_slot: 1
|
||||||
|
},
|
||||||
|
extra_data: {
|
||||||
|
ranking_mode: 'FixedOrder',
|
||||||
|
priority_mode: 'Provider',
|
||||||
|
ranking_index: 0,
|
||||||
|
priority_slot: 1
|
||||||
|
},
|
||||||
latency_ms: undefined,
|
latency_ms: undefined,
|
||||||
created_at: now.toISOString(),
|
created_at: now.toISOString(),
|
||||||
started_at: now.toISOString(),
|
started_at: now.toISOString(),
|
||||||
|
|||||||
Reference in New Issue
Block a user