mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
Merge pull request #3176 from jianjianai/fix/precompute-model-body-replacement
优化 OpenAI 网关 failover 流程,避免账号切换时重复对请求体执行 JSON model 替换。
This commit is contained in:
@@ -47,6 +47,30 @@ func resolveOpenAIMessagesDispatchMappedModel(apiKey *service.APIKey, requestedM
|
||||
return strings.TrimSpace(apiKey.Group.ResolveMessagesDispatchModel(requestedModel))
|
||||
}
|
||||
|
||||
type openAIModelBodyReplaceFunc func([]byte, string) []byte
|
||||
|
||||
func openAIModelMappedBody(body []byte, mapped bool, mappedModel string, replace openAIModelBodyReplaceFunc) []byte {
|
||||
if !mapped || replace == nil {
|
||||
return body
|
||||
}
|
||||
return replace(body, mappedModel)
|
||||
}
|
||||
|
||||
func newOpenAIModelMappedBodyCache(body []byte, replace openAIModelBodyReplaceFunc) func(bool, string) []byte {
|
||||
replacedBodies := make(map[string][]byte)
|
||||
return func(mapped bool, mappedModel string) []byte {
|
||||
if !mapped {
|
||||
return body
|
||||
}
|
||||
if cachedBody, ok := replacedBodies[mappedModel]; ok {
|
||||
return cachedBody
|
||||
}
|
||||
replacedBody := openAIModelMappedBody(body, true, mappedModel, replace)
|
||||
replacedBodies[mappedModel] = replacedBody
|
||||
return replacedBody
|
||||
}
|
||||
}
|
||||
|
||||
func usageRecordContext(parent context.Context, base context.Context) context.Context {
|
||||
if base == nil {
|
||||
base = context.Background()
|
||||
@@ -241,6 +265,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
|
||||
|
||||
// 解析渠道级模型映射
|
||||
channelMapping, _ := h.gatewayService.ResolveChannelMappingAndRestrict(c.Request.Context(), apiKey.GroupID, reqModel)
|
||||
forwardBody := openAIModelMappedBody(body, channelMapping.Mapped, channelMapping.MappedModel, h.gatewayService.ReplaceModelInBody)
|
||||
|
||||
// 提前校验 function_call_output 是否具备可关联上下文,避免上游 400。
|
||||
if !h.validateFunctionCallOutputRequest(c, body, reqLog) {
|
||||
@@ -353,11 +378,6 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
|
||||
// Forward request
|
||||
service.SetOpsLatencyMs(c, service.OpsRoutingLatencyMsKey, time.Since(routingStart).Milliseconds())
|
||||
forwardStart := time.Now()
|
||||
// 应用渠道模型映射到请求体
|
||||
forwardBody := body
|
||||
if channelMapping.Mapped {
|
||||
forwardBody = h.gatewayService.ReplaceModelInBody(body, channelMapping.MappedModel)
|
||||
}
|
||||
writerSizeBeforeForward := c.Writer.Size()
|
||||
result, err := func() (*service.OpenAIForwardResult, error) {
|
||||
defer func() {
|
||||
@@ -660,6 +680,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
|
||||
|
||||
// 解析渠道级模型映射
|
||||
channelMappingMsg, _ := h.gatewayService.ResolveChannelMappingAndRestrict(c.Request.Context(), apiKey.GroupID, reqModel)
|
||||
mappedBodyForMessages := newOpenAIModelMappedBodyCache(body, h.gatewayService.ReplaceModelInBody)
|
||||
|
||||
// 绑定错误透传服务,允许 service 层在非 failover 错误场景复用规则。
|
||||
if h.errorPassthroughService != nil {
|
||||
@@ -758,10 +779,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
|
||||
|
||||
defaultMappedModel := strings.TrimSpace(effectiveMappedModel)
|
||||
// 应用渠道模型映射到请求体
|
||||
forwardBody := body
|
||||
if channelMappingMsg.Mapped {
|
||||
forwardBody = h.gatewayService.ReplaceModelInBody(body, channelMappingMsg.MappedModel)
|
||||
}
|
||||
forwardBody := mappedBodyForMessages(channelMappingMsg.Mapped, channelMappingMsg.MappedModel)
|
||||
writerSizeBeforeForward := c.Writer.Size()
|
||||
result, err := func() (*service.OpenAIForwardResult, error) {
|
||||
defer func() {
|
||||
|
||||
@@ -445,6 +445,41 @@ func TestResolveOpenAIMessagesDispatchMappedModel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenAIModelMappedBody(t *testing.T) {
|
||||
body := []byte(`{"model":"alias","input":"hello"}`)
|
||||
calls := 0
|
||||
|
||||
forwardBody := openAIModelMappedBody(body, true, "gpt-5.4", func(body []byte, newModel string) []byte {
|
||||
calls++
|
||||
return service.ReplaceModelInBody(body, newModel)
|
||||
})
|
||||
|
||||
require.Equal(t, 1, calls)
|
||||
require.Equal(t, "gpt-5.4", gjson.GetBytes(forwardBody, "model").String())
|
||||
require.Equal(t, "alias", gjson.GetBytes(body, "model").String())
|
||||
}
|
||||
|
||||
func TestOpenAIModelMappedBodyCache(t *testing.T) {
|
||||
body := []byte(`{"model":"alias","input":"hello"}`)
|
||||
calls := 0
|
||||
mappedBody := newOpenAIModelMappedBodyCache(body, func(body []byte, newModel string) []byte {
|
||||
calls++
|
||||
return service.ReplaceModelInBody(body, newModel)
|
||||
})
|
||||
|
||||
first := mappedBody(true, "gpt-5.4")
|
||||
second := mappedBody(true, "gpt-5.4")
|
||||
third := mappedBody(true, "gpt-5.3-codex")
|
||||
unmapped := mappedBody(false, "ignored")
|
||||
|
||||
require.Equal(t, 2, calls)
|
||||
require.Equal(t, "gpt-5.4", gjson.GetBytes(first, "model").String())
|
||||
require.Equal(t, "gpt-5.4", gjson.GetBytes(second, "model").String())
|
||||
require.Equal(t, "gpt-5.3-codex", gjson.GetBytes(third, "model").String())
|
||||
require.Equal(t, body, unmapped)
|
||||
require.Same(t, &first[0], &second[0])
|
||||
}
|
||||
|
||||
func TestOpenAIResponses_MissingDependencies_ReturnsServiceUnavailable(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user