Revert "Merge PR #504"

This reverts commit d216a9e219, reversing
changes made to 21e82abd54.
This commit is contained in:
fawney19
2026-05-19 17:23:57 +08:00
parent 8bcd5b8189
commit 1b570daf72
15 changed files with 24 additions and 626 deletions

View File

@@ -73,10 +73,6 @@ impl AiRuntimeMissDiagnosticPort for GatewayRuntimeMissDiagnosticPort<'_> {
candidate_count: None,
skipped_candidate_count: None,
skip_reasons: std::collections::BTreeMap::new(),
provider_hint_id: None,
provider_hint_name: None,
endpoint_hint_id: None,
endpoint_hint_api_format: None,
}
}

View File

@@ -428,20 +428,6 @@ impl<'a> PoolKeyCursor<'a> {
if record_runtime_miss_diagnostic {
self.runtime_miss_trace_id = Some(trace_id.to_string());
self.record_runtime_miss_diagnostic = true;
let provider_id = self.group.candidate.provider_id.clone();
let provider_name = self.group.transport.provider.name.clone();
let endpoint_id = self.group.candidate.endpoint_id.clone();
let endpoint_api_format = self.group.provider_api_format.clone();
self.state
.app()
.mutate_local_execution_runtime_miss_diagnostic(trace_id, move |diagnostic| {
diagnostic.provider_hint_id.get_or_insert(provider_id);
diagnostic.provider_hint_name.get_or_insert(provider_name);
diagnostic.endpoint_hint_id.get_or_insert(endpoint_id);
diagnostic
.endpoint_hint_api_format
.get_or_insert(endpoint_api_format);
});
}
self
}
@@ -2572,14 +2558,6 @@ mod tests {
assert_eq!(diagnostic.reason, "all_candidates_skipped");
assert_eq!(diagnostic.skipped_candidate_count, Some(1));
assert_eq!(diagnostic.skip_reasons.get("pool_cooldown"), Some(&1));
assert_eq!(
diagnostic.provider_hint_id.as_deref(),
Some("provider-pool")
);
assert_eq!(
diagnostic.provider_hint_name.as_deref(),
Some("provider-pool")
);
}
#[tokio::test]

View File

