From 6e81faa6983fc9ea7b93d7fe23eab32258433cae Mon Sep 17 00:00:00 2001 From: fengsilin Date: Wed, 27 May 2026 14:49:39 +0800 Subject: [PATCH] perf: lazy-read body in DoApiRequest for passthrough model mapping DoApiRequest previously called io.ReadAll on every request body to apply passthrough model mapping, even when the feature was disabled. This caused unnecessary memory pressure for large request bodies (long conversations, multipart image uploads). Split the guard logic into needsPassthroughModelMapping() so that ReadAll is only called when all conditions are met (feature enabled + passthrough mode + model mapped). Non-passthrough requests now pass the original io.Reader directly to http.NewRequest without buffering. Rewrite tests to verify combined guard + modify behavior. --- relay/channel/api_request.go | 31 ++++++++++++--------- relay/channel/api_request_test.go | 45 +++++++++++-------------------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/relay/channel/api_request.go b/relay/channel/api_request.go index cbed0bd..26fcab6 100644 --- a/relay/channel/api_request.go +++ b/relay/channel/api_request.go @@ -279,17 +279,24 @@ func applyHeaderOverrideToRequest(req *http.Request, headerOverride map[string]s } } -func applyPassthroughModelMapping(body []byte, info *common.RelayInfo) []byte { - if len(body) == 0 || info == nil || info.ChannelMeta == nil { - return body +func needsPassthroughModelMapping(info *common.RelayInfo) bool { + if info == nil || info.ChannelMeta == nil { + return false } if !model_setting.GetGlobalSettings().PassthroughApplyModelMapping { - return body + return false } if !model_setting.GetGlobalSettings().PassThroughRequestEnabled && !info.ChannelMeta.ChannelSetting.PassThroughBodyEnabled { - return body + return false } if !info.ChannelMeta.IsModelMapped || strings.TrimSpace(info.ChannelMeta.UpstreamModelName) == "" { + return false + } + return true +} + +func applyPassthroughModelMapping(body []byte, info *common.RelayInfo) []byte { + if len(body) == 0 { return body } if common2.GetJsonType(body) != "object" { @@ -310,15 +317,15 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody if common2.DebugEnabled { common2.SysLog("fullRequestURL: " + fullRequestURL) } - var bodyBytes []byte - if requestBody != nil { - bodyBytes, err = io.ReadAll(requestBody) - if err != nil { - return nil, fmt.Errorf("read request body failed: %w", err) + var bodyReader io.Reader = requestBody + if needsPassthroughModelMapping(info) && requestBody != nil { + bodyBytes, readErr := io.ReadAll(requestBody) + if readErr != nil { + return nil, fmt.Errorf("read request body failed: %w", readErr) } + bodyReader = bytes.NewReader(applyPassthroughModelMapping(bodyBytes, info)) } - bodyBytes = applyPassthroughModelMapping(bodyBytes, info) - req, err := http.NewRequest(c.Request.Method, fullRequestURL, bytes.NewReader(bodyBytes)) + req, err := http.NewRequest(c.Request.Method, fullRequestURL, bodyReader) if err != nil { return nil, fmt.Errorf("new request failed: %w", err) } diff --git a/relay/channel/api_request_test.go b/relay/channel/api_request_test.go index ede3f41..f8c5db8 100644 --- a/relay/channel/api_request_test.go +++ b/relay/channel/api_request_test.go @@ -12,10 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestApplyPassthroughModelMapping(t *testing.T) { - body := []byte(`{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}`) - info := &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{IsModelMapped: true, UpstreamModelName: "gpt-4.1"}} - +func TestPassthroughModelMapping_AppliedWhenEnabled(t *testing.T) { origRequestEnabled := model_setting.GetGlobalSettings().PassThroughRequestEnabled origApplyMapping := model_setting.GetGlobalSettings().PassthroughApplyModelMapping defer func() { @@ -26,14 +23,15 @@ func TestApplyPassthroughModelMapping(t *testing.T) { model_setting.GetGlobalSettings().PassThroughRequestEnabled = true model_setting.GetGlobalSettings().PassthroughApplyModelMapping = true + info := &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{IsModelMapped: true, UpstreamModelName: "gpt-4.1"}} + body := []byte(`{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}`) + + assert.True(t, needsPassthroughModelMapping(info)) got := applyPassthroughModelMapping(body, info) assert.JSONEq(t, `{"model":"gpt-4.1","messages":[{"role":"user","content":"hi"}]}`, string(got)) } -func TestApplyPassthroughModelMapping_NonJSONKeepsOriginal(t *testing.T) { - body := []byte(`model=gpt-4o`) - info := &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{IsModelMapped: true, UpstreamModelName: "gpt-4.1"}} - +func TestPassthroughModelMapping_SkippedWhenNotMapped(t *testing.T) { origRequestEnabled := model_setting.GetGlobalSettings().PassThroughRequestEnabled origApplyMapping := model_setting.GetGlobalSettings().PassthroughApplyModelMapping defer func() { @@ -44,14 +42,11 @@ func TestApplyPassthroughModelMapping_NonJSONKeepsOriginal(t *testing.T) { model_setting.GetGlobalSettings().PassThroughRequestEnabled = true model_setting.GetGlobalSettings().PassthroughApplyModelMapping = true - got := applyPassthroughModelMapping(body, info) - assert.Equal(t, body, got) -} - -func TestApplyPassthroughModelMapping_DisabledKeepsOriginal(t *testing.T) { - body := []byte(`{"model":"gpt-4o"}`) info := &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{IsModelMapped: false, UpstreamModelName: "gpt-4.1"}} + assert.False(t, needsPassthroughModelMapping(info)) +} +func TestPassthroughModelMapping_SkippedWhenPassthroughOff(t *testing.T) { origRequestEnabled := model_setting.GetGlobalSettings().PassThroughRequestEnabled origApplyMapping := model_setting.GetGlobalSettings().PassthroughApplyModelMapping defer func() { @@ -59,26 +54,18 @@ func TestApplyPassthroughModelMapping_DisabledKeepsOriginal(t *testing.T) { model_setting.GetGlobalSettings().PassthroughApplyModelMapping = origApplyMapping }() - model_setting.GetGlobalSettings().PassThroughRequestEnabled = true + model_setting.GetGlobalSettings().PassThroughRequestEnabled = false model_setting.GetGlobalSettings().PassthroughApplyModelMapping = true - got := applyPassthroughModelMapping(body, info) - assert.Equal(t, body, got) + info := &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{IsModelMapped: true, UpstreamModelName: "gpt-4.1"}} + assert.False(t, needsPassthroughModelMapping(info)) } -func TestDoApiRequest_DoesNotChangeNonPassthroughBody(t *testing.T) { - origRequestEnabled := model_setting.GetGlobalSettings().PassThroughRequestEnabled - origApplyMapping := model_setting.GetGlobalSettings().PassthroughApplyModelMapping - defer func() { - model_setting.GetGlobalSettings().PassThroughRequestEnabled = origRequestEnabled - model_setting.GetGlobalSettings().PassthroughApplyModelMapping = origApplyMapping - }() - - model_setting.GetGlobalSettings().PassThroughRequestEnabled = false - model_setting.GetGlobalSettings().PassthroughApplyModelMapping = true +func TestPassthroughModelMapping_NonJSONKeepsOriginal(t *testing.T) { + body := []byte(`model=gpt-4o`) + info := &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{IsModelMapped: true, UpstreamModelName: "gpt-4.1"}} - body := []byte(`{"model":"gpt-4o"}`) - got := applyPassthroughModelMapping(body, &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{IsModelMapped: true, UpstreamModelName: "gpt-4.1"}}) + got := applyPassthroughModelMapping(body, info) assert.Equal(t, body, got) }