feat: 添加 built in tools 计费前端显示
This commit is contained in:
@@ -361,11 +361,12 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
|
|||||||
|
|
||||||
// openai web search 工具计费
|
// openai web search 工具计费
|
||||||
var dWebSearchQuota decimal.Decimal
|
var dWebSearchQuota decimal.Decimal
|
||||||
|
var webSearchPrice float64
|
||||||
if relayInfo.ResponsesUsageInfo != nil {
|
if relayInfo.ResponsesUsageInfo != nil {
|
||||||
if webSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolWebSearchPreview]; exists && webSearchTool.CallCount > 0 {
|
if webSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolWebSearchPreview]; exists && webSearchTool.CallCount > 0 {
|
||||||
priceWebSearchPerThousandCalls := operation_setting.GetWebSearchPricePerThousand(modelName, webSearchTool.SearchContextSize)
|
|
||||||
// 计算 web search 调用的配额 (配额 = 价格 * 调用次数 / 1000)
|
// 计算 web search 调用的配额 (配额 = 价格 * 调用次数 / 1000)
|
||||||
dWebSearchQuota = decimal.NewFromFloat(priceWebSearchPerThousandCalls).
|
webSearchPrice = operation_setting.GetWebSearchPricePerThousand(modelName, webSearchTool.SearchContextSize)
|
||||||
|
dWebSearchQuota = decimal.NewFromFloat(webSearchPrice).
|
||||||
Mul(decimal.NewFromInt(int64(webSearchTool.CallCount))).
|
Mul(decimal.NewFromInt(int64(webSearchTool.CallCount))).
|
||||||
Div(decimal.NewFromInt(1000))
|
Div(decimal.NewFromInt(1000))
|
||||||
extraContent += fmt.Sprintf("Web Search 调用 %d 次,上下文大小 %s,调用花费 $%s",
|
extraContent += fmt.Sprintf("Web Search 调用 %d 次,上下文大小 %s,调用花费 $%s",
|
||||||
@@ -374,9 +375,11 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
|
|||||||
}
|
}
|
||||||
// file search tool 计费
|
// file search tool 计费
|
||||||
var dFileSearchQuota decimal.Decimal
|
var dFileSearchQuota decimal.Decimal
|
||||||
|
var fileSearchPrice float64
|
||||||
if relayInfo.ResponsesUsageInfo != nil {
|
if relayInfo.ResponsesUsageInfo != nil {
|
||||||
if fileSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolFileSearch]; exists && fileSearchTool.CallCount > 0 {
|
if fileSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolFileSearch]; exists && fileSearchTool.CallCount > 0 {
|
||||||
dFileSearchQuota = decimal.NewFromFloat(operation_setting.GetFileSearchPricePerThousand()).
|
fileSearchPrice = operation_setting.GetFileSearchPricePerThousand()
|
||||||
|
dFileSearchQuota = decimal.NewFromFloat(fileSearchPrice).
|
||||||
Mul(decimal.NewFromInt(int64(fileSearchTool.CallCount))).
|
Mul(decimal.NewFromInt(int64(fileSearchTool.CallCount))).
|
||||||
Div(decimal.NewFromInt(1000))
|
Div(decimal.NewFromInt(1000))
|
||||||
extraContent += fmt.Sprintf("File Search 调用 %d 次,调用花费 $%s",
|
extraContent += fmt.Sprintf("File Search 调用 %d 次,调用花费 $%s",
|
||||||
@@ -463,13 +466,14 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
|
|||||||
if webSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolWebSearchPreview]; exists {
|
if webSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolWebSearchPreview]; exists {
|
||||||
other["web_search"] = true
|
other["web_search"] = true
|
||||||
other["web_search_call_count"] = webSearchTool.CallCount
|
other["web_search_call_count"] = webSearchTool.CallCount
|
||||||
other["web_search_context_size"] = webSearchTool.SearchContextSize
|
other["web_search_price"] = webSearchPrice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !dFileSearchQuota.IsZero() && relayInfo.ResponsesUsageInfo != nil {
|
if !dFileSearchQuota.IsZero() && relayInfo.ResponsesUsageInfo != nil {
|
||||||
if fileSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolFileSearch]; exists {
|
if fileSearchTool, exists := relayInfo.ResponsesUsageInfo.BuiltInTools[dto.BuildInToolFileSearch]; exists {
|
||||||
other["file_search"] = true
|
other["file_search"] = true
|
||||||
other["file_search_call_count"] = fileSearchTool.CallCount
|
other["file_search_call_count"] = fileSearchTool.CallCount
|
||||||
|
other["file_search_price"] = fileSearchPrice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel,
|
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel,
|
||||||
|
|||||||
@@ -618,7 +618,6 @@ const LogsTable = () => {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = other?.claude
|
let content = other?.claude
|
||||||
? renderClaudeModelPriceSimple(
|
? renderClaudeModelPriceSimple(
|
||||||
other.model_ratio,
|
other.model_ratio,
|
||||||
@@ -935,6 +934,13 @@ const LogsTable = () => {
|
|||||||
other.model_price,
|
other.model_price,
|
||||||
other.group_ratio,
|
other.group_ratio,
|
||||||
other?.user_group_ratio,
|
other?.user_group_ratio,
|
||||||
|
false,
|
||||||
|
1.0,
|
||||||
|
undefined,
|
||||||
|
other.web_search || false,
|
||||||
|
other.web_search_call_count || 0,
|
||||||
|
other.file_search || false,
|
||||||
|
other.file_search_call_count || 0,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -995,6 +1001,12 @@ const LogsTable = () => {
|
|||||||
other?.image || false,
|
other?.image || false,
|
||||||
other?.image_ratio || 0,
|
other?.image_ratio || 0,
|
||||||
other?.image_output || 0,
|
other?.image_output || 0,
|
||||||
|
other?.web_search || false,
|
||||||
|
other?.web_search_call_count || 0,
|
||||||
|
other?.web_search_price || 0,
|
||||||
|
other?.file_search || false,
|
||||||
|
other?.file_search_call_count || 0,
|
||||||
|
other?.file_search_price || 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
expandDataLocal.push({
|
expandDataLocal.push({
|
||||||
|
|||||||
@@ -317,6 +317,12 @@ export function renderModelPrice(
|
|||||||
image = false,
|
image = false,
|
||||||
imageRatio = 1.0,
|
imageRatio = 1.0,
|
||||||
imageOutputTokens = 0,
|
imageOutputTokens = 0,
|
||||||
|
webSearch = false,
|
||||||
|
webSearchCallCount = 0,
|
||||||
|
webSearchPrice = 0,
|
||||||
|
fileSearch = false,
|
||||||
|
fileSearchCallCount = 0,
|
||||||
|
fileSearchPrice = 0,
|
||||||
) {
|
) {
|
||||||
if (modelPrice !== -1) {
|
if (modelPrice !== -1) {
|
||||||
return i18next.t(
|
return i18next.t(
|
||||||
@@ -339,14 +345,17 @@ export function renderModelPrice(
|
|||||||
// Calculate effective input tokens (non-cached + cached with ratio applied)
|
// Calculate effective input tokens (non-cached + cached with ratio applied)
|
||||||
let effectiveInputTokens =
|
let effectiveInputTokens =
|
||||||
inputTokens - cacheTokens + cacheTokens * cacheRatio;
|
inputTokens - cacheTokens + cacheTokens * cacheRatio;
|
||||||
// Handle image tokens if present
|
// Handle image tokens if present
|
||||||
if (image && imageOutputTokens > 0) {
|
if (image && imageOutputTokens > 0) {
|
||||||
effectiveInputTokens = inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
|
effectiveInputTokens =
|
||||||
|
inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
let price =
|
let price =
|
||||||
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
|
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
|
||||||
(completionTokens / 1000000) * completionRatioPrice * groupRatio;
|
(completionTokens / 1000000) * completionRatioPrice * groupRatio +
|
||||||
|
(webSearchCallCount / 1000) * webSearchPrice +
|
||||||
|
(fileSearchCallCount / 1000) * fileSearchPrice;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -391,9 +400,23 @@ export function renderModelPrice(
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{webSearch && webSearchCallCount > 0 && (
|
||||||
|
<p>
|
||||||
|
{i18next.t('Web搜索价格:${{price}} / 1K 次', {
|
||||||
|
price: webSearchPrice,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{fileSearch && fileSearchCallCount > 0 && (
|
||||||
|
<p>
|
||||||
|
{i18next.t('文件搜索价格:${{price}} / 1K 次', {
|
||||||
|
price: fileSearchPrice,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p></p>
|
<p></p>
|
||||||
<p>
|
<p>
|
||||||
{cacheTokens > 0 && !image
|
{cacheTokens > 0 && !image && !webSearch && !fileSearch
|
||||||
? i18next.t(
|
? i18next.t(
|
||||||
'输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
'输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||||
{
|
{
|
||||||
@@ -407,31 +430,75 @@ export function renderModelPrice(
|
|||||||
total: price.toFixed(6),
|
total: price.toFixed(6),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: image && imageOutputTokens > 0
|
: image && imageOutputTokens > 0 && !webSearch && !fileSearch
|
||||||
? i18next.t(
|
? i18next.t(
|
||||||
'输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
'输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||||
{
|
{
|
||||||
nonImageInput: inputTokens - imageOutputTokens,
|
nonImageInput: inputTokens - imageOutputTokens,
|
||||||
imageInput: imageOutputTokens,
|
imageInput: imageOutputTokens,
|
||||||
imageRatio: imageRatio,
|
imageRatio: imageRatio,
|
||||||
price: inputRatioPrice,
|
price: inputRatioPrice,
|
||||||
completion: completionTokens,
|
completion: completionTokens,
|
||||||
compPrice: completionRatioPrice,
|
compPrice: completionRatioPrice,
|
||||||
ratio: groupRatio,
|
ratio: groupRatio,
|
||||||
total: price.toFixed(6),
|
total: price.toFixed(6),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: i18next.t(
|
: webSearch && webSearchCallCount > 0 && !image && !fileSearch
|
||||||
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
? i18next.t(
|
||||||
{
|
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} = ${{total}}',
|
||||||
input: inputTokens,
|
{
|
||||||
price: inputRatioPrice,
|
input: inputTokens,
|
||||||
completion: completionTokens,
|
price: inputRatioPrice,
|
||||||
compPrice: completionRatioPrice,
|
completion: completionTokens,
|
||||||
ratio: groupRatio,
|
compPrice: completionRatioPrice,
|
||||||
total: price.toFixed(6),
|
ratio: groupRatio,
|
||||||
},
|
webSearchCallCount,
|
||||||
)}
|
webSearchPrice,
|
||||||
|
total: price.toFixed(6),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: fileSearch && fileSearchCallCount > 0 && !image && !webSearch
|
||||||
|
? i18next.t(
|
||||||
|
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} = ${{total}}',
|
||||||
|
{
|
||||||
|
input: inputTokens,
|
||||||
|
price: inputRatioPrice,
|
||||||
|
completion: completionTokens,
|
||||||
|
compPrice: completionRatioPrice,
|
||||||
|
ratio: groupRatio,
|
||||||
|
fileSearchCallCount,
|
||||||
|
fileSearchPrice,
|
||||||
|
total: price.toFixed(6),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: webSearch && webSearchCallCount > 0 && fileSearch && fileSearchCallCount > 0 && !image
|
||||||
|
? i18next.t(
|
||||||
|
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} + 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} = ${{total}}',
|
||||||
|
{
|
||||||
|
input: inputTokens,
|
||||||
|
price: inputRatioPrice,
|
||||||
|
completion: completionTokens,
|
||||||
|
compPrice: completionRatioPrice,
|
||||||
|
ratio: groupRatio,
|
||||||
|
webSearchCallCount,
|
||||||
|
webSearchPrice,
|
||||||
|
fileSearchCallCount,
|
||||||
|
fileSearchPrice,
|
||||||
|
total: price.toFixed(6),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: i18next.t(
|
||||||
|
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||||
|
{
|
||||||
|
input: inputTokens,
|
||||||
|
price: inputRatioPrice,
|
||||||
|
completion: completionTokens,
|
||||||
|
compPrice: completionRatioPrice,
|
||||||
|
ratio: groupRatio,
|
||||||
|
total: price.toFixed(6),
|
||||||
|
},
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||||
</article>
|
</article>
|
||||||
@@ -448,33 +515,56 @@ export function renderLogContent(
|
|||||||
user_group_ratio,
|
user_group_ratio,
|
||||||
image = false,
|
image = false,
|
||||||
imageRatio = 1.0,
|
imageRatio = 1.0,
|
||||||
useUserGroupRatio = undefined
|
useUserGroupRatio = undefined,
|
||||||
|
webSearch = false,
|
||||||
|
webSearchCallCount = 0,
|
||||||
|
fileSearch = false,
|
||||||
|
fileSearchCallCount = 0,
|
||||||
) {
|
) {
|
||||||
const ratioLabel = useUserGroupRatio ? i18next.t('专属倍率') : i18next.t('分组倍率');
|
const ratioLabel = useUserGroupRatio
|
||||||
|
? i18next.t('专属倍率')
|
||||||
|
: i18next.t('分组倍率');
|
||||||
const ratio = useUserGroupRatio ? user_group_ratio : groupRatio;
|
const ratio = useUserGroupRatio ? user_group_ratio : groupRatio;
|
||||||
|
|
||||||
if (modelPrice !== -1) {
|
if (modelPrice !== -1) {
|
||||||
return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
|
return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
|
||||||
price: modelPrice,
|
price: modelPrice,
|
||||||
ratioType: ratioLabel,
|
ratioType: ratioLabel,
|
||||||
ratio
|
ratio,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (image) {
|
if (image) {
|
||||||
return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}', {
|
return i18next.t(
|
||||||
modelRatio: modelRatio,
|
'模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}',
|
||||||
completionRatio: completionRatio,
|
{
|
||||||
imageRatio: imageRatio,
|
modelRatio: modelRatio,
|
||||||
ratioType: ratioLabel,
|
completionRatio: completionRatio,
|
||||||
ratio
|
imageRatio: imageRatio,
|
||||||
});
|
ratioType: ratioLabel,
|
||||||
|
ratio,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (webSearch) {
|
||||||
|
return i18next.t(
|
||||||
|
'模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}},Web 搜索调用 {{webSearchCallCount}} 次',
|
||||||
|
{
|
||||||
|
modelRatio: modelRatio,
|
||||||
|
completionRatio: completionRatio,
|
||||||
|
ratioType: ratioLabel,
|
||||||
|
ratio,
|
||||||
|
webSearchCallCount,
|
||||||
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}', {
|
return i18next.t(
|
||||||
modelRatio: modelRatio,
|
'模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}',
|
||||||
completionRatio: completionRatio,
|
{
|
||||||
ratioType: ratioLabel,
|
modelRatio: modelRatio,
|
||||||
ratio
|
completionRatio: completionRatio,
|
||||||
});
|
ratioType: ratioLabel,
|
||||||
|
ratio,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user