using MS.Microservice.AI.Abstractions; namespace MS.Microservice.AI.Core; /// 统一 Chat/Media 的每次尝试超时、瞬时失败重试和取消分类,不承担各能力的响应解析。 internal static class AIHttpExecution { public static async Task ExecuteAsync(string provider, AICapability capability, AIResolvedModel model, string? requestId, TimeProvider clock, Func> send, CancellationToken cancellationToken) { for (var attempt = 0; ; attempt++) { cancellationToken.ThrowIfCancellationRequested(); using var timeout = new CancellationTokenSource(model.Timeout, clock); using var linked = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeout.Token); Exception failure; TimeSpan? retryAfter = null; try { return await send(linked.Token).ConfigureAwait(false); } // 调用方主动结束请求不代表服务不可用,必须先于超时分类处理,且不能重试。 catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (OperationCanceledException exception) { failure = new AITimeoutException($"AI provider '{provider}' timed out.", capability, provider: provider, model: model.Model, scenario: model.Scenario, requestId: requestId, innerException: exception); } catch (HttpRequestException exception) { failure = new AIProviderException($"AI provider '{provider}' is unavailable.", AIErrorCodes.ProviderUnavailable, capability, provider: provider, model: model.Model, scenario: model.Scenario, requestId: requestId, isTransient: true, innerException: exception); } catch (AIProviderException exception) when (exception.IsTransient) { failure = exception; retryAfter = exception.RetryAfter; } if (attempt >= model.MaxRetryAttempts) System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(failure).Throw(); var delay = retryAfter ?? TimeSpan.FromMilliseconds(Math.Min(500 * Math.Pow(2, attempt), 2000)); if (delay > TimeSpan.Zero) await Task.Delay(delay, clock, cancellationToken).ConfigureAwait(false); } } }