mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 03:43:39 +00:00
Merge pull request #2703 from seefs001/feature/log-conversion-info
feat: log shows request conversion
This commit is contained in:
@@ -56,8 +56,9 @@ func formatUserLogs(logs []*Log) {
|
||||
var otherMap map[string]interface{}
|
||||
otherMap, _ = common.StrToMap(logs[i].Other)
|
||||
if otherMap != nil {
|
||||
// delete admin
|
||||
// Remove admin-only debug fields.
|
||||
delete(otherMap, "admin_info")
|
||||
delete(otherMap, "request_conversion")
|
||||
}
|
||||
logs[i].Other = common.MapToJsonStr(otherMap)
|
||||
logs[i].Id = logs[i].Id % 1024
|
||||
|
||||
@@ -97,6 +97,7 @@ func chatCompletionsViaResponses(c *gin.Context, info *relaycommon.RelayInfo, ad
|
||||
if err != nil {
|
||||
return nil, types.NewErrorWithStatusCode(err, types.ErrorCodeInvalidRequest, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
info.AppendRequestConversion(types.RelayFormatOpenAIResponses)
|
||||
|
||||
savedRelayMode := info.RelayMode
|
||||
savedRequestURLPath := info.RequestURLPath
|
||||
@@ -112,6 +113,7 @@ func chatCompletionsViaResponses(c *gin.Context, info *relaycommon.RelayInfo, ad
|
||||
if err != nil {
|
||||
return nil, types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
|
||||
jsonData, err := common.Marshal(convertedRequest)
|
||||
if err != nil {
|
||||
|
||||
@@ -110,6 +110,7 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
jsonData, err := common.Marshal(convertedRequest)
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
|
||||
@@ -121,6 +121,10 @@ type RelayInfo struct {
|
||||
|
||||
Request dto.Request
|
||||
|
||||
// RequestConversionChain records request format conversions in order, e.g.
|
||||
// ["openai", "openai_responses"] or ["openai", "claude"].
|
||||
RequestConversionChain []types.RelayFormat
|
||||
|
||||
ThinkingContentInfo
|
||||
TokenCountMeta
|
||||
*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) {
|
||||
var info *RelayInfo
|
||||
var err error
|
||||
switch relayFormat {
|
||||
case types.RelayFormatOpenAI:
|
||||
return GenRelayInfoOpenAI(c, request), nil
|
||||
info = GenRelayInfoOpenAI(c, request)
|
||||
case types.RelayFormatOpenAIAudio:
|
||||
return GenRelayInfoOpenAIAudio(c, request), nil
|
||||
info = GenRelayInfoOpenAIAudio(c, request)
|
||||
case types.RelayFormatOpenAIImage:
|
||||
return GenRelayInfoImage(c, request), nil
|
||||
info = GenRelayInfoImage(c, request)
|
||||
case types.RelayFormatOpenAIRealtime:
|
||||
return GenRelayInfoWs(c, ws), nil
|
||||
info = GenRelayInfoWs(c, ws)
|
||||
case types.RelayFormatClaude:
|
||||
return GenRelayInfoClaude(c, request), nil
|
||||
info = GenRelayInfoClaude(c, request)
|
||||
case types.RelayFormatRerank:
|
||||
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:
|
||||
return GenRelayInfoGemini(c, request), nil
|
||||
info = GenRelayInfoGemini(c, request)
|
||||
case types.RelayFormatEmbedding:
|
||||
return GenRelayInfoEmbedding(c, request), nil
|
||||
info = GenRelayInfoEmbedding(c, request)
|
||||
case types.RelayFormatOpenAIResponses:
|
||||
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:
|
||||
return genBaseRelayInfo(c, nil), nil
|
||||
info = genBaseRelayInfo(c, nil)
|
||||
case types.RelayFormatMjProxy:
|
||||
return genBaseRelayInfo(c, nil), nil
|
||||
info = genBaseRelayInfo(c, nil)
|
||||
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) {
|
||||
|
||||
40
relay/common/request_conversion.go
Normal file
40
relay/common/request_conversion.go
Normal 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)
|
||||
}
|
||||
@@ -113,6 +113,7 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
|
||||
if info.ChannelSetting.SystemPrompt != "" {
|
||||
// 如果有系统提示,则将其添加到请求中
|
||||
|
||||
@@ -45,6 +45,7 @@ func EmbeddingHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
jsonData, err := json.Marshal(convertedRequest)
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
|
||||
@@ -149,6 +149,7 @@ func GeminiHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
jsonData, err := common.Marshal(convertedRequest)
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
|
||||
@@ -57,6 +57,7 @@ func ImageHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *type
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed)
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
|
||||
switch convertedRequest.(type) {
|
||||
case *bytes.Buffer:
|
||||
|
||||
@@ -53,6 +53,7 @@ func RerankHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
jsonData, err := common.Marshal(convertedRequest)
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
|
||||
@@ -53,6 +53,7 @@ func ResponsesHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
|
||||
jsonData, err := common.Marshal(convertedRequest)
|
||||
if err != nil {
|
||||
return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
|
||||
|
||||
@@ -70,9 +70,38 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
|
||||
|
||||
other["admin_info"] = adminInfo
|
||||
appendRequestPath(ctx, relayInfo, other)
|
||||
appendRequestConversionChain(relayInfo, 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 {
|
||||
switch f {
|
||||
case types.RelayFormatOpenAI:
|
||||
chain = append(chain, "OpenAI Compatible")
|
||||
case types.RelayFormatClaude:
|
||||
chain = append(chain, "Claude Messages")
|
||||
case types.RelayFormatGemini:
|
||||
chain = append(chain, "Google Gemini")
|
||||
case types.RelayFormatOpenAIResponses:
|
||||
chain = append(chain, "OpenAI Responses")
|
||||
default:
|
||||
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{} {
|
||||
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
|
||||
info["ws"] = true
|
||||
|
||||
@@ -306,6 +306,16 @@ export const useLogsData = () => {
|
||||
|
||||
// Format logs data
|
||||
const setLogsFormat = (logs) => {
|
||||
const requestConversionDisplayValue = (conversionChain) => {
|
||||
const chain = Array.isArray(conversionChain)
|
||||
? conversionChain.filter(Boolean)
|
||||
: [];
|
||||
if (chain.length <= 1) {
|
||||
return t('原生格式');
|
||||
}
|
||||
return `${chain.join(' -> ')}`;
|
||||
};
|
||||
|
||||
let expandDatesLocal = {};
|
||||
for (let i = 0; i < logs.length; i++) {
|
||||
logs[i].timestamp2string = timestamp2string(logs[i].created_at);
|
||||
@@ -482,6 +492,12 @@ export const useLogsData = () => {
|
||||
value: other.request_path,
|
||||
});
|
||||
}
|
||||
if (isAdminUser) {
|
||||
expandDataLocal.push({
|
||||
key: t('请求转换'),
|
||||
value: requestConversionDisplayValue(other?.request_conversion),
|
||||
});
|
||||
}
|
||||
if (isAdminUser) {
|
||||
let localCountMode = '';
|
||||
if (other?.admin_info?.local_count_tokens) {
|
||||
|
||||
@@ -2093,6 +2093,9 @@
|
||||
"请求结束后多退少补": "Adjust after request completion",
|
||||
"请求超时,请刷新页面后重新发起 GitHub 登录": "Request timed out, please refresh and restart GitHub login",
|
||||
"请求路径": "Request path",
|
||||
"请求转换": "Request conversion",
|
||||
"原生格式": "Native format",
|
||||
"转换": "Convert",
|
||||
"请求预扣费额度": "Pre-deduction quota for requests",
|
||||
"请点击我": "Please click me",
|
||||
"请确认以下设置信息,点击\"初始化系统\"开始配置": "Please confirm the following settings information, click \"Initialize system\" to start configuration",
|
||||
|
||||
@@ -2079,6 +2079,9 @@
|
||||
"请求结束后多退少补": "请求结束后多退少补",
|
||||
"请求超时,请刷新页面后重新发起 GitHub 登录": "请求超时,请刷新页面后重新发起 GitHub 登录",
|
||||
"请求路径": "请求路径",
|
||||
"请求转换": "请求转换",
|
||||
"原生格式": "原生格式",
|
||||
"转换": "转换",
|
||||
"请求预扣费额度": "请求预扣费额度",
|
||||
"请点击我": "请点击我",
|
||||
"请确认以下设置信息,点击\"初始化系统\"开始配置": "请确认以下设置信息,点击\"初始化系统\"开始配置",
|
||||
|
||||
Reference in New Issue
Block a user