feat: sync deployed v1.0.0-rc.38-custom.42
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
c2b44332d6
commit
a5f7b8e586
828 files changed
+122253
-13702
No files matched your search
+190
-198
@@ -25,12 +25,8 @@ import (
|
||||
"github.com/QuantumNous/new-api/relaykit/dto"
|
||||
"github.com/QuantumNous/new-api/relaykit/types"
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
"github.com/QuantumNous/new-api/setting"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
"github.com/bytedance/gopkg/util/gopool"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
@@ -77,6 +73,13 @@ func publicUpstreamErrorContent(statusCode int) string {
|
||||
}
|
||||
|
||||
func clientFacingOpenAIError(err *types.NewAPIError, upstreamFailure bool, requestId string) types.OpenAIError {
|
||||
if err.GetErrorCode() == relaycommon.ResponseGuardErrorCode {
|
||||
return types.OpenAIError{
|
||||
Message: common.MessageWithRequestId(err.Error(), requestId),
|
||||
Type: "api_error",
|
||||
Code: relaycommon.ResponseGuardErrorCode,
|
||||
}
|
||||
}
|
||||
if upstreamFailure {
|
||||
return types.OpenAIError{
|
||||
Message: common.MessageWithRequestId(publicUpstreamErrorMessage, requestId),
|
||||
@@ -122,7 +125,11 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
|
||||
defer func() {
|
||||
if newAPIError != nil {
|
||||
service.RecordRequestPolicyTermination(c, newAPIError)
|
||||
logger.LogError(c, fmt.Sprintf("relay error: %s", common.LocalLogPreview(newAPIError.Error())))
|
||||
if c.GetBool("response_guard_error_sent") {
|
||||
return
|
||||
}
|
||||
switch relayFormat {
|
||||
case types.RelayFormatOpenAIRealtime:
|
||||
helper.WssError(c, ws, clientFacingOpenAIError(newAPIError, upstreamFailure, requestId))
|
||||
@@ -156,59 +163,25 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
return
|
||||
}
|
||||
|
||||
needSensitiveCheck := setting.ShouldCheckPromptSensitive()
|
||||
needCountToken := constant.CountToken
|
||||
// Avoid building huge CombineText (strings.Join) when token counting and sensitive check are both disabled.
|
||||
var meta *types.TokenCountMeta
|
||||
if needSensitiveCheck || needCountToken {
|
||||
meta = request.GetTokenCountMeta()
|
||||
} else {
|
||||
meta = fastTokenCountMetaForPricing(request)
|
||||
}
|
||||
|
||||
if needSensitiveCheck && meta != nil {
|
||||
contains, words := service.CheckSensitiveText(meta.CombineText)
|
||||
if contains {
|
||||
logger.LogWarn(c, fmt.Sprintf("user sensitive words detected: %s", strings.Join(words, ", ")))
|
||||
newAPIError = types.NewError(err, types.ErrorCodeSensitiveWordsDetected)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tokens, err := service.EstimateRequestToken(c, meta, relayInfo)
|
||||
if err != nil {
|
||||
newAPIError = types.NewError(err, types.ErrorCodeCountTokenFailed)
|
||||
return
|
||||
}
|
||||
|
||||
relayInfo.SetEstimatePromptTokens(tokens)
|
||||
|
||||
priceData, err := helper.ModelPriceHelper(c, relayInfo, tokens, meta)
|
||||
if err != nil {
|
||||
newAPIError = types.NewError(err, types.ErrorCodeModelPriceError, types.ErrOptionWithStatusCode(http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
|
||||
// common.SetContextKey(c, constant.ContextKeyTokenCountMeta, meta)
|
||||
|
||||
if priceData.FreeModel {
|
||||
logger.LogInfo(c, fmt.Sprintf("模型 %s 免费,跳过预扣费", relayInfo.OriginModelName))
|
||||
} else {
|
||||
newAPIError = service.PreConsumeBilling(c, priceData.QuotaToPreConsume, relayInfo)
|
||||
if newAPIError != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Only return quota if downstream failed and quota was actually pre-consumed
|
||||
if newAPIError != nil {
|
||||
newAPIError = service.NormalizeViolationFeeError(newAPIError)
|
||||
if relayInfo.Billing != nil {
|
||||
relayInfo.Billing.Refund(c)
|
||||
}
|
||||
service.ChargeViolationFeeIfNeeded(c, relayInfo, newAPIError)
|
||||
recovered := recover()
|
||||
resultErr := newAPIError
|
||||
if recovered != nil {
|
||||
resultErr = types.NewError(fmt.Errorf("relay panic: %v", recovered), types.ErrorCodeBadResponse)
|
||||
}
|
||||
if relayFormat != types.RelayFormatOpenAIRealtime {
|
||||
perfmetrics.RecordRelayResult(c.Request.Context(), relayInfo, resultErr)
|
||||
}
|
||||
if recovered != nil {
|
||||
panic(recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
if newAPIError = relay.PrepareRequestBilling(c, relayInfo); newAPIError != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
newAPIError = relay.RefundFailedRequestBilling(c, relayInfo, newAPIError)
|
||||
}()
|
||||
|
||||
retryParam := &service.RetryParam{
|
||||
@@ -220,8 +193,30 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
}
|
||||
relayInfo.RetryIndex = 0
|
||||
relayInfo.LastError = nil
|
||||
maxRetries := common.RetryTimes
|
||||
ordinaryRetries := 0
|
||||
if relayInfo.DurableAttemptBilling {
|
||||
maxRetries = 2
|
||||
timeout := constant.StreamingTimeout
|
||||
if timeout <= 0 {
|
||||
timeout = 300
|
||||
}
|
||||
relayInfo.GuardDeadline = time.Now().Add(time.Duration(timeout) * time.Second)
|
||||
defer func() { service.EndFailedRelayAttempt(c, relayInfo) }()
|
||||
}
|
||||
|
||||
for ; retryParam.GetRetry() <= common.RetryTimes; retryParam.IncreaseRetry() {
|
||||
for ; retryParam.GetRetry() <= maxRetries; retryParam.IncreaseRetry() {
|
||||
if relayInfo.DurableAttemptBilling && time.Now().After(relayInfo.GuardDeadline) {
|
||||
newAPIError = types.NewErrorWithStatusCode(errors.New("response model confirmation budget exhausted"), relaycommon.ResponseGuardErrorCode, http.StatusGatewayTimeout, types.ErrOptionWithSkipRetry())
|
||||
break
|
||||
}
|
||||
if relayInfo.ResponseGuard != nil && relayInfo.UpstreamRequests >= relaycommon.ResponseGuardMaxAttempts {
|
||||
newAPIError = types.NewErrorWithStatusCode(errors.New("response model attempt budget exhausted"), relaycommon.ResponseGuardErrorCode, http.StatusBadGateway, types.ErrOptionWithSkipRetry())
|
||||
break
|
||||
}
|
||||
relayInfo.StreamStatus = nil
|
||||
relayInfo.PerformanceBusinessRejection = false
|
||||
relayInfo.PerformanceOutputTokens = 0
|
||||
relayInfo.RetryIndex = retryParam.GetRetry()
|
||||
channel, channelErr := getChannel(c, relayInfo, retryParam)
|
||||
if channelErr != nil {
|
||||
@@ -229,11 +224,15 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
newAPIError = channelErr
|
||||
break
|
||||
}
|
||||
addUsedChannel(c, channel.Id)
|
||||
service.AppendUsedChannel(c, channel.Id)
|
||||
if billingErr := service.PrepareTieredBillingForSelectedGroup(c, relayInfo); billingErr != nil {
|
||||
newAPIError = billingErr
|
||||
break
|
||||
}
|
||||
if billingErr := service.BeginRelayAttempt(c, relayInfo); billingErr != nil {
|
||||
newAPIError = billingErr
|
||||
break
|
||||
}
|
||||
|
||||
bodyStorage, bodyErr := common.GetBodyStorage(c)
|
||||
if bodyErr != nil {
|
||||
@@ -259,19 +258,40 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
}
|
||||
|
||||
if newAPIError == nil {
|
||||
service.MarkRequestPolicySuccess(c, relayInfo.StreamStatus)
|
||||
relayInfo.LastError = nil
|
||||
service.RecordGroupOutcome(currentRequestGroup(c, channel), true)
|
||||
return
|
||||
}
|
||||
upstreamFailure = true
|
||||
if isGroupAvailabilityFailure(newAPIError) {
|
||||
service.RecordGroupOutcome(currentRequestGroup(c, channel), false)
|
||||
}
|
||||
|
||||
newAPIError = service.NormalizeViolationFeeError(newAPIError)
|
||||
relayInfo.LastError = newAPIError
|
||||
|
||||
service.EndFailedRelayAttempt(c, relayInfo)
|
||||
remaining := common.RetryTimes - retryParam.GetRetry()
|
||||
if relayInfo.DurableAttemptBilling {
|
||||
remaining = common.RetryTimes - ordinaryRetries
|
||||
}
|
||||
decision := service.DecideRelayRetry(c, newAPIError, remaining)
|
||||
if canSwitchRejectedAstra(c, relayInfo, newAPIError) {
|
||||
decision = service.PolicyDecision{Action: "retry", Reason: "model_mismatch_switch", Source: "model_guard"}
|
||||
service.ClearPoolAffinity(c)
|
||||
} else if decision.Action == "retry" {
|
||||
ordinaryRetries++
|
||||
}
|
||||
service.RecordPolicyFailure(c, channel.Id, newAPIError, decision)
|
||||
processChannelError(c, *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, common.GetContextKeyString(c, constant.ContextKeyChannelKey), channel.GetAutoBan()), newAPIError, relayInfo)
|
||||
|
||||
if !shouldRetry(c, newAPIError, common.RetryTimes-retryParam.GetRetry()) {
|
||||
if decision.Action != "retry" {
|
||||
break
|
||||
}
|
||||
if service.RequestRoutingPolicy(c) == nil {
|
||||
service.MarkRequestGroupFailed(c, currentRequestGroup(c, channel))
|
||||
}
|
||||
}
|
||||
|
||||
useChannel := c.GetStringSlice("use_channel")
|
||||
@@ -279,11 +299,24 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
retryLogStr := fmt.Sprintf("重试:%s", strings.Trim(strings.Join(strings.Fields(fmt.Sprint(useChannel)), "->"), "[]"))
|
||||
logger.LogInfo(c, retryLogStr)
|
||||
}
|
||||
if newAPIError != nil {
|
||||
gopool.Go(func() {
|
||||
perfmetrics.RecordRelaySample(relayInfo, false, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func canSwitchRejectedAstra(c *gin.Context, info *relaycommon.RelayInfo, err *types.NewAPIError) bool {
|
||||
if !info.AstraAutoSwitch || info.ResponseGuard == nil {
|
||||
return false
|
||||
}
|
||||
if !info.DurableAttemptBilling || info.UpstreamRequests >= 3 || err.GetErrorCode() != relaycommon.ResponseGuardErrorCode || c.Writer.Written() || c.Request.Context().Err() != nil || service.RequestRoutingPolicy(c) == nil {
|
||||
return false
|
||||
}
|
||||
if _, pinned, _ := service.GetChannelConstraints(c).ResolvedPin(); pinned {
|
||||
return false
|
||||
}
|
||||
attempt := info.ResponseGuard.Last()
|
||||
if attempt == nil || attempt.Reason != "model_mismatch" {
|
||||
return false
|
||||
}
|
||||
request, ok := info.Request.(*dto.OpenAIResponsesRequest)
|
||||
return ok && request.PreviousResponseID == "" && (len(request.Conversation) == 0 || string(request.Conversation) == "null")
|
||||
}
|
||||
|
||||
// CountClaudeTokens implements Anthropic's token-counting utility endpoint.
|
||||
@@ -319,47 +352,12 @@ func CountClaudeTokens(c *gin.Context) {
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
Subprotocols: []string{"realtime"}, // WS 握手支持的协议,如果有使用 Sec-WebSocket-Protocol,则必须在此声明对应的 Protocol TODO add other protocol
|
||||
Subprotocols: []string{"realtime", "responses"}, // WS 握手支持的协议,如果有使用 Sec-WebSocket-Protocol,则必须在此声明对应的 Protocol
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true // 允许跨域
|
||||
},
|
||||
}
|
||||
|
||||
func addUsedChannel(c *gin.Context, channelId int) {
|
||||
useChannel := c.GetStringSlice("use_channel")
|
||||
useChannel = append(useChannel, fmt.Sprintf("%d", channelId))
|
||||
c.Set("use_channel", useChannel)
|
||||
}
|
||||
|
||||
func fastTokenCountMetaForPricing(request dto.Request) *types.TokenCountMeta {
|
||||
if request == nil {
|
||||
return &types.TokenCountMeta{}
|
||||
}
|
||||
meta := &types.TokenCountMeta{
|
||||
TokenType: types.TokenTypeTokenizer,
|
||||
}
|
||||
switch r := request.(type) {
|
||||
case *dto.GeneralOpenAIRequest:
|
||||
maxCompletionTokens := lo.FromPtrOr(r.MaxCompletionTokens, uint(0))
|
||||
maxTokens := lo.FromPtrOr(r.MaxTokens, uint(0))
|
||||
if maxCompletionTokens > maxTokens {
|
||||
meta.MaxTokens = int(maxCompletionTokens)
|
||||
} else {
|
||||
meta.MaxTokens = int(maxTokens)
|
||||
}
|
||||
case *dto.OpenAIResponsesRequest:
|
||||
meta.MaxTokens = int(lo.FromPtrOr(r.MaxOutputTokens, uint(0)))
|
||||
case *dto.ClaudeRequest:
|
||||
meta.MaxTokens = int(lo.FromPtr(r.MaxTokens))
|
||||
case *dto.ImageRequest:
|
||||
// Pricing for image requests depends on ImagePriceRatio; safe to compute even when CountToken is disabled.
|
||||
return r.GetTokenCountMeta()
|
||||
default:
|
||||
// Best-effort: leave CombineText empty to avoid large allocations.
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
func getChannel(c *gin.Context, info *relaycommon.RelayInfo, retryParam *service.RetryParam) (*model.Channel, *types.NewAPIError) {
|
||||
if info.ChannelMeta == nil {
|
||||
autoBan := c.GetBool("auto_ban")
|
||||
@@ -367,12 +365,14 @@ func getChannel(c *gin.Context, info *relaycommon.RelayInfo, retryParam *service
|
||||
if !autoBan {
|
||||
autoBanInt = 0
|
||||
}
|
||||
return &model.Channel{
|
||||
channel := &model.Channel{
|
||||
Id: c.GetInt("channel_id"),
|
||||
Type: c.GetInt("channel_type"),
|
||||
Name: c.GetString("channel_name"),
|
||||
AutoBan: &autoBanInt,
|
||||
}, nil
|
||||
}
|
||||
service.RequestPolicy(c).BeginAttempt(channel, info.UsingGroup)
|
||||
return channel, nil
|
||||
}
|
||||
channel, selectGroup, err := service.CacheGetRandomSatisfiedChannel(retryParam)
|
||||
if err != nil {
|
||||
@@ -384,6 +384,7 @@ func getChannel(c *gin.Context, info *relaycommon.RelayInfo, retryParam *service
|
||||
|
||||
info.PriceData.GroupRatioInfo = helper.HandleGroupRatio(c, info)
|
||||
|
||||
service.RequestPolicy(c).BeginAttempt(channel, selectGroup)
|
||||
newAPIError := middleware.SetupContextForSelectedChannel(c, channel, info.OriginModelName)
|
||||
if newAPIError != nil {
|
||||
return nil, newAPIError
|
||||
@@ -391,76 +392,45 @@ func getChannel(c *gin.Context, info *relaycommon.RelayInfo, retryParam *service
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func shouldRetry(c *gin.Context, openaiErr *types.NewAPIError, retryTimes int) bool {
|
||||
if openaiErr == nil {
|
||||
// currentRequestGroup returns the group that handled the current attempt,
|
||||
// falling back to the channel's own group when the selection did not record one.
|
||||
func currentRequestGroup(c *gin.Context, channel *model.Channel) string {
|
||||
if group := common.GetContextKeyString(c, constant.ContextKeyAutoGroup); group != "" {
|
||||
return group
|
||||
}
|
||||
if channel != nil {
|
||||
return channel.Group
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isGroupAvailabilityFailure reports whether an error says something about the
|
||||
// selected group's health rather than about the client request itself.
|
||||
func isGroupAvailabilityFailure(err *types.NewAPIError) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if service.ShouldSkipRetryAfterChannelAffinityFailure(c) {
|
||||
return false
|
||||
}
|
||||
if types.IsChannelError(openaiErr) {
|
||||
if types.IsChannelError(err) {
|
||||
return true
|
||||
}
|
||||
if types.IsSkipRetryError(openaiErr) {
|
||||
return false
|
||||
}
|
||||
if retryTimes <= 0 {
|
||||
return false
|
||||
}
|
||||
if service.GetChannelConstraints(c).SuppressesRetry() {
|
||||
return false
|
||||
}
|
||||
code := openaiErr.StatusCode
|
||||
if code >= 200 && code < 300 {
|
||||
return false
|
||||
}
|
||||
if code < 100 || code > 599 {
|
||||
return true
|
||||
}
|
||||
if operation_setting.IsAlwaysSkipRetryCode(openaiErr.GetErrorCode()) {
|
||||
return false
|
||||
}
|
||||
return operation_setting.ShouldRetryByStatusCode(code)
|
||||
return err.StatusCode >= 500
|
||||
}
|
||||
|
||||
func shouldRetry(c *gin.Context, err *types.NewAPIError, retryTimes int) bool {
|
||||
return service.ShouldRetryRelayError(c, err, retryTimes)
|
||||
}
|
||||
|
||||
func processChannelError(c *gin.Context, channelError types.ChannelError, err *types.NewAPIError, relayInfo *relaycommon.RelayInfo) {
|
||||
logger.LogError(c, fmt.Sprintf("channel error (channel #%d, status code: %d): %s", channelError.ChannelId, err.StatusCode, common.LocalLogPreview(err.Error())))
|
||||
// 不要使用context获取渠道信息,异步处理时可能会出现渠道信息不一致的情况
|
||||
// do not use context to get channel info, there may be inconsistent channel info when processing asynchronously
|
||||
if service.ShouldDisableChannel(err) && channelError.AutoBan {
|
||||
gopool.Go(func() {
|
||||
service.DisableChannel(channelError, err.ErrorWithStatusCode())
|
||||
})
|
||||
}
|
||||
|
||||
if constant.ErrorLogEnabled && types.IsRecordErrorLog(err) {
|
||||
// 保存错误日志到mysql中
|
||||
userId := c.GetInt("id")
|
||||
tokenName := c.GetString("token_name")
|
||||
modelName := c.GetString("original_model")
|
||||
tokenId := c.GetInt("token_id")
|
||||
userGroup := c.GetString("group")
|
||||
other := model.NewLogOther()
|
||||
if c.Request != nil && c.Request.URL != nil {
|
||||
other.SetPublic("request_path", c.Request.URL.Path)
|
||||
}
|
||||
other.SetPublic("error_type", err.GetErrorType())
|
||||
other.SetPublic("error_code", err.GetErrorCode())
|
||||
other.SetPublic("status_code", err.StatusCode)
|
||||
service.AppendRelayLogAdminInfo(c, relayInfo, other)
|
||||
service.AppendTaskPluginContextAuditInfo(c, other)
|
||||
startTime := common.GetContextKeyTime(c, constant.ContextKeyRequestStartTime)
|
||||
if startTime.IsZero() {
|
||||
startTime = time.Now()
|
||||
}
|
||||
useTimeSeconds := int(time.Since(startTime).Seconds())
|
||||
other.SetRoot("upstream_error", err.MaskSensitiveErrorWithStatusCode())
|
||||
model.RecordErrorLog(c, userId, channelError.ChannelId, modelName, tokenName, publicUpstreamErrorContent(err.StatusCode), tokenId, useTimeSeconds, common.GetContextKeyBool(c, constant.ContextKeyIsStream), userGroup, other)
|
||||
}
|
||||
|
||||
service.ProcessChannelError(c, channelError, err, relayInfo)
|
||||
}
|
||||
|
||||
func RelayMidjourney(c *gin.Context) {
|
||||
policy := service.RequestPolicy(c)
|
||||
defer func() {
|
||||
if policy.Attempts > 0 && !policy.Successful {
|
||||
service.RecordRequestPolicyTermination(c, types.NewErrorWithStatusCode(errors.New("Midjourney submission failed"), types.ErrorCodeBadResponseStatusCode, http.StatusBadGateway, types.ErrOptionWithSkipRetry()))
|
||||
}
|
||||
}()
|
||||
relayInfo, err := relaycommon.GenRelayInfo(c, types.RelayFormatMjProxy, nil, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -488,6 +458,7 @@ func RelayMidjourney(c *gin.Context) {
|
||||
//err = relayMidjourneySubmit(c, relayMode)
|
||||
log.Println(mjErr)
|
||||
if mjErr != nil {
|
||||
policy.Successful = false
|
||||
statusCode := http.StatusBadRequest
|
||||
if mjErr.Code == 30 {
|
||||
mjErr.Result = "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。"
|
||||
@@ -627,6 +598,7 @@ func executeTaskSubmissionWith(
|
||||
relayInfo *relaycommon.RelayInfo,
|
||||
submit taskSubmitAttempt,
|
||||
) (*taskSubmissionOutcome, *taskdto.TaskError) {
|
||||
policy := service.RequestPolicy(c)
|
||||
diagnostics := newTaskPluginSubmitDiagnostics(c)
|
||||
diagnostics.start(relayInfo)
|
||||
var result *relay.TaskSubmitResult
|
||||
@@ -664,6 +636,7 @@ func executeTaskSubmissionWith(
|
||||
|
||||
if lockedCh, ok := relayInfo.LockedChannel.(*model.Channel); ok && lockedCh != nil {
|
||||
channel = lockedCh
|
||||
policy.BeginAttempt(channel, relayInfo.UsingGroup)
|
||||
if retryParam.GetRetry() > 0 {
|
||||
if setupErr := middleware.SetupContextForSelectedChannel(c, channel, relayInfo.OriginModelName); setupErr != nil {
|
||||
taskErr = service.TaskErrorWrapperLocal(setupErr.Err, "setup_locked_channel_failed", http.StatusInternalServerError)
|
||||
@@ -675,13 +648,13 @@ func executeTaskSubmissionWith(
|
||||
channel, channelErr = getChannel(c, relayInfo, retryParam)
|
||||
if channelErr != nil {
|
||||
logger.LogError(c, channelErr.Error())
|
||||
taskErr = service.TaskErrorWrapperLocal(channelErr.Err, "get_channel_failed", http.StatusInternalServerError)
|
||||
taskErr = service.TaskErrorWrapperLocal(channelErr.Err, "get_channel_failed", channelErr.StatusCode)
|
||||
break
|
||||
}
|
||||
}
|
||||
diagnostics.attempt(retryParam.GetRetry()+1, channel, relayInfo.LockedChannel != nil)
|
||||
|
||||
addUsedChannel(c, channel.Id)
|
||||
service.AppendUsedChannel(c, channel.Id)
|
||||
bodyStorage, bodyErr := common.GetBodyStorage(c)
|
||||
if bodyErr != nil {
|
||||
stage = "read_body"
|
||||
@@ -706,15 +679,19 @@ func executeTaskSubmissionWith(
|
||||
break
|
||||
}
|
||||
|
||||
taskAPIError := taskSubmissionAPIError(taskErr)
|
||||
relayInfo.LastError = taskAPIError
|
||||
decision := decideTaskRetry(c, taskErr, common.RetryTimes-retryParam.GetRetry())
|
||||
service.RecordPolicyFailure(c, channel.Id, taskAPIError, decision)
|
||||
if !taskErr.LocalError {
|
||||
processChannelError(c,
|
||||
*types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey,
|
||||
common.GetContextKeyString(c, constant.ContextKeyChannelKey), channel.GetAutoBan()),
|
||||
types.NewOpenAIError(taskErr.Error, types.ErrorCodeBadResponseStatusCode, taskErr.StatusCode),
|
||||
taskAPIError,
|
||||
relayInfo)
|
||||
}
|
||||
|
||||
willRetry := shouldRetryTaskRelay(c, channel.Id, taskErr, common.RetryTimes-retryParam.GetRetry())
|
||||
willRetry := decision.Action == "retry"
|
||||
diagnostics.attemptFailed(retryParam.GetRetry()+1, channel, taskErr, willRetry)
|
||||
if !willRetry {
|
||||
break
|
||||
@@ -816,6 +793,11 @@ func executeTaskSubmissionWith(
|
||||
diagnostics.failed("settle", "billing_error", taskErr, true)
|
||||
return nil, taskErr
|
||||
}
|
||||
if task.Status != model.TaskStatusFailure {
|
||||
service.MarkRequestPolicySuccess(c, nil)
|
||||
} else {
|
||||
policy.AddEvent(service.PolicyEvent{Decision: service.PolicyDecision{Action: "stop", Reason: "task_failed", Source: "upstream"}})
|
||||
}
|
||||
service.LogTaskConsumption(c, relayInfo, task)
|
||||
diagnostics.complete(task, result.Quota)
|
||||
|
||||
@@ -876,6 +858,7 @@ func presentTaskSubmission(c *gin.Context, outcome *taskSubmissionOutcome) {
|
||||
}
|
||||
|
||||
func respondTaskSubmissionError(c *gin.Context, taskErr *taskdto.TaskError) {
|
||||
service.RecordRequestPolicyTermination(c, taskSubmissionAPIError(taskErr))
|
||||
newTaskPluginSubmitDiagnostics(c).presentError(taskErr)
|
||||
if middleware.RespondTaskPluginError(c, taskErr) {
|
||||
return
|
||||
@@ -891,44 +874,53 @@ func respondTaskError(c *gin.Context, taskErr *taskdto.TaskError) {
|
||||
c.JSON(taskErr.StatusCode, taskErr)
|
||||
}
|
||||
|
||||
func shouldRetryTaskRelay(c *gin.Context, channelId int, taskErr *taskdto.TaskError, retryTimes int) bool {
|
||||
if taskErr == nil {
|
||||
return false
|
||||
// taskSubmissionAPIError adapts a task error for the shared relay error paths.
|
||||
// TaskError.Error is nil for many local rejections, so fall back to the message.
|
||||
func taskSubmissionAPIError(taskErr *taskdto.TaskError) *types.NewAPIError {
|
||||
err := taskErr.Error
|
||||
if err == nil {
|
||||
err = errors.New(taskErr.Message)
|
||||
}
|
||||
if service.ShouldSkipRetryAfterChannelAffinityFailure(c) {
|
||||
return false
|
||||
}
|
||||
if retryTimes <= 0 {
|
||||
return false
|
||||
}
|
||||
if service.GetChannelConstraints(c).SuppressesRetry() {
|
||||
return false
|
||||
}
|
||||
if taskErr.StatusCode == http.StatusTooManyRequests {
|
||||
return true
|
||||
}
|
||||
if taskErr.StatusCode == 307 {
|
||||
return true
|
||||
}
|
||||
if taskErr.StatusCode/100 == 5 {
|
||||
return types.NewOpenAIError(err, types.ErrorCodeBadResponseStatusCode, taskErr.StatusCode)
|
||||
}
|
||||
|
||||
// decideTaskRetry is the single retry decision for task submissions. The
|
||||
// reason is recorded in the request policy decision events of the log details.
|
||||
func decideTaskRetry(c *gin.Context, taskErr *taskdto.TaskError, retryTimes int) service.PolicyDecision {
|
||||
stop := service.PolicyDecision{Action: "stop", Source: "system"}
|
||||
retry := service.PolicyDecision{Action: "retry", Reason: "retry_status_matched", Source: "system"}
|
||||
switch {
|
||||
case taskErr == nil:
|
||||
stop.Reason = "request_completed"
|
||||
case taskErr.NoRetry:
|
||||
stop.Reason = "task_accepted"
|
||||
case service.ShouldSkipRetryAfterChannelAffinityFailure(c):
|
||||
stop.Reason, stop.Source = "strict_session", "session_rule"
|
||||
if source := service.RequestPolicy(c).SessionModeSource; source != "" {
|
||||
stop.Source = source
|
||||
}
|
||||
case retryTimes <= 0:
|
||||
stop.Reason, stop.Source = "attempt_budget_exhausted", "global"
|
||||
case service.GetChannelConstraints(c).SuppressesRetry():
|
||||
stop.Reason, stop.Source = "pinned_channel", "channel_constraint"
|
||||
case taskErr.StatusCode == http.StatusTooManyRequests, taskErr.StatusCode == 307:
|
||||
return retry
|
||||
case taskErr.StatusCode/100 == 5:
|
||||
// 超时不重试
|
||||
if operation_setting.IsAlwaysSkipRetryStatusCode(taskErr.StatusCode) {
|
||||
return false
|
||||
stop.Reason = "system_retry_exclusion"
|
||||
break
|
||||
}
|
||||
return true
|
||||
}
|
||||
if taskErr.StatusCode == http.StatusBadRequest {
|
||||
return false
|
||||
}
|
||||
if taskErr.StatusCode == 408 {
|
||||
return retry
|
||||
case taskErr.StatusCode == http.StatusBadRequest, taskErr.StatusCode == 408:
|
||||
// azure处理超时不重试
|
||||
return false
|
||||
stop.Reason = "status_not_retryable"
|
||||
case taskErr.LocalError:
|
||||
stop.Reason = "local_rejection"
|
||||
case taskErr.StatusCode/100 == 2:
|
||||
stop.Reason = "system_retry_exclusion"
|
||||
default:
|
||||
return retry
|
||||
}
|
||||
if taskErr.LocalError {
|
||||
return false
|
||||
}
|
||||
if taskErr.StatusCode/100 == 2 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return stop
|
||||
}
|
||||
Reference in new issue
Block a user