2025-12-10 20:52:44 +08:00
|
|
|
|
import apiClient from './client'
|
|
|
|
|
|
|
2026-04-27 17:16:30 +08:00
|
|
|
|
export interface CandidateRankingMetadata {
|
|
|
|
|
|
mode?: string
|
|
|
|
|
|
priority_mode?: string
|
|
|
|
|
|
index?: number
|
|
|
|
|
|
priority_slot?: number
|
|
|
|
|
|
promoted_by?: string
|
|
|
|
|
|
demoted_by?: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 01:21:26 +08:00
|
|
|
|
export interface ImageProgress {
|
|
|
|
|
|
phase?: 'upstream_connecting' | 'upstream_streaming' | 'upstream_completed' | 'failed' | string
|
|
|
|
|
|
upstream_ttfb_ms?: number | null
|
|
|
|
|
|
upstream_sse_frame_count?: number | null
|
|
|
|
|
|
last_upstream_event?: string | null
|
|
|
|
|
|
last_upstream_frame_at_unix_ms?: number | null
|
|
|
|
|
|
partial_image_count?: number | null
|
|
|
|
|
|
last_client_visible_event?: string | null
|
|
|
|
|
|
downstream_heartbeat_count?: number | null
|
|
|
|
|
|
last_downstream_heartbeat_at_unix_ms?: number | null
|
|
|
|
|
|
downstream_heartbeat_interval_ms?: number | null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 10:46:04 +08:00
|
|
|
|
export interface CandidateResponseBoundary {
|
|
|
|
|
|
source?: string
|
|
|
|
|
|
status_code?: number | null
|
|
|
|
|
|
headers?: Record<string, unknown> | null
|
|
|
|
|
|
body?: unknown
|
|
|
|
|
|
body_ref?: string | null
|
|
|
|
|
|
body_state?: string | null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-07 21:14:27 +08:00
|
|
|
|
export interface CandidateProxyTiming {
|
|
|
|
|
|
body_read_ms?: number | null
|
|
|
|
|
|
decompress_ms?: number | null
|
|
|
|
|
|
wire_size?: number | null
|
|
|
|
|
|
body_size?: number | null
|
|
|
|
|
|
ttfb_ms?: number | null
|
|
|
|
|
|
upstream_ms?: number | null
|
|
|
|
|
|
response_wait_ms?: number | null
|
|
|
|
|
|
connection_acquire_ms?: number | null
|
|
|
|
|
|
upstream_processing_ms?: number | null
|
|
|
|
|
|
connect_ms?: number | null
|
|
|
|
|
|
tls_ms?: number | null
|
|
|
|
|
|
dns_ms?: number | null
|
|
|
|
|
|
total_ms?: number | null
|
|
|
|
|
|
connection_reused?: boolean | null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CandidateProxy {
|
|
|
|
|
|
node_name?: string | null
|
|
|
|
|
|
node_id?: string | null
|
|
|
|
|
|
url?: string | null
|
|
|
|
|
|
source?: string | null
|
|
|
|
|
|
ttfb_ms?: number | null
|
|
|
|
|
|
timing?: CandidateProxyTiming | null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
export interface CandidateRecord {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
request_id: string
|
|
|
|
|
|
candidate_index: number
|
|
|
|
|
|
retry_index: number
|
|
|
|
|
|
provider_id?: string
|
|
|
|
|
|
provider_name?: string
|
|
|
|
|
|
provider_website?: string // Provider 官网
|
2026-04-10 01:46:14 +08:00
|
|
|
|
provider_priority?: number
|
|
|
|
|
|
provider_keep_priority_on_conversion?: boolean
|
2026-04-16 13:00:44 +08:00
|
|
|
|
provider_enable_format_conversion?: boolean
|
2025-12-10 20:52:44 +08:00
|
|
|
|
endpoint_id?: string
|
|
|
|
|
|
endpoint_name?: string // 端点显示名称(api_format)
|
2026-04-16 13:00:44 +08:00
|
|
|
|
endpoint_api_family?: string
|
|
|
|
|
|
endpoint_kind?: string
|
|
|
|
|
|
endpoint_format_acceptance_config?: Record<string, unknown> | null
|
2025-12-10 20:52:44 +08:00
|
|
|
|
key_id?: string
|
|
|
|
|
|
key_name?: string // 密钥名称
|
2026-03-06 21:06:45 +08:00
|
|
|
|
key_account_label?: string // 更适合展示的测试账号标签(优先 OAuth 邮箱)
|
2026-02-19 14:51:08 +08:00
|
|
|
|
key_preview?: string // 密钥脱敏预览(如 sk-***abc),OAuth 类型不返回
|
2026-03-01 23:32:48 +08:00
|
|
|
|
key_auth_type?: string // 密钥认证类型(api_key, service_account, oauth 等)
|
2026-04-16 13:00:44 +08:00
|
|
|
|
key_api_formats?: unknown
|
2026-04-10 01:46:14 +08:00
|
|
|
|
key_internal_priority?: number
|
|
|
|
|
|
key_global_priority_by_format?: Record<string, number> | null
|
2026-02-19 14:51:08 +08:00
|
|
|
|
key_oauth_plan_type?: string // OAuth 账号套餐类型(free/plus/team/enterprise)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
key_capabilities?: Record<string, boolean> | null // Key 支持的能力
|
|
|
|
|
|
required_capabilities?: Record<string, boolean> | null // 请求实际需要的能力标签
|
2026-02-04 13:32:42 +08:00
|
|
|
|
status: 'pending' | 'streaming' | 'success' | 'failed' | 'skipped' | 'cancelled' | 'available' | 'unused' | 'stream_interrupted'
|
2025-12-10 20:52:44 +08:00
|
|
|
|
skip_reason?: string
|
|
|
|
|
|
is_cached: boolean
|
|
|
|
|
|
// 执行结果字段
|
|
|
|
|
|
status_code?: number
|
|
|
|
|
|
error_type?: string
|
|
|
|
|
|
error_message?: string
|
|
|
|
|
|
latency_ms?: number
|
|
|
|
|
|
concurrent_requests?: number
|
2026-04-27 17:16:30 +08:00
|
|
|
|
ranking?: CandidateRankingMetadata | null
|
2026-05-09 01:21:26 +08:00
|
|
|
|
image_progress?: ImageProgress | null
|
2026-05-09 10:46:04 +08:00
|
|
|
|
extra_data?: Record<string, unknown> & {
|
2026-09-07 21:14:27 +08:00
|
|
|
|
first_byte_time_ms?: number | null
|
|
|
|
|
|
needs_conversion?: boolean
|
|
|
|
|
|
provider_api_format?: string | null
|
|
|
|
|
|
proxy?: CandidateProxy | null
|
|
|
|
|
|
pool_selection?: {
|
|
|
|
|
|
reason: string
|
|
|
|
|
|
cost_soft_threshold?: number | boolean | null
|
|
|
|
|
|
cost_window_usage?: number | null
|
|
|
|
|
|
cost_limit?: number | null
|
|
|
|
|
|
} | null
|
|
|
|
|
|
pool_skip?: {
|
|
|
|
|
|
type: string
|
|
|
|
|
|
cooldown_reason?: string | null
|
|
|
|
|
|
cooldown_ttl?: number | null
|
|
|
|
|
|
cost_window_usage?: number | null
|
|
|
|
|
|
} | null
|
2026-05-09 10:46:04 +08:00
|
|
|
|
upstream_response?: CandidateResponseBoundary
|
2026-05-10 02:10:23 +08:00
|
|
|
|
image_progress?: ImageProgress | null
|
2026-05-09 10:46:04 +08:00
|
|
|
|
}
|
2025-12-10 20:52:44 +08:00
|
|
|
|
created_at: string
|
|
|
|
|
|
started_at?: string
|
|
|
|
|
|
finished_at?: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RequestTrace {
|
|
|
|
|
|
request_id: string
|
2026-05-09 10:46:04 +08:00
|
|
|
|
request_path?: string
|
|
|
|
|
|
request_query_string?: string
|
|
|
|
|
|
request_path_and_query?: string
|
2025-12-10 20:52:44 +08:00
|
|
|
|
total_candidates: number
|
2026-01-20 15:40:16 +08:00
|
|
|
|
final_status: 'success' | 'failed' | 'streaming' | 'pending' | 'cancelled'
|
2025-12-10 20:52:44 +08:00
|
|
|
|
total_latency_ms: number
|
|
|
|
|
|
candidates: CandidateRecord[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProviderStats {
|
|
|
|
|
|
total_attempts: number
|
|
|
|
|
|
success_count: number
|
|
|
|
|
|
failed_count: number
|
2026-01-20 15:40:16 +08:00
|
|
|
|
cancelled_count: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
skipped_count: number
|
|
|
|
|
|
pending_count: number
|
|
|
|
|
|
available_count: number
|
|
|
|
|
|
failure_rate: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const requestTraceApi = {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取特定请求的完整追踪信息
|
|
|
|
|
|
*/
|
2026-03-05 17:32:21 +08:00
|
|
|
|
async getRequestTrace(
|
|
|
|
|
|
requestId: string,
|
|
|
|
|
|
options: { attemptedOnly?: boolean } = {},
|
|
|
|
|
|
): Promise<RequestTrace> {
|
2026-03-07 02:30:48 +08:00
|
|
|
|
const attemptedOnly = options.attemptedOnly ?? false
|
2026-03-05 17:32:21 +08:00
|
|
|
|
const response = await apiClient.get<RequestTrace>(`/api/admin/monitoring/trace/${requestId}`, {
|
|
|
|
|
|
params: { attempted_only: attemptedOnly },
|
|
|
|
|
|
})
|
2025-12-10 20:52:44 +08:00
|
|
|
|
return response.data
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取某个 Provider 的失败率统计
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getProviderStats(providerId: string, limit: number = 100): Promise<ProviderStats> {
|
|
|
|
|
|
const response = await apiClient.get<ProviderStats>(`/api/admin/monitoring/trace/stats/provider/${providerId}`, {
|
|
|
|
|
|
params: { limit }
|
|
|
|
|
|
})
|
|
|
|
|
|
return response.data
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|