@@ -274,7 +274,6 @@ pub(crate) async fn record_failed_usage_for_exhausted_request(
diagnostic,
None,
None,
0,
);
data.request_metadata = Some(Value::Object(request_metadata));
@@ -413,7 +412,6 @@ pub(crate) async fn record_failed_usage_for_runtime_miss_request(
diagnostic,
decision.and_then(|value| value.route_family.as_deref()),
decision.and_then(|value| value.route_kind.as_deref()),
context.persisted_candidate_count(),
);
data.request_metadata =
(!request_metadata.is_empty()).then_some(Value::Object(request_metadata));
@@ -985,7 +983,6 @@ fn apply_runtime_miss_usage_routing(
diagnostic: Option<&LocalExecutionRuntimeMissDiagnostic>,
route_family_fallback: Option<&str>,
route_kind_fallback: Option<&str>,
persisted_candidate_count: usize,
) {
data.candidate_id = data
.candidate_id
@@ -1024,68 +1021,7 @@ fn apply_runtime_miss_usage_routing(
.planner_kind
.clone()
.or_else(|| trimmed_non_empty(diagnostic.and_then(|value| value.plan_kind.as_deref())));
if let Some(diagnostic) = diagnostic {
insert_runtime_miss_diagnostic_metadata(
request_metadata,
diagnostic,
persisted_candidate_count,
);
}
}
fn insert_runtime_miss_diagnostic_metadata(
request_metadata: &mut Map<String, Value>,
diagnostic: &LocalExecutionRuntimeMissDiagnostic,
persisted_candidate_count: usize,
) {
let mut runtime_miss = Map::new();
if let Some(count) = diagnostic.candidate_count {
runtime_miss.insert("candidate_count".to_string(), json!(count));
}
runtime_miss.insert(
"persisted_candidate_count".to_string(),
json!(persisted_candidate_count),
);
if let Some(count) = diagnostic.skipped_candidate_count {
runtime_miss.insert("skipped_candidate_count".to_string(), json!(count));
}
if !diagnostic.skip_reasons.is_empty() {
runtime_miss.insert("skip_reasons".to_string(), json!(diagnostic.skip_reasons));
}
if let Some(requested_model) = trimmed_non_empty(diagnostic.requested_model.as_deref()) {
runtime_miss.insert(
"requested_model".to_string(),
Value::String(requested_model),
);
}
let mut provider_hint = Map::new();
if let Some(provider_id) = trimmed_provider_hint(diagnostic.provider_hint_id.as_deref()) {
provider_hint.insert("id".to_string(), Value::String(provider_id));
}
if let Some(provider_name) = trimmed_provider_hint(diagnostic.provider_hint_name.as_deref()) {
provider_hint.insert("name".to_string(), Value::String(provider_name));
}
if !provider_hint.is_empty() {
runtime_miss.insert("provider_hint".to_string(), Value::Object(provider_hint));
}
let mut endpoint_hint = Map::new();
if let Some(endpoint_id) = trimmed_non_empty(diagnostic.endpoint_hint_id.as_deref()) {
endpoint_hint.insert("id".to_string(), Value::String(endpoint_id));
}
if let Some(endpoint_api_format) =
trimmed_non_empty(diagnostic.endpoint_hint_api_format.as_deref())
{
endpoint_hint.insert("api_format".to_string(), Value::String(endpoint_api_format));
}
if !endpoint_hint.is_empty() {
runtime_miss.insert("endpoint_hint".to_string(), Value::Object(endpoint_hint));
}
if !runtime_miss.is_empty() {
request_metadata.insert("runtime_miss".to_string(), Value::Object(runtime_miss));
}
let _ = request_metadata;
}
fn trimmed_non_empty(value: Option<&str>) -> Option<String> {
@@ -1095,15 +1031,6 @@ fn trimmed_non_empty(value: Option<&str>) -> Option<String> {
.map(ToOwned::to_owned)
}
fn trimmed_provider_hint(value: Option<&str>) -> Option<String> {
trimmed_non_empty(value).filter(|value| {
!matches!(
value.to_ascii_lowercase().as_str(),
"unknown" | "unknow" | "pending"
)
})
}
#[cfg(test)]
mod tests {
use super::{
@@ -1118,7 +1045,6 @@ mod tests {
};
use aether_usage_runtime::UsageEventData;
use serde_json::{json, Map, Value};
use std::collections::BTreeMap;
#[test]
fn local_execution_client_error_message_is_client_friendly() {
@@ -1143,7 +1069,7 @@ mod tests {
}
#[test]
fn runtime_miss_routing_records_compact_diagnostic_metadata() {
fn runtime_miss_routing_moves_to_typed_usage_fields_and_keeps_metadata_lightweight() {
let mut data = UsageEventData::default();
let mut request_metadata =
Map::from_iter([("trace_id".to_string(), Value::String("trace-1".to_string()))]);
@@ -1160,19 +1086,10 @@ mod tests {
route_family: Some("claude".to_string()),
route_kind: Some("cli".to_string()),
plan_kind: Some("claude_cli_sync".to_string()),
requested_model: Some("gpt-5".to_string()),
candidate_count: Some(1),
skipped_candidate_count: Some(4),
skip_reasons: BTreeMap::from([("pool_cooldown".to_string(), 4)]),
provider_hint_id: Some("provider-google-api".to_string()),
provider_hint_name: Some("Google API".to_string()),
endpoint_hint_id: Some("endpoint-gemini".to_string()),
endpoint_hint_api_format: Some("gemini:generate_content".to_string()),
..LocalExecutionRuntimeMissDiagnostic::default()
}),
None,
None,
0,
);
assert_eq!(data.candidate_id.as_deref(), Some("cand-1"));
@@ -1192,24 +1109,7 @@ mod tests {
assert_eq!(
Value::Object(request_metadata),
json!({
"trace_id": "trace-1",
"runtime_miss": {
"candidate_count": 1,
"persisted_candidate_count": 0,
"skipped_candidate_count": 4,
"skip_reasons": {
"pool_cooldown": 4
},
"provider_hint": {
"id": "provider-google-api",
"name": "Google API"
},
"endpoint_hint": {
"id": "endpoint-gemini",
"api_format": "gemini:generate_content"
},
"requested_model": "gpt-5"
}
"trace_id": "trace-1"
})
);
}

View File

@@ -30,10 +30,6 @@ pub(crate) struct LocalExecutionRuntimeMissDiagnostic {
pub(crate) candidate_count: Option<usize>,
pub(crate) skipped_candidate_count: Option<usize>,
pub(crate) skip_reasons: std::collections::BTreeMap<String, usize>,
pub(crate) provider_hint_id: Option<String>,
pub(crate) provider_hint_name: Option<String>,
pub(crate) endpoint_hint_id: Option<String>,
pub(crate) endpoint_hint_api_format: Option<String>,
}
impl LocalExecutionRuntimeMissDiagnostic {

View File

@@ -492,60 +492,6 @@ fn admin_usage_extract_local_runtime_miss_reason_summary(message: &str) -> Optio
(!summary.is_empty()).then(|| summary.to_string())
}
fn admin_usage_runtime_miss_metadata(
item: &StoredRequestUsageAudit,
) -> Option<&serde_json::Map<String, Value>> {
item.request_metadata
.as_ref()
.and_then(Value::as_object)
.and_then(|metadata| metadata.get("runtime_miss"))
.and_then(Value::as_object)
}
fn admin_usage_json_value_has_content(value: &Value) -> bool {
match value {
Value::Null => false,
Value::String(value) => !value.trim().is_empty(),
Value::Array(value) => !value.is_empty(),
Value::Object(value) => !value.is_empty(),
Value::Bool(_) | Value::Number(_) => true,
}
}
fn maybe_insert_runtime_miss_field(
object: &mut serde_json::Map<String, Value>,
runtime_miss: &serde_json::Map<String, Value>,
key: &str,
) {
let Some(value) = runtime_miss
.get(key)
.filter(|value| admin_usage_json_value_has_content(value))
else {
return;
};
object.insert(key.to_string(), value.clone());
}
fn admin_usage_insert_runtime_miss_fields(
object: &mut serde_json::Map<String, Value>,
item: &StoredRequestUsageAudit,
) {
let Some(runtime_miss) = admin_usage_runtime_miss_metadata(item) else {
return;
};
for key in [
"candidate_count",
"persisted_candidate_count",
"skipped_candidate_count",
"skip_reasons",
"requested_model",
"provider_hint",
"endpoint_hint",
] {
maybe_insert_runtime_miss_field(object, runtime_miss, key);
}
}
fn admin_usage_scheduling_failure_json(
item: &StoredRequestUsageAudit,
client_error: &Value,
@@ -568,38 +514,20 @@ fn admin_usage_scheduling_failure_json(
let reason_summary =
raw_message.and_then(admin_usage_extract_local_runtime_miss_reason_summary);
let mut object = serde_json::Map::new();
object.insert(
"source".to_string(),
Value::String("local_execution_runtime_miss".to_string()),
);
object.insert("reason".to_string(), Value::String(reason.to_string()));
object.insert(
"reason_label".to_string(),
Value::String(admin_usage_local_runtime_miss_reason_label(reason).to_string()),
);
object.insert(
"title".to_string(),
Value::String(format!(
"本地调度失败:{}",
admin_usage_local_runtime_miss_reason_label(reason)
)),
);
object.insert("message".to_string(), json!(message));
object.insert("reason_summary".to_string(), json!(reason_summary));
object.insert("status_code".to_string(), json!(item.status_code));
object.insert(
"no_upstream_attempt".to_string(),
json!(
item.candidate_id.is_none()
&& item.provider_api_key_id.is_none()
&& item.provider_request_headers.is_none()
&& item.provider_request_body.is_none()
&& item.provider_request_body_ref.is_none()
),
);
admin_usage_insert_runtime_miss_fields(&mut object, item);
Value::Object(object)
json!({
"source": "local_execution_runtime_miss",
"reason": reason,
"reason_label": admin_usage_local_runtime_miss_reason_label(reason),
"title": format!("本地调度失败:{}", admin_usage_local_runtime_miss_reason_label(reason)),
"message": message,
"reason_summary": reason_summary,
"status_code": item.status_code,
"no_upstream_attempt": item.candidate_id.is_none()
&& item.provider_api_key_id.is_none()
&& item.provider_request_headers.is_none()
&& item.provider_request_body.is_none()
&& item.provider_request_body_ref.is_none(),
})
}
fn admin_usage_extract_local_execution_request_mode(message: &str) -> Option<&str> {
@@ -1234,7 +1162,6 @@ fn admin_usage_active_request_json(
"request_path": admin_usage_metadata_string(item, "request_path"),
"request_path_and_query": admin_usage_metadata_string(item, "request_path_and_query"),
"has_fallback": admin_usage_has_fallback(item),
"scheduling_failure": admin_usage_scheduling_failure_json(item, &Value::Null),
});
if let Some(api_format) = item.api_format.as_ref() {
value["api_format"] = json!(api_format);
@@ -1324,7 +1251,6 @@ pub fn admin_usage_record_json(
"api_key_name": api_key_name,
"provider_key_name": provider_key_name,
"model_version": Value::Null,
"scheduling_failure": admin_usage_scheduling_failure_json(item, &Value::Null),
});
let object = payload
.as_object_mut()
@@ -3295,122 +3221,6 @@ mod tests {
assert_eq!(payload["scheduling_failure"]["no_upstream_attempt"], true);
}
#[test]
fn detail_payload_exposes_runtime_miss_context_inside_scheduling_failure() {
let message = "找到 1 个支持模型 gemma-4-31b-it 的候选提供商但本次同步请求全部不可用provider_quota_blocked 1 次(原因代码: all_candidates_skipped";
let item = StoredRequestUsageAudit {
provider_name: "unknown".to_string(),
provider_id: None,
provider_endpoint_id: None,
provider_api_key_id: None,
provider_request_headers: None,
provider_request_body: None,
provider_request_body_ref: None,
candidate_id: None,
execution_path: Some("local_execution_runtime_miss".to_string()),
local_execution_runtime_miss_reason: Some("all_candidates_skipped".to_string()),
request_metadata: Some(json!({
"runtime_miss": {
"candidate_count": 1,
"persisted_candidate_count": 0,
"skipped_candidate_count": 1,
"skip_reasons": {
"provider_quota_blocked": 1
},
"requested_model": "gemma-4-31b-it",
"provider_hint": {
"id": "provider-google-api",
"name": "Google API"
},
"endpoint_hint": {
"id": "endpoint-gemini",
"api_format": "gemini:generate_content"
}
}
})),
..sample_usage("failed", Some(503), Some(message))
};
let payload = build_admin_usage_detail_payload(
&item,
&BTreeMap::new(),
&BTreeMap::new(),
false,
false,
None,
false,
Some(json!({"model": "gemma-4-31b-it"})),
&BTreeMap::new(),
);
assert_eq!(
payload["scheduling_failure"]["provider_hint"]["name"],
"Google API"
);
assert_eq!(
payload["scheduling_failure"]["endpoint_hint"]["api_format"],
"gemini:generate_content"
);
assert_eq!(
payload["scheduling_failure"]["requested_model"],
"gemma-4-31b-it"
);
assert_eq!(payload["scheduling_failure"]["candidate_count"], 1);
assert_eq!(
payload["scheduling_failure"]["persisted_candidate_count"],
0
);
assert_eq!(payload["scheduling_failure"]["skipped_candidate_count"], 1);
assert_eq!(
payload["scheduling_failure"]["skip_reasons"]["provider_quota_blocked"],
1
);
}
#[test]
fn record_json_exposes_scheduling_failure_for_local_runtime_miss_records() {
let message = "没有可用提供商支持模型 gemma-4-31b-it 的同步请求。请检查模型映射、端点启用状态和 API Key 权限(原因代码: candidate_list_empty";
let item = StoredRequestUsageAudit {
provider_name: "unknown".to_string(),
provider_id: None,
provider_endpoint_id: None,
provider_api_key_id: None,
provider_request_headers: None,
provider_request_body: None,
provider_request_body_ref: None,
candidate_id: None,
execution_path: Some("local_execution_runtime_miss".to_string()),
local_execution_runtime_miss_reason: Some("candidate_list_empty".to_string()),
request_metadata: Some(json!({
"runtime_miss": {
"candidate_count": 0,
"persisted_candidate_count": 0,
"requested_model": "gemma-4-31b-it"
}
})),
..sample_usage("failed", Some(503), Some(message))
};
let payload = admin_usage_record_json(
&item,
&BTreeMap::new(),
&BTreeMap::new(),
false,
false,
None,
);
assert_eq!(
payload["scheduling_failure"]["title"],
"本地调度失败:没有可调度候选"
);
assert_eq!(
payload["scheduling_failure"]["requested_model"],
"gemma-4-31b-it"
);
assert_eq!(payload["scheduling_failure"]["no_upstream_attempt"], true);
}
#[test]
fn detail_payload_preserves_legacy_body_capture_metadata_keys() {
let item = StoredRequestUsageAudit {

View File

@@ -151,19 +151,6 @@ export interface RequestSchedulingFailure {
reason_summary?: string | null
status_code?: number | null
no_upstream_attempt?: boolean | null
requested_model?: string | null
candidate_count?: number | null
persisted_candidate_count?: number | null
skipped_candidate_count?: number | null
skip_reasons?: Record<string, number> | null
provider_hint?: {
id?: string | null
name?: string | null
} | null
endpoint_hint?: {
id?: string | null
api_format?: string | null
} | null
}
export interface RequestDetail {

View File

@@ -547,38 +547,6 @@
</Card>
</div>
<!-- Local Scheduling Failure State -->
<Card
v-else-if="schedulingFailureNotice"
class="border-red-200 dark:border-red-800"
>
<div class="p-4 space-y-2">
<div class="flex flex-wrap items-center gap-2">
<Badge variant="destructive">
调度失败
</Badge>
<h4 class="text-sm font-semibold text-red-950 dark:text-red-100">
{{ schedulingFailureNotice.title }}
</h4>
</div>
<p class="text-sm leading-6 text-red-900 dark:text-red-100">
{{ schedulingFailureNotice.message }}
</p>
<div
v-if="schedulingFailureNotice.meta.length > 0"
class="flex flex-wrap gap-1.5"
>
<span
v-for="item in schedulingFailureNotice.meta"
:key="item"
class="rounded-full border border-red-200 bg-white/70 px-2 py-0.5 text-[11px] font-mono text-red-700 dark:border-red-900 dark:bg-red-950/50 dark:text-red-200"
>
{{ item }}
</span>
</div>
</div>
</Card>
<!-- Empty State -->
<Card
v-else
@@ -607,7 +575,6 @@ import { parseApiError } from '@/utils/errorParser'
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import { useDarkMode } from '@/composables/useDarkMode'
import { resolveTimelineFinalStatus } from '../utils/status'
import type { RequestSchedulingFailure } from '@/api/dashboard'
import {
buildPoolGroupVisibleAttempts,
buildPoolParticipatedCandidates,
@@ -670,8 +637,6 @@ const props = defineProps<{
usageData?: UsageData | null
/** 请求元数据(用于号池调度组装) */
requestMetadata?: Record<string, unknown> | null
/** 本地调度失败摘要;用于没有 trace candidate 时替代空态 */
schedulingFailure?: RequestSchedulingFailure | null
/** 已获取的追踪数据;传入时不再内部拉取 */
traceData?: RequestTrace | null
}>()
@@ -847,62 +812,6 @@ const computedFinalStatus = computed(() => {
})
})
const nonEmptyNoticeString = (value: string | null | undefined): string | null => {
const trimmed = value?.trim()
return trimmed ? trimmed : null
}
const uniqueNoticeMeta = (values: Array<string | null | undefined>): string[] => {
return Array.from(new Set(values.map(value => value?.trim()).filter((value): value is string => Boolean(value))))
}
const isUnknownProviderHint = (value: string | null): boolean => {
const normalized = value?.trim().toLowerCase()
return !normalized || ['unknown', 'unknow', 'pending'].includes(normalized)
}
const schedulingFailureProviderHint = (failure: RequestSchedulingFailure): string | null => {
const name = nonEmptyNoticeString(failure.provider_hint?.name)
if (name && !isUnknownProviderHint(name)) return name
const id = nonEmptyNoticeString(failure.provider_hint?.id)
if (id && !isUnknownProviderHint(id)) return id
return null
}
const schedulingFailureEndpointHint = (failure: RequestSchedulingFailure): string | null => {
return nonEmptyNoticeString(failure.endpoint_hint?.api_format)
?? nonEmptyNoticeString(failure.endpoint_hint?.id)
}
const schedulingFailureNotice = computed(() => {
const failure = props.schedulingFailure
if (!failure) return null
const title = nonEmptyNoticeString(failure.title) ?? '本地调度失败'
const message = nonEmptyNoticeString(failure.message)
?? nonEmptyNoticeString(failure.reason_summary)
?? nonEmptyNoticeString(failure.reason_label)
?? nonEmptyNoticeString(failure.reason)
?? '本地调度阶段没有选出可用上游提供商'
return {
title,
message,
meta: uniqueNoticeMeta([
schedulingFailureProviderHint(failure),
schedulingFailureEndpointHint(failure),
nonEmptyNoticeString(failure.requested_model),
nonEmptyNoticeString(failure.reason_summary),
nonEmptyNoticeString(failure.reason_label),
nonEmptyNoticeString(failure.reason),
typeof failure.status_code === 'number' ? `HTTP ${failure.status_code}` : null,
failure.no_upstream_attempt ? '未进入上游执行' : null,
]),
}
})
const compareBySchedulingOrder = (a: CandidateRecord, b: CandidateRecord): number => {
if (a.candidate_index !== b.candidate_index) {
return a.candidate_index - b.candidate_index

View File

@@ -544,7 +544,6 @@
:request-status="detail.status"
:request-api-format="detail.api_format || null"
:request-metadata="traceRequestMetadata"
:scheduling-failure="detail.scheduling_failure"
@trace-state="handleTraceState"
/>
</div>

View File

@@ -584,18 +584,15 @@
>
<div class="flex min-w-0 items-center gap-1">
<div class="flex min-w-0 flex-col text-xs gap-0.5">
<span class="truncate">{{ record.provider }}</span>
<span
class="truncate"
:title="getRecordProviderTitle(record)"
>{{ getRecordProviderDisplay(record) }}</span>
<span
v-if="getRecordProviderSecondaryText(record)"
v-if="record.provider_key_name"
class="text-muted-foreground truncate"
:title="getRecordProviderSecondaryTitle(record)"
:title="record.provider_key_name"
>
{{ getRecordProviderSecondaryText(record) }}
{{ record.provider_key_name }}
<span
v-if="record.provider_key_name && record.rate_multiplier && record.rate_multiplier !== 1.0"
v-if="record.rate_multiplier && record.rate_multiplier !== 1.0"
class="text-foreground/60"
>({{ record.rate_multiplier }}x)</span>
</span>
@@ -1177,57 +1174,6 @@ function getDisplayStatus(record: UsageRecord) {
return resolveDisplayRequestStatus(record)
}
function nonEmptyDisplayString(value: string | null | undefined): string | null {
const trimmed = value?.trim()
return trimmed ? trimmed : null
}
function isUnknownProvider(value: string | null | undefined): boolean {
const normalized = value?.trim().toLowerCase()
return !normalized || ['unknown', 'unknow', 'pending'].includes(normalized)
}
function getRecordSchedulingProviderHint(record: UsageRecord): string | null {
const name = nonEmptyDisplayString(record.scheduling_failure?.provider_hint?.name)
if (name && !isUnknownProvider(name)) return name
const id = nonEmptyDisplayString(record.scheduling_failure?.provider_hint?.id)
if (id && !isUnknownProvider(id)) return id
return null
}
function getRecordProviderDisplay(record: UsageRecord): string {
const provider = nonEmptyDisplayString(record.provider)
if (provider && !isUnknownProvider(provider)) return provider
const providerHint = getRecordSchedulingProviderHint(record)
if (providerHint) return providerHint
if (record.scheduling_failure) return '未选定提供商'
return provider ?? 'unknown'
}
function getRecordProviderTitle(record: UsageRecord): string {
const schedulingMessage = nonEmptyDisplayString(record.scheduling_failure?.message)
const providerDisplay = getRecordProviderDisplay(record)
return schedulingMessage ? `${providerDisplay}\n${schedulingMessage}` : providerDisplay
}
function getRecordProviderSecondaryText(record: UsageRecord): string | null {
return nonEmptyDisplayString(record.provider_key_name)
?? nonEmptyDisplayString(record.scheduling_failure?.reason_label)
?? nonEmptyDisplayString(record.scheduling_failure?.reason_summary)
?? nonEmptyDisplayString(record.scheduling_failure?.reason)
}
function getRecordProviderSecondaryTitle(record: UsageRecord): string | undefined {
return nonEmptyDisplayString(record.provider_key_name)
?? nonEmptyDisplayString(record.scheduling_failure?.message)
?? getRecordProviderSecondaryText(record)
?? undefined
}
function getStreamModeLabel(record: UsageRecord): string {
return formatUsageStreamLabel(record)
}

View File

@@ -350,40 +350,6 @@ describe('HorizontalRequestTimeline', () => {
expect(nodeDot?.classList.contains('status-success')).toBe(false)
})
it('shows scheduling failure context instead of an empty trace state when no candidates exist', async () => {
const trace = buildTrace([])
trace.final_status = 'failed'
const root = mountTimeline(trace, {
requestStatus: 'failed',
overrideStatusCode: 503,
schedulingFailure: {
source: 'local_execution_runtime_miss',
reason: 'all_candidates_skipped',
reason_label: '所有候选均被跳过',
title: '本地调度失败:所有候选均被跳过',
message: '没有可用提供商支持模型 gemma-4-31b-it 的同步请求',
status_code: 503,
no_upstream_attempt: true,
provider_hint: {
id: 'provider-google-api',
name: 'Google API',
},
endpoint_hint: {
id: 'endpoint-gemini',
api_format: 'gemini:generate_content',
},
},
})
await nextTick()
expect(root.textContent).toContain('本地调度失败:所有候选均被跳过')
expect(root.textContent).toContain('没有可用提供商支持模型 gemma-4-31b-it 的同步请求')
expect(root.textContent).toContain('Google API')
expect(root.textContent).toContain('gemini:generate_content')
expect(root.textContent).not.toContain('暂无追踪数据')
})
it('keeps emitted trace state active while the request lifecycle is still streaming', async () => {
const onTraceState = vi.fn()
const trace = buildTrace([

View File

@@ -255,31 +255,6 @@ describe('UsageRecordsTable', () => {
expect(root.textContent).not.toContain('等待中')
})
it('uses scheduling failure provider hints instead of rendering unknown provider', () => {
const root = mountUsageRecordsTable([buildRecord({
provider: 'unknown',
status: 'failed',
status_code: 503,
scheduling_failure: {
source: 'local_execution_runtime_miss',
reason: 'all_candidates_skipped',
reason_label: '所有候选均被跳过',
title: '本地调度失败:所有候选均被跳过',
message: '没有可用提供商支持模型 gemma-4-31b-it 的同步请求',
status_code: 503,
no_upstream_attempt: true,
provider_hint: {
id: 'provider-google-api',
name: 'Google API',
},
},
} as Partial<UsageRecord>)])
expect(root.textContent).toContain('Google API')
expect(root.textContent).toContain('所有候选均被跳过')
expect(root.textContent).not.toContain('unknown')
})
it('renders output TPS in the non-admin usage table', () => {
const root = mountUsageRecordsTable([buildRecord()], { isAdmin: false })

View File

@@ -515,8 +515,7 @@ export function useUsageData(options: UseUsageDataOptions) {
api_key_name: existing.api_key_name || record.api_key_name,
provider_key_name: existing.provider_key_name || record.provider_key_name,
rate_multiplier: existing.rate_multiplier ?? record.rate_multiplier,
target_model: existing.target_model || record.target_model,
scheduling_failure: existing.scheduling_failure ?? record.scheduling_failure
target_model: existing.target_model || record.target_model
}
}
@@ -524,8 +523,7 @@ export function useUsageData(options: UseUsageDataOptions) {
if (protectProvider) {
return {
...record,
provider: existing.provider,
scheduling_failure: existing.scheduling_failure ?? record.scheduling_failure
provider: existing.provider
}
}

View File

@@ -1,5 +1,4 @@
import type { ImageProgress } from '@/api/requestTrace'
import type { RequestSchedulingFailure } from '@/api/dashboard'
// 统计数据状态
export interface UsageStatsState {
@@ -121,7 +120,6 @@ export interface UsageRecord {
status_code?: number
error_message?: string
status?: RequestStatus // 请求状态: pending, streaming, completed, failed
scheduling_failure?: RequestSchedulingFailure | null
created_at: string
has_fallback?: boolean
has_retry?: boolean

View File

@@ -73,43 +73,6 @@ describe('request failure notice', () => {
})
})
it('includes scheduling failure provider and endpoint hints in metadata', () => {
const notice = resolveRequestFailureNotice(buildRequestDetail({
failure_summary: {
status_code: 503,
message: '没有可用提供商支持模型 gemma-4-31b-it 的同步请求',
},
scheduling_failure: {
source: 'local_execution_runtime_miss',
reason: 'all_candidates_skipped',
reason_label: '所有候选均被跳过',
title: '本地调度失败:所有候选均被跳过',
message: '没有可用提供商支持模型 gemma-4-31b-it 的同步请求',
status_code: 503,
no_upstream_attempt: true,
requested_model: 'gemma-4-31b-it',
provider_hint: {
id: 'provider-google-api',
name: 'Google API',
},
endpoint_hint: {
id: 'endpoint-gemini',
api_format: 'gemini:generate_content',
},
} as NonNullable<RequestDetail['scheduling_failure']>,
}))
expect(notice?.meta).toEqual([
'Google API',
'gemini:generate_content',
'gemma-4-31b-it',
'所有候选均被跳过',
'all_candidates_skipped',
'HTTP 503',
'未进入上游执行',
])
})
it('falls back to the failure summary for upstream failures', () => {
const notice = resolveRequestFailureNotice(buildRequestDetail({
failure_summary: {

View File

@@ -25,11 +25,6 @@ function uniqueMeta(values: Array<string | null | undefined>): string[] {
return Array.from(new Set(values.map(value => value?.trim()).filter((value): value is string => Boolean(value))))
}
function isUnknownProviderHint(value: string | null): boolean {
const normalized = value?.trim().toLowerCase()
return !normalized || ['unknown', 'unknow', 'pending'].includes(normalized)
}
function schedulingFailureMessage(
failure: RequestSchedulingFailure,
fallbackDomain: RequestErrorDomain | null,
@@ -42,21 +37,6 @@ function schedulingFailureMessage(
?? nonEmptyString(failure.reason)
}
function schedulingFailureProviderHint(failure: RequestSchedulingFailure): string | null {
const name = nonEmptyString(failure.provider_hint?.name)
if (name && !isUnknownProviderHint(name)) return name
const id = nonEmptyString(failure.provider_hint?.id)
if (id && !isUnknownProviderHint(id)) return id
return null
}
function schedulingFailureEndpointHint(failure: RequestSchedulingFailure): string | null {
return nonEmptyString(failure.endpoint_hint?.api_format)
?? nonEmptyString(failure.endpoint_hint?.id)
}
export function resolveRequestFailureNotice(detail: RequestDetail | null | undefined): RequestFailureNotice | null {
if (!detail) return null
@@ -75,9 +55,6 @@ export function resolveRequestFailureNotice(detail: RequestDetail | null | undef
message,
isSchedulingFailure: true,
meta: uniqueMeta([
schedulingFailureProviderHint(schedulingFailure),
schedulingFailureEndpointHint(schedulingFailure),
nonEmptyString(schedulingFailure.requested_model),
nonEmptyString(schedulingFailure.reason_summary),
nonEmptyString(schedulingFailure.reason_label),
nonEmptyString(schedulingFailure.reason),