fix(*): 模型请求协议 - 支持模型独立选协议并修正视频多参考请求
This commit is contained in:
@@ -663,7 +663,9 @@ func proxySystemRequest(c *gin.Context, svc *service.Service, user *model.User,
|
||||
switch channel.InterfaceType {
|
||||
case model.ChannelInterfaceOpenAIImage:
|
||||
capability = "image"
|
||||
case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo:
|
||||
case model.ChannelInterfaceOpenAIAudio:
|
||||
capability = "audio"
|
||||
case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo, model.ChannelInterfaceGeminiVeo:
|
||||
capability = "video"
|
||||
}
|
||||
order, err := svc.ReserveProxyBilling(user.ID, channel.ID, strings.TrimPrefix(modelName, "models/"), capability, c.GetHeader("X-Canvas-Scene"), c.GetHeader("X-Idempotency-Key"), proxyRequestVideoSeconds(c.GetHeader("Content-Type"), body))
|
||||
@@ -752,7 +754,9 @@ func apiCallLog(user *model.User, channel *model.ModelChannel, billingOrderID st
|
||||
switch channel.InterfaceType {
|
||||
case model.ChannelInterfaceOpenAIImage:
|
||||
capability = "image"
|
||||
case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo:
|
||||
case model.ChannelInterfaceOpenAIAudio:
|
||||
capability = "audio"
|
||||
case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo, model.ChannelInterfaceGeminiVeo:
|
||||
capability = "video"
|
||||
}
|
||||
requestKind := "create"
|
||||
@@ -763,24 +767,25 @@ func apiCallLog(user *model.User, channel *model.ModelChannel, billingOrderID st
|
||||
}
|
||||
}
|
||||
return model.ApiCallLog{
|
||||
UserID: user.ID,
|
||||
ChannelID: channel.ID,
|
||||
BillingOrderID: billingOrderID,
|
||||
Source: "system-channel",
|
||||
Capability: capability,
|
||||
RequestKind: requestKind,
|
||||
Billable: method == http.MethodPost,
|
||||
APIFormat: channel.APIFormat,
|
||||
Method: method,
|
||||
Path: path,
|
||||
Model: readPayloadModel(body),
|
||||
Status: status,
|
||||
StatusCode: statusCode,
|
||||
DurationMs: duration.Milliseconds(),
|
||||
Error: errorText,
|
||||
ConcurrencyLimit: concurrencyLimit,
|
||||
UpstreamURL: target,
|
||||
RequestBody: service.SanitizeAPICallPayload(body, contentType),
|
||||
UserID: user.ID,
|
||||
ChannelID: channel.ID,
|
||||
BillingOrderID: billingOrderID,
|
||||
Source: "system-channel",
|
||||
Capability: capability,
|
||||
RequestKind: requestKind,
|
||||
Billable: method == http.MethodPost,
|
||||
APIFormat: channel.APIFormat,
|
||||
Method: method,
|
||||
Path: path,
|
||||
Model: readPayloadModel(body),
|
||||
Status: status,
|
||||
StatusCode: statusCode,
|
||||
DurationMs: duration.Milliseconds(),
|
||||
Error: errorText,
|
||||
ConcurrencyLimit: concurrencyLimit,
|
||||
UpstreamURL: target,
|
||||
RequestContentType: contentType,
|
||||
RequestBody: service.SanitizeAPICallPayload(body, contentType),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
runtimeService *service.Service
|
||||
geminiGeneratePath = regexp.MustCompile(`^/models/([^/:]+):(generateContent|streamGenerateContent)$`)
|
||||
customGeminiRelayPath = regexp.MustCompile(`(?:^|/)models/[^/:]+:(generateContent|streamGenerateContent)$`)
|
||||
openAIPostEndpoints = map[string]bool{
|
||||
runtimeService *service.Service
|
||||
geminiGeneratePath = regexp.MustCompile(`^/models/([^/:]+):(generateContent|streamGenerateContent)$`)
|
||||
customGeminiRelayPath = regexp.MustCompile(`(?:^|/)models/[^/:]+:(generateContent|streamGenerateContent|predictLongRunning)$`)
|
||||
customVideoTaskPath = regexp.MustCompile(`(?:^|/)video/generations/[^/]+$`)
|
||||
customXAIVideoTaskPath = regexp.MustCompile(`(?:^|/)videos/[^/]+$`)
|
||||
customGeminiOperationPath = regexp.MustCompile(`(?:^|/)(?:models/[^/]+/)?operations/[^/]+$`)
|
||||
openAIPostEndpoints = map[string]bool{
|
||||
"/responses": true, "/chat/completions": true, "/images/generations": true, "/images/edits": true,
|
||||
"/audio/speech": true,
|
||||
}
|
||||
@@ -57,7 +60,13 @@ func authorizeCustomRelay(method string, target *url.URL, apiFormat string, cont
|
||||
return errors.New("自定义渠道调用格式无效")
|
||||
}
|
||||
if method == http.MethodGet {
|
||||
if len(query) != 0 || (requestPath != "/models" && !strings.HasSuffix(requestPath, "/models")) {
|
||||
allowed := requestPath == "/models" || strings.HasSuffix(requestPath, "/models")
|
||||
if apiFormat == "openai" {
|
||||
allowed = allowed || customVideoTaskPath.MatchString(requestPath) || customXAIVideoTaskPath.MatchString(requestPath)
|
||||
} else {
|
||||
allowed = allowed || customGeminiOperationPath.MatchString(requestPath)
|
||||
}
|
||||
if len(query) != 0 || !allowed {
|
||||
return errors.New("自定义渠道不允许访问该上游接口")
|
||||
}
|
||||
return nil
|
||||
@@ -70,7 +79,7 @@ func authorizeCustomRelay(method string, target *url.URL, apiFormat string, cont
|
||||
return errors.New("自定义渠道不允许使用该请求方法")
|
||||
}
|
||||
if apiFormat == "openai" {
|
||||
if len(query) != 0 || (!strings.HasSuffix(requestPath, "/responses") && !strings.HasSuffix(requestPath, "/chat/completions")) {
|
||||
if len(query) != 0 || (!strings.HasSuffix(requestPath, "/responses") && !strings.HasSuffix(requestPath, "/chat/completions") && !strings.HasSuffix(requestPath, "/video/generations") && !strings.HasSuffix(requestPath, "/videos/generations")) {
|
||||
return errors.New("自定义渠道不允许访问该上游接口")
|
||||
}
|
||||
return nil
|
||||
@@ -169,7 +178,9 @@ func interfaceAllowsProxyPath(interfaceType model.ChannelInterfaceType, requestP
|
||||
return requestPath == "/responses"
|
||||
case model.ChannelInterfaceOpenAIImage:
|
||||
return requestPath == "/images/generations" || requestPath == "/images/edits"
|
||||
case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo:
|
||||
case model.ChannelInterfaceOpenAIAudio:
|
||||
return requestPath == "/audio/speech"
|
||||
case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo, model.ChannelInterfaceGeminiVeo:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
||||
@@ -28,7 +28,13 @@ func TestAuthorizeCustomRelayAllowsModelsAndAgentEndpoints(t *testing.T) {
|
||||
{method: http.MethodGet, target: "https://api.example.com/v1/models", apiFormat: "openai"},
|
||||
{method: http.MethodPost, target: "https://api.example.com/v1/responses", apiFormat: "openai", contentType: "application/json"},
|
||||
{method: http.MethodPost, target: "https://api.example.com/v1/chat/completions", apiFormat: "openai", contentType: "application/json; charset=utf-8"},
|
||||
{method: http.MethodPost, target: "https://api.example.com/v1/video/generations", apiFormat: "openai", contentType: "application/json"},
|
||||
{method: http.MethodGet, target: "https://api.example.com/v1/video/generations/task-1", apiFormat: "openai"},
|
||||
{method: http.MethodPost, target: "https://api.x.ai/v1/videos/generations", apiFormat: "openai", contentType: "application/json"},
|
||||
{method: http.MethodGet, target: "https://api.x.ai/v1/videos/request-1", apiFormat: "openai"},
|
||||
{method: http.MethodPost, target: "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:streamGenerateContent?alt=sse", apiFormat: "gemini", contentType: "application/json"},
|
||||
{method: http.MethodPost, target: "https://generativelanguage.googleapis.com/v1beta/models/veo-3.0-generate-preview:predictLongRunning", apiFormat: "gemini", contentType: "application/json"},
|
||||
{method: http.MethodGet, target: "https://generativelanguage.googleapis.com/v1beta/operations/operation-1", apiFormat: "gemini"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
target, err := url.Parse(test.target)
|
||||
|
||||
Reference in New Issue
Block a user