refactor(gateway): 重构 ai pipeline 规划链路

This commit is contained in:
fawney19
2026-04-14 01:27:04 +08:00
parent 37bb120d20
commit 53acfbabf6
74 changed files with 7357 additions and 4380 deletions
@@ -1,6 +1,12 @@
import { describe, expect, it } from 'vitest'
import { isUsageRecordFailed, isUsageRecordSuccessful } from '../status'
import {
isUsageRecordFailed,
isUsageRecordSuccessful,
mapRequestStatusToTimelineStatus,
normalizeRequestStatus,
resolveTimelineFinalStatus,
} from '../status'
import type { UsageRecord } from '../../types'
function buildUsageRecord(overrides: Partial<UsageRecord> = {}): UsageRecord {
@@ -40,4 +46,44 @@ describe('usage status helpers', () => {
expect(isUsageRecordFailed(record)).toBe(true)
expect(isUsageRecordSuccessful(record)).toBe(false)
})
it('treats explicit failed status with a 2xx status code as successful for display', () => {
const record = buildUsageRecord({
status: 'failed',
status_code: 200,
error_message: 'stale failure flag'
})
expect(isUsageRecordFailed(record)).toBe(false)
expect(isUsageRecordSuccessful(record)).toBe(true)
})
it('normalizes request status strings before mapping timeline status', () => {
expect(normalizeRequestStatus(' Completed ')).toBe('completed')
expect(mapRequestStatusToTimelineStatus('completed')).toBe('success')
expect(mapRequestStatusToTimelineStatus('failed')).toBe('failed')
})
it('treats explicit success status code as authoritative for the timeline', () => {
expect(resolveTimelineFinalStatus({
traceFinalStatus: 'success',
requestStatus: 'failed',
statusCode: 200,
})).toBe('success')
})
it('falls back to request lifecycle status when status code and trace are missing', () => {
expect(resolveTimelineFinalStatus({
requestStatus: 'failed',
})).toBe('failed')
})
it('uses status code only as a last fallback for timeline status', () => {
expect(resolveTimelineFinalStatus({
statusCode: 200,
})).toBe('success')
expect(resolveTimelineFinalStatus({
statusCode: 503,
})).toBe('failed')
})
})
+111 -2
View File
@@ -1,4 +1,8 @@
import type { UsageRecord } from '../types'
import type { RequestStatus, UsageRecord } from '../types'
export type TimelineFinalStatus = 'success' | 'failed' | 'streaming' | 'pending' | 'cancelled'
type RequestStatusLike = RequestStatus | string | null | undefined
function hasLegacyFailureSignal(
record: Pick<UsageRecord, 'status_code' | 'error_message'>
@@ -7,10 +11,32 @@ function hasLegacyFailureSignal(
(typeof record.error_message === 'string' && record.error_message.trim().length > 0)
}
function hasTerminalSuccessStatusCode(
record: Pick<UsageRecord, 'status_code'>
): boolean {
return typeof record.status_code === 'number' &&
record.status_code >= 200 &&
record.status_code < 400
}
export function isUsageRecordFailed(
record: Pick<UsageRecord, 'status' | 'status_code' | 'error_message'>
): boolean {
const status = typeof record.status === 'string' ? record.status.trim().toLowerCase() : ''
if (status) {
if (status === 'pending' || status === 'streaming' || status === 'cancelled') {
return false
}
if (status === 'completed') {
return false
}
if (status === 'failed') {
return !hasTerminalSuccessStatusCode(record)
}
}
if (hasTerminalSuccessStatusCode(record)) {
return false
}
if (status) {
return status === 'failed'
}
@@ -22,7 +48,90 @@ export function isUsageRecordSuccessful(
): boolean {
const status = typeof record.status === 'string' ? record.status.trim().toLowerCase() : ''
if (status) {
return status === 'completed'
if (status === 'completed') {
return true
}
if (status === 'failed') {
return hasTerminalSuccessStatusCode(record)
}
return false
}
if (hasTerminalSuccessStatusCode(record)) {
return true
}
return !hasLegacyFailureSignal(record)
}
export function normalizeRequestStatus(status: RequestStatusLike): RequestStatus | undefined {
const normalized = typeof status === 'string' ? status.trim().toLowerCase() : ''
switch (normalized) {
case 'pending':
case 'streaming':
case 'completed':
case 'failed':
case 'cancelled':
return normalized
default:
return undefined
}
}
export function mapRequestStatusToTimelineStatus(
status: RequestStatusLike
): TimelineFinalStatus | undefined {
switch (normalizeRequestStatus(status)) {
case 'completed':
return 'success'
case 'failed':
return 'failed'
case 'streaming':
return 'streaming'
case 'pending':
return 'pending'
case 'cancelled':
return 'cancelled'
default:
return undefined
}
}
function normalizeTimelineFinalStatus(status: string | null | undefined): TimelineFinalStatus | undefined {
const normalized = typeof status === 'string' ? status.trim().toLowerCase() : ''
switch (normalized) {
case 'success':
case 'failed':
case 'streaming':
case 'pending':
case 'cancelled':
return normalized
default:
return undefined
}
}
export function resolveTimelineFinalStatus(params: {
hasPendingCandidates?: boolean
traceFinalStatus?: string | null
requestStatus?: RequestStatusLike
statusCode?: number
}): TimelineFinalStatus {
if (params.hasPendingCandidates) {
return 'pending'
}
if (typeof params.statusCode === 'number') {
return params.statusCode >= 200 && params.statusCode < 400 ? 'success' : 'failed'
}
const traceStatus = normalizeTimelineFinalStatus(params.traceFinalStatus)
if (traceStatus) {
return traceStatus
}
const requestStatus = mapRequestStatusToTimelineStatus(params.requestStatus)
if (requestStatus) {
return requestStatus
}
return 'pending'
}