Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions dto/channel_settings.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dto

type ChannelSettings struct {
ForceFormat bool `json:"force_format,omitempty"`
ThinkingToContent bool `json:"thinking_to_content,omitempty"`
Proxy string `json:"proxy"`
ForceFormat bool `json:"force_format,omitempty"`
ThinkingToContent bool `json:"thinking_to_content,omitempty"`
Proxy string `json:"proxy"`
PassThroughBodyEnabled bool `json:"pass_through_body_enabled,omitempty"`
SystemPrompt string `json:"system_prompt,omitempty"`
}
21 changes: 15 additions & 6 deletions dto/openai_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import (
)

type ResponseFormat struct {
Type string `json:"type,omitempty"`
JsonSchema *FormatJsonSchema `json:"json_schema,omitempty"`
Type string `json:"type,omitempty"`
JsonSchema json.RawMessage `json:"json_schema,omitempty"`
}

type FormatJsonSchema struct {
Description string `json:"description,omitempty"`
Name string `json:"name"`
Schema any `json:"schema,omitempty"`
Strict any `json:"strict,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name"`
Schema any `json:"schema,omitempty"`
Strict json.RawMessage `json:"strict,omitempty"`
}

type GeneralOpenAIRequest struct {
Expand Down Expand Up @@ -73,6 +73,15 @@ func (r *GeneralOpenAIRequest) ToMap() map[string]any {
return result
}

func (r *GeneralOpenAIRequest) GetSystemRoleName() string {
if strings.HasPrefix(r.Model, "o") {
if !strings.HasPrefix(r.Model, "o1-mini") && !strings.HasPrefix(r.Model, "o1-preview") {
return "developer"
}
}
return "system"
}

type ToolCallRequest struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Expand Down
14 changes: 14 additions & 0 deletions i18n/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,20 @@
"渠道权重": "渠道权重",
"渠道额外设置": "渠道额外设置",
"此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:": "此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:",
"强制格式化": "强制格式化",
"强制格式化(只适用于OpenAI渠道类型)": "强制格式化(只适用于OpenAI渠道类型)",
"强制将响应格式化为 OpenAI 标准格式": "强制将响应格式化为 OpenAI 标准格式",
"思考内容转换": "思考内容转换",
"将 reasoning_content 转换为 <think> 标签拼接到内容中": "将 reasoning_content 转换为 <think> 标签拼接到内容中",
"透传请求体": "透传请求体",
"启用请求体透传功能": "启用请求体透传功能",
"代理地址": "代理地址",
"例如: socks5://user:pass@host:port": "例如: socks5://user:pass@host:port",
"用于配置网络代理": "用于配置网络代理",
"用于配置网络代理,支持 socks5 协议": "用于配置网络代理,支持 socks5 协议",
"系统提示词": "系统提示词",
"输入系统提示词,用户的系统提示词将优先于此设置": "输入系统提示词,用户的系统提示词将优先于此设置",
"用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置": "用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置",
"参数覆盖": "参数覆盖",
"此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:": "此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:",
"请输入组织org-xxx": "请输入组织org-xxx",
Expand Down
13 changes: 6 additions & 7 deletions middleware/distributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,17 @@ func Distribute() func(c *gin.Context) {
if userGroup == "auto" {
showGroup = fmt.Sprintf("auto(%s)", selectGroup)
}
message := fmt.Sprintf("获取分组 %s 下模型 %s 的可用渠道失败(distributor): %s", showGroup, modelRequest.Model, err.Error())
message := fmt.Sprintf("获取分组 %s 下模型 %s 的可用渠道失败(数据库一致性已被破坏,distributor): %s", showGroup, modelRequest.Model, err.Error())
// 如果错误,但是渠道不为空,说明是数据库一致性问题
if channel != nil {
common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
message = "数据库一致性已被破坏,请联系管理员"
}
// 如果错误,而且渠道为空,说明是没有可用渠道
//if channel != nil {
// common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
// message = "数据库一致性已被破坏,请联系管理员"
//}
Comment on lines +116 to +119

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify impact of removing database consistency check logic.

The commented out code was responsible for:

  1. Detecting database consistency issues (error + non-nil channel)
  2. Logging system errors for administrator attention
  3. Providing user-friendly error messages

Removing this logic could result in:

  • Undetected database consistency problems
  • Missing system error logs
  • Technical error messages exposed to users

Please confirm this logic is handled elsewhere or provide justification for its removal.


🏁 Script executed:

#!/bin/bash
# Search for alternative database consistency checks in the codebase
echo "Searching for database consistency checks..."
rg -A 3 -B 3 "数据库一致性|database consistency|consistency.*broken" --type go

echo -e "\nSearching for system error logging patterns..."
rg -A 2 "common\.SysError|SysError" --type go

echo -e "\nSearching for channel nil checks in error scenarios..."
ast-grep --pattern 'if $_ != nil {
  $$$
  if channel != nil {
    $$$
  }
  $$$
}'

Length of output: 28269


Restore database consistency handling in distributor middleware

The commented-out block in middleware/distributor.go (lines 116–119) removed both the system error log and the generic user-friendly message without any replacement:

  • Loss of common.SysError call means DB consistency breaches won’t be logged for admins
  • Overwriting the message to a generic “数据库一致性已被破坏,请联系管理员” was removed, so users now always see technical err.Error() details
  • No other code in this middleware logs or handles the channel != nil && err != nil scenario

Please re-introduce explicit consistency detection and logging, for example:

-   // 如果错误,但是渠道不为空,说明是数据库一致性问题
-   //if channel != nil {
-   //  common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
-   //  message = "数据库一致性已被破坏,请联系管理员"
-   //}
+   // 如果错误,但是渠道不为空,说明是数据库一致性问题
+   if channel != nil {
+     common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
+     message = "数据库一致性已被破坏,请联系管理员"
+   }

By restoring this block, you’ll ensure:

  1. Consistency breaches are logged to the error stream
  2. Users receive a clear, non-technical message when the data layer is corrupt
  3. Technical error details aren’t exposed to end users
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//if channel != nil {
// common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
// message = "数据库一致性已被破坏,请联系管理员"
//}
// 如果错误,但是渠道不为空,说明是数据库一致性问题
if channel != nil {
common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
message = "数据库一致性已被破坏,请联系管理员"
}
🤖 Prompt for AI Agents
In middleware/distributor.go around lines 116 to 119, the commented-out code
that logs a system error and sets a generic user-friendly message when database
consistency is broken should be restored. Re-enable the common.SysError call to
log the inconsistency for administrators and set the message variable to a
clear, non-technical string like "数据库一致性已被破坏,请联系管理员" to prevent exposing
technical error details to users. This will ensure proper logging and user
communication in case of data consistency issues.

abortWithOpenAiMessage(c, http.StatusServiceUnavailable, message)
return
}
if channel == nil {
abortWithOpenAiMessage(c, http.StatusServiceUnavailable, fmt.Sprintf("分组 %s 下模型 %s 的可用渠道不存在(数据库一致性已被破坏,distributor)", userGroup, modelRequest.Model))
abortWithOpenAiMessage(c, http.StatusServiceUnavailable, fmt.Sprintf("分组 %s 下模型 %s 无可用渠道(distributor)", userGroup, modelRequest.Model))
return
}
}
Expand Down
10 changes: 7 additions & 3 deletions relay/channel/gemini/relay-gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,13 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon
if textRequest.ResponseFormat != nil && (textRequest.ResponseFormat.Type == "json_schema" || textRequest.ResponseFormat.Type == "json_object") {
geminiRequest.GenerationConfig.ResponseMimeType = "application/json"

if textRequest.ResponseFormat.JsonSchema != nil && textRequest.ResponseFormat.JsonSchema.Schema != nil {
cleanedSchema := removeAdditionalPropertiesWithDepth(textRequest.ResponseFormat.JsonSchema.Schema, 0)
geminiRequest.GenerationConfig.ResponseSchema = cleanedSchema
if len(textRequest.ResponseFormat.JsonSchema) > 0 {
// 先将json.RawMessage解析
var jsonSchema dto.FormatJsonSchema
if err := common.Unmarshal(textRequest.ResponseFormat.JsonSchema, &jsonSchema); err == nil {
cleanedSchema := removeAdditionalPropertiesWithDepth(jsonSchema.Schema, 0)
geminiRequest.GenerationConfig.ResponseSchema = cleanedSchema
}
}
}
tool_call_ids := make(map[string]string)
Expand Down
46 changes: 34 additions & 12 deletions relay/claude_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ func ClaudeHelper(c *gin.Context) (newAPIError *types.NewAPIError) {
return types.NewError(fmt.Errorf("invalid api type: %d", relayInfo.ApiType), types.ErrorCodeInvalidApiType)
}
adaptor.Init(relayInfo)
var requestBody io.Reader

if textRequest.MaxTokens == 0 {
textRequest.MaxTokens = uint(model_setting.GetClaudeSettings().GetDefaultMaxTokens(textRequest.Model))
Expand Down Expand Up @@ -108,18 +107,41 @@ func ClaudeHelper(c *gin.Context) (newAPIError *types.NewAPIError) {
relayInfo.UpstreamModelName = textRequest.Model
}

convertedRequest, err := adaptor.ConvertClaudeRequest(c, relayInfo, textRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}
jsonData, err := common.Marshal(convertedRequest)
if common.DebugEnabled {
println("requestBody: ", string(jsonData))
}
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
var requestBody io.Reader
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled {
body, err := common.GetRequestBody(c)
if err != nil {
return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest)
}
requestBody = bytes.NewBuffer(body)
} else {
convertedRequest, err := adaptor.ConvertClaudeRequest(c, relayInfo, textRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}
jsonData, err := common.Marshal(convertedRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}

// apply param override
if len(relayInfo.ParamOverride) > 0 {
reqMap := make(map[string]interface{})
_ = common.Unmarshal(jsonData, &reqMap)
for key, value := range relayInfo.ParamOverride {
reqMap[key] = value
}
jsonData, err = common.Marshal(reqMap)
if err != nil {
return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid)
}
}

if common.DebugEnabled {
println("requestBody: ", string(jsonData))
}
requestBody = bytes.NewBuffer(jsonData)
}
requestBody = bytes.NewBuffer(jsonData)

statusCodeMappingStr := c.GetString("status_code_mapping")
var httpResp *http.Response
Expand Down
38 changes: 31 additions & 7 deletions relay/gemini_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"one-api/common"
"one-api/dto"
Expand Down Expand Up @@ -194,16 +195,39 @@ func GeminiHelper(c *gin.Context) (newAPIError *types.NewAPIError) {
}
}

requestBody, err := json.Marshal(req)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}
var requestBody io.Reader
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled {
body, err := common.GetRequestBody(c)
if err != nil {
return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest)
}
requestBody = bytes.NewReader(body)
} else {
jsonData, err := json.Marshal(req)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}

// apply param override
if len(relayInfo.ParamOverride) > 0 {
reqMap := make(map[string]interface{})
_ = common.Unmarshal(jsonData, &reqMap)
for key, value := range relayInfo.ParamOverride {
reqMap[key] = value
}
jsonData, err = common.Marshal(reqMap)
if err != nil {
return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid)
}
}

if common.DebugEnabled {
println("Gemini request body: %s", string(requestBody))
if common.DebugEnabled {
println("Gemini request body: %s", string(jsonData))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect println usage.

The println function doesn't support format strings. Use fmt.Printf or fmt.Println instead.

-			println("Gemini request body: %s", string(jsonData))
+			fmt.Printf("Gemini request body: %s\n", string(jsonData))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
println("Gemini request body: %s", string(jsonData))
fmt.Printf("Gemini request body: %s\n", string(jsonData))
🤖 Prompt for AI Agents
In relay/gemini_handler.go at line 225, the code incorrectly uses println with a
format string, which println does not support. Replace println with fmt.Printf
to correctly handle the format string and print the Gemini request body as
intended.

}
requestBody = bytes.NewReader(jsonData)
}

resp, err := adaptor.DoRequest(c, relayInfo, bytes.NewReader(requestBody))
resp, err := adaptor.DoRequest(c, relayInfo, requestBody)
if err != nil {
common.LogError(c, "Do gemini request failed: "+err.Error())
return types.NewError(err, types.ErrorCodeDoRequestFailed)
Expand Down
44 changes: 33 additions & 11 deletions relay/image_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"one-api/relay/helper"
"one-api/service"
"one-api/setting"
"one-api/setting/model_setting"
"one-api/types"
"strings"

Expand Down Expand Up @@ -187,22 +188,43 @@ func ImageHelper(c *gin.Context) (newAPIError *types.NewAPIError) {

var requestBody io.Reader

convertedRequest, err := adaptor.ConvertImageRequest(c, relayInfo, *imageRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}
if relayInfo.RelayMode == relayconstant.RelayModeImagesEdits {
requestBody = convertedRequest.(io.Reader)
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled {
body, err := common.GetRequestBody(c)
if err != nil {
return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest)
}
requestBody = bytes.NewBuffer(body)
} else {
jsonData, err := json.Marshal(convertedRequest)
convertedRequest, err := adaptor.ConvertImageRequest(c, relayInfo, *imageRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}
requestBody = bytes.NewBuffer(jsonData)
}
if relayInfo.RelayMode == relayconstant.RelayModeImagesEdits {
requestBody = convertedRequest.(io.Reader)
} else {
jsonData, err := json.Marshal(convertedRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}

if common.DebugEnabled {
println(fmt.Sprintf("image request body: %s", requestBody))
// apply param override
if len(relayInfo.ParamOverride) > 0 {
reqMap := make(map[string]interface{})
_ = common.Unmarshal(jsonData, &reqMap)
for key, value := range relayInfo.ParamOverride {
reqMap[key] = value
}
jsonData, err = common.Marshal(reqMap)
if err != nil {
return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid)
}
}

if common.DebugEnabled {
println(fmt.Sprintf("image request body: %s", string(jsonData)))
}
requestBody = bytes.NewBuffer(jsonData)
}
}

statusCodeMappingStr := c.GetString("status_code_mapping")
Expand Down
29 changes: 26 additions & 3 deletions relay/relay-text.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package relay

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -171,18 +170,42 @@ func TextHelper(c *gin.Context) (newAPIError *types.NewAPIError) {
adaptor.Init(relayInfo)
var requestBody io.Reader

if model_setting.GetGlobalSettings().PassThroughRequestEnabled {
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled {
body, err := common.GetRequestBody(c)
if err != nil {
return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest)
}
if common.DebugEnabled {
println("requestBody: ", string(body))
}
requestBody = bytes.NewBuffer(body)
} else {
convertedRequest, err := adaptor.ConvertOpenAIRequest(c, relayInfo, textRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}
jsonData, err := json.Marshal(convertedRequest)

if relayInfo.ChannelSetting.SystemPrompt != "" {
// 如果有系统提示,则将其添加到请求中
request := convertedRequest.(*dto.GeneralOpenAIRequest)
containSystemPrompt := false
for _, message := range request.Messages {
if message.Role == request.GetSystemRoleName() {
containSystemPrompt = true
break
}
}
if !containSystemPrompt {
// 如果没有系统提示,则添加系统提示
systemMessage := dto.Message{
Role: request.GetSystemRoleName(),
Content: relayInfo.ChannelSetting.SystemPrompt,
}
request.Messages = append([]dto.Message{systemMessage}, request.Messages...)
}
}

jsonData, err := common.Marshal(convertedRequest)
if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
}
Expand Down
Loading