fix(runner): record failed task attempts
This commit is contained in:
@@ -971,28 +971,33 @@ function TaskAttemptPopoverContent(props: { task: GatewayTask }) {
|
||||
const attempts = props.task.attempts ?? [];
|
||||
return (
|
||||
<span className="taskRecordAttemptPopover" role="tooltip">
|
||||
{attempts.map((attempt) => (
|
||||
<span
|
||||
key={attempt.id || `${props.task.id}-${attempt.attemptNo}`}
|
||||
className={`taskRecordAttemptDetail ${attempt.status === 'failed' ? 'failed' : attempt.status === 'succeeded' ? 'succeeded' : ''}`}
|
||||
>
|
||||
<span className="taskRecordAttemptDetailHeader">
|
||||
<strong>#{attempt.attemptNo} {taskAttemptTarget(attempt)}</strong>
|
||||
<Badge variant={attempt.status === 'succeeded' ? 'success' : attempt.status === 'failed' ? 'destructive' : 'secondary'}>{taskAttemptStatusText(attempt.status)}</Badge>
|
||||
</span>
|
||||
<small>{taskAttemptMeta(attempt)}</small>
|
||||
{attempt.status === 'failed' && <span className="taskRecordAttemptError">{taskAttemptFailureReason(attempt)}</span>}
|
||||
{taskAttemptTrace(attempt).length > 0 && (
|
||||
<span className="taskRecordAttemptTrace">
|
||||
{taskAttemptTrace(attempt).map((entry, index) => (
|
||||
<span key={`${attempt.id || attempt.attemptNo}-trace-${index}`} className="taskRecordAttemptTraceItem">
|
||||
{taskAttemptTraceText(entry)}
|
||||
</span>
|
||||
))}
|
||||
{attempts.map((attempt) => {
|
||||
const trace = taskAttemptTrace(attempt);
|
||||
const rateLimitText = taskAttemptRateLimitText(attempt);
|
||||
return (
|
||||
<span
|
||||
key={attempt.id || `${props.task.id}-${attempt.attemptNo}`}
|
||||
className={`taskRecordAttemptDetail ${attempt.status === 'failed' ? 'failed' : attempt.status === 'succeeded' ? 'succeeded' : ''}`}
|
||||
>
|
||||
<span className="taskRecordAttemptDetailHeader">
|
||||
<strong>#{attempt.attemptNo} {taskAttemptTarget(attempt)}</strong>
|
||||
<Badge variant={attempt.status === 'succeeded' ? 'success' : attempt.status === 'failed' ? 'destructive' : 'secondary'}>{taskAttemptStatusText(attempt.status)}</Badge>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
<small>{taskAttemptMeta(attempt)}</small>
|
||||
{attempt.status === 'failed' && <span className="taskRecordAttemptError">{taskAttemptFailureReason(attempt)}</span>}
|
||||
{(rateLimitText || trace.length > 0) && (
|
||||
<span className="taskRecordAttemptTrace">
|
||||
{rateLimitText && <span className="taskRecordAttemptTraceItem">{rateLimitText}</span>}
|
||||
{trace.map((entry, index) => (
|
||||
<span key={`${attempt.id || attempt.attemptNo}-trace-${index}`} className="taskRecordAttemptTraceItem">
|
||||
{taskAttemptTraceText(entry)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1055,6 +1060,29 @@ function taskAttemptTrace(attempt: NonNullable<GatewayTask['attempts']>[number])
|
||||
return raw.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === 'object' && !Array.isArray(item));
|
||||
}
|
||||
|
||||
function taskAttemptRateLimitText(attempt: NonNullable<GatewayTask['attempts']>[number]) {
|
||||
const detail = metadataObject(attempt.metrics, 'rateLimit');
|
||||
if (!Object.keys(detail).length) return '';
|
||||
const scopeName = objectString(detail, 'scopeName') || objectString(detail, 'scopeKey') || '限流对象';
|
||||
const metric = objectString(detail, 'metric') || 'rate_limit';
|
||||
const current = metadataNumber(detail, 'current');
|
||||
const amount = metadataNumber(detail, 'amount');
|
||||
const projected = metadataNumber(detail, 'projected');
|
||||
const limit = metadataNumber(detail, 'limit');
|
||||
const windowSeconds = metadataNumber(detail, 'windowSeconds');
|
||||
const retryAfterMs = metadataNumber(detail, 'retryAfterMs');
|
||||
const values = [
|
||||
`限流 ${scopeName} · ${metric}`,
|
||||
current !== null ? `当前 ${formatCellValue(current)}` : '',
|
||||
amount !== null ? `本次 ${formatCellValue(amount)}` : '',
|
||||
projected !== null ? `预计 ${formatCellValue(projected)}` : '',
|
||||
limit !== null ? `限制 ${formatCellValue(limit)}` : '',
|
||||
windowSeconds !== null ? `窗口 ${Math.trunc(windowSeconds)} 秒` : '',
|
||||
retryAfterMs !== null ? `约 ${formatDuration(Math.trunc(retryAfterMs))} 后可重试` : '',
|
||||
].filter(Boolean);
|
||||
return values.join(' · ');
|
||||
}
|
||||
|
||||
function taskAttemptTraceText(entry: Record<string, unknown>) {
|
||||
const event = objectString(entry, 'event');
|
||||
const action = objectString(entry, 'action');
|
||||
@@ -1116,6 +1144,12 @@ function taskAttemptTraceReasonLabel(reason: string) {
|
||||
client_retryable: '客户端标记可重试',
|
||||
client_non_retryable: '客户端标记不可重试',
|
||||
same_client_max_attempts: '达到本平台最大尝试次数',
|
||||
request_validation_failed: '请求校验失败',
|
||||
candidate_selection_failed: '候选模型选择失败',
|
||||
parameter_preprocessing_failed: '参数预处理失败',
|
||||
wallet_balance_check_failed: '余额校验失败',
|
||||
local_rate_limit_blocked: '本地限流拦截',
|
||||
pre_provider_failed: '调用上游前失败',
|
||||
local_rate_limit_wait_queue: '本地限流排队等待',
|
||||
failover_time_budget_exceeded: '超过全局切换时间预算',
|
||||
runner_policy_disabled: '全局调度策略停用',
|
||||
|
||||
Reference in New Issue
Block a user