feat: log shows request conversion

This commit is contained in:
Seefs
2026-01-20 23:43:29 +08:00
parent 57ed2b3dae
commit d4582ede98
15 changed files with 151 additions and 18 deletions

View File

@@ -56,8 +56,9 @@ func formatUserLogs(logs []*Log) {
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 {
// delete admin // Remove admin-only debug fields.
delete(otherMap, "admin_info") delete(otherMap, "admin_info")
delete(otherMap, "request_conversion")
} }
logs[i].Other = common.MapToJsonStr(otherMap) logs[i].Other = common.MapToJsonStr(otherMap)
logs[i].Id = logs[i].Id % 1024 logs[i].Id = logs[i].Id % 1024

View File

@@ -97,6 +97,7 @@ func chatCompletionsViaResponses(c *gin.Context, info *relaycommon.RelayInfo, ad
if err != nil { if err != nil {
return nil, types.NewErrorWithStatusCode(err, types.ErrorCodeInvalidRequest, http.StatusBadRequest, types.ErrOptionWithSkipRetry()) return nil, types.NewErrorWithStatusCode(err, types.ErrorCodeInvalidRequest, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
} }
info.AppendRequestConversion(types.RelayFormatOpenAIResponses)
savedRelayMode := info.RelayMode savedRelayMode := info.RelayMode
savedRequestURLPath := info.RequestURLPath savedRequestURLPath := info.RequestURLPath
@@ -112,6 +113,7 @@ func chatCompletionsViaResponses(c *gin.Context, info *relaycommon.RelayInfo, ad
if err != nil { if err != nil {
return nil, types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return nil, types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
jsonData, err := common.Marshal(convertedRequest) jsonData, err := common.Marshal(convertedRequest)
if err != nil { if err != nil {

View File

@@ -110,6 +110,7 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
jsonData, err := common.Marshal(convertedRequest) jsonData, err := common.Marshal(convertedRequest)
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())

View File

@@ -121,6 +121,10 @@ type RelayInfo struct {
Request dto.Request Request dto.Request
// RequestConversionChain records request format conversions in order, e.g.
// ["openai", "openai_responses"] or ["openai", "claude"].
RequestConversionChain []types.RelayFormat
ThinkingContentInfo ThinkingContentInfo
TokenCountMeta TokenCountMeta
*ClaudeConvertInfo *ClaudeConvertInfo
@@ -448,38 +452,83 @@ func genBaseRelayInfo(c *gin.Context, request dto.Request) *RelayInfo {
} }
func GenRelayInfo(c *gin.Context, relayFormat types.RelayFormat, request dto.Request, ws *websocket.Conn) (*RelayInfo, error) { func GenRelayInfo(c *gin.Context, relayFormat types.RelayFormat, request dto.Request, ws *websocket.Conn) (*RelayInfo, error) {
var info *RelayInfo
var err error
switch relayFormat { switch relayFormat {
case types.RelayFormatOpenAI: case types.RelayFormatOpenAI:
return GenRelayInfoOpenAI(c, request), nil info = GenRelayInfoOpenAI(c, request)
case types.RelayFormatOpenAIAudio: case types.RelayFormatOpenAIAudio:
return GenRelayInfoOpenAIAudio(c, request), nil info = GenRelayInfoOpenAIAudio(c, request)
case types.RelayFormatOpenAIImage: case types.RelayFormatOpenAIImage:
return GenRelayInfoImage(c, request), nil info = GenRelayInfoImage(c, request)
case types.RelayFormatOpenAIRealtime: case types.RelayFormatOpenAIRealtime:
return GenRelayInfoWs(c, ws), nil info = GenRelayInfoWs(c, ws)
case types.RelayFormatClaude: case types.RelayFormatClaude:
return GenRelayInfoClaude(c, request), nil info = GenRelayInfoClaude(c, request)
case types.RelayFormatRerank: case types.RelayFormatRerank:
if request, ok := request.(*dto.RerankRequest); ok { if request, ok := request.(*dto.RerankRequest); ok {
return GenRelayInfoRerank(c, request), nil info = GenRelayInfoRerank(c, request)
break
} }
return nil, errors.New("request is not a RerankRequest") err = errors.New("request is not a RerankRequest")
case types.RelayFormatGemini: case types.RelayFormatGemini:
return GenRelayInfoGemini(c, request), nil info = GenRelayInfoGemini(c, request)
case types.RelayFormatEmbedding: case types.RelayFormatEmbedding:
return GenRelayInfoEmbedding(c, request), nil info = GenRelayInfoEmbedding(c, request)
case types.RelayFormatOpenAIResponses: case types.RelayFormatOpenAIResponses:
if request, ok := request.(*dto.OpenAIResponsesRequest); ok { if request, ok := request.(*dto.OpenAIResponsesRequest); ok {
return GenRelayInfoResponses(c, request), nil info = GenRelayInfoResponses(c, request)
break
} }
return nil, errors.New("request is not a OpenAIResponsesRequest") err = errors.New("request is not a OpenAIResponsesRequest")
case types.RelayFormatTask: case types.RelayFormatTask:
return genBaseRelayInfo(c, nil), nil info = genBaseRelayInfo(c, nil)
case types.RelayFormatMjProxy: case types.RelayFormatMjProxy:
return genBaseRelayInfo(c, nil), nil info = genBaseRelayInfo(c, nil)
default: default:
return nil, errors.New("invalid relay format") err = errors.New("invalid relay format")
} }
if err != nil {
return nil, err
}
if info == nil {
return nil, errors.New("failed to build relay info")
}
info.InitRequestConversionChain()
return info, nil
}
func (info *RelayInfo) InitRequestConversionChain() {
if info == nil {
return
}
if len(info.RequestConversionChain) > 0 {
return
}
if info.RelayFormat == "" {
return
}
info.RequestConversionChain = []types.RelayFormat{info.RelayFormat}
}
func (info *RelayInfo) AppendRequestConversion(format types.RelayFormat) {
if info == nil {
return
}
if format == "" {
return
}
if len(info.RequestConversionChain) == 0 {
info.RequestConversionChain = []types.RelayFormat{format}
return
}
last := info.RequestConversionChain[len(info.RequestConversionChain)-1]
if last == format {
return
}
info.RequestConversionChain = append(info.RequestConversionChain, format)
} }
//func (info *RelayInfo) SetPromptTokens(promptTokens int) { //func (info *RelayInfo) SetPromptTokens(promptTokens int) {

View File

@@ -0,0 +1,40 @@
package common
import (
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/types"
)
func GuessRelayFormatFromRequest(req any) (types.RelayFormat, bool) {
switch req.(type) {
case *dto.GeneralOpenAIRequest, dto.GeneralOpenAIRequest:
return types.RelayFormatOpenAI, true
case *dto.OpenAIResponsesRequest, dto.OpenAIResponsesRequest:
return types.RelayFormatOpenAIResponses, true
case *dto.ClaudeRequest, dto.ClaudeRequest:
return types.RelayFormatClaude, true
case *dto.GeminiChatRequest, dto.GeminiChatRequest:
return types.RelayFormatGemini, true
case *dto.EmbeddingRequest, dto.EmbeddingRequest:
return types.RelayFormatEmbedding, true
case *dto.RerankRequest, dto.RerankRequest:
return types.RelayFormatRerank, true
case *dto.ImageRequest, dto.ImageRequest:
return types.RelayFormatOpenAIImage, true
case *dto.AudioRequest, dto.AudioRequest:
return types.RelayFormatOpenAIAudio, true
default:
return "", false
}
}
func AppendRequestConversionFromRequest(info *RelayInfo, req any) {
if info == nil {
return
}
format, ok := GuessRelayFormatFromRequest(req)
if !ok {
return
}
info.AppendRequestConversion(format)
}

View File

@@ -113,6 +113,7 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
if info.ChannelSetting.SystemPrompt != "" { if info.ChannelSetting.SystemPrompt != "" {
// 如果有系统提示,则将其添加到请求中 // 如果有系统提示,则将其添加到请求中

View File

@@ -45,6 +45,7 @@ func EmbeddingHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
jsonData, err := json.Marshal(convertedRequest) jsonData, err := json.Marshal(convertedRequest)
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())

View File

@@ -149,6 +149,7 @@ func GeminiHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
jsonData, err := common.Marshal(convertedRequest) jsonData, err := common.Marshal(convertedRequest)
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())

View File

@@ -57,6 +57,7 @@ func ImageHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *type
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed) return types.NewError(err, types.ErrorCodeConvertRequestFailed)
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
switch convertedRequest.(type) { switch convertedRequest.(type) {
case *bytes.Buffer: case *bytes.Buffer:

View File

@@ -53,6 +53,7 @@ func RerankHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
jsonData, err := common.Marshal(convertedRequest) jsonData, err := common.Marshal(convertedRequest)
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())

View File

@@ -53,6 +53,7 @@ func ResponsesHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
} }
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
jsonData, err := common.Marshal(convertedRequest) jsonData, err := common.Marshal(convertedRequest)
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())

View File

@@ -70,9 +70,30 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
other["admin_info"] = adminInfo other["admin_info"] = adminInfo
appendRequestPath(ctx, relayInfo, other) appendRequestPath(ctx, relayInfo, other)
appendRequestConversionChain(relayInfo, other)
return other return other
} }
func appendRequestConversionChain(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
if relayInfo == nil || other == nil {
return
}
if len(relayInfo.RequestConversionChain) == 0 {
return
}
chain := make([]string, 0, len(relayInfo.RequestConversionChain))
for _, f := range relayInfo.RequestConversionChain {
if f == "" {
continue
}
chain = append(chain, string(f))
}
if len(chain) == 0 {
return
}
other["request_conversion"] = chain
}
func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} { func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio) info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
info["ws"] = true info["ws"] = true

View File

@@ -476,10 +476,17 @@ export const useLogsData = () => {
}); });
} }
} }
if (other?.request_path) { if (isAdminUser) {
const requestConversionChain = other?.request_conversion;
const chain = Array.isArray(requestConversionChain)
? requestConversionChain.filter(Boolean)
: [];
expandDataLocal.push({ expandDataLocal.push({
key: t('请求路径'), key: t('请求转换'),
value: other.request_path, value:
chain.length > 1
? `${chain.join(' -> ')}`
: t('原生格式'),
}); });
} }
if (isAdminUser) { if (isAdminUser) {

View File

@@ -2091,6 +2091,9 @@
"请求结束后多退少补": "Adjust after request completion", "请求结束后多退少补": "Adjust after request completion",
"请求超时,请刷新页面后重新发起 GitHub 登录": "Request timed out, please refresh and restart GitHub login", "请求超时,请刷新页面后重新发起 GitHub 登录": "Request timed out, please refresh and restart GitHub login",
"请求路径": "Request path", "请求路径": "Request path",
"请求转换": "Request conversion",
"原生格式": "Native format",
"转换": "Convert",
"请求预扣费额度": "Pre-deduction quota for requests", "请求预扣费额度": "Pre-deduction quota for requests",
"请点击我": "Please click me", "请点击我": "Please click me",
"请确认以下设置信息,点击\"初始化系统\"开始配置": "Please confirm the following settings information, click \"Initialize system\" to start configuration", "请确认以下设置信息,点击\"初始化系统\"开始配置": "Please confirm the following settings information, click \"Initialize system\" to start configuration",

View File

@@ -2077,6 +2077,9 @@
"请求结束后多退少补": "请求结束后多退少补", "请求结束后多退少补": "请求结束后多退少补",
"请求超时,请刷新页面后重新发起 GitHub 登录": "请求超时,请刷新页面后重新发起 GitHub 登录", "请求超时,请刷新页面后重新发起 GitHub 登录": "请求超时,请刷新页面后重新发起 GitHub 登录",
"请求路径": "请求路径", "请求路径": "请求路径",
"请求转换": "请求转换",
"原生格式": "原生格式",
"转换": "转换",
"请求预扣费额度": "请求预扣费额度", "请求预扣费额度": "请求预扣费额度",
"请点击我": "请点击我", "请点击我": "请点击我",
"请确认以下设置信息,点击\"初始化系统\"开始配置": "请确认以下设置信息,点击\"初始化系统\"开始配置", "请确认以下设置信息,点击\"初始化系统\"开始配置": "请确认以下设置信息,点击\"初始化系统\"开始配置",