Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
f6984272
authored
Sep 16, 2025
by
creamlike1024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: openai responses api 未统计图像生成调用计费
parent
46c561ee
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
142 additions
and
13 deletions
+142
-13
dto/openai_response.go
+42
-0
relay/channel/openai/relay_responses.go
+24
-11
relay/compatible_handler.go
+13
-0
setting/operation_setting/tools.go
+40
-0
web/src/helpers/render.jsx
+21
-2
web/src/hooks/usage-logs/useUsageLogsData.jsx
+2
-0
No files found.
dto/openai_response.go
View file @
f6984272
...
@@ -6,6 +6,10 @@ import (
...
@@ -6,6 +6,10 @@ import (
"one-api/types"
"one-api/types"
)
)
const
(
ResponsesOutputTypeImageGenerationCall
=
"image_generation_call"
)
type
SimpleResponse
struct
{
type
SimpleResponse
struct
{
Usage
`json:"usage"`
Usage
`json:"usage"`
Error
any
`json:"error"`
Error
any
`json:"error"`
...
@@ -273,6 +277,42 @@ func (o *OpenAIResponsesResponse) GetOpenAIError() *types.OpenAIError {
...
@@ -273,6 +277,42 @@ func (o *OpenAIResponsesResponse) GetOpenAIError() *types.OpenAIError {
return
GetOpenAIError
(
o
.
Error
)
return
GetOpenAIError
(
o
.
Error
)
}
}
func
(
o
*
OpenAIResponsesResponse
)
HasImageGenerationCall
()
bool
{
if
len
(
o
.
Output
)
==
0
{
return
false
}
for
_
,
output
:=
range
o
.
Output
{
if
output
.
Type
==
ResponsesOutputTypeImageGenerationCall
{
return
true
}
}
return
false
}
func
(
o
*
OpenAIResponsesResponse
)
GetQuality
()
string
{
if
len
(
o
.
Output
)
==
0
{
return
""
}
for
_
,
output
:=
range
o
.
Output
{
if
output
.
Type
==
ResponsesOutputTypeImageGenerationCall
{
return
output
.
Quality
}
}
return
""
}
func
(
o
*
OpenAIResponsesResponse
)
GetSize
()
string
{
if
len
(
o
.
Output
)
==
0
{
return
""
}
for
_
,
output
:=
range
o
.
Output
{
if
output
.
Type
==
ResponsesOutputTypeImageGenerationCall
{
return
output
.
Size
}
}
return
""
}
type
IncompleteDetails
struct
{
type
IncompleteDetails
struct
{
Reasoning
string
`json:"reasoning"`
Reasoning
string
`json:"reasoning"`
}
}
...
@@ -283,6 +323,8 @@ type ResponsesOutput struct {
...
@@ -283,6 +323,8 @@ type ResponsesOutput struct {
Status
string
`json:"status"`
Status
string
`json:"status"`
Role
string
`json:"role"`
Role
string
`json:"role"`
Content
[]
ResponsesOutputContent
`json:"content"`
Content
[]
ResponsesOutputContent
`json:"content"`
Quality
string
`json:"quality"`
Size
string
`json:"size"`
}
}
type
ResponsesOutputContent
struct
{
type
ResponsesOutputContent
struct
{
...
...
relay/channel/openai/relay_responses.go
View file @
f6984272
...
@@ -33,6 +33,12 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
...
@@ -33,6 +33,12 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
return
nil
,
types
.
WithOpenAIError
(
*
oaiError
,
resp
.
StatusCode
)
return
nil
,
types
.
WithOpenAIError
(
*
oaiError
,
resp
.
StatusCode
)
}
}
if
responsesResponse
.
HasImageGenerationCall
()
{
c
.
Set
(
"image_generation_call"
,
true
)
c
.
Set
(
"image_generation_call_quality"
,
responsesResponse
.
GetQuality
())
c
.
Set
(
"image_generation_call_size"
,
responsesResponse
.
GetSize
())
}
// 写入新的 response body
// 写入新的 response body
service
.
IOCopyBytesGracefully
(
c
,
resp
,
responseBody
)
service
.
IOCopyBytesGracefully
(
c
,
resp
,
responseBody
)
...
@@ -80,18 +86,25 @@ func OaiResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
...
@@ -80,18 +86,25 @@ func OaiResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
sendResponsesStreamData
(
c
,
streamResponse
,
data
)
sendResponsesStreamData
(
c
,
streamResponse
,
data
)
switch
streamResponse
.
Type
{
switch
streamResponse
.
Type
{
case
"response.completed"
:
case
"response.completed"
:
if
streamResponse
.
Response
!=
nil
&&
streamResponse
.
Response
.
Usage
!=
nil
{
if
streamResponse
.
Response
!=
nil
{
if
streamResponse
.
Response
.
Usage
.
InputTokens
!=
0
{
if
streamResponse
.
Response
.
Usage
!=
nil
{
usage
.
PromptTokens
=
streamResponse
.
Response
.
Usage
.
InputTokens
if
streamResponse
.
Response
.
Usage
.
InputTokens
!=
0
{
}
usage
.
PromptTokens
=
streamResponse
.
Response
.
Usage
.
InputTokens
if
streamResponse
.
Response
.
Usage
.
OutputTokens
!=
0
{
}
usage
.
CompletionTokens
=
streamResponse
.
Response
.
Usage
.
OutputTokens
if
streamResponse
.
Response
.
Usage
.
OutputTokens
!=
0
{
}
usage
.
CompletionTokens
=
streamResponse
.
Response
.
Usage
.
OutputTokens
if
streamResponse
.
Response
.
Usage
.
TotalTokens
!=
0
{
}
usage
.
TotalTokens
=
streamResponse
.
Response
.
Usage
.
TotalTokens
if
streamResponse
.
Response
.
Usage
.
TotalTokens
!=
0
{
usage
.
TotalTokens
=
streamResponse
.
Response
.
Usage
.
TotalTokens
}
if
streamResponse
.
Response
.
Usage
.
InputTokensDetails
!=
nil
{
usage
.
PromptTokensDetails
.
CachedTokens
=
streamResponse
.
Response
.
Usage
.
InputTokensDetails
.
CachedTokens
}
}
}
if
streamResponse
.
Response
.
Usage
.
InputTokensDetails
!=
nil
{
if
streamResponse
.
Response
.
HasImageGenerationCall
()
{
usage
.
PromptTokensDetails
.
CachedTokens
=
streamResponse
.
Response
.
Usage
.
InputTokensDetails
.
CachedTokens
c
.
Set
(
"image_generation_call"
,
true
)
c
.
Set
(
"image_generation_call_quality"
,
streamResponse
.
Response
.
GetQuality
())
c
.
Set
(
"image_generation_call_size"
,
streamResponse
.
Response
.
GetSize
())
}
}
}
}
case
"response.output_text.delta"
:
case
"response.output_text.delta"
:
...
...
relay/compatible_handler.go
View file @
f6984272
...
@@ -276,6 +276,13 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
...
@@ -276,6 +276,13 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
fileSearchTool
.
CallCount
,
dFileSearchQuota
.
String
())
fileSearchTool
.
CallCount
,
dFileSearchQuota
.
String
())
}
}
}
}
var
dImageGenerationCallQuota
decimal
.
Decimal
var
imageGenerationCallPrice
float64
if
ctx
.
GetBool
(
"image_generation_call"
)
{
imageGenerationCallPrice
=
operation_setting
.
GetGPTImage1PriceOnceCall
(
ctx
.
GetString
(
"image_generation_call_quality"
),
ctx
.
GetString
(
"image_generation_call_size"
))
dImageGenerationCallQuota
=
decimal
.
NewFromFloat
(
imageGenerationCallPrice
)
.
Mul
(
dGroupRatio
)
.
Mul
(
dQuotaPerUnit
)
extraContent
+=
fmt
.
Sprintf
(
"Image Generation Call 花费 %s"
,
dImageGenerationCallQuota
.
String
())
}
var
quotaCalculateDecimal
decimal
.
Decimal
var
quotaCalculateDecimal
decimal
.
Decimal
...
@@ -331,6 +338,8 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
...
@@ -331,6 +338,8 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
dFileSearchQuota
)
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
dFileSearchQuota
)
// 添加 audio input 独立计费
// 添加 audio input 独立计费
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
audioInputQuota
)
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
audioInputQuota
)
// 添加 image generation call 计费
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
dImageGenerationCallQuota
)
quota
:=
int
(
quotaCalculateDecimal
.
Round
(
0
)
.
IntPart
())
quota
:=
int
(
quotaCalculateDecimal
.
Round
(
0
)
.
IntPart
())
totalTokens
:=
promptTokens
+
completionTokens
totalTokens
:=
promptTokens
+
completionTokens
...
@@ -429,6 +438,10 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
...
@@ -429,6 +438,10 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
other
[
"audio_input_token_count"
]
=
audioTokens
other
[
"audio_input_token_count"
]
=
audioTokens
other
[
"audio_input_price"
]
=
audioInputPrice
other
[
"audio_input_price"
]
=
audioInputPrice
}
}
if
!
dImageGenerationCallQuota
.
IsZero
()
{
other
[
"image_generation_call"
]
=
true
other
[
"image_generation_call_price"
]
=
imageGenerationCallPrice
}
model
.
RecordConsumeLog
(
ctx
,
relayInfo
.
UserId
,
model
.
RecordConsumeLogParams
{
model
.
RecordConsumeLog
(
ctx
,
relayInfo
.
UserId
,
model
.
RecordConsumeLogParams
{
ChannelId
:
relayInfo
.
ChannelId
,
ChannelId
:
relayInfo
.
ChannelId
,
PromptTokens
:
promptTokens
,
PromptTokens
:
promptTokens
,
...
...
setting/operation_setting/tools.go
View file @
f6984272
...
@@ -11,6 +11,18 @@ const (
...
@@ -11,6 +11,18 @@ const (
)
)
const
(
const
(
GPTImage1Low1024x1024
=
0.011
GPTImage1Low1024x1536
=
0.016
GPTImage1Low1536x1024
=
0.016
GPTImage1Medium1024x1024
=
0.042
GPTImage1Medium1024x1536
=
0.063
GPTImage1Medium1536x1024
=
0.063
GPTImage1High1024x1024
=
0.167
GPTImage1High1024x1536
=
0.25
GPTImage1High1536x1024
=
0.25
)
const
(
// Gemini Audio Input Price
// Gemini Audio Input Price
Gemini25FlashPreviewInputAudioPrice
=
1.00
Gemini25FlashPreviewInputAudioPrice
=
1.00
Gemini25FlashProductionInputAudioPrice
=
1.00
// for `gemini-2.5-flash`
Gemini25FlashProductionInputAudioPrice
=
1.00
// for `gemini-2.5-flash`
...
@@ -65,3 +77,31 @@ func GetGeminiInputAudioPricePerMillionTokens(modelName string) float64 {
...
@@ -65,3 +77,31 @@ func GetGeminiInputAudioPricePerMillionTokens(modelName string) float64 {
}
}
return
0
return
0
}
}
func
GetGPTImage1PriceOnceCall
(
quality
string
,
size
string
)
float64
{
prices
:=
map
[
string
]
map
[
string
]
float64
{
"low"
:
{
"1024x1024"
:
GPTImage1Low1024x1024
,
"1024x1536"
:
GPTImage1Low1024x1536
,
"1536x1024"
:
GPTImage1Low1536x1024
,
},
"medium"
:
{
"1024x1024"
:
GPTImage1Medium1024x1024
,
"1024x1536"
:
GPTImage1Medium1024x1536
,
"1536x1024"
:
GPTImage1Medium1536x1024
,
},
"high"
:
{
"1024x1024"
:
GPTImage1High1024x1024
,
"1024x1536"
:
GPTImage1High1024x1536
,
"1536x1024"
:
GPTImage1High1536x1024
,
},
}
if
qualityMap
,
exists
:=
prices
[
quality
];
exists
{
if
price
,
exists
:=
qualityMap
[
size
];
exists
{
return
price
}
}
return
GPTImage1High1024x1024
}
web/src/helpers/render.jsx
View file @
f6984272
...
@@ -1027,6 +1027,8 @@ export function renderModelPrice(
...
@@ -1027,6 +1027,8 @@ export function renderModelPrice(
audioInputSeperatePrice
=
false
,
audioInputSeperatePrice
=
false
,
audioInputTokens
=
0
,
audioInputTokens
=
0
,
audioInputPrice
=
0
,
audioInputPrice
=
0
,
imageGenerationCall
=
false
,
imageGenerationCallPrice
=
0
,
)
{
)
{
const
{
ratio
:
effectiveGroupRatio
,
label
:
ratioLabel
}
=
getEffectiveRatio
(
const
{
ratio
:
effectiveGroupRatio
,
label
:
ratioLabel
}
=
getEffectiveRatio
(
groupRatio
,
groupRatio
,
...
@@ -1069,7 +1071,8 @@ export function renderModelPrice(
...
@@ -1069,7 +1071,8 @@ export function renderModelPrice(
(
audioInputTokens
/
1000000
)
*
audioInputPrice
*
groupRatio
+
(
audioInputTokens
/
1000000
)
*
audioInputPrice
*
groupRatio
+
(
completionTokens
/
1000000
)
*
completionRatioPrice
*
groupRatio
+
(
completionTokens
/
1000000
)
*
completionRatioPrice
*
groupRatio
+
(
webSearchCallCount
/
1000
)
*
webSearchPrice
*
groupRatio
+
(
webSearchCallCount
/
1000
)
*
webSearchPrice
*
groupRatio
+
(
fileSearchCallCount
/
1000
)
*
fileSearchPrice
*
groupRatio
;
(
fileSearchCallCount
/
1000
)
*
fileSearchPrice
*
groupRatio
+
(
imageGenerationCall
*
imageGenerationCallPrice
*
groupRatio
);
return
(
return
(
<>
<>
...
@@ -1131,7 +1134,13 @@ export function renderModelPrice(
...
@@ -1131,7 +1134,13 @@ export function renderModelPrice(
})
}
})
}
</
p
>
</
p
>
)
}
)
}
<
p
></
p
>
{
imageGenerationCall
&&
imageGenerationCallPrice
>
0
&&
(
<
p
>
{
i18next
.
t
(
'图片生成调用:
$
{{
price
}}
/ 1次', {
price
:
imageGenerationCallPrice
,
})
}
</
p
>
)
}
<
p
>
<
p
>
{
(()
=>
{
{
(()
=>
{
// 构建输入部分描述
// 构建输入部分描述
...
@@ -1211,6 +1220,16 @@ export function renderModelPrice(
...
@@ -1211,6 +1220,16 @@ export function renderModelPrice(
},
},
)
)
:
''
,
:
''
,
imageGenerationCall
&&
imageGenerationCallPrice
>
0
?
i18next
.
t
(
'
+
图片生成调用
$
{{
price
}}
/ 1次 * {{ratioType}} {{ratio}}'
,
{
price
:
imageGenerationCallPrice
,
ratio
:
groupRatio
,
ratioType
:
ratioLabel
,
},
)
:
''
,
].
join
(
''
);
].
join
(
''
);
return
i18next
.
t
(
return
i18next
.
t
(
...
...
web/src/hooks/usage-logs/useUsageLogsData.jsx
View file @
f6984272
...
@@ -447,6 +447,8 @@ export const useLogsData = () => {
...
@@ -447,6 +447,8 @@ export const useLogsData = () => {
other
?.
audio_input_seperate_price
||
false
,
other
?.
audio_input_seperate_price
||
false
,
other
?.
audio_input_token_count
||
0
,
other
?.
audio_input_token_count
||
0
,
other
?.
audio_input_price
||
0
,
other
?.
audio_input_price
||
0
,
other
?.
image_generation_call
||
false
,
other
?.
image_generation_call_price
||
0
,
);
);
}
}
expandDataLocal
.
push
({
expandDataLocal
.
push
({
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment