diff --git a/controller/channel-test.go b/controller/channel-test.go index 32486a8b..95a4313f 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -144,7 +144,9 @@ func testChannel(channel *model.Channel, testModel string) testResult { } } - err = helper.ModelMappedHelper(c, info, nil) + info.InitChannelMeta(c) + + err = helper.ModelMappedHelper(c, info, request) if err != nil { return testResult{ context: c, @@ -166,10 +168,10 @@ func testChannel(channel *model.Channel, testModel string) testResult { } } - // 创建一个用于日志的 info 副本,移除 ApiKey - logInfo := *info - logInfo.ApiKey = "" - common.SysLog(fmt.Sprintf("testing channel %d with model %s , info %+v ", channel.Id, testModel, logInfo)) + //// 创建一个用于日志的 info 副本,移除 ApiKey + //logInfo := info + //logInfo.ApiKey = "" + common.SysLog(fmt.Sprintf("testing channel %d with model %s , info %+v ", channel.Id, testModel, info.ToString())) priceData, err := helper.ModelPriceHelper(c, info, 0, request.GetTokenCountMeta()) if err != nil { diff --git a/controller/relay.go b/controller/relay.go index 8b67fd89..57955a18 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -133,13 +133,13 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) { return } - preConsumedQuota, newApiErr := service.PreConsumeQuota(c, priceData.ShouldPreConsumedQuota, relayInfo) - if newApiErr != nil { + preConsumedQuota, newAPIError := service.PreConsumeQuota(c, priceData.ShouldPreConsumedQuota, relayInfo) + if newAPIError != nil { return } defer func() { - if newApiErr != nil { + if newAPIError != nil { service.ReturnPreConsumedQuota(c, relayInfo, preConsumedQuota) } }() diff --git a/model/channel.go b/model/channel.go index af769f63..7c3ff915 100644 --- a/model/channel.go +++ b/model/channel.go @@ -406,7 +406,11 @@ func (channel *Channel) GetBaseURL() string { if channel.BaseURL == nil { return "" } - return *channel.BaseURL + url := *channel.BaseURL + if url == "" { + url = constant.ChannelBaseURLs[channel.Type] + } + return url } func (channel *Channel) GetModelMapping() string { diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index 742cd61c..31f9ec6d 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -2,6 +2,7 @@ package common import ( "errors" + "fmt" "one-api/common" "one-api/constant" "one-api/dto" @@ -155,6 +156,81 @@ func (info *RelayInfo) InitChannelMeta(c *gin.Context) { info.ChannelMeta = channelMeta } +func (info *RelayInfo) ToString() string { + if info == nil { + return "RelayInfo" + } + + // Basic info + b := &strings.Builder{} + fmt.Fprintf(b, "RelayInfo{ ") + fmt.Fprintf(b, "RelayFormat: %s, ", info.RelayFormat) + fmt.Fprintf(b, "RelayMode: %d, ", info.RelayMode) + fmt.Fprintf(b, "IsStream: %t, ", info.IsStream) + fmt.Fprintf(b, "IsPlayground: %t, ", info.IsPlayground) + fmt.Fprintf(b, "RequestURLPath: %q, ", info.RequestURLPath) + fmt.Fprintf(b, "OriginModelName: %q, ", info.OriginModelName) + fmt.Fprintf(b, "PromptTokens: %d, ", info.PromptTokens) + fmt.Fprintf(b, "ShouldIncludeUsage: %t, ", info.ShouldIncludeUsage) + fmt.Fprintf(b, "DisablePing: %t, ", info.DisablePing) + fmt.Fprintf(b, "SendResponseCount: %d, ", info.SendResponseCount) + fmt.Fprintf(b, "FinalPreConsumedQuota: %d, ", info.FinalPreConsumedQuota) + + // User & token info (mask secrets) + fmt.Fprintf(b, "User{ Id: %d, Email: %q, Group: %q, UsingGroup: %q, Quota: %d }, ", + info.UserId, info.UserEmail, info.UserGroup, info.UsingGroup, info.UserQuota) + fmt.Fprintf(b, "Token{ Id: %d, Unlimited: %t, Key: ***masked*** }, ", info.TokenId, info.TokenUnlimited) + + // Time info + latencyMs := info.FirstResponseTime.Sub(info.StartTime).Milliseconds() + fmt.Fprintf(b, "Timing{ Start: %s, FirstResponse: %s, LatencyMs: %d }, ", + info.StartTime.Format(time.RFC3339Nano), info.FirstResponseTime.Format(time.RFC3339Nano), latencyMs) + + // Audio / realtime + if info.InputAudioFormat != "" || info.OutputAudioFormat != "" || len(info.RealtimeTools) > 0 || info.AudioUsage { + fmt.Fprintf(b, "Realtime{ AudioUsage: %t, InFmt: %q, OutFmt: %q, Tools: %d }, ", + info.AudioUsage, info.InputAudioFormat, info.OutputAudioFormat, len(info.RealtimeTools)) + } + + // Reasoning + if info.ReasoningEffort != "" { + fmt.Fprintf(b, "ReasoningEffort: %q, ", info.ReasoningEffort) + } + + // Price data (non-sensitive) + if info.PriceData.UsePrice { + fmt.Fprintf(b, "PriceData{ %s }, ", info.PriceData.ToSetting()) + } + + // Channel metadata (mask ApiKey) + if info.ChannelMeta != nil { + cm := info.ChannelMeta + fmt.Fprintf(b, "ChannelMeta{ Type: %d, Id: %d, IsMultiKey: %t, MultiKeyIndex: %d, BaseURL: %q, ApiType: %d, ApiVersion: %q, Organization: %q, CreateTime: %d, UpstreamModelName: %q, IsModelMapped: %t, SupportStreamOptions: %t, ApiKey: ***masked*** }, ", + cm.ChannelType, cm.ChannelId, cm.ChannelIsMultiKey, cm.ChannelMultiKeyIndex, cm.ChannelBaseUrl, cm.ApiType, cm.ApiVersion, cm.Organization, cm.ChannelCreateTime, cm.UpstreamModelName, cm.IsModelMapped, cm.SupportStreamOptions) + } + + // Responses usage info (non-sensitive) + if info.ResponsesUsageInfo != nil && len(info.ResponsesUsageInfo.BuiltInTools) > 0 { + fmt.Fprintf(b, "ResponsesTools{ ") + first := true + for name, tool := range info.ResponsesUsageInfo.BuiltInTools { + if !first { + fmt.Fprintf(b, ", ") + } + first = false + if tool != nil { + fmt.Fprintf(b, "%s: calls=%d", name, tool.CallCount) + } else { + fmt.Fprintf(b, "%s: calls=0", name) + } + } + fmt.Fprintf(b, " }, ") + } + + fmt.Fprintf(b, "}") + return b.String() +} + // 定义支持流式选项的通道类型 var streamSupportedChannels = map[int]bool{ constant.ChannelTypeOpenAI: true, diff --git a/service/pre_consume_quota.go b/service/pre_consume_quota.go index 3906414a..ef466d8d 100644 --- a/service/pre_consume_quota.go +++ b/service/pre_consume_quota.go @@ -37,7 +37,7 @@ func PreConsumeQuota(c *gin.Context, preConsumedQuota int, relayInfo *relaycommo return 0, types.NewErrorWithStatusCode(errors.New("user quota is not enough"), types.ErrorCodeInsufficientUserQuota, http.StatusForbidden, types.ErrOptionWithSkipRetry(), types.ErrOptionWithNoRecordErrorLog()) } if userQuota-preConsumedQuota < 0 { - return 0, types.NewErrorWithStatusCode(fmt.Errorf("pre-consume quota failed, user quota: %s, need quota: %s", logger.FormatQuota(userQuota), logger.FormatQuota(preConsumedQuota)), types.ErrorCodeInsufficientUserQuota, http.StatusForbidden, types.ErrOptionWithSkipRetry(), types.ErrOptionWithNoRecordErrorLog()) + return 0, types.NewErrorWithStatusCode(fmt.Errorf("预扣费额度失败, 用户剩余额度: %s, 需要预扣费额度: %s", logger.FormatQuota(userQuota), logger.FormatQuota(preConsumedQuota)), types.ErrorCodeInsufficientUserQuota, http.StatusForbidden, types.ErrOptionWithSkipRetry(), types.ErrOptionWithNoRecordErrorLog()) } trustQuota := common.GetTrustQuota()