2024-02-29 01:08:18 +08:00
package relay
import (
"bytes"
"fmt"
"io"
"net/http"
"one-api/common"
2024-03-20 17:07:42 +08:00
"one-api/constant"
2024-02-29 01:08:18 +08:00
"one-api/dto"
2025-08-14 20:05:06 +08:00
"one-api/logger"
2024-02-29 01:08:18 +08:00
"one-api/model"
relaycommon "one-api/relay/common"
2025-02-20 16:41:46 +08:00
"one-api/relay/helper"
2024-02-29 01:08:18 +08:00
"one-api/service"
2025-03-11 17:02:35 +08:00
"one-api/setting/model_setting"
2025-05-06 23:25:16 +08:00
"one-api/setting/operation_setting"
2025-07-10 15:02:40 +08:00
"one-api/types"
2024-02-29 01:08:18 +08:00
"strings"
"time"
2025-03-08 21:55:50 +08:00
"github.com/shopspring/decimal"
2024-02-29 01:08:18 +08:00
"github.com/gin-gonic/gin"
)
2025-08-14 20:05:06 +08:00
func TextHelper ( c * gin . Context , info * relaycommon . RelayInfo ) ( newAPIError * types . NewAPIError ) {
info . InitChannelMeta ( c )
2024-02-29 01:08:18 +08:00
2025-08-23 13:12:15 +08:00
textReq , ok := info . Request . ( * dto . GeneralOpenAIRequest )
2025-08-14 20:05:06 +08:00
if ! ok {
2025-08-23 13:34:56 +08:00
return types . NewErrorWithStatusCode ( fmt . Errorf ( "invalid request type, expected dto.GeneralOpenAIRequest, got %T" , info . Request ) , types . ErrorCodeInvalidRequest , http . StatusBadRequest , types . ErrOptionWithSkipRetry ( ) )
2024-02-29 01:08:18 +08:00
}
2025-08-23 13:12:15 +08:00
request , err := common . DeepCopy ( textReq )
if err != nil {
return types . NewError ( fmt . Errorf ( "failed to copy request to GeneralOpenAIRequest: %w" , err ) , types . ErrorCodeInvalidRequest , types . ErrOptionWithSkipRetry ( ) )
}
if request . WebSearchOptions != nil {
c . Set ( "chat_completion_web_search_context_size" , request . WebSearchOptions . SearchContextSize )
2025-06-18 00:37:22 +08:00
}
2025-08-23 13:12:15 +08:00
err = helper . ModelMappedHelper ( c , info , request )
2025-02-20 16:41:46 +08:00
if err != nil {
2025-07-30 22:35:31 +08:00
return types . NewError ( err , types . ErrorCodeChannelModelMappedError , types . ErrOptionWithSkipRetry ( ) )
2025-02-20 16:41:46 +08:00
}
2025-08-14 21:10:04 +08:00
includeUsage := true
// 判断用户是否需要返回使用情况
2025-08-23 13:12:15 +08:00
if request . StreamOptions != nil {
includeUsage = request . StreamOptions . IncludeUsage
2025-08-14 21:10:04 +08:00
}
// 如果不支持StreamOptions, 将StreamOptions设置为nil
2025-08-23 13:12:15 +08:00
if ! info . SupportStreamOptions || ! request . Stream {
request . StreamOptions = nil
2025-08-14 21:10:04 +08:00
} else {
// 如果支持StreamOptions, 且请求中没有设置StreamOptions, 根据配置文件设置StreamOptions
if constant . ForceStreamOption {
2025-08-23 13:12:15 +08:00
request . StreamOptions = & dto . StreamOptions {
2025-08-14 21:10:04 +08:00
IncludeUsage : true ,
}
}
}
info . ShouldIncludeUsage = includeUsage
2025-08-14 20:05:06 +08:00
adaptor := GetAdaptor ( info . ApiType )
2024-02-29 01:08:18 +08:00
if adaptor == nil {
2025-08-14 20:05:06 +08:00
return types . NewError ( fmt . Errorf ( "invalid api type: %d" , info . ApiType ) , types . ErrorCodeInvalidApiType , types . ErrOptionWithSkipRetry ( ) )
2024-02-29 01:08:18 +08:00
}
2025-08-14 20:05:06 +08:00
adaptor . Init ( info )
2024-02-29 01:08:18 +08:00
var requestBody io . Reader
2024-07-08 01:27:57 +08:00
2025-08-14 20:05:06 +08:00
if model_setting . GetGlobalSettings ( ) . PassThroughRequestEnabled || info . ChannelSetting . PassThroughBodyEnabled {
2025-03-11 17:02:35 +08:00
body , err := common . GetRequestBody ( c )
if err != nil {
2025-07-30 22:35:31 +08:00
return types . NewErrorWithStatusCode ( err , types . ErrorCodeReadRequestBodyFailed , http . StatusBadRequest , types . ErrOptionWithSkipRetry ( ) )
2025-03-11 17:02:35 +08:00
}
2025-07-26 12:11:20 +08:00
if common . DebugEnabled {
println ( "requestBody: " , string ( body ) )
}
2025-03-11 17:02:35 +08:00
requestBody = bytes . NewBuffer ( body )
} else {
2025-08-23 13:12:15 +08:00
convertedRequest , err := adaptor . ConvertOpenAIRequest ( c , info , request )
2025-03-11 17:02:35 +08:00
if err != nil {
2025-07-30 22:35:31 +08:00
return types . NewError ( err , types . ErrorCodeConvertRequestFailed , types . ErrOptionWithSkipRetry ( ) )
2025-03-11 17:02:35 +08:00
}
2025-07-26 11:39:09 +08:00
2025-08-14 20:05:06 +08:00
if info . ChannelSetting . SystemPrompt != "" {
2025-07-26 11:39:09 +08:00
// 如果有系统提示,则将其添加到请求中
request := convertedRequest . ( * dto . GeneralOpenAIRequest )
containSystemPrompt := false
for _ , message := range request . Messages {
if message . Role == request . GetSystemRoleName ( ) {
containSystemPrompt = true
break
}
}
if ! containSystemPrompt {
// 如果没有系统提示,则添加系统提示
systemMessage := dto . Message {
Role : request . GetSystemRoleName ( ) ,
2025-08-14 20:05:06 +08:00
Content : info . ChannelSetting . SystemPrompt ,
2025-07-26 11:39:09 +08:00
}
request . Messages = append ( [ ] dto . Message { systemMessage } , request . Messages ... )
2025-08-14 20:05:06 +08:00
} else if info . ChannelSetting . SystemPromptOverride {
2025-08-09 12:53:06 +08:00
common . SetContextKey ( c , constant . ContextKeySystemPromptOverride , true )
// 如果有系统提示,且允许覆盖,则拼接到前面
for i , message := range request . Messages {
if message . Role == request . GetSystemRoleName ( ) {
if message . IsStringContent ( ) {
2025-08-14 20:05:06 +08:00
request . Messages [ i ] . SetStringContent ( info . ChannelSetting . SystemPrompt + "\n" + message . StringContent ( ) )
2025-08-09 12:53:06 +08:00
} else {
contents := message . ParseContent ( )
contents = append ( [ ] dto . MediaContent {
{
Type : dto . ContentTypeText ,
2025-08-14 20:05:06 +08:00
Text : info . ChannelSetting . SystemPrompt ,
2025-08-09 12:53:06 +08:00
} ,
} , contents ... )
request . Messages [ i ] . Content = contents
}
break
}
}
2025-07-26 11:39:09 +08:00
}
}
jsonData , err := common . Marshal ( convertedRequest )
2025-03-11 17:02:35 +08:00
if err != nil {
2025-07-30 22:35:31 +08:00
return types . NewError ( err , types . ErrorCodeConvertRequestFailed , types . ErrOptionWithSkipRetry ( ) )
2025-03-11 17:02:35 +08:00
}
2025-03-29 14:39:39 +08:00
// apply param override
2025-08-14 20:05:06 +08:00
if len ( info . ParamOverride ) > 0 {
2025-08-16 11:27:47 +08:00
jsonData , err = relaycommon . ApplyParamOverride ( jsonData , info . ParamOverride )
2025-03-29 14:39:39 +08:00
if err != nil {
2025-07-30 22:35:31 +08:00
return types . NewError ( err , types . ErrorCodeChannelParamOverrideInvalid , types . ErrOptionWithSkipRetry ( ) )
2025-03-29 14:39:39 +08:00
}
}
2025-08-14 20:05:06 +08:00
logger . LogDebug ( c , fmt . Sprintf ( "text request body: %s" , string ( jsonData ) ) )
2025-03-11 17:02:35 +08:00
requestBody = bytes . NewBuffer ( jsonData )
2024-02-29 01:08:18 +08:00
}
2024-10-04 16:08:18 +08:00
var httpResp * http . Response
2025-08-14 20:05:06 +08:00
resp , err := adaptor . DoRequest ( c , info , requestBody )
2024-03-06 17:41:55 +08:00
if err != nil {
2025-07-19 11:28:18 +08:00
return types . NewOpenAIError ( err , types . ErrorCodeDoRequestFailed , http . StatusInternalServerError )
2024-03-06 17:41:55 +08:00
}
2024-02-29 01:08:18 +08:00
2025-03-11 17:02:35 +08:00
statusCodeMappingStr := c . GetString ( "status_code_mapping" )
2024-04-23 11:44:40 +08:00
if resp != nil {
2024-10-04 16:08:18 +08:00
httpResp = resp . ( * http . Response )
2025-08-14 20:05:06 +08:00
info . IsStream = info . IsStream || strings . HasPrefix ( httpResp . Header . Get ( "Content-Type" ) , "text/event-stream" )
2024-10-04 16:08:18 +08:00
if httpResp . StatusCode != http . StatusOK {
2025-08-14 20:05:06 +08:00
newApiErr := service . RelayErrorHandler ( httpResp , false )
2024-04-23 11:44:40 +08:00
// reset status code 重置状态码
2025-07-10 15:02:40 +08:00
service . ResetStatusCode ( newApiErr , statusCodeMappingStr )
return newApiErr
2024-04-23 11:44:40 +08:00
}
2024-03-06 17:41:55 +08:00
}
2025-08-14 20:05:06 +08:00
usage , newApiErr := adaptor . DoResponse ( c , httpResp , info )
2025-07-10 15:02:40 +08:00
if newApiErr != nil {
2024-04-20 21:05:23 +08:00
// reset status code 重置状态码
2025-07-10 15:02:40 +08:00
service . ResetStatusCode ( newApiErr , statusCodeMappingStr )
return newApiErr
2024-02-29 01:08:18 +08:00
}
2024-11-07 16:12:09 +08:00
2025-08-14 20:05:06 +08:00
if strings . HasPrefix ( info . OriginModelName , "gpt-4o-audio" ) {
service . PostAudioConsumeQuota ( c , info , usage . ( * dto . Usage ) , "" )
2024-11-07 16:12:09 +08:00
} else {
2025-08-14 20:05:06 +08:00
postConsumeQuota ( c , info , usage . ( * dto . Usage ) , "" )
2024-11-07 16:12:09 +08:00
}
2024-02-29 01:08:18 +08:00
return nil
}
2025-08-14 20:05:06 +08:00
func postConsumeQuota ( ctx * gin . Context , relayInfo * relaycommon . RelayInfo , usage * dto . Usage , extraContent string ) {
2024-08-01 16:13:08 +08:00
if usage == nil {
usage = & dto . Usage {
PromptTokens : relayInfo . PromptTokens ,
CompletionTokens : 0 ,
TotalTokens : relayInfo . PromptTokens ,
}
2025-03-08 01:30:50 +08:00
extraContent += "(可能是请求出错)"
2024-08-01 16:13:08 +08:00
}
2024-02-29 01:08:18 +08:00
useTimeSeconds := time . Now ( ) . Unix ( ) - relayInfo . StartTime . Unix ( )
promptTokens := usage . PromptTokens
2025-03-08 01:30:50 +08:00
cacheTokens := usage . PromptTokensDetails . CachedTokens
2025-04-24 19:25:08 +08:00
imageTokens := usage . PromptTokensDetails . ImageTokens
2025-06-07 12:26:23 +08:00
audioTokens := usage . PromptTokensDetails . AudioTokens
2024-02-29 01:08:18 +08:00
completionTokens := usage . CompletionTokens
2025-02-20 16:41:46 +08:00
modelName := relayInfo . OriginModelName
2024-02-29 01:08:18 +08:00
tokenName := ctx . GetString ( "token_name" )
2025-08-14 20:05:06 +08:00
completionRatio := relayInfo . PriceData . CompletionRatio
cacheRatio := relayInfo . PriceData . CacheRatio
imageRatio := relayInfo . PriceData . ImageRatio
modelRatio := relayInfo . PriceData . ModelRatio
groupRatio := relayInfo . PriceData . GroupRatioInfo . GroupRatio
modelPrice := relayInfo . PriceData . ModelPrice
2024-02-29 01:08:18 +08:00
2025-03-08 21:55:50 +08:00
// Convert values to decimal for precise calculation
dPromptTokens := decimal . NewFromInt ( int64 ( promptTokens ) )
dCacheTokens := decimal . NewFromInt ( int64 ( cacheTokens ) )
2025-04-24 19:25:08 +08:00
dImageTokens := decimal . NewFromInt ( int64 ( imageTokens ) )
2025-06-07 12:26:23 +08:00
dAudioTokens := decimal . NewFromInt ( int64 ( audioTokens ) )
2025-03-08 21:55:50 +08:00
dCompletionTokens := decimal . NewFromInt ( int64 ( completionTokens ) )
dCompletionRatio := decimal . NewFromFloat ( completionRatio )
dCacheRatio := decimal . NewFromFloat ( cacheRatio )
2025-04-24 19:25:08 +08:00
dImageRatio := decimal . NewFromFloat ( imageRatio )
2025-03-08 21:55:50 +08:00
dModelRatio := decimal . NewFromFloat ( modelRatio )
dGroupRatio := decimal . NewFromFloat ( groupRatio )
dModelPrice := decimal . NewFromFloat ( modelPrice )
dQuotaPerUnit := decimal . NewFromFloat ( common . QuotaPerUnit )
ratio := dModelRatio . Mul ( dGroupRatio )
2025-05-06 21:58:01 +08:00
// openai web search 工具计费
var dWebSearchQuota decimal . Decimal
2025-05-07 01:08:20 +08:00
var webSearchPrice float64
2025-07-15 18:57:22 +08:00
// response api 格式工具计费
2025-05-06 21:58:01 +08:00
if relayInfo . ResponsesUsageInfo != nil {
if webSearchTool , exists := relayInfo . ResponsesUsageInfo . BuiltInTools [ dto . BuildInToolWebSearchPreview ] ; exists && webSearchTool . CallCount > 0 {
2025-05-07 19:33:32 +08:00
// 计算 web search 调用的配额 (配额 = 价格 * 调用次数 / 1000 * 分组倍率)
2025-05-07 01:08:20 +08:00
webSearchPrice = operation_setting . GetWebSearchPricePerThousand ( modelName , webSearchTool . SearchContextSize )
dWebSearchQuota = decimal . NewFromFloat ( webSearchPrice ) .
2025-05-06 21:58:01 +08:00
Mul ( decimal . NewFromInt ( int64 ( webSearchTool . CallCount ) ) ) .
2025-05-07 19:33:32 +08:00
Div ( decimal . NewFromInt ( 1000 ) ) . Mul ( dGroupRatio ) . Mul ( dQuotaPerUnit )
2025-05-26 18:53:41 +08:00
extraContent += fmt . Sprintf ( "Web Search 调用 %d 次,上下文大小 %s, 调用花费 %s" ,
2025-05-06 21:58:01 +08:00
webSearchTool . CallCount , webSearchTool . SearchContextSize , dWebSearchQuota . String ( ) )
}
2025-05-26 18:53:41 +08:00
} else if strings . HasSuffix ( modelName , "search-preview" ) {
// search-preview 模型不支持 response api
searchContextSize := ctx . GetString ( "chat_completion_web_search_context_size" )
if searchContextSize == "" {
searchContextSize = "medium"
}
webSearchPrice = operation_setting . GetWebSearchPricePerThousand ( modelName , searchContextSize )
dWebSearchQuota = decimal . NewFromFloat ( webSearchPrice ) .
Div ( decimal . NewFromInt ( 1000 ) ) . Mul ( dGroupRatio ) . Mul ( dQuotaPerUnit )
extraContent += fmt . Sprintf ( "Web Search 调用 1 次,上下文大小 %s, 调用花费 %s" ,
searchContextSize , dWebSearchQuota . String ( ) )
2025-05-06 21:58:01 +08:00
}
2025-07-15 18:57:22 +08:00
// claude web search tool 计费
var dClaudeWebSearchQuota decimal . Decimal
var claudeWebSearchPrice float64
claudeWebSearchCallCount := ctx . GetInt ( "claude_web_search_requests" )
if claudeWebSearchCallCount > 0 {
claudeWebSearchPrice = operation_setting . GetClaudeWebSearchPricePerThousand ( )
dClaudeWebSearchQuota = decimal . NewFromFloat ( claudeWebSearchPrice ) .
Div ( decimal . NewFromInt ( 1000 ) ) . Mul ( dGroupRatio ) . Mul ( dQuotaPerUnit ) . Mul ( decimal . NewFromInt ( int64 ( claudeWebSearchCallCount ) ) )
extraContent += fmt . Sprintf ( "Claude Web Search 调用 %d 次,调用花费 %s" ,
claudeWebSearchCallCount , dClaudeWebSearchQuota . String ( ) )
}
2025-05-06 21:58:01 +08:00
// file search tool 计费
var dFileSearchQuota decimal . Decimal
2025-05-07 01:08:20 +08:00
var fileSearchPrice float64
2025-05-06 21:58:01 +08:00
if relayInfo . ResponsesUsageInfo != nil {
if fileSearchTool , exists := relayInfo . ResponsesUsageInfo . BuiltInTools [ dto . BuildInToolFileSearch ] ; exists && fileSearchTool . CallCount > 0 {
2025-05-07 01:08:20 +08:00
fileSearchPrice = operation_setting . GetFileSearchPricePerThousand ( )
dFileSearchQuota = decimal . NewFromFloat ( fileSearchPrice ) .
2025-05-06 21:58:01 +08:00
Mul ( decimal . NewFromInt ( int64 ( fileSearchTool . CallCount ) ) ) .
2025-05-07 19:33:32 +08:00
Div ( decimal . NewFromInt ( 1000 ) ) . Mul ( dGroupRatio ) . Mul ( dQuotaPerUnit )
2025-06-07 12:26:23 +08:00
extraContent += fmt . Sprintf ( "File Search 调用 %d 次,调用花费 %s" ,
2025-05-06 21:58:01 +08:00
fileSearchTool . CallCount , dFileSearchQuota . String ( ) )
}
}
2025-03-08 21:55:50 +08:00
var quotaCalculateDecimal decimal . Decimal
2025-06-07 12:26:23 +08:00
var audioInputQuota decimal . Decimal
var audioInputPrice float64
2025-08-14 20:05:06 +08:00
if ! relayInfo . PriceData . UsePrice {
2025-06-07 12:26:23 +08:00
baseTokens := dPromptTokens
// 减去 cached tokens
var cachedTokensWithRatio decimal . Decimal
if ! dCacheTokens . IsZero ( ) {
baseTokens = baseTokens . Sub ( dCacheTokens )
cachedTokensWithRatio = dCacheTokens . Mul ( dCacheRatio )
}
// 减去 image tokens
var imageTokensWithRatio decimal . Decimal
if ! dImageTokens . IsZero ( ) {
baseTokens = baseTokens . Sub ( dImageTokens )
imageTokensWithRatio = dImageTokens . Mul ( dImageRatio )
2025-04-24 19:25:08 +08:00
}
2025-06-07 12:26:23 +08:00
// 减去 Gemini audio tokens
if ! dAudioTokens . IsZero ( ) {
audioInputPrice = operation_setting . GetGeminiInputAudioPricePerMillionTokens ( modelName )
if audioInputPrice > 0 {
// 重新计算 base tokens
baseTokens = baseTokens . Sub ( dAudioTokens )
audioInputQuota = decimal . NewFromFloat ( audioInputPrice ) . Div ( decimal . NewFromInt ( 1000000 ) ) . Mul ( dAudioTokens ) . Mul ( dGroupRatio ) . Mul ( dQuotaPerUnit )
extraContent += fmt . Sprintf ( "Audio Input 花费 %s" , audioInputQuota . String ( ) )
}
}
promptQuota := baseTokens . Add ( cachedTokensWithRatio ) . Add ( imageTokensWithRatio )
2025-03-08 21:55:50 +08:00
completionQuota := dCompletionTokens . Mul ( dCompletionRatio )
quotaCalculateDecimal = promptQuota . Add ( completionQuota ) . Mul ( ratio )
if ! ratio . IsZero ( ) && quotaCalculateDecimal . LessThanOrEqual ( decimal . Zero ) {
quotaCalculateDecimal = decimal . NewFromInt ( 1 )
2024-02-29 01:08:18 +08:00
}
} else {
2025-03-08 21:55:50 +08:00
quotaCalculateDecimal = dModelPrice . Mul ( dQuotaPerUnit ) . Mul ( dGroupRatio )
2024-02-29 01:08:18 +08:00
}
2025-05-06 21:58:01 +08:00
// 添加 responses tools call 调用的配额
quotaCalculateDecimal = quotaCalculateDecimal . Add ( dWebSearchQuota )
quotaCalculateDecimal = quotaCalculateDecimal . Add ( dFileSearchQuota )
2025-06-07 12:26:23 +08:00
// 添加 audio input 独立计费
quotaCalculateDecimal = quotaCalculateDecimal . Add ( audioInputQuota )
2025-03-08 21:55:50 +08:00
quota := int ( quotaCalculateDecimal . Round ( 0 ) . IntPart ( ) )
2024-02-29 01:08:18 +08:00
totalTokens := promptTokens + completionTokens
2025-03-08 16:44:08 +08:00
2024-02-29 01:08:18 +08:00
var logContent string
// record all the consume log even if quota is 0
if totalTokens == 0 {
// in this case, must be some error happened
// we cannot just return, because we may have to return the pre-consumed quota
quota = 0
logContent += fmt . Sprintf ( "(可能是上游超时)" )
2025-08-14 20:05:06 +08:00
logger . LogError ( ctx , fmt . Sprintf ( "total tokens is 0, cannot consume quota, userId %d, channelId %d, " +
"tokenId %d, model %s, pre-consumed quota %d" , relayInfo . UserId , relayInfo . ChannelId , relayInfo . TokenId , modelName , relayInfo . FinalPreConsumedQuota ) )
2024-02-29 01:08:18 +08:00
} else {
2025-07-27 16:32:14 +08:00
if ! ratio . IsZero ( ) && quota == 0 {
quota = 1
}
2024-02-29 01:08:18 +08:00
model . UpdateUserUsedQuotaAndRequestCount ( relayInfo . UserId , quota )
model . UpdateChannelUsedQuota ( relayInfo . ChannelId , quota )
}
2025-08-14 20:05:06 +08:00
quotaDelta := quota - relayInfo . FinalPreConsumedQuota
2025-08-14 21:30:03 +08:00
//logger.LogInfo(ctx, fmt.Sprintf("request quota delta: %s", logger.FormatQuota(quotaDelta)))
if quotaDelta > 0 {
logger . LogInfo ( ctx , fmt . Sprintf ( "预扣费后补扣费:%s( 实际消耗: %s, 预扣费: %s) " ,
logger . FormatQuota ( quotaDelta ) ,
logger . FormatQuota ( quota ) ,
logger . FormatQuota ( relayInfo . FinalPreConsumedQuota ) ,
) )
} else if quotaDelta < 0 {
logger . LogInfo ( ctx , fmt . Sprintf ( "预扣费后返还扣费:%s( 实际消耗: %s, 预扣费: %s) " ,
logger . FormatQuota ( - quotaDelta ) ,
logger . FormatQuota ( quota ) ,
logger . FormatQuota ( relayInfo . FinalPreConsumedQuota ) ,
) )
}
2025-03-17 17:52:54 +08:00
if quotaDelta != 0 {
2025-08-14 20:05:06 +08:00
err := service . PostConsumeQuota ( relayInfo , quotaDelta , relayInfo . FinalPreConsumedQuota , true )
2025-03-17 17:52:54 +08:00
if err != nil {
2025-08-14 20:05:06 +08:00
logger . LogError ( ctx , "error consuming token remain quota: " + err . Error ( ) )
2025-03-17 17:52:54 +08:00
}
}
2024-07-06 17:09:22 +08:00
logModel := modelName
2024-02-29 01:08:18 +08:00
if strings . HasPrefix ( logModel , "gpt-4-gizmo" ) {
logModel = "gpt-4-gizmo-*"
2024-07-06 17:09:22 +08:00
logContent += fmt . Sprintf ( ",模型 %s" , modelName )
2024-02-29 01:08:18 +08:00
}
2024-08-16 17:25:03 +08:00
if strings . HasPrefix ( logModel , "gpt-4o-gizmo" ) {
logModel = "gpt-4o-gizmo-*"
logContent += fmt . Sprintf ( ",模型 %s" , modelName )
}
2024-07-17 23:50:37 +08:00
if extraContent != "" {
2024-07-18 00:41:31 +08:00
logContent += ", " + extraContent
2024-07-17 23:50:37 +08:00
}
2025-08-14 20:05:06 +08:00
other := service . GenerateTextOtherInfo ( ctx , relayInfo , modelRatio , groupRatio , completionRatio , cacheTokens , cacheRatio , modelPrice , relayInfo . PriceData . GroupRatioInfo . GroupSpecialRatio )
2025-04-24 21:41:38 +08:00
if imageTokens != 0 {
other [ "image" ] = true
other [ "image_ratio" ] = imageRatio
other [ "image_output" ] = imageTokens
}
2025-05-26 18:53:41 +08:00
if ! dWebSearchQuota . IsZero ( ) {
if relayInfo . ResponsesUsageInfo != nil {
if webSearchTool , exists := relayInfo . ResponsesUsageInfo . BuiltInTools [ dto . BuildInToolWebSearchPreview ] ; exists {
other [ "web_search" ] = true
other [ "web_search_call_count" ] = webSearchTool . CallCount
other [ "web_search_price" ] = webSearchPrice
}
} else if strings . HasSuffix ( modelName , "search-preview" ) {
2025-05-06 21:58:01 +08:00
other [ "web_search" ] = true
2025-05-26 18:53:41 +08:00
other [ "web_search_call_count" ] = 1
2025-05-07 01:08:20 +08:00
other [ "web_search_price" ] = webSearchPrice
2025-05-06 21:58:01 +08:00
}
2025-07-15 18:57:22 +08:00
} else if ! dClaudeWebSearchQuota . IsZero ( ) {
other [ "web_search" ] = true
other [ "web_search_call_count" ] = claudeWebSearchCallCount
other [ "web_search_price" ] = claudeWebSearchPrice
2025-05-06 21:58:01 +08:00
}
if ! dFileSearchQuota . IsZero ( ) && relayInfo . ResponsesUsageInfo != nil {
if fileSearchTool , exists := relayInfo . ResponsesUsageInfo . BuiltInTools [ dto . BuildInToolFileSearch ] ; exists {
other [ "file_search" ] = true
other [ "file_search_call_count" ] = fileSearchTool . CallCount
2025-05-07 01:08:20 +08:00
other [ "file_search_price" ] = fileSearchPrice
2025-05-06 21:58:01 +08:00
}
}
2025-06-07 12:26:23 +08:00
if ! audioInputQuota . IsZero ( ) {
other [ "audio_input_seperate_price" ] = true
other [ "audio_input_token_count" ] = audioTokens
other [ "audio_input_price" ] = audioInputPrice
}
2025-07-07 14:26:37 +08:00
model . RecordConsumeLog ( ctx , relayInfo . UserId , model . RecordConsumeLogParams {
ChannelId : relayInfo . ChannelId ,
PromptTokens : promptTokens ,
CompletionTokens : completionTokens ,
ModelName : logModel ,
TokenName : tokenName ,
Quota : quota ,
Content : logContent ,
TokenId : relayInfo . TokenId ,
UseTimeSeconds : int ( useTimeSeconds ) ,
IsStream : relayInfo . IsStream ,
Group : relayInfo . UsingGroup ,
Other : other ,
} )
2024-02-29 01:08:18 +08:00
}