Merge branch 'main' into main

This commit is contained in:
kuwork
2025-02-04 22:52:37 +08:00
committed by GitHub
42 changed files with 385 additions and 110 deletions

View File

@@ -17,7 +17,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18
- name: Build Frontend - name: Build Frontend
env: env:
CI: "" CI: ""

View File

@@ -17,7 +17,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18
- name: Build Frontend - name: Build Frontend
env: env:
CI: "" CI: ""

View File

@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18
- name: Build Frontend - name: Build Frontend
env: env:
CI: "" CI: ""

View File

@@ -24,8 +24,8 @@ FROM alpine
RUN apk update \ RUN apk update \
&& apk upgrade \ && apk upgrade \
&& apk add --no-cache ca-certificates tzdata ffmpeg\ && apk add --no-cache ca-certificates tzdata ffmpeg \
&& update-ca-certificates 2>/dev/null || true && update-ca-certificates
COPY --from=builder2 /build/one-api / COPY --from=builder2 /build/one-api /
EXPOSE 3000 EXPOSE 3000

View File

@@ -59,6 +59,10 @@
13. 🎵 Added [Suno API](https://github.com/Suno-API/Suno-API) interface support, [Integration Guide](Suno.md) 13. 🎵 Added [Suno API](https://github.com/Suno-API/Suno-API) interface support, [Integration Guide](Suno.md)
14. 🔄 Support for Rerank models, compatible with Cohere and Jina, can integrate with Dify, [Integration Guide](Rerank.md) 14. 🔄 Support for Rerank models, compatible with Cohere and Jina, can integrate with Dify, [Integration Guide](Rerank.md)
15.**[OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/integration)** - Support for OpenAI's Realtime API, including Azure channels 15.**[OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/integration)** - Support for OpenAI's Realtime API, including Azure channels
16. 🧠 Support for setting reasoning effort through model name suffix:
- Add suffix `-high` to set high reasoning effort (e.g., `o3-mini-high`)
- Add suffix `-medium` to set medium reasoning effort
- Add suffix `-low` to set low reasoning effort
## Model Support ## Model Support
This version additionally supports: This version additionally supports:
@@ -84,6 +88,7 @@ You can add custom models gpt-4-gizmo-* in channels. These are third-party model
- `GEMINI_VISION_MAX_IMAGE_NUM`: Gemini model maximum image number, default `16`, set to `-1` to disable - `GEMINI_VISION_MAX_IMAGE_NUM`: Gemini model maximum image number, default `16`, set to `-1` to disable
- `MAX_FILE_DOWNLOAD_MB`: Maximum file download size in MB, default `20` - `MAX_FILE_DOWNLOAD_MB`: Maximum file download size in MB, default `20`
- `CRYPTO_SECRET`: Encryption key for encrypting database content - `CRYPTO_SECRET`: Encryption key for encrypting database content
- `AZURE_DEFAULT_API_VERSION`: Azure channel default API version, if not specified in channel settings, use this version, default `2024-12-01-preview`
## Deployment ## Deployment
> [!TIP] > [!TIP]

View File

@@ -65,6 +65,10 @@
14. 🔄 支持Rerank模型目前兼容Cohere和Jina可接入Dify[对接文档](Rerank.md) 14. 🔄 支持Rerank模型目前兼容Cohere和Jina可接入Dify[对接文档](Rerank.md)
15.**[OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/integration)** - 支持OpenAI的Realtime API支持Azure渠道 15.**[OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/integration)** - 支持OpenAI的Realtime API支持Azure渠道
16. 支持使用路由/chat2link 进入聊天界面 16. 支持使用路由/chat2link 进入聊天界面
17. 🧠 支持通过模型名称后缀设置 reasoning effort
- 添加后缀 `-high` 设置为 high reasoning effort (例如: `o3-mini-high`)
- 添加后缀 `-medium` 设置为 medium reasoning effort (例如: `o3-mini-medium`)
- 添加后缀 `-low` 设置为 low reasoning effort (例如: `o3-mini-low`)
## 模型支持 ## 模型支持
此版本额外支持以下模型: 此版本额外支持以下模型:
@@ -85,11 +89,12 @@
- `GET_MEDIA_TOKEN`是否统计图片token默认为 `true`关闭后将不再在本地计算图片token可能会导致和上游计费不同此项覆盖 `GET_MEDIA_TOKEN_NOT_STREAM` 选项作用。 - `GET_MEDIA_TOKEN`是否统计图片token默认为 `true`关闭后将不再在本地计算图片token可能会导致和上游计费不同此项覆盖 `GET_MEDIA_TOKEN_NOT_STREAM` 选项作用。
- `GET_MEDIA_TOKEN_NOT_STREAM`:是否在非流(`stream=false`情况下统计图片token默认为 `true` - `GET_MEDIA_TOKEN_NOT_STREAM`:是否在非流(`stream=false`情况下统计图片token默认为 `true`
- `UPDATE_TASK`是否更新异步任务Midjourney、Suno默认为 `true`,关闭后将不会更新任务进度。 - `UPDATE_TASK`是否更新异步任务Midjourney、Suno默认为 `true`,关闭后将不会更新任务进度。
- `GEMINI_MODEL_MAP`Gemini模型指定版本(v1/v1beta),使用模型:版本指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置(v1beta) - `GEMINI_MODEL_MAP`Gemini模型指定版本(v1/v1beta),使用"模型:版本"指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置(v1beta)
- `COHERE_SAFETY_SETTING`Cohere模型[安全设置](https://docs.cohere.com/docs/safety-modes#overview),可选值为 `NONE`, `CONTEXTUAL``STRICT`,默认为 `NONE` - `COHERE_SAFETY_SETTING`Cohere模型[安全设置](https://docs.cohere.com/docs/safety-modes#overview),可选值为 `NONE`, `CONTEXTUAL`, `STRICT`,默认为 `NONE`
- `GEMINI_VISION_MAX_IMAGE_NUM`Gemini模型最大图片数量默认为 `16`,设置为 `-1` 则不限制。 - `GEMINI_VISION_MAX_IMAGE_NUM`Gemini模型最大图片数量默认为 `16`,设置为 `-1` 则不限制。
- `MAX_FILE_DOWNLOAD_MB`: 最大文件下载大小,单位 MB默认为 `20` - `MAX_FILE_DOWNLOAD_MB`: 最大文件下载大小,单位 MB默认为 `20`
- `CRYPTO_SECRET`:加密密钥,用于加密数据库内容。 - `CRYPTO_SECRET`:加密密钥,用于加密数据库内容。
- `AZURE_DEFAULT_API_VERSION`Azure渠道默认API版本如果渠道设置中未指定API版本则使用此版本默认为 `2024-12-01-preview`
## 部署 ## 部署
> [!TIP] > [!TIP]
> 最新版Docker镜像`calciumion/new-api:latest` > 最新版Docker镜像`calciumion/new-api:latest`

View File

@@ -36,7 +36,7 @@ func SetupLogger() {
setupLogLock.Unlock() setupLogLock.Unlock()
setupLogWorking = false setupLogWorking = false
}() }()
logPath := filepath.Join(*LogDir, fmt.Sprintf("oneapi-%s.log", time.Now().Format("20060102"))) logPath := filepath.Join(*LogDir, fmt.Sprintf("oneapi-%s.log", time.Now().Format("20060102150405")))
fd, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) fd, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
log.Fatal("failed to open log file") log.Fatal("failed to open log file")

View File

@@ -50,16 +50,24 @@ var defaultModelRatio = map[string]float64{
"gpt-4o-realtime-preview-2024-12-17": 2.5, "gpt-4o-realtime-preview-2024-12-17": 2.5,
"gpt-4o-mini-realtime-preview": 0.3, "gpt-4o-mini-realtime-preview": 0.3,
"gpt-4o-mini-realtime-preview-2024-12-17": 0.3, "gpt-4o-mini-realtime-preview-2024-12-17": 0.3,
"o1": 7.5, "o1": 7.5,
"o1-2024-12-17": 7.5, "o1-2024-12-17": 7.5,
"o1-preview": 7.5, "o1-preview": 7.5,
"o1-preview-2024-09-12": 7.5, "o1-preview-2024-09-12": 7.5,
"o1-mini": 1.5, "o1-mini": 0.55,
"o1-mini-2024-09-12": 1.5, "o1-mini-2024-09-12": 0.55,
"gpt-4o-mini": 0.075, "o3-mini": 0.55,
"gpt-4o-mini-2024-07-18": 0.075, "o3-mini-2025-01-31": 0.55,
"gpt-4-turbo": 5, // $0.01 / 1K tokens "o3-mini-high": 0.55,
"gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens "o3-mini-2025-01-31-high": 0.55,
"o3-mini-low": 0.55,
"o3-mini-2025-01-31-low": 0.55,
"o3-mini-medium": 0.55,
"o3-mini-2025-01-31-medium": 0.55,
"gpt-4o-mini": 0.075,
"gpt-4o-mini-2024-07-18": 0.075,
"gpt-4-turbo": 5, // $0.01 / 1K tokens
"gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens
//"gpt-3.5-turbo-0301": 0.75, //deprecated //"gpt-3.5-turbo-0301": 0.75, //deprecated
"gpt-3.5-turbo": 0.25, "gpt-3.5-turbo": 0.25,
"gpt-3.5-turbo-0613": 0.75, "gpt-3.5-turbo-0613": 0.75,
@@ -342,6 +350,12 @@ func UpdateCompletionRatioByJSONString(jsonStr string) error {
} }
func GetCompletionRatio(name string) float64 { func GetCompletionRatio(name string) float64 {
if strings.Contains(name, "/") {
if ratio, ok := CompletionRatio[name]; ok {
return ratio
}
}
lowercaseName := strings.ToLower(name)
if strings.HasPrefix(name, "gpt-4-gizmo") { if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*" name = "gpt-4-gizmo-*"
} }
@@ -360,7 +374,7 @@ func GetCompletionRatio(name string) float64 {
} }
return 2 return 2
} }
if strings.HasPrefix(name, "o1") { if strings.HasPrefix(name, "o1") || strings.HasPrefix(name, "o3") {
return 4 return 4
} }
if name == "chatgpt-4o-latest" { if name == "chatgpt-4o-latest" {
@@ -404,8 +418,8 @@ func GetCompletionRatio(name string) float64 {
return 4 return 4
} }
} }
if strings.HasPrefix(name, "deepseek") { if strings.HasPrefix(lowercaseName, "deepseek") {
if name == "deepseek-reasoner" { if strings.HasSuffix(lowercaseName, "reasoner") || strings.HasSuffix(lowercaseName, "r1") {
return 4 return 4
} }
return 2 return 2

View File

@@ -1,5 +1,6 @@
package constant package constant
var ( var (
ForceFormat = "force_format" // ForceFormat 强制格式化为OpenAI格式 ForceFormat = "force_format" // ForceFormat 强制格式化为OpenAI格式
ChanelSettingProxy = "proxy" // Proxy 代理
) )

View File

@@ -21,6 +21,8 @@ var GetMediaTokenNotStream = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STR
var UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true) var UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
var AzureDefaultAPIVersion = common.GetEnvOrDefaultString("AZURE_DEFAULT_API_VERSION", "2024-12-01-preview")
var GeminiModelMap = map[string]string{ var GeminiModelMap = map[string]string{
"gemini-1.0-pro": "v1", "gemini-1.0-pro": "v1",
} }

View File

@@ -78,6 +78,36 @@ type APGC2DGPTUsageResponse struct {
TotalUsed float64 `json:"total_used"` TotalUsed float64 `json:"total_used"`
} }
type SiliconFlowUsageResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Status bool `json:"status"`
Data struct {
ID string `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
Email string `json:"email"`
IsAdmin bool `json:"isAdmin"`
Balance string `json:"balance"`
Status string `json:"status"`
Introduction string `json:"introduction"`
Role string `json:"role"`
ChargeBalance string `json:"chargeBalance"`
TotalBalance string `json:"totalBalance"`
Category string `json:"category"`
} `json:"data"`
}
type DeepSeekUsageResponse struct {
IsAvailable bool `json:"is_available"`
BalanceInfos []struct {
Currency string `json:"currency"`
TotalBalance string `json:"total_balance"`
GrantedBalance string `json:"granted_balance"`
ToppedUpBalance string `json:"topped_up_balance"`
} `json:"balance_infos"`
}
// GetAuthHeader get auth header // GetAuthHeader get auth header
func GetAuthHeader(token string) http.Header { func GetAuthHeader(token string) http.Header {
h := http.Header{} h := http.Header{}
@@ -185,6 +215,57 @@ func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
return response.TotalRemaining, nil return response.TotalRemaining, nil
} }
func updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {
url := "https://api.siliconflow.cn/v1/user/info"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
response := SiliconFlowUsageResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
if response.Code != 20000 {
return 0, fmt.Errorf("code: %d, message: %s", response.Code, response.Message)
}
balance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)
if err != nil {
return 0, err
}
channel.UpdateBalance(balance)
return balance, nil
}
func updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {
url := "https://api.deepseek.com/user/balance"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
response := DeepSeekUsageResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
index := -1
for i, balanceInfo := range response.BalanceInfos {
if balanceInfo.Currency == "CNY" {
index = i
break
}
}
if index == -1 {
return 0, errors.New("currency CNY not found")
}
balance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)
if err != nil {
return 0, err
}
channel.UpdateBalance(balance)
return balance, nil
}
func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) { func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
url := "https://api.aigc2d.com/dashboard/billing/credit_grants" url := "https://api.aigc2d.com/dashboard/billing/credit_grants"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key)) body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
@@ -222,6 +303,10 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
return updateChannelAPI2GPTBalance(channel) return updateChannelAPI2GPTBalance(channel)
case common.ChannelTypeAIGC2D: case common.ChannelTypeAIGC2D:
return updateChannelAIGC2DBalance(channel) return updateChannelAIGC2DBalance(channel)
case common.ChannelTypeSiliconFlow:
return updateChannelSiliconFlowBalance(channel)
case common.ChannelTypeDeepSeek:
return updateChannelDeepSeekBalance(channel)
default: default:
return 0, errors.New("尚未实现") return 0, errors.New("尚未实现")
} }
@@ -300,9 +385,9 @@ func updateAllChannelsBalance() error {
continue continue
} }
// TODO: support Azure // TODO: support Azure
if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom { //if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {
continue // continue
} //}
balance, err := updateChannelBalance(channel) balance, err := updateChannelBalance(channel)
if err != nil { if err != nil {
continue continue

View File

@@ -170,6 +170,7 @@ func buildTestRequest(model string) *dto.GeneralOpenAIRequest {
Model: "", // this will be set later Model: "", // this will be set later
Stream: false, Stream: false,
} }
// 先判断是否为 Embedding 模型 // 先判断是否为 Embedding 模型
if strings.Contains(strings.ToLower(model), "embedding") || if strings.Contains(strings.ToLower(model), "embedding") ||
strings.HasPrefix(model, "m3e") || // m3e 系列模型 strings.HasPrefix(model, "m3e") || // m3e 系列模型
@@ -180,10 +181,10 @@ func buildTestRequest(model string) *dto.GeneralOpenAIRequest {
return testRequest return testRequest
} }
// 并非Embedding 模型 // 并非Embedding 模型
if strings.HasPrefix(model, "o1") { if strings.HasPrefix(model, "o1") || strings.HasPrefix(model, "o3") {
testRequest.MaxCompletionTokens = 10 testRequest.MaxCompletionTokens = 10
} else if strings.HasPrefix(model, "gemini-2.0-flash-thinking") { } else if strings.HasPrefix(model, "gemini-2.0-flash-thinking") {
testRequest.MaxTokens = 2 testRequest.MaxTokens = 10
} else { } else {
testRequest.MaxTokens = 1 testRequest.MaxTokens = 1
} }

View File

@@ -510,6 +510,7 @@ func UpdateChannel(c *gin.Context) {
func FetchModels(c *gin.Context) { func FetchModels(c *gin.Context) {
var req struct { var req struct {
BaseURL string `json:"base_url"` BaseURL string `json:"base_url"`
Type int `json:"type"`
Key string `json:"key"` Key string `json:"key"`
} }
@@ -523,7 +524,7 @@ func FetchModels(c *gin.Context) {
baseURL := req.BaseURL baseURL := req.BaseURL
if baseURL == "" { if baseURL == "" {
baseURL = "https://api.openai.com" baseURL = common.ChannelBaseURLs[req.Type]
} }
client := &http.Client{} client := &http.Client{}
@@ -538,7 +539,11 @@ func FetchModels(c *gin.Context) {
return return
} }
request.Header.Set("Authorization", "Bearer "+req.Key) // remove line breaks and extra spaces.
key := strings.TrimSpace(req.Key)
// If the key contains a line break, only take the first part.
key = strings.Split(key, "\n")[0]
request.Header.Set("Authorization", "Bearer "+key)
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {

View File

@@ -66,6 +66,7 @@ func GetStatus(c *gin.Context) {
"enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "", "enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "",
"mj_notify_enabled": setting.MjNotifyEnabled, "mj_notify_enabled": setting.MjNotifyEnabled,
"chats": setting.Chats, "chats": setting.Chats,
"demo_site_enabled": setting.DemoSiteEnabled,
}, },
}) })
return return

View File

@@ -0,0 +1,28 @@
# 渠道而外设置说明
该配置用于设置一些额外的渠道参数,可以通过 JSON 对象进行配置。主要包含以下两个设置项:
1. force_format
- 用于标识是否对数据进行强制格式化为 OpenAI 格式
- 类型为布尔值,设置为 true 时启用强制格式化
2. proxy
- 用于配置网络代理
- 类型为字符串,填写代理地址(例如 socks5 协议的代理地址)
--------------------------------------------------------------
## JSON 格式示例
以下是一个示例配置,启用强制格式化并设置了代理地址:
```json
{
"force_format": true,
"proxy": "socks5://xxxxxxx"
}
```
--------------------------------------------------------------
通过调整上述 JSON 配置中的值,可以灵活控制渠道的额外行为,比如是否进行格式化以及使用特定的网络代理。

View File

@@ -23,7 +23,7 @@ type GeneralOpenAIRequest struct {
MaxTokens uint `json:"max_tokens,omitempty"` MaxTokens uint `json:"max_tokens,omitempty"`
MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"` MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"` ReasoningEffort string `json:"reasoning_effort,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
Stop any `json:"stop,omitempty"` Stop any `json:"stop,omitempty"`

View File

@@ -43,6 +43,7 @@ const (
func formatUserLogs(logs []*Log) { func formatUserLogs(logs []*Log) {
for i := range logs { for i := range logs {
logs[i].ChannelName = ""
var otherMap map[string]interface{} var otherMap map[string]interface{}
otherMap = common.StrToMap(logs[i].Other) otherMap = common.StrToMap(logs[i].Other)
if otherMap != nil { if otherMap != nil {
@@ -129,38 +130,38 @@ func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName
if logType == LogTypeUnknown { if logType == LogTypeUnknown {
tx = LOG_DB tx = LOG_DB
} else { } else {
tx = LOG_DB.Where("type = ?", logType) tx = LOG_DB.Where("logs.type = ?", logType)
} }
tx = tx.Joins("LEFT JOIN channels ON logs.channel_id = channels.id") tx = tx.Joins("LEFT JOIN channels ON logs.channel_id = channels.id")
tx = tx.Select("logs.*, channels.name as channel_name") tx = tx.Select("logs.*, channels.name as channel_name")
if modelName != "" { if modelName != "" {
tx = tx.Where("model_name like ?", modelName) tx = tx.Where("logs.model_name like ?", modelName)
} }
if username != "" { if username != "" {
tx = tx.Where("username = ?", username) tx = tx.Where("logs.username = ?", username)
} }
if tokenName != "" { if tokenName != "" {
tx = tx.Where("token_name = ?", tokenName) tx = tx.Where("logs.token_name = ?", tokenName)
} }
if startTimestamp != 0 { if startTimestamp != 0 {
tx = tx.Where("created_at >= ?", startTimestamp) tx = tx.Where("logs.created_at >= ?", startTimestamp)
} }
if endTimestamp != 0 { if endTimestamp != 0 {
tx = tx.Where("created_at <= ?", endTimestamp) tx = tx.Where("logs.created_at <= ?", endTimestamp)
} }
if channel != 0 { if channel != 0 {
tx = tx.Where("channel_id = ?", channel) tx = tx.Where("logs.channel_id = ?", channel)
} }
if group != "" { if group != "" {
tx = tx.Where(groupCol+" = ?", group) tx = tx.Where("logs."+groupCol+" = ?", group)
} }
err = tx.Model(&Log{}).Count(&total).Error err = tx.Model(&Log{}).Count(&total).Error
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error err = tx.Order("logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@@ -170,34 +171,34 @@ func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int, group string) (logs []*Log, total int64, err error) { func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int, group string) (logs []*Log, total int64, err error) {
var tx *gorm.DB var tx *gorm.DB
if logType == LogTypeUnknown { if logType == LogTypeUnknown {
tx = LOG_DB.Where("user_id = ?", userId) tx = LOG_DB.Where("logs.user_id = ?", userId)
} else { } else {
tx = LOG_DB.Where("user_id = ? and type = ?", userId, logType) tx = LOG_DB.Where("logs.user_id = ? and logs.type = ?", userId, logType)
} }
tx = tx.Joins("LEFT JOIN channels ON logs.channel_id = channels.id") tx = tx.Joins("LEFT JOIN channels ON logs.channel_id = channels.id")
tx = tx.Select("logs.*, channels.name as channel_name") tx = tx.Select("logs.*, channels.name as channel_name")
if modelName != "" { if modelName != "" {
tx = tx.Where("model_name like ?", modelName) tx = tx.Where("logs.model_name like ?", modelName)
} }
if tokenName != "" { if tokenName != "" {
tx = tx.Where("token_name = ?", tokenName) tx = tx.Where("logs.token_name = ?", tokenName)
} }
if startTimestamp != 0 { if startTimestamp != 0 {
tx = tx.Where("created_at >= ?", startTimestamp) tx = tx.Where("logs.created_at >= ?", startTimestamp)
} }
if endTimestamp != 0 { if endTimestamp != 0 {
tx = tx.Where("created_at <= ?", endTimestamp) tx = tx.Where("logs.created_at <= ?", endTimestamp)
} }
if group != "" { if group != "" {
tx = tx.Where(groupCol+" = ?", group) tx = tx.Where("logs."+groupCol+" = ?", group)
} }
err = tx.Model(&Log{}).Count(&total).Error err = tx.Model(&Log{}).Count(&total).Error
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error err = tx.Order("logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
formatUserLogs(logs) formatUserLogs(logs)
return logs, total, err return logs, total, err
} }

View File

@@ -104,6 +104,7 @@ func InitOptionMap() {
common.OptionMap["MjForwardUrlEnabled"] = strconv.FormatBool(setting.MjForwardUrlEnabled) common.OptionMap["MjForwardUrlEnabled"] = strconv.FormatBool(setting.MjForwardUrlEnabled)
common.OptionMap["MjActionCheckSuccessEnabled"] = strconv.FormatBool(setting.MjActionCheckSuccessEnabled) common.OptionMap["MjActionCheckSuccessEnabled"] = strconv.FormatBool(setting.MjActionCheckSuccessEnabled)
common.OptionMap["CheckSensitiveEnabled"] = strconv.FormatBool(setting.CheckSensitiveEnabled) common.OptionMap["CheckSensitiveEnabled"] = strconv.FormatBool(setting.CheckSensitiveEnabled)
common.OptionMap["DemoSiteEnabled"] = strconv.FormatBool(setting.DemoSiteEnabled)
common.OptionMap["CheckSensitiveOnPromptEnabled"] = strconv.FormatBool(setting.CheckSensitiveOnPromptEnabled) common.OptionMap["CheckSensitiveOnPromptEnabled"] = strconv.FormatBool(setting.CheckSensitiveOnPromptEnabled)
//common.OptionMap["CheckSensitiveOnCompletionEnabled"] = strconv.FormatBool(constant.CheckSensitiveOnCompletionEnabled) //common.OptionMap["CheckSensitiveOnCompletionEnabled"] = strconv.FormatBool(constant.CheckSensitiveOnCompletionEnabled)
common.OptionMap["StopOnSensitiveEnabled"] = strconv.FormatBool(setting.StopOnSensitiveEnabled) common.OptionMap["StopOnSensitiveEnabled"] = strconv.FormatBool(setting.StopOnSensitiveEnabled)
@@ -220,6 +221,8 @@ func updateOptionMap(key string, value string) (err error) {
setting.MjActionCheckSuccessEnabled = boolValue setting.MjActionCheckSuccessEnabled = boolValue
case "CheckSensitiveEnabled": case "CheckSensitiveEnabled":
setting.CheckSensitiveEnabled = boolValue setting.CheckSensitiveEnabled = boolValue
case "DemoSiteEnabled":
setting.DemoSiteEnabled = boolValue
case "CheckSensitiveOnPromptEnabled": case "CheckSensitiveOnPromptEnabled":
setting.CheckSensitiveOnPromptEnabled = boolValue setting.CheckSensitiveOnPromptEnabled = boolValue
//case "CheckSensitiveOnCompletionEnabled": //case "CheckSensitiveOnCompletionEnabled":

View File

@@ -39,7 +39,7 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody
if err != nil { if err != nil {
return nil, fmt.Errorf("setup request header failed: %w", err) return nil, fmt.Errorf("setup request header failed: %w", err)
} }
resp, err := doRequest(c, req) resp, err := doRequest(c, req, info)
if err != nil { if err != nil {
return nil, fmt.Errorf("do request failed: %w", err) return nil, fmt.Errorf("do request failed: %w", err)
} }
@@ -62,7 +62,7 @@ func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBod
if err != nil { if err != nil {
return nil, fmt.Errorf("setup request header failed: %w", err) return nil, fmt.Errorf("setup request header failed: %w", err)
} }
resp, err := doRequest(c, req) resp, err := doRequest(c, req, info)
if err != nil { if err != nil {
return nil, fmt.Errorf("do request failed: %w", err) return nil, fmt.Errorf("do request failed: %w", err)
} }
@@ -90,8 +90,18 @@ func DoWssRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody
return targetConn, nil return targetConn, nil
} }
func doRequest(c *gin.Context, req *http.Request) (*http.Response, error) { func doRequest(c *gin.Context, req *http.Request, info *common.RelayInfo) (*http.Response, error) {
resp, err := service.GetHttpClient().Do(req) var client *http.Client
var err error
if proxyURL, ok := info.ChannelSetting["proxy"]; ok {
client, err = service.NewProxyHttpClient(proxyURL.(string))
if err != nil {
return nil, fmt.Errorf("new proxy http client failed: %w", err)
}
} else {
client = service.GetHttpClient()
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -120,7 +130,7 @@ func DoTaskApiRequest(a TaskAdaptor, c *gin.Context, info *common.TaskRelayInfo,
if err != nil { if err != nil {
return nil, fmt.Errorf("setup request header failed: %w", err) return nil, fmt.Errorf("setup request header failed: %w", err)
} }
resp, err := doRequest(c, req) resp, err := doRequest(c, req, info.ToRelayInfo())
if err != nil { if err != nil {
return nil, fmt.Errorf("do request failed: %w", err) return nil, fmt.Errorf("do request failed: %w", err)
} }

View File

@@ -10,7 +10,7 @@ type AwsClaudeRequest struct {
System string `json:"system,omitempty"` System string `json:"system,omitempty"`
Messages []claude.ClaudeMessage `json:"messages"` Messages []claude.ClaudeMessage `json:"messages"`
MaxTokens uint `json:"max_tokens,omitempty"` MaxTokens uint `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"` StopSequences []string `json:"stop_sequences,omitempty"`

View File

@@ -12,7 +12,7 @@ type BaiduMessage struct {
type BaiduChatRequest struct { type BaiduChatRequest struct {
Messages []BaiduMessage `json:"messages"` Messages []BaiduMessage `json:"messages"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
PenaltyScore float64 `json:"penalty_score,omitempty"` PenaltyScore float64 `json:"penalty_score,omitempty"`
Stream bool `json:"stream,omitempty"` Stream bool `json:"stream,omitempty"`

View File

@@ -50,7 +50,7 @@ type ClaudeRequest struct {
MaxTokens uint `json:"max_tokens,omitempty"` MaxTokens uint `json:"max_tokens,omitempty"`
MaxTokensToSample uint `json:"max_tokens_to_sample,omitempty"` MaxTokensToSample uint `json:"max_tokens_to_sample,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"` StopSequences []string `json:"stop_sequences,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
//ClaudeMetadata `json:"metadata,omitempty"` //ClaudeMetadata `json:"metadata,omitempty"`

View File

@@ -9,7 +9,7 @@ type CfRequest struct {
Prompt string `json:"prompt,omitempty"` Prompt string `json:"prompt,omitempty"`
Raw bool `json:"raw,omitempty"` Raw bool `json:"raw,omitempty"`
Stream bool `json:"stream,omitempty"` Stream bool `json:"stream,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
} }
type CfAudioResponse struct { type CfAudioResponse struct {

View File

@@ -71,7 +71,7 @@ type GeminiChatTool struct {
} }
type GeminiChatGenerationConfig struct { type GeminiChatGenerationConfig struct {
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"topP,omitempty"` TopP float64 `json:"topP,omitempty"`
TopK float64 `json:"topK,omitempty"` TopK float64 `json:"topK,omitempty"`
MaxOutputTokens uint `json:"maxOutputTokens,omitempty"` MaxOutputTokens uint `json:"maxOutputTokens,omitempty"`

View File

@@ -6,7 +6,7 @@ type OllamaRequest struct {
Model string `json:"model,omitempty"` Model string `json:"model,omitempty"`
Messages []dto.Message `json:"messages,omitempty"` Messages []dto.Message `json:"messages,omitempty"`
Stream bool `json:"stream,omitempty"` Stream bool `json:"stream,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
Seed float64 `json:"seed,omitempty"` Seed float64 `json:"seed,omitempty"`
Topp float64 `json:"top_p,omitempty"` Topp float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
@@ -18,14 +18,14 @@ type OllamaRequest struct {
} }
type Options struct { type Options struct {
Seed int `json:"seed,omitempty"` Seed int `json:"seed,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"` FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"` PresencePenalty float64 `json:"presence_penalty,omitempty"`
NumPredict int `json:"num_predict,omitempty"` NumPredict int `json:"num_predict,omitempty"`
NumCtx int `json:"num_ctx,omitempty"` NumCtx int `json:"num_ctx,omitempty"`
} }
type OllamaEmbeddingRequest struct { type OllamaEmbeddingRequest struct {

View File

@@ -10,6 +10,7 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"one-api/common" "one-api/common"
constant2 "one-api/constant"
"one-api/dto" "one-api/dto"
"one-api/relay/channel" "one-api/relay/channel"
"one-api/relay/channel/ai360" "one-api/relay/channel/ai360"
@@ -44,16 +45,20 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
} }
switch info.ChannelType { switch info.ChannelType {
case common.ChannelTypeAzure: case common.ChannelTypeAzure:
apiVersion := info.ApiVersion
if apiVersion == "" {
apiVersion = constant2.AzureDefaultAPIVersion
}
// https://learn.microsoft.com/en-us/azure/cognitive-services/openai/chatgpt-quickstart?pivots=rest-api&tabs=command-line#rest-api // https://learn.microsoft.com/en-us/azure/cognitive-services/openai/chatgpt-quickstart?pivots=rest-api&tabs=command-line#rest-api
requestURL := strings.Split(info.RequestURLPath, "?")[0] requestURL := strings.Split(info.RequestURLPath, "?")[0]
requestURL = fmt.Sprintf("%s?api-version=%s", requestURL, info.ApiVersion) requestURL = fmt.Sprintf("%s?api-version=%s", requestURL, apiVersion)
task := strings.TrimPrefix(requestURL, "/v1/") task := strings.TrimPrefix(requestURL, "/v1/")
model_ := info.UpstreamModelName model_ := info.UpstreamModelName
model_ = strings.Replace(model_, ".", "", -1) model_ = strings.Replace(model_, ".", "", -1)
// https://github.com/songquanpeng/one-api/issues/67 // https://github.com/songquanpeng/one-api/issues/67
requestURL = fmt.Sprintf("/openai/deployments/%s/%s", model_, task) requestURL = fmt.Sprintf("/openai/deployments/%s/%s", model_, task)
if info.RelayMode == constant.RelayModeRealtime { if info.RelayMode == constant.RelayModeRealtime {
requestURL = fmt.Sprintf("/openai/realtime?deployment=%s&api-version=%s", model_, info.ApiVersion) requestURL = fmt.Sprintf("/openai/realtime?deployment=%s&api-version=%s", model_, apiVersion)
} }
return relaycommon.GetFullRequestURL(info.BaseUrl, requestURL, info.ChannelType), nil return relaycommon.GetFullRequestURL(info.BaseUrl, requestURL, info.ChannelType), nil
case common.ChannelTypeMiniMax: case common.ChannelTypeMiniMax:
@@ -109,13 +114,28 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, info *relaycommon.RelayInfo, re
if info.ChannelType != common.ChannelTypeOpenAI && info.ChannelType != common.ChannelTypeAzure { if info.ChannelType != common.ChannelTypeOpenAI && info.ChannelType != common.ChannelTypeAzure {
request.StreamOptions = nil request.StreamOptions = nil
} }
if strings.HasPrefix(request.Model, "o1") { if strings.HasPrefix(request.Model, "o1") || strings.HasPrefix(request.Model, "o3") {
if request.MaxCompletionTokens == 0 && request.MaxTokens != 0 { if request.MaxCompletionTokens == 0 && request.MaxTokens != 0 {
request.MaxCompletionTokens = request.MaxTokens request.MaxCompletionTokens = request.MaxTokens
request.MaxTokens = 0 request.MaxTokens = 0
} }
if strings.HasPrefix(request.Model, "o3") {
request.Temperature = nil
}
if strings.HasSuffix(request.Model, "-high") {
request.ReasoningEffort = "high"
request.Model = strings.TrimSuffix(request.Model, "-high")
} else if strings.HasSuffix(request.Model, "-low") {
request.ReasoningEffort = "low"
request.Model = strings.TrimSuffix(request.Model, "-low")
} else if strings.HasSuffix(request.Model, "-medium") {
request.ReasoningEffort = "medium"
request.Model = strings.TrimSuffix(request.Model, "-medium")
}
info.ReasoningEffort = request.ReasoningEffort
info.UpstreamModelName = request.Model
} }
if request.Model == "o1" || request.Model == "o1-2024-12-17" { if request.Model == "o1" || request.Model == "o1-2024-12-17" || strings.HasPrefix(request.Model, "o3") {
//修改第一个Message的内容将system改为developer //修改第一个Message的内容将system改为developer
if len(request.Messages) > 0 && request.Messages[0].Role == "system" { if len(request.Messages) > 0 && request.Messages[0].Role == "system" {
request.Messages[0].Role = "developer" request.Messages[0].Role = "developer"

View File

@@ -13,6 +13,10 @@ var ModelList = []string{
"gpt-4o-mini", "gpt-4o-mini-2024-07-18", "gpt-4o-mini", "gpt-4o-mini-2024-07-18",
"o1-preview", "o1-preview-2024-09-12", "o1-preview", "o1-preview-2024-09-12",
"o1-mini", "o1-mini-2024-09-12", "o1-mini", "o1-mini-2024-09-12",
"o3-mini", "o3-mini-2025-01-31",
"o3-mini-high", "o3-mini-2025-01-31-high",
"o3-mini-low", "o3-mini-2025-01-31-low",
"o3-mini-medium", "o3-mini-2025-01-31-medium",
"o1", "o1-2024-12-17", "o1", "o1-2024-12-17",
"gpt-4o-audio-preview", "gpt-4o-audio-preview-2024-10-01", "gpt-4o-audio-preview", "gpt-4o-audio-preview-2024-10-01",
"gpt-4o-realtime-preview", "gpt-4o-realtime-preview-2024-10-01", "gpt-4o-realtime-preview-2024-12-17", "gpt-4o-realtime-preview", "gpt-4o-realtime-preview-2024-10-01", "gpt-4o-realtime-preview-2024-12-17",

View File

@@ -18,7 +18,7 @@ type PaLMPrompt struct {
type PaLMChatRequest struct { type PaLMChatRequest struct {
Prompt PaLMPrompt `json:"prompt"` Prompt PaLMPrompt `json:"prompt"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
CandidateCount int `json:"candidateCount,omitempty"` CandidateCount int `json:"candidateCount,omitempty"`
TopP float64 `json:"topP,omitempty"` TopP float64 `json:"topP,omitempty"`
TopK uint `json:"topK,omitempty"` TopK uint `json:"topK,omitempty"`

View File

@@ -39,9 +39,7 @@ func requestOpenAI2Tencent(a *Adaptor, request dto.GeneralOpenAIRequest) *Tencen
if request.TopP != 0 { if request.TopP != 0 {
req.TopP = &request.TopP req.TopP = &request.TopP
} }
if request.Temperature != 0 { req.Temperature = request.Temperature
req.Temperature = &request.Temperature
}
return &req return &req
} }

View File

@@ -9,7 +9,7 @@ type VertexAIClaudeRequest struct {
MaxTokens int `json:"max_tokens,omitempty"` MaxTokens int `json:"max_tokens,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"` StopSequences []string `json:"stop_sequences,omitempty"`
Stream bool `json:"stream,omitempty"` Stream bool `json:"stream,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
Tools []claude.Tool `json:"tools,omitempty"` Tools []claude.Tool `json:"tools,omitempty"`

View File

@@ -13,11 +13,11 @@ type XunfeiChatRequest struct {
} `json:"header"` } `json:"header"`
Parameter struct { Parameter struct {
Chat struct { Chat struct {
Domain string `json:"domain,omitempty"` Domain string `json:"domain,omitempty"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopK int `json:"top_k,omitempty"` TopK int `json:"top_k,omitempty"`
MaxTokens uint `json:"max_tokens,omitempty"` MaxTokens uint `json:"max_tokens,omitempty"`
Auditing bool `json:"auditing,omitempty"` Auditing bool `json:"auditing,omitempty"`
} `json:"chat"` } `json:"chat"`
} `json:"parameter"` } `json:"parameter"`
Payload struct { Payload struct {

View File

@@ -12,7 +12,7 @@ type ZhipuMessage struct {
type ZhipuRequest struct { type ZhipuRequest struct {
Prompt []ZhipuMessage `json:"prompt"` Prompt []ZhipuMessage `json:"prompt"`
Temperature float64 `json:"temperature,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"` TopP float64 `json:"top_p,omitempty"`
RequestId string `json:"request_id,omitempty"` RequestId string `json:"request_id,omitempty"`
Incremental bool `json:"incremental,omitempty"` Incremental bool `json:"incremental,omitempty"`

View File

@@ -30,6 +30,7 @@ type RelayInfo struct {
RelayMode int RelayMode int
UpstreamModelName string UpstreamModelName string
OriginModelName string OriginModelName string
RecodeModelName string
RequestURLPath string RequestURLPath string
ApiVersion string ApiVersion string
PromptTokens int PromptTokens int
@@ -45,6 +46,7 @@ type RelayInfo struct {
RealtimeTools []dto.RealTimeTool RealtimeTools []dto.RealTimeTool
IsFirstRequest bool IsFirstRequest bool
AudioUsage bool AudioUsage bool
ReasoningEffort string
ChannelSetting map[string]interface{} ChannelSetting map[string]interface{}
} }
@@ -87,6 +89,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
FirstResponseTime: startTime.Add(-time.Second), FirstResponseTime: startTime.Add(-time.Second),
OriginModelName: c.GetString("original_model"), OriginModelName: c.GetString("original_model"),
UpstreamModelName: c.GetString("original_model"), UpstreamModelName: c.GetString("original_model"),
RecodeModelName: c.GetString("recode_model"),
ApiType: apiType, ApiType: apiType,
ApiVersion: c.GetString("api_version"), ApiVersion: c.GetString("api_version"),
ApiKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "), ApiKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),

View File

@@ -93,6 +93,7 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
} }
} }
relayInfo.UpstreamModelName = textRequest.Model relayInfo.UpstreamModelName = textRequest.Model
relayInfo.RecodeModelName = textRequest.Model
modelPrice, getModelPriceSuccess := common.GetModelPrice(textRequest.Model, false) modelPrice, getModelPriceSuccess := common.GetModelPrice(textRequest.Model, false)
groupRatio := setting.GetGroupRatio(relayInfo.Group) groupRatio := setting.GetGroupRatio(relayInfo.Group)
@@ -218,10 +219,10 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
return openaiErr return openaiErr
} }
if strings.HasPrefix(relayInfo.UpstreamModelName, "gpt-4o-audio") { if strings.HasPrefix(relayInfo.RecodeModelName, "gpt-4o-audio") {
service.PostAudioConsumeQuota(c, relayInfo, usage.(*dto.Usage), preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice, getModelPriceSuccess, "") service.PostAudioConsumeQuota(c, relayInfo, usage.(*dto.Usage), preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice, getModelPriceSuccess, "")
} else { } else {
postConsumeQuota(c, relayInfo, textRequest.Model, usage.(*dto.Usage), ratio, preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice, getModelPriceSuccess, "") postConsumeQuota(c, relayInfo, relayInfo.RecodeModelName, usage.(*dto.Usage), ratio, preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice, getModelPriceSuccess, "")
} }
return nil return nil
} }

View File

@@ -1,7 +1,12 @@
package service package service
import ( import (
"context"
"fmt"
"golang.org/x/net/proxy"
"net"
"net/http" "net/http"
"net/url"
"one-api/common" "one-api/common"
"time" "time"
) )
@@ -30,3 +35,43 @@ func GetHttpClient() *http.Client {
func GetImpatientHttpClient() *http.Client { func GetImpatientHttpClient() *http.Client {
return impatientHTTPClient return impatientHTTPClient
} }
// NewProxyHttpClient 创建支持代理的 HTTP 客户端
func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
if proxyURL == "" {
return http.DefaultClient, nil
}
// 解析代理URL
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return nil, err
}
switch parsedURL.Scheme {
case "http", "https":
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(parsedURL),
},
}, nil
case "socks5":
// 创建 SOCKS5 代理拨号器
dialer, err := proxy.SOCKS5("tcp", parsedURL.Host, nil, proxy.Direct)
if err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
},
}, nil
default:
return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)
}
}

View File

@@ -13,6 +13,9 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
other["completion_ratio"] = completionRatio other["completion_ratio"] = completionRatio
other["model_price"] = modelPrice other["model_price"] = modelPrice
other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli()) other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli())
if relayInfo.ReasoningEffort != "" {
other["reasoning_effort"] = relayInfo.ReasoningEffort
}
adminInfo := make(map[string]interface{}) adminInfo := make(map[string]interface{})
adminInfo["use_channel"] = ctx.GetStringSlice("use_channel") adminInfo["use_channel"] = ctx.GetStringSlice("use_channel")
other["admin_info"] = adminInfo other["admin_info"] = adminInfo

View File

@@ -182,9 +182,9 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
audioOutTokens := usage.CompletionTokenDetails.AudioTokens audioOutTokens := usage.CompletionTokenDetails.AudioTokens
tokenName := ctx.GetString("token_name") tokenName := ctx.GetString("token_name")
completionRatio := common.GetCompletionRatio(relayInfo.UpstreamModelName) completionRatio := common.GetCompletionRatio(relayInfo.RecodeModelName)
audioRatio := common.GetAudioRatio(relayInfo.UpstreamModelName) audioRatio := common.GetAudioRatio(relayInfo.RecodeModelName)
audioCompletionRatio := common.GetAudioCompletionRatio(relayInfo.UpstreamModelName) audioCompletionRatio := common.GetAudioCompletionRatio(relayInfo.RecodeModelName)
quotaInfo := QuotaInfo{ quotaInfo := QuotaInfo{
InputDetails: TokenDetails{ InputDetails: TokenDetails{
@@ -195,7 +195,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
TextTokens: textOutTokens, TextTokens: textOutTokens,
AudioTokens: audioOutTokens, AudioTokens: audioOutTokens,
}, },
ModelName: relayInfo.UpstreamModelName, ModelName: relayInfo.RecodeModelName,
UsePrice: usePrice, UsePrice: usePrice,
ModelRatio: modelRatio, ModelRatio: modelRatio,
GroupRatio: groupRatio, GroupRatio: groupRatio,
@@ -218,7 +218,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
quota = 0 quota = 0
logContent += fmt.Sprintf("(可能是上游超时)") logContent += fmt.Sprintf("(可能是上游超时)")
common.LogError(ctx, fmt.Sprintf("total tokens is 0, cannot consume quota, userId %d, channelId %d, "+ common.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, relayInfo.UpstreamModelName, preConsumedQuota)) "tokenId %d, model %s pre-consumed quota %d", relayInfo.UserId, relayInfo.ChannelId, relayInfo.TokenId, relayInfo.RecodeModelName, preConsumedQuota))
} else { } else {
quotaDelta := quota - preConsumedQuota quotaDelta := quota - preConsumedQuota
if quotaDelta != 0 { if quotaDelta != 0 {
@@ -231,7 +231,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
model.UpdateChannelUsedQuota(relayInfo.ChannelId, quota) model.UpdateChannelUsedQuota(relayInfo.ChannelId, quota)
} }
logModel := relayInfo.UpstreamModelName logModel := relayInfo.RecodeModelName
if extraContent != "" { if extraContent != "" {
logContent += ", " + extraContent logContent += ", " + extraContent
} }

View File

@@ -0,0 +1,3 @@
package setting
var DemoSiteEnabled = false

View File

@@ -608,7 +608,12 @@ const LogsTable = () => {
key: t('计费过程'), key: t('计费过程'),
value: content, value: content,
}); });
if (other?.reasoning_effort) {
expandDataLocal.push({
key: t('Reasoning Effort'),
value: other.reasoning_effort,
});
}
} }
expandDatesLocal[logs[i].key] = expandDataLocal; expandDatesLocal[logs[i].key] = expandDataLocal;
} }

View File

@@ -58,6 +58,7 @@ const OperationSetting = () => {
DefaultCollapseSidebar: false, // 默认折叠侧边栏 DefaultCollapseSidebar: false, // 默认折叠侧边栏
RetryTimes: 0, RetryTimes: 0,
Chats: "[]", Chats: "[]",
DemoSiteEnabled: false,
}); });
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);

View File

@@ -218,6 +218,7 @@ const EditChannel = (props) => {
try { try {
const res = await API.post('/api/channel/fetch_models', { const res = await API.post('/api/channel/fetch_models', {
base_url: inputs['base_url'], base_url: inputs['base_url'],
type: inputs['type'],
key: inputs['key'] key: inputs['key']
}); });
@@ -885,7 +886,7 @@ const EditChannel = (props) => {
</Typography.Text> </Typography.Text>
</div> </div>
<TextArea <TextArea
placeholder={t('此项可选用于复写返回的状态码比如将claude渠道的400错误复写为500用于重试请勿滥用该功能例如') + placeholder={t('此项可选用于复写返回的状态码比如将claude渠道的400错误复写为500用于重试请勿滥用该功能例如') +
'\n' + JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)} '\n' + JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)}
name="status_code_mapping" name="status_code_mapping"
onChange={(value) => { onChange={(value) => {
@@ -965,7 +966,6 @@ const EditChannel = (props) => {
value={inputs.weight} value={inputs.weight}
autoComplete="new-password" autoComplete="new-password"
/> />
{inputs.type === 8 && (
<> <>
<div style={{ marginTop: 10 }}> <div style={{ marginTop: 10 }}>
<Typography.Text strong> <Typography.Text strong>
@@ -982,25 +982,38 @@ const EditChannel = (props) => {
value={inputs.setting} value={inputs.setting}
autoComplete="new-password" autoComplete="new-password"
/> />
<Typography.Text <Space>
style={{ <Typography.Text
color: 'rgba(var(--semi-blue-5), 1)', style={{
userSelect: 'none', color: 'rgba(var(--semi-blue-5), 1)',
cursor: 'pointer' userSelect: 'none',
}} cursor: 'pointer'
onClick={() => { }}
handleInputChange( onClick={() => {
'setting', handleInputChange(
JSON.stringify({ 'setting',
force_format: true JSON.stringify({
}, null, 2) force_format: true
); }, null, 2)
}} );
> }}
{t('填入模板')} >
{t('填入模板')}
</Typography.Text> </Typography.Text>
</> <Typography.Text
)} style={{
color: 'rgba(var(--semi-blue-5), 1)',
userSelect: 'none',
cursor: 'pointer'
}}
onClick={() => {
window.open('https://github.com/Calcium-Ion/new-api/blob/main/docs/channel/other_setting.md');
}}
>
{t('设置说明')}
</Typography.Text>
</Space>
</>
</Spin> </Spin>
</SideSheet> </SideSheet>
</> </>

View File

@@ -21,6 +21,7 @@ export default function GeneralSettings(props) {
DisplayInCurrencyEnabled: false, DisplayInCurrencyEnabled: false,
DisplayTokenStatEnabled: false, DisplayTokenStatEnabled: false,
DefaultCollapseSidebar: false, DefaultCollapseSidebar: false,
DemoSiteEnabled: false,
}); });
const refForm = useRef(); const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs); const [inputsRow, setInputsRow] = useState(inputs);
@@ -188,6 +189,23 @@ export default function GeneralSettings(props) {
/> />
</Col> </Col>
</Row> </Row>
<Row>
<Col span={8}>
<Form.Switch
field={'DemoSiteEnabled'}
label={t('演示站点模式')}
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
setInputs({
...inputs,
DemoSiteEnabled: value
})
}
/>
</Col>
</Row>
<Row> <Row>
<Button size='default' onClick={onSubmit}> <Button size='default' onClick={onSubmit}>
{t('保存通用设置')} {t('保存通用设置')}