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
05cf55ac
authored
Aug 07, 2025
by
t0ng7u
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing
parents
1d28e4dd
15c11bfe
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
236 additions
and
107 deletions
+236
-107
constant/context_key.go
+0
-1
go.mod
+7
-7
go.sum
+12
-13
middleware/auth.go
+32
-1
middleware/distributor.go
+10
-37
model/channel_cache.go
+2
-6
relay/channel/aws/constants.go
+4
-0
relay/channel/aws/relay-aws.go
+15
-4
relay/channel/claude/constants.go
+2
-0
relay/channel/gemini/relay-gemini.go
+76
-6
relay/channel/openai/adaptor.go
+18
-0
relay/channel/openai/relay_responses.go
+10
-0
relay/channel/vertex/adaptor.go
+1
-0
setting/ratio_setting/cache_ratio.go
+4
-0
setting/ratio_setting/model_ratio.go
+21
-17
types/error.go
+9
-0
web/src/helpers/render.js
+7
-3
web/src/hooks/usage-logs/useUsageLogsData.js
+1
-0
web/src/pages/Setting/Ratio/ModelSettingsVisualEditor.js
+5
-12
No files found.
constant/context_key.go
View file @
05cf55ac
...
@@ -11,7 +11,6 @@ const (
...
@@ -11,7 +11,6 @@ const (
ContextKeyTokenKey
ContextKey
=
"token_key"
ContextKeyTokenKey
ContextKey
=
"token_key"
ContextKeyTokenId
ContextKey
=
"token_id"
ContextKeyTokenId
ContextKey
=
"token_id"
ContextKeyTokenGroup
ContextKey
=
"token_group"
ContextKeyTokenGroup
ContextKey
=
"token_group"
ContextKeyTokenAllowIps
ContextKey
=
"allow_ips"
ContextKeyTokenSpecificChannelId
ContextKey
=
"specific_channel_id"
ContextKeyTokenSpecificChannelId
ContextKey
=
"specific_channel_id"
ContextKeyTokenModelLimitEnabled
ContextKey
=
"token_model_limit_enabled"
ContextKeyTokenModelLimitEnabled
ContextKey
=
"token_model_limit_enabled"
ContextKeyTokenModelLimit
ContextKey
=
"token_model_limit"
ContextKeyTokenModelLimit
ContextKey
=
"token_model_limit"
...
...
go.mod
View file @
05cf55ac
...
@@ -7,9 +7,10 @@ require (
...
@@ -7,9 +7,10 @@ require (
github.com/Calcium-Ion/go-epay v0.0.4
github.com/Calcium-Ion/go-epay v0.0.4
github.com/andybalholm/brotli v1.1.1
github.com/andybalholm/brotli v1.1.1
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0
github.com/aws/aws-sdk-go-v2 v1.
26.1
github.com/aws/aws-sdk-go-v2 v1.
37.2
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.4
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.33.0
github.com/aws/smithy-go v1.22.5
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b
github.com/gin-contrib/cors v1.7.2
github.com/gin-contrib/cors v1.7.2
github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/gzip v0.0.6
...
@@ -24,6 +25,7 @@ require (
...
@@ -24,6 +25,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/gorilla/websocket v1.5.0
github.com/joho/godotenv v1.5.1
github.com/joho/godotenv v1.5.1
github.com/pkg/errors v0.9.1
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.5.0
github.com/samber/lo v1.39.0
github.com/samber/lo v1.39.0
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shopspring/decimal v1.4.0
github.com/shopspring/decimal v1.4.0
...
@@ -42,10 +44,9 @@ require (
...
@@ -42,10 +44,9 @@ require (
require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 // indirect
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
...
@@ -81,7 +82,6 @@ require (
...
@@ -81,7 +82,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.1 // indirect
github.com/pquerna/otp v1.5.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
...
...
go.sum
View file @
05cf55ac
...
@@ -8,21 +8,20 @@ github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0 h1:onfun1RA+Kc
...
@@ -8,21 +8,20 @@ github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0 h1:onfun1RA+Kc
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0/go.mod h1:4yg+jNTYlDEzBjhGS96v+zjyA3lfXlFd5CiTLIkPBLI=
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0/go.mod h1:4yg+jNTYlDEzBjhGS96v+zjyA3lfXlFd5CiTLIkPBLI=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 h1:HblK3eJHq54yET63qPCTJnks3loDse5xRmmqHgHzwoI=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 h1:HblK3eJHq54yET63qPCTJnks3loDse5xRmmqHgHzwoI=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6/go.mod h1:pbiaLIeYLUbgMY1kwEAdwO6UKD5ZNwdPGQlwokS9fe8=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6/go.mod h1:pbiaLIeYLUbgMY1kwEAdwO6UKD5ZNwdPGQlwokS9fe8=
github.com/aws/aws-sdk-go-v2 v1.
26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA
=
github.com/aws/aws-sdk-go-v2 v1.
37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo
=
github.com/aws/aws-sdk-go-v2 v1.
26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM
=
github.com/aws/aws-sdk-go-v2 v1.
37.2/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg
=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.
6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to
=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.
7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg
=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.
6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGm
g=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.
7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeab
g=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 h1:sPiRHLVUIIQcoVZTNwqQcdtjkqkPopyYmIX0M5ElRf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2/go.mod h1:ik86P3sgV+Bk7c1tBFCwI3VxMoSEwl4YkRB9xn1s340=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 h1:ZdzDAg075H6stMZtbD2o+PyB933M/f20e9WmCBC17wA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2/go.mod h1:eE1IIzXG9sdZCB0pNNpMpsYTLl4YdOQD3njiVN1e/E4=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.4 h1:JgHnonzbnA3pbqj76wYsSZIZZQYBxkmMEjvL6GHy8XU=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.33.0 h1:JzidOz4Hcn2RbP5fvIS1iAP+DcRv5VJtgixbEYDsI5g=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.4/go.mod h1:nZspkhg+9p8iApLFoyAqfyuMP0F38acy2Hm3r5r95Cg=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.33.0/go.mod h1:9A4/PJYlWjvjEzzoOLGQjkLt4bYK9fRWi7uz1GSsAcA=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
...
...
middleware/auth.go
View file @
05cf55ac
...
@@ -4,7 +4,10 @@ import (
...
@@ -4,7 +4,10 @@ import (
"fmt"
"fmt"
"net/http"
"net/http"
"one-api/common"
"one-api/common"
"one-api/constant"
"one-api/model"
"one-api/model"
"one-api/setting"
"one-api/setting/ratio_setting"
"strconv"
"strconv"
"strings"
"strings"
...
@@ -234,6 +237,16 @@ func TokenAuth() func(c *gin.Context) {
...
@@ -234,6 +237,16 @@ func TokenAuth() func(c *gin.Context) {
abortWithOpenAiMessage
(
c
,
http
.
StatusUnauthorized
,
err
.
Error
())
abortWithOpenAiMessage
(
c
,
http
.
StatusUnauthorized
,
err
.
Error
())
return
return
}
}
allowIpsMap
:=
token
.
GetIpLimitsMap
()
if
len
(
allowIpsMap
)
!=
0
{
clientIp
:=
c
.
ClientIP
()
if
_
,
ok
:=
allowIpsMap
[
clientIp
];
!
ok
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
"您的 IP 不在令牌允许访问的列表中"
)
return
}
}
userCache
,
err
:=
model
.
GetUserCache
(
token
.
UserId
)
userCache
,
err
:=
model
.
GetUserCache
(
token
.
UserId
)
if
err
!=
nil
{
if
err
!=
nil
{
abortWithOpenAiMessage
(
c
,
http
.
StatusInternalServerError
,
err
.
Error
())
abortWithOpenAiMessage
(
c
,
http
.
StatusInternalServerError
,
err
.
Error
())
...
@@ -247,6 +260,25 @@ func TokenAuth() func(c *gin.Context) {
...
@@ -247,6 +260,25 @@ func TokenAuth() func(c *gin.Context) {
userCache
.
WriteContext
(
c
)
userCache
.
WriteContext
(
c
)
userGroup
:=
userCache
.
Group
tokenGroup
:=
token
.
Group
if
tokenGroup
!=
""
{
// check common.UserUsableGroups[userGroup]
if
_
,
ok
:=
setting
.
GetUserUsableGroups
(
userGroup
)[
tokenGroup
];
!
ok
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"令牌分组 %s 已被禁用"
,
tokenGroup
))
return
}
// check group in common.GroupRatio
if
!
ratio_setting
.
ContainsGroupRatio
(
tokenGroup
)
{
if
tokenGroup
!=
"auto"
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"分组 %s 已被弃用"
,
tokenGroup
))
return
}
}
userGroup
=
tokenGroup
}
common
.
SetContextKey
(
c
,
constant
.
ContextKeyUsingGroup
,
userGroup
)
err
=
SetupContextForToken
(
c
,
token
,
parts
...
)
err
=
SetupContextForToken
(
c
,
token
,
parts
...
)
if
err
!=
nil
{
if
err
!=
nil
{
return
return
...
@@ -273,7 +305,6 @@ func SetupContextForToken(c *gin.Context, token *model.Token, parts ...string) e
...
@@ -273,7 +305,6 @@ func SetupContextForToken(c *gin.Context, token *model.Token, parts ...string) e
}
else
{
}
else
{
c
.
Set
(
"token_model_limit_enabled"
,
false
)
c
.
Set
(
"token_model_limit_enabled"
,
false
)
}
}
c
.
Set
(
"allow_ips"
,
token
.
GetIpLimitsMap
())
c
.
Set
(
"token_group"
,
token
.
Group
)
c
.
Set
(
"token_group"
,
token
.
Group
)
if
len
(
parts
)
>
1
{
if
len
(
parts
)
>
1
{
if
model
.
IsAdmin
(
token
.
UserId
)
{
if
model
.
IsAdmin
(
token
.
UserId
)
{
...
...
middleware/distributor.go
View file @
05cf55ac
...
@@ -10,7 +10,6 @@ import (
...
@@ -10,7 +10,6 @@ import (
"one-api/model"
"one-api/model"
relayconstant
"one-api/relay/constant"
relayconstant
"one-api/relay/constant"
"one-api/service"
"one-api/service"
"one-api/setting"
"one-api/setting/ratio_setting"
"one-api/setting/ratio_setting"
"one-api/types"
"one-api/types"
"strconv"
"strconv"
...
@@ -27,14 +26,6 @@ type ModelRequest struct {
...
@@ -27,14 +26,6 @@ type ModelRequest struct {
func
Distribute
()
func
(
c
*
gin
.
Context
)
{
func
Distribute
()
func
(
c
*
gin
.
Context
)
{
return
func
(
c
*
gin
.
Context
)
{
return
func
(
c
*
gin
.
Context
)
{
allowIpsMap
:=
common
.
GetContextKeyStringMap
(
c
,
constant
.
ContextKeyTokenAllowIps
)
if
len
(
allowIpsMap
)
!=
0
{
clientIp
:=
c
.
ClientIP
()
if
_
,
ok
:=
allowIpsMap
[
clientIp
];
!
ok
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
"您的 IP 不在令牌允许访问的列表中"
)
return
}
}
var
channel
*
model
.
Channel
var
channel
*
model
.
Channel
channelId
,
ok
:=
common
.
GetContextKey
(
c
,
constant
.
ContextKeyTokenSpecificChannelId
)
channelId
,
ok
:=
common
.
GetContextKey
(
c
,
constant
.
ContextKeyTokenSpecificChannelId
)
modelRequest
,
shouldSelectChannel
,
err
:=
getModelRequest
(
c
)
modelRequest
,
shouldSelectChannel
,
err
:=
getModelRequest
(
c
)
...
@@ -42,24 +33,6 @@ func Distribute() func(c *gin.Context) {
...
@@ -42,24 +33,6 @@ func Distribute() func(c *gin.Context) {
abortWithOpenAiMessage
(
c
,
http
.
StatusBadRequest
,
"Invalid request, "
+
err
.
Error
())
abortWithOpenAiMessage
(
c
,
http
.
StatusBadRequest
,
"Invalid request, "
+
err
.
Error
())
return
return
}
}
userGroup
:=
common
.
GetContextKeyString
(
c
,
constant
.
ContextKeyUserGroup
)
tokenGroup
:=
common
.
GetContextKeyString
(
c
,
constant
.
ContextKeyTokenGroup
)
if
tokenGroup
!=
""
{
// check common.UserUsableGroups[userGroup]
if
_
,
ok
:=
setting
.
GetUserUsableGroups
(
userGroup
)[
tokenGroup
];
!
ok
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"令牌分组 %s 已被禁用"
,
tokenGroup
))
return
}
// check group in common.GroupRatio
if
!
ratio_setting
.
ContainsGroupRatio
(
tokenGroup
)
{
if
tokenGroup
!=
"auto"
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"分组 %s 已被弃用"
,
tokenGroup
))
return
}
}
userGroup
=
tokenGroup
}
common
.
SetContextKey
(
c
,
constant
.
ContextKeyUsingGroup
,
userGroup
)
if
ok
{
if
ok
{
id
,
err
:=
strconv
.
Atoi
(
channelId
.
(
string
))
id
,
err
:=
strconv
.
Atoi
(
channelId
.
(
string
))
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -81,22 +54,21 @@ func Distribute() func(c *gin.Context) {
...
@@ -81,22 +54,21 @@ func Distribute() func(c *gin.Context) {
modelLimitEnable
:=
common
.
GetContextKeyBool
(
c
,
constant
.
ContextKeyTokenModelLimitEnabled
)
modelLimitEnable
:=
common
.
GetContextKeyBool
(
c
,
constant
.
ContextKeyTokenModelLimitEnabled
)
if
modelLimitEnable
{
if
modelLimitEnable
{
s
,
ok
:=
common
.
GetContextKey
(
c
,
constant
.
ContextKeyTokenModelLimit
)
s
,
ok
:=
common
.
GetContextKey
(
c
,
constant
.
ContextKeyTokenModelLimit
)
if
!
ok
{
// token model limit is empty, all models are not allowed
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
"该令牌无权访问任何模型"
)
return
}
var
tokenModelLimit
map
[
string
]
bool
var
tokenModelLimit
map
[
string
]
bool
if
ok
{
tokenModelLimit
,
ok
=
s
.
(
map
[
string
]
bool
)
tokenModelLimit
=
s
.
(
map
[
string
]
bool
)
if
!
ok
{
}
else
{
tokenModelLimit
=
map
[
string
]
bool
{}
tokenModelLimit
=
map
[
string
]
bool
{}
}
}
if
tokenModelLimit
!=
nil
{
matchName
:=
ratio_setting
.
FormatMatchingModelName
(
modelRequest
.
Model
)
// match gpts & thinking-*
if
_
,
ok
:=
tokenModelLimit
[
modelRequest
.
Model
];
!
ok
{
if
_
,
ok
:=
tokenModelLimit
[
matchName
];
!
ok
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
"该令牌无权访问模型 "
+
modelRequest
.
Model
)
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
"该令牌无权访问模型 "
+
modelRequest
.
Model
)
return
return
}
}
}
else
{
// token model limit is empty, all models are not allowed
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
"该令牌无权访问任何模型"
)
return
}
}
}
if
shouldSelectChannel
{
if
shouldSelectChannel
{
...
@@ -105,6 +77,7 @@ func Distribute() func(c *gin.Context) {
...
@@ -105,6 +77,7 @@ func Distribute() func(c *gin.Context) {
return
return
}
}
var
selectGroup
string
var
selectGroup
string
userGroup
:=
common
.
GetContextKeyString
(
c
,
constant
.
ContextKeyUsingGroup
)
channel
,
selectGroup
,
err
=
model
.
CacheGetRandomSatisfiedChannel
(
c
,
userGroup
,
modelRequest
.
Model
,
0
)
channel
,
selectGroup
,
err
=
model
.
CacheGetRandomSatisfiedChannel
(
c
,
userGroup
,
modelRequest
.
Model
,
0
)
if
err
!=
nil
{
if
err
!=
nil
{
showGroup
:=
userGroup
showGroup
:=
userGroup
...
...
model/channel_cache.go
View file @
05cf55ac
...
@@ -7,6 +7,7 @@ import (
...
@@ -7,6 +7,7 @@ import (
"one-api/common"
"one-api/common"
"one-api/constant"
"one-api/constant"
"one-api/setting"
"one-api/setting"
"one-api/setting/ratio_setting"
"sort"
"sort"
"strings"
"strings"
"sync"
"sync"
...
@@ -128,12 +129,7 @@ func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, model string,
...
@@ -128,12 +129,7 @@ func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, model string,
}
}
func
getRandomSatisfiedChannel
(
group
string
,
model
string
,
retry
int
)
(
*
Channel
,
error
)
{
func
getRandomSatisfiedChannel
(
group
string
,
model
string
,
retry
int
)
(
*
Channel
,
error
)
{
if
strings
.
HasPrefix
(
model
,
"gpt-4-gizmo"
)
{
model
=
ratio_setting
.
FormatMatchingModelName
(
model
)
model
=
"gpt-4-gizmo-*"
}
if
strings
.
HasPrefix
(
model
,
"gpt-4o-gizmo"
)
{
model
=
"gpt-4o-gizmo-*"
}
// if memory cache is disabled, get channel directly from database
// if memory cache is disabled, get channel directly from database
if
!
common
.
MemoryCacheEnabled
{
if
!
common
.
MemoryCacheEnabled
{
...
...
relay/channel/aws/constants.go
View file @
05cf55ac
...
@@ -13,6 +13,7 @@ var awsModelIDMap = map[string]string{
...
@@ -13,6 +13,7 @@ var awsModelIDMap = map[string]string{
"claude-3-7-sonnet-20250219"
:
"anthropic.claude-3-7-sonnet-20250219-v1:0"
,
"claude-3-7-sonnet-20250219"
:
"anthropic.claude-3-7-sonnet-20250219-v1:0"
,
"claude-sonnet-4-20250514"
:
"anthropic.claude-sonnet-4-20250514-v1:0"
,
"claude-sonnet-4-20250514"
:
"anthropic.claude-sonnet-4-20250514-v1:0"
,
"claude-opus-4-20250514"
:
"anthropic.claude-opus-4-20250514-v1:0"
,
"claude-opus-4-20250514"
:
"anthropic.claude-opus-4-20250514-v1:0"
,
"claude-opus-4-1-20250805"
:
"anthropic.claude-opus-4-1-20250805-v1:0"
,
}
}
var
awsModelCanCrossRegionMap
=
map
[
string
]
map
[
string
]
bool
{
var
awsModelCanCrossRegionMap
=
map
[
string
]
map
[
string
]
bool
{
...
@@ -54,6 +55,9 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{
...
@@ -54,6 +55,9 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{
"anthropic.claude-opus-4-20250514-v1:0"
:
{
"anthropic.claude-opus-4-20250514-v1:0"
:
{
"us"
:
true
,
"us"
:
true
,
},
},
"anthropic.claude-opus-4-1-20250805-v1:0"
:
{
"us"
:
true
,
},
}
}
var
awsRegionCrossModelPrefixMap
=
map
[
string
]
string
{
var
awsRegionCrossModelPrefixMap
=
map
[
string
]
string
{
...
...
relay/channel/aws/relay-aws.go
View file @
05cf55ac
...
@@ -19,20 +19,31 @@ import (
...
@@ -19,20 +19,31 @@ import (
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
bedrockruntimeTypes
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
bedrockruntimeTypes
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
"github.com/aws/smithy-go/auth/bearer"
)
)
func
newAwsClient
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
)
(
*
bedrockruntime
.
Client
,
error
)
{
func
newAwsClient
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
)
(
*
bedrockruntime
.
Client
,
error
)
{
awsSecret
:=
strings
.
Split
(
info
.
ApiKey
,
"|"
)
awsSecret
:=
strings
.
Split
(
info
.
ApiKey
,
"|"
)
if
len
(
awsSecret
)
!=
3
{
var
client
*
bedrockruntime
.
Client
return
nil
,
errors
.
New
(
"invalid aws secret key"
)
switch
len
(
awsSecret
)
{
}
case
2
:
apiKey
:=
awsSecret
[
0
]
region
:=
awsSecret
[
1
]
client
=
bedrockruntime
.
New
(
bedrockruntime
.
Options
{
Region
:
region
,
BearerAuthTokenProvider
:
bearer
.
StaticTokenProvider
{
Token
:
bearer
.
Token
{
Value
:
apiKey
}},
})
case
3
:
ak
:=
awsSecret
[
0
]
ak
:=
awsSecret
[
0
]
sk
:=
awsSecret
[
1
]
sk
:=
awsSecret
[
1
]
region
:=
awsSecret
[
2
]
region
:=
awsSecret
[
2
]
client
:
=
bedrockruntime
.
New
(
bedrockruntime
.
Options
{
client
=
bedrockruntime
.
New
(
bedrockruntime
.
Options
{
Region
:
region
,
Region
:
region
,
Credentials
:
aws
.
NewCredentialsCache
(
credentials
.
NewStaticCredentialsProvider
(
ak
,
sk
,
""
)),
Credentials
:
aws
.
NewCredentialsCache
(
credentials
.
NewStaticCredentialsProvider
(
ak
,
sk
,
""
)),
})
})
default
:
return
nil
,
errors
.
New
(
"invalid aws secret key"
)
}
return
client
,
nil
return
client
,
nil
}
}
...
...
relay/channel/claude/constants.go
View file @
05cf55ac
...
@@ -17,6 +17,8 @@ var ModelList = []string{
...
@@ -17,6 +17,8 @@ var ModelList = []string{
"claude-sonnet-4-20250514-thinking"
,
"claude-sonnet-4-20250514-thinking"
,
"claude-opus-4-20250514"
,
"claude-opus-4-20250514"
,
"claude-opus-4-20250514-thinking"
,
"claude-opus-4-20250514-thinking"
,
"claude-opus-4-1-20250805"
,
"claude-opus-4-1-20250805-thinking"
,
}
}
var
ChannelName
=
"claude"
var
ChannelName
=
"claude"
relay/channel/gemini/relay-gemini.go
View file @
05cf55ac
...
@@ -49,12 +49,20 @@ const (
...
@@ -49,12 +49,20 @@ const (
flash25LiteMaxBudget
=
24576
flash25LiteMaxBudget
=
24576
)
)
// clampThinkingBudget 根据模型名称将预算限制在允许的范围内
func
isNew25ProModel
(
modelName
string
)
bool
{
func
clampThinkingBudget
(
modelName
string
,
budget
int
)
int
{
return
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro"
)
&&
isNew25Pro
:=
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro"
)
&&
!
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro-preview-05-06"
)
&&
!
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro-preview-05-06"
)
&&
!
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro-preview-03-25"
)
!
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro-preview-03-25"
)
is25FlashLite
:=
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-flash-lite"
)
}
func
is25FlashLiteModel
(
modelName
string
)
bool
{
return
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-flash-lite"
)
}
// clampThinkingBudget 根据模型名称将预算限制在允许的范围内
func
clampThinkingBudget
(
modelName
string
,
budget
int
)
int
{
isNew25Pro
:=
isNew25ProModel
(
modelName
)
is25FlashLite
:=
is25FlashLiteModel
(
modelName
)
if
is25FlashLite
{
if
is25FlashLite
{
if
budget
<
flash25LiteMinBudget
{
if
budget
<
flash25LiteMinBudget
{
...
@@ -81,7 +89,34 @@ func clampThinkingBudget(modelName string, budget int) int {
...
@@ -81,7 +89,34 @@ func clampThinkingBudget(modelName string, budget int) int {
return
budget
return
budget
}
}
func
ThinkingAdaptor
(
geminiRequest
*
dto
.
GeminiChatRequest
,
info
*
relaycommon
.
RelayInfo
)
{
// "effort": "high" - Allocates a large portion of tokens for reasoning (approximately 80% of max_tokens)
// "effort": "medium" - Allocates a moderate portion of tokens (approximately 50% of max_tokens)
// "effort": "low" - Allocates a smaller portion of tokens (approximately 20% of max_tokens)
func
clampThinkingBudgetByEffort
(
modelName
string
,
effort
string
)
int
{
isNew25Pro
:=
isNew25ProModel
(
modelName
)
is25FlashLite
:=
is25FlashLiteModel
(
modelName
)
maxBudget
:=
0
if
is25FlashLite
{
maxBudget
=
flash25LiteMaxBudget
}
if
isNew25Pro
{
maxBudget
=
pro25MaxBudget
}
else
{
maxBudget
=
flash25MaxBudget
}
switch
effort
{
case
"high"
:
maxBudget
=
maxBudget
*
80
/
100
case
"medium"
:
maxBudget
=
maxBudget
*
50
/
100
case
"low"
:
maxBudget
=
maxBudget
*
20
/
100
}
return
clampThinkingBudget
(
modelName
,
maxBudget
)
}
func
ThinkingAdaptor
(
geminiRequest
*
dto
.
GeminiChatRequest
,
info
*
relaycommon
.
RelayInfo
,
oaiRequest
...
dto
.
GeneralOpenAIRequest
)
{
if
model_setting
.
GetGeminiSettings
()
.
ThinkingAdapterEnabled
{
if
model_setting
.
GetGeminiSettings
()
.
ThinkingAdapterEnabled
{
modelName
:=
info
.
UpstreamModelName
modelName
:=
info
.
UpstreamModelName
isNew25Pro
:=
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro"
)
&&
isNew25Pro
:=
strings
.
HasPrefix
(
modelName
,
"gemini-2.5-pro"
)
&&
...
@@ -124,6 +159,11 @@ func ThinkingAdaptor(geminiRequest *dto.GeminiChatRequest, info *relaycommon.Rel
...
@@ -124,6 +159,11 @@ func ThinkingAdaptor(geminiRequest *dto.GeminiChatRequest, info *relaycommon.Rel
budgetTokens
:=
model_setting
.
GetGeminiSettings
()
.
ThinkingAdapterBudgetTokensPercentage
*
float64
(
geminiRequest
.
GenerationConfig
.
MaxOutputTokens
)
budgetTokens
:=
model_setting
.
GetGeminiSettings
()
.
ThinkingAdapterBudgetTokensPercentage
*
float64
(
geminiRequest
.
GenerationConfig
.
MaxOutputTokens
)
clampedBudget
:=
clampThinkingBudget
(
modelName
,
int
(
budgetTokens
))
clampedBudget
:=
clampThinkingBudget
(
modelName
,
int
(
budgetTokens
))
geminiRequest
.
GenerationConfig
.
ThinkingConfig
.
ThinkingBudget
=
common
.
GetPointer
(
clampedBudget
)
geminiRequest
.
GenerationConfig
.
ThinkingConfig
.
ThinkingBudget
=
common
.
GetPointer
(
clampedBudget
)
}
else
{
if
len
(
oaiRequest
)
>
0
{
// 如果有reasoningEffort参数,则根据其值设置思考预算
geminiRequest
.
GenerationConfig
.
ThinkingConfig
.
ThinkingBudget
=
common
.
GetPointer
(
clampThinkingBudgetByEffort
(
modelName
,
oaiRequest
[
0
]
.
ReasoningEffort
))
}
}
}
}
}
}
else
if
strings
.
HasSuffix
(
modelName
,
"-nothinking"
)
{
}
else
if
strings
.
HasSuffix
(
modelName
,
"-nothinking"
)
{
...
@@ -156,7 +196,37 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon
...
@@ -156,7 +196,37 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon
}
}
}
}
ThinkingAdaptor
(
&
geminiRequest
,
info
)
adaptorWithExtraBody
:=
false
if
len
(
textRequest
.
ExtraBody
)
>
0
{
if
!
strings
.
HasSuffix
(
info
.
UpstreamModelName
,
"-nothinking"
)
{
var
extraBody
map
[
string
]
interface
{}
if
err
:=
common
.
Unmarshal
(
textRequest
.
ExtraBody
,
&
extraBody
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid extra body: %w"
,
err
)
}
// eg. {"google":{"thinking_config":{"thinking_budget":5324,"include_thoughts":true}}}
if
googleBody
,
ok
:=
extraBody
[
"google"
]
.
(
map
[
string
]
interface
{});
ok
{
adaptorWithExtraBody
=
true
if
thinkingConfig
,
ok
:=
googleBody
[
"thinking_config"
]
.
(
map
[
string
]
interface
{});
ok
{
if
budget
,
ok
:=
thinkingConfig
[
"thinking_budget"
]
.
(
float64
);
ok
{
budgetInt
:=
int
(
budget
)
geminiRequest
.
GenerationConfig
.
ThinkingConfig
=
&
dto
.
GeminiThinkingConfig
{
ThinkingBudget
:
common
.
GetPointer
(
budgetInt
),
IncludeThoughts
:
true
,
}
}
else
{
geminiRequest
.
GenerationConfig
.
ThinkingConfig
=
&
dto
.
GeminiThinkingConfig
{
IncludeThoughts
:
true
,
}
}
}
}
}
}
if
!
adaptorWithExtraBody
{
ThinkingAdaptor
(
&
geminiRequest
,
info
,
textRequest
)
}
safetySettings
:=
make
([]
dto
.
GeminiChatSafetySettings
,
0
,
len
(
SafetySettingList
))
safetySettings
:=
make
([]
dto
.
GeminiChatSafetySettings
,
0
,
len
(
SafetySettingList
))
for
_
,
category
:=
range
SafetySettingList
{
for
_
,
category
:=
range
SafetySettingList
{
...
...
relay/channel/openai/adaptor.go
View file @
05cf55ac
...
@@ -9,6 +9,7 @@ import (
...
@@ -9,6 +9,7 @@ import (
"mime/multipart"
"mime/multipart"
"net/http"
"net/http"
"net/textproto"
"net/textproto"
"one-api/common"
"one-api/constant"
"one-api/constant"
"one-api/dto"
"one-api/dto"
"one-api/relay/channel"
"one-api/relay/channel"
...
@@ -172,6 +173,23 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
...
@@ -172,6 +173,23 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
if
len
(
request
.
Usage
)
==
0
{
if
len
(
request
.
Usage
)
==
0
{
request
.
Usage
=
json
.
RawMessage
(
`{"include":true}`
)
request
.
Usage
=
json
.
RawMessage
(
`{"include":true}`
)
}
}
if
strings
.
HasSuffix
(
info
.
UpstreamModelName
,
"-thinking"
)
{
info
.
UpstreamModelName
=
strings
.
TrimSuffix
(
info
.
UpstreamModelName
,
"-thinking"
)
request
.
Model
=
info
.
UpstreamModelName
if
len
(
request
.
Reasoning
)
==
0
{
reasoning
:=
map
[
string
]
any
{
"enabled"
:
true
,
}
if
request
.
ReasoningEffort
!=
""
{
reasoning
[
"effort"
]
=
request
.
ReasoningEffort
}
marshal
,
err
:=
common
.
Marshal
(
reasoning
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error marshalling reasoning: %w"
,
err
)
}
request
.
Reasoning
=
marshal
}
}
}
}
if
strings
.
HasPrefix
(
request
.
Model
,
"o"
)
{
if
strings
.
HasPrefix
(
request
.
Model
,
"o"
)
{
if
request
.
MaxCompletionTokens
==
0
&&
request
.
MaxTokens
!=
0
{
if
request
.
MaxCompletionTokens
==
0
&&
request
.
MaxTokens
!=
0
{
...
...
relay/channel/openai/relay_responses.go
View file @
05cf55ac
...
@@ -37,9 +37,14 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
...
@@ -37,9 +37,14 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
// compute usage
// compute usage
usage
:=
dto
.
Usage
{}
usage
:=
dto
.
Usage
{}
if
responsesResponse
.
Usage
!=
nil
{
usage
.
PromptTokens
=
responsesResponse
.
Usage
.
InputTokens
usage
.
PromptTokens
=
responsesResponse
.
Usage
.
InputTokens
usage
.
CompletionTokens
=
responsesResponse
.
Usage
.
OutputTokens
usage
.
CompletionTokens
=
responsesResponse
.
Usage
.
OutputTokens
usage
.
TotalTokens
=
responsesResponse
.
Usage
.
TotalTokens
usage
.
TotalTokens
=
responsesResponse
.
Usage
.
TotalTokens
if
responsesResponse
.
Usage
.
InputTokensDetails
!=
nil
{
usage
.
PromptTokensDetails
.
CachedTokens
=
responsesResponse
.
Usage
.
InputTokensDetails
.
CachedTokens
}
}
// 解析 Tools 用量
// 解析 Tools 用量
for
_
,
tool
:=
range
responsesResponse
.
Tools
{
for
_
,
tool
:=
range
responsesResponse
.
Tools
{
info
.
ResponsesUsageInfo
.
BuiltInTools
[
common
.
Interface2String
(
tool
[
"type"
])]
.
CallCount
++
info
.
ResponsesUsageInfo
.
BuiltInTools
[
common
.
Interface2String
(
tool
[
"type"
])]
.
CallCount
++
...
@@ -64,9 +69,14 @@ func OaiResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
...
@@ -64,9 +69,14 @@ 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
.
Usage
!=
nil
{
usage
.
PromptTokens
=
streamResponse
.
Response
.
Usage
.
InputTokens
usage
.
PromptTokens
=
streamResponse
.
Response
.
Usage
.
InputTokens
usage
.
CompletionTokens
=
streamResponse
.
Response
.
Usage
.
OutputTokens
usage
.
CompletionTokens
=
streamResponse
.
Response
.
Usage
.
OutputTokens
usage
.
TotalTokens
=
streamResponse
.
Response
.
Usage
.
TotalTokens
usage
.
TotalTokens
=
streamResponse
.
Response
.
Usage
.
TotalTokens
if
streamResponse
.
Response
.
Usage
.
InputTokensDetails
!=
nil
{
usage
.
PromptTokensDetails
.
CachedTokens
=
streamResponse
.
Response
.
Usage
.
InputTokensDetails
.
CachedTokens
}
}
case
"response.output_text.delta"
:
case
"response.output_text.delta"
:
// 处理输出文本
// 处理输出文本
responseTextBuilder
.
WriteString
(
streamResponse
.
Delta
)
responseTextBuilder
.
WriteString
(
streamResponse
.
Delta
)
...
...
relay/channel/vertex/adaptor.go
View file @
05cf55ac
...
@@ -35,6 +35,7 @@ var claudeModelMap = map[string]string{
...
@@ -35,6 +35,7 @@ var claudeModelMap = map[string]string{
"claude-3-7-sonnet-20250219"
:
"claude-3-7-sonnet@20250219"
,
"claude-3-7-sonnet-20250219"
:
"claude-3-7-sonnet@20250219"
,
"claude-sonnet-4-20250514"
:
"claude-sonnet-4@20250514"
,
"claude-sonnet-4-20250514"
:
"claude-sonnet-4@20250514"
,
"claude-opus-4-20250514"
:
"claude-opus-4@20250514"
,
"claude-opus-4-20250514"
:
"claude-opus-4@20250514"
,
"claude-opus-4-1-20250805"
:
"claude-opus-4-1@20250805"
,
}
}
const
anthropicVersion
=
"vertex-2023-10-16"
const
anthropicVersion
=
"vertex-2023-10-16"
...
...
setting/ratio_setting/cache_ratio.go
View file @
05cf55ac
...
@@ -40,6 +40,8 @@ var defaultCacheRatio = map[string]float64{
...
@@ -40,6 +40,8 @@ var defaultCacheRatio = map[string]float64{
"claude-sonnet-4-20250514-thinking"
:
0.1
,
"claude-sonnet-4-20250514-thinking"
:
0.1
,
"claude-opus-4-20250514"
:
0.1
,
"claude-opus-4-20250514"
:
0.1
,
"claude-opus-4-20250514-thinking"
:
0.1
,
"claude-opus-4-20250514-thinking"
:
0.1
,
"claude-opus-4-1-20250805"
:
0.1
,
"claude-opus-4-1-20250805-thinking"
:
0.1
,
}
}
var
defaultCreateCacheRatio
=
map
[
string
]
float64
{
var
defaultCreateCacheRatio
=
map
[
string
]
float64
{
...
@@ -55,6 +57,8 @@ var defaultCreateCacheRatio = map[string]float64{
...
@@ -55,6 +57,8 @@ var defaultCreateCacheRatio = map[string]float64{
"claude-sonnet-4-20250514-thinking"
:
1.25
,
"claude-sonnet-4-20250514-thinking"
:
1.25
,
"claude-opus-4-20250514"
:
1.25
,
"claude-opus-4-20250514"
:
1.25
,
"claude-opus-4-20250514-thinking"
:
1.25
,
"claude-opus-4-20250514-thinking"
:
1.25
,
"claude-opus-4-1-20250805"
:
1.25
,
"claude-opus-4-1-20250805-thinking"
:
1.25
,
}
}
//var defaultCreateCacheRatio = map[string]float64{}
//var defaultCreateCacheRatio = map[string]float64{}
...
...
setting/ratio_setting/model_ratio.go
View file @
05cf55ac
...
@@ -118,6 +118,7 @@ var defaultModelRatio = map[string]float64{
...
@@ -118,6 +118,7 @@ var defaultModelRatio = map[string]float64{
"claude-sonnet-4-20250514"
:
1.5
,
"claude-sonnet-4-20250514"
:
1.5
,
"claude-3-opus-20240229"
:
7.5
,
// $15 / 1M tokens
"claude-3-opus-20240229"
:
7.5
,
// $15 / 1M tokens
"claude-opus-4-20250514"
:
7.5
,
"claude-opus-4-20250514"
:
7.5
,
"claude-opus-4-1-20250805"
:
7.5
,
"ERNIE-4.0-8K"
:
0.120
*
RMB
,
"ERNIE-4.0-8K"
:
0.120
*
RMB
,
"ERNIE-3.5-8K"
:
0.012
*
RMB
,
"ERNIE-3.5-8K"
:
0.012
*
RMB
,
"ERNIE-3.5-8K-0205"
:
0.024
*
RMB
,
"ERNIE-3.5-8K-0205"
:
0.024
*
RMB
,
...
@@ -334,12 +335,8 @@ func GetModelPrice(name string, printErr bool) (float64, bool) {
...
@@ -334,12 +335,8 @@ func GetModelPrice(name string, printErr bool) (float64, bool) {
modelPriceMapMutex
.
RLock
()
modelPriceMapMutex
.
RLock
()
defer
modelPriceMapMutex
.
RUnlock
()
defer
modelPriceMapMutex
.
RUnlock
()
if
strings
.
HasPrefix
(
name
,
"gpt-4-gizmo"
)
{
name
=
FormatMatchingModelName
(
name
)
name
=
"gpt-4-gizmo-*"
}
if
strings
.
HasPrefix
(
name
,
"gpt-4o-gizmo"
)
{
name
=
"gpt-4o-gizmo-*"
}
price
,
ok
:=
modelPriceMap
[
name
]
price
,
ok
:=
modelPriceMap
[
name
]
if
!
ok
{
if
!
ok
{
if
printErr
{
if
printErr
{
...
@@ -373,11 +370,8 @@ func GetModelRatio(name string) (float64, bool, string) {
...
@@ -373,11 +370,8 @@ func GetModelRatio(name string) (float64, bool, string) {
modelRatioMapMutex
.
RLock
()
modelRatioMapMutex
.
RLock
()
defer
modelRatioMapMutex
.
RUnlock
()
defer
modelRatioMapMutex
.
RUnlock
()
name
=
handleThinkingBudgetModel
(
name
,
"gemini-2.5-flash"
,
"gemini-2.5-flash-thinking-*"
)
name
=
FormatMatchingModelName
(
name
)
name
=
handleThinkingBudgetModel
(
name
,
"gemini-2.5-pro"
,
"gemini-2.5-pro-thinking-*"
)
if
strings
.
HasPrefix
(
name
,
"gpt-4-gizmo"
)
{
name
=
"gpt-4-gizmo-*"
}
ratio
,
ok
:=
modelRatioMap
[
name
]
ratio
,
ok
:=
modelRatioMap
[
name
]
if
!
ok
{
if
!
ok
{
return
37.5
,
operation_setting
.
SelfUseModeEnabled
,
name
return
37.5
,
operation_setting
.
SelfUseModeEnabled
,
name
...
@@ -428,12 +422,9 @@ func UpdateCompletionRatioByJSONString(jsonStr string) error {
...
@@ -428,12 +422,9 @@ func UpdateCompletionRatioByJSONString(jsonStr string) error {
func
GetCompletionRatio
(
name
string
)
float64
{
func
GetCompletionRatio
(
name
string
)
float64
{
CompletionRatioMutex
.
RLock
()
CompletionRatioMutex
.
RLock
()
defer
CompletionRatioMutex
.
RUnlock
()
defer
CompletionRatioMutex
.
RUnlock
()
if
strings
.
HasPrefix
(
name
,
"gpt-4-gizmo"
)
{
name
=
"gpt-4-gizmo-*"
name
=
FormatMatchingModelName
(
name
)
}
if
strings
.
HasPrefix
(
name
,
"gpt-4o-gizmo"
)
{
name
=
"gpt-4o-gizmo-*"
}
if
strings
.
Contains
(
name
,
"/"
)
{
if
strings
.
Contains
(
name
,
"/"
)
{
if
ratio
,
ok
:=
CompletionRatio
[
name
];
ok
{
if
ratio
,
ok
:=
CompletionRatio
[
name
];
ok
{
return
ratio
return
ratio
...
@@ -663,3 +654,16 @@ func GetCompletionRatioCopy() map[string]float64 {
...
@@ -663,3 +654,16 @@ func GetCompletionRatioCopy() map[string]float64 {
}
}
return
copyMap
return
copyMap
}
}
// 转换模型名,减少渠道必须配置各种带参数模型
func
FormatMatchingModelName
(
name
string
)
string
{
name
=
handleThinkingBudgetModel
(
name
,
"gemini-2.5-flash"
,
"gemini-2.5-flash-thinking-*"
)
name
=
handleThinkingBudgetModel
(
name
,
"gemini-2.5-pro"
,
"gemini-2.5-pro-thinking-*"
)
if
strings
.
HasPrefix
(
name
,
"gpt-4-gizmo"
)
{
name
=
"gpt-4-gizmo-*"
}
if
strings
.
HasPrefix
(
name
,
"gpt-4o-gizmo"
)
{
name
=
"gpt-4o-gizmo-*"
}
return
name
}
types/error.go
View file @
05cf55ac
...
@@ -189,9 +189,13 @@ func NewError(err error, errorCode ErrorCode, ops ...NewAPIErrorOptions) *NewAPI
...
@@ -189,9 +189,13 @@ func NewError(err error, errorCode ErrorCode, ops ...NewAPIErrorOptions) *NewAPI
}
}
func
NewOpenAIError
(
err
error
,
errorCode
ErrorCode
,
statusCode
int
,
ops
...
NewAPIErrorOptions
)
*
NewAPIError
{
func
NewOpenAIError
(
err
error
,
errorCode
ErrorCode
,
statusCode
int
,
ops
...
NewAPIErrorOptions
)
*
NewAPIError
{
if
errorCode
==
ErrorCodeDoRequestFailed
{
err
=
errors
.
New
(
"upstream error: do request failed"
)
}
openaiError
:=
OpenAIError
{
openaiError
:=
OpenAIError
{
Message
:
err
.
Error
(),
Message
:
err
.
Error
(),
Type
:
string
(
errorCode
),
Type
:
string
(
errorCode
),
Code
:
errorCode
,
}
}
return
WithOpenAIError
(
openaiError
,
statusCode
,
ops
...
)
return
WithOpenAIError
(
openaiError
,
statusCode
,
ops
...
)
}
}
...
@@ -199,6 +203,7 @@ func NewOpenAIError(err error, errorCode ErrorCode, statusCode int, ops ...NewAP
...
@@ -199,6 +203,7 @@ func NewOpenAIError(err error, errorCode ErrorCode, statusCode int, ops ...NewAP
func
InitOpenAIError
(
errorCode
ErrorCode
,
statusCode
int
,
ops
...
NewAPIErrorOptions
)
*
NewAPIError
{
func
InitOpenAIError
(
errorCode
ErrorCode
,
statusCode
int
,
ops
...
NewAPIErrorOptions
)
*
NewAPIError
{
openaiError
:=
OpenAIError
{
openaiError
:=
OpenAIError
{
Type
:
string
(
errorCode
),
Type
:
string
(
errorCode
),
Code
:
errorCode
,
}
}
return
WithOpenAIError
(
openaiError
,
statusCode
,
ops
...
)
return
WithOpenAIError
(
openaiError
,
statusCode
,
ops
...
)
}
}
...
@@ -224,7 +229,11 @@ func NewErrorWithStatusCode(err error, errorCode ErrorCode, statusCode int, ops
...
@@ -224,7 +229,11 @@ func NewErrorWithStatusCode(err error, errorCode ErrorCode, statusCode int, ops
func
WithOpenAIError
(
openAIError
OpenAIError
,
statusCode
int
,
ops
...
NewAPIErrorOptions
)
*
NewAPIError
{
func
WithOpenAIError
(
openAIError
OpenAIError
,
statusCode
int
,
ops
...
NewAPIErrorOptions
)
*
NewAPIError
{
code
,
ok
:=
openAIError
.
Code
.
(
string
)
code
,
ok
:=
openAIError
.
Code
.
(
string
)
if
!
ok
{
if
!
ok
{
if
openAIError
.
Code
==
nil
{
code
=
fmt
.
Sprintf
(
"%v"
,
openAIError
.
Code
)
code
=
fmt
.
Sprintf
(
"%v"
,
openAIError
.
Code
)
}
else
{
code
=
"unknown_error"
}
}
}
if
openAIError
.
Type
==
""
{
if
openAIError
.
Type
==
""
{
openAIError
.
Type
=
"upstream_error"
openAIError
.
Type
=
"upstream_error"
...
...
web/src/helpers/render.js
View file @
05cf55ac
...
@@ -1182,6 +1182,7 @@ export function renderLogContent(
...
@@ -1182,6 +1182,7 @@ export function renderLogContent(
modelPrice
=
-
1
,
modelPrice
=
-
1
,
groupRatio
,
groupRatio
,
user_group_ratio
,
user_group_ratio
,
cacheRatio
=
1.0
,
image
=
false
,
image
=
false
,
imageRatio
=
1.0
,
imageRatio
=
1.0
,
webSearch
=
false
,
webSearch
=
false
,
...
@@ -1200,9 +1201,10 @@ export function renderLogContent(
...
@@ -1200,9 +1201,10 @@ export function renderLogContent(
}
else
{
}
else
{
if
(
image
)
{
if
(
image
)
{
return
i18next
.
t
(
return
i18next
.
t
(
'模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}'
,
'模型倍率 {{modelRatio}},
缓存倍率 {{cacheRatio}},
输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}'
,
{
{
modelRatio
:
modelRatio
,
modelRatio
:
modelRatio
,
cacheRatio
:
cacheRatio
,
completionRatio
:
completionRatio
,
completionRatio
:
completionRatio
,
imageRatio
:
imageRatio
,
imageRatio
:
imageRatio
,
ratioType
:
ratioLabel
,
ratioType
:
ratioLabel
,
...
@@ -1211,9 +1213,10 @@ export function renderLogContent(
...
@@ -1211,9 +1213,10 @@ export function renderLogContent(
);
);
}
else
if
(
webSearch
)
{
}
else
if
(
webSearch
)
{
return
i18next
.
t
(
return
i18next
.
t
(
'模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}},Web 搜索调用 {{webSearchCallCount}} 次'
,
'模型倍率 {{modelRatio}},
缓存倍率 {{cacheRatio}},
输出倍率 {{completionRatio}},{{ratioType}} {{ratio}},Web 搜索调用 {{webSearchCallCount}} 次'
,
{
{
modelRatio
:
modelRatio
,
modelRatio
:
modelRatio
,
cacheRatio
:
cacheRatio
,
completionRatio
:
completionRatio
,
completionRatio
:
completionRatio
,
ratioType
:
ratioLabel
,
ratioType
:
ratioLabel
,
ratio
,
ratio
,
...
@@ -1222,9 +1225,10 @@ export function renderLogContent(
...
@@ -1222,9 +1225,10 @@ export function renderLogContent(
);
);
}
else
{
}
else
{
return
i18next
.
t
(
return
i18next
.
t
(
'模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}'
,
'模型倍率 {{modelRatio}},
缓存倍率 {{cacheRatio}},
输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}'
,
{
{
modelRatio
:
modelRatio
,
modelRatio
:
modelRatio
,
cacheRatio
:
cacheRatio
,
completionRatio
:
completionRatio
,
completionRatio
:
completionRatio
,
ratioType
:
ratioLabel
,
ratioType
:
ratioLabel
,
ratio
,
ratio
,
...
...
web/src/hooks/usage-logs/useUsageLogsData.js
View file @
05cf55ac
...
@@ -366,6 +366,7 @@ export const useLogsData = () => {
...
@@ -366,6 +366,7 @@ export const useLogsData = () => {
other
.
model_price
,
other
.
model_price
,
other
.
group_ratio
,
other
.
group_ratio
,
other
?.
user_group_ratio
,
other
?.
user_group_ratio
,
other
.
cache_ratio
||
1.0
,
false
,
false
,
1.0
,
1.0
,
other
.
web_search
||
false
,
other
.
web_search
||
false
,
...
...
web/src/pages/Setting/Ratio/ModelSettingsVisualEditor.js
View file @
05cf55ac
...
@@ -44,6 +44,7 @@ export default function ModelSettingsVisualEditor(props) {
...
@@ -44,6 +44,7 @@ export default function ModelSettingsVisualEditor(props) {
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
[
models
,
setModels
]
=
useState
([]);
const
[
models
,
setModels
]
=
useState
([]);
const
[
visible
,
setVisible
]
=
useState
(
false
);
const
[
visible
,
setVisible
]
=
useState
(
false
);
const
[
isEditMode
,
setIsEditMode
]
=
useState
(
false
);
const
[
currentModel
,
setCurrentModel
]
=
useState
(
null
);
const
[
currentModel
,
setCurrentModel
]
=
useState
(
null
);
const
[
searchText
,
setSearchText
]
=
useState
(
''
);
const
[
searchText
,
setSearchText
]
=
useState
(
''
);
const
[
currentPage
,
setCurrentPage
]
=
useState
(
1
);
const
[
currentPage
,
setCurrentPage
]
=
useState
(
1
);
...
@@ -386,9 +387,11 @@ export default function ModelSettingsVisualEditor(props) {
...
@@ -386,9 +387,11 @@ export default function ModelSettingsVisualEditor(props) {
setCurrentModel
(
null
);
setCurrentModel
(
null
);
setPricingMode
(
'per-token'
);
setPricingMode
(
'per-token'
);
setPricingSubMode
(
'ratio'
);
setPricingSubMode
(
'ratio'
);
setIsEditMode
(
false
);
};
};
const
editModel
=
(
record
)
=>
{
const
editModel
=
(
record
)
=>
{
setIsEditMode
(
true
);
// Determine which pricing mode to use based on the model's current configuration
// Determine which pricing mode to use based on the model's current configuration
let
initialPricingMode
=
'per-token'
;
let
initialPricingMode
=
'per-token'
;
let
initialPricingSubMode
=
'ratio'
;
let
initialPricingSubMode
=
'ratio'
;
...
@@ -500,13 +503,7 @@ export default function ModelSettingsVisualEditor(props) {
...
@@ -500,13 +503,7 @@ export default function ModelSettingsVisualEditor(props) {
<
/Space
>
<
/Space
>
<
Modal
<
Modal
title
=
{
title
=
{
isEditMode
?
t
(
'编辑模型'
)
:
t
(
'添加模型'
)}
currentModel
&&
currentModel
.
name
&&
models
.
some
((
model
)
=>
model
.
name
===
currentModel
.
name
)
?
t
(
'编辑模型'
)
:
t
(
'添加模型'
)
}
visible
=
{
visible
}
visible
=
{
visible
}
onCancel
=
{()
=>
{
onCancel
=
{()
=>
{
resetModalState
();
resetModalState
();
...
@@ -562,11 +559,7 @@ export default function ModelSettingsVisualEditor(props) {
...
@@ -562,11 +559,7 @@ export default function ModelSettingsVisualEditor(props) {
label
=
{
t
(
'模型名称'
)}
label
=
{
t
(
'模型名称'
)}
placeholder
=
'strawberry'
placeholder
=
'strawberry'
required
required
disabled
=
{
disabled
=
{
isEditMode
}
currentModel
&&
currentModel
.
name
&&
models
.
some
((
model
)
=>
model
.
name
===
currentModel
.
name
)
}
onChange
=
{(
value
)
=>
onChange
=
{(
value
)
=>
setCurrentModel
((
prev
)
=>
({
...
prev
,
name
:
value
}))
setCurrentModel
((
prev
)
=>
({
...
prev
,
name
:
value
}))
}
}
...
...
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