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.
88 lines
3.4 KiB
Go
88 lines
3.4 KiB
Go
package relay
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/QuantumNous/new-api/constant"
|
|
"github.com/QuantumNous/new-api/logger"
|
|
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
|
"github.com/QuantumNous/new-api/relay/helper"
|
|
"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/gin-gonic/gin"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
// PrepareRequestBilling estimates and reserves one request's charge. Transports
|
|
// provide the current request body through BodyStorage or BillingRequestInput;
|
|
// channel retries retain the resulting billing session and pricing snapshot.
|
|
func PrepareRequestBilling(c *gin.Context, info *relaycommon.RelayInfo) *types.NewAPIError {
|
|
durable := info.OriginModelName == "gpt-6-astra" && c.Request.Method == http.MethodPost && c.Request.URL.Path == "/v1/responses"
|
|
needSensitiveCheck := setting.ShouldCheckPromptSensitive()
|
|
meta := &types.TokenCountMeta{TokenType: types.TokenTypeTokenizer}
|
|
if info.Request != nil && (needSensitiveCheck || constant.CountToken || durable) {
|
|
meta = info.Request.GetTokenCountMeta()
|
|
} else {
|
|
// Avoid building CombineText when only the pricing quantities are needed.
|
|
switch request := info.Request.(type) {
|
|
case *dto.GeneralOpenAIRequest:
|
|
meta.MaxTokens = int(max(lo.FromPtr(request.MaxTokens), lo.FromPtr(request.MaxCompletionTokens)))
|
|
case *dto.OpenAIResponsesRequest:
|
|
meta.MaxTokens = int(lo.FromPtr(request.MaxOutputTokens))
|
|
case *dto.ClaudeRequest:
|
|
meta.MaxTokens = int(lo.FromPtr(request.MaxTokens))
|
|
case *dto.ImageRequest:
|
|
meta = request.GetTokenCountMeta()
|
|
}
|
|
}
|
|
|
|
if needSensitiveCheck && meta != nil {
|
|
if contains, words := service.CheckSensitiveText(meta.CombineText); contains {
|
|
service.RequestPolicy(c).AddEvent(service.PolicyEvent{ErrorCode: string(types.ErrorCodeSensitiveWordsDetected), ErrorSource: "local", Decision: service.PolicyDecision{Action: "stop", Reason: "local_rejection", Source: "global"}, Health: "unchanged"})
|
|
message := fmt.Sprintf("user sensitive words detected: %s", strings.Join(words, ", "))
|
|
logger.LogWarn(c, message)
|
|
return types.NewError(errors.New(message), types.ErrorCodeSensitiveWordsDetected)
|
|
}
|
|
}
|
|
|
|
tokens, err := service.EstimateRequestToken(c, meta, info)
|
|
if durable { tokens,err=service.CountRequestToken(c,meta,info) }
|
|
if err != nil {
|
|
return types.NewError(err, types.ErrorCodeCountTokenFailed)
|
|
}
|
|
info.SetEstimatePromptTokens(tokens)
|
|
|
|
priceData, err := helper.ModelPriceHelper(c, info, tokens, meta)
|
|
if err != nil {
|
|
return types.NewError(err, types.ErrorCodeModelPriceError, types.ErrOptionWithStatusCode(http.StatusBadRequest))
|
|
}
|
|
if durable {
|
|
info.DurableAttemptBilling = true
|
|
return nil
|
|
}
|
|
if priceData.FreeModel {
|
|
logger.LogInfo(c, fmt.Sprintf("模型 %s 免费,跳过预扣费", info.OriginModelName))
|
|
return nil
|
|
}
|
|
return service.PreConsumeBilling(c, priceData.QuotaToPreConsume, info)
|
|
}
|
|
|
|
// RefundFailedRequestBilling applies the common final-failure policy after all
|
|
// eligible attempts have ended. A settled BillingSession never refunds again.
|
|
func RefundFailedRequestBilling(c *gin.Context, info *relaycommon.RelayInfo, apiErr *types.NewAPIError) *types.NewAPIError {
|
|
if apiErr == nil {
|
|
return nil
|
|
}
|
|
apiErr = service.NormalizeViolationFeeError(apiErr)
|
|
if info.Billing != nil {
|
|
info.Billing.Refund(c)
|
|
}
|
|
service.ChargeViolationFeeIfNeeded(c, info, apiErr)
|
|
return apiErr
|
|
}
|