Commit 2cf177ac by mingisrookie Committed by GitHub

perf(common): 批量复制 RawMessage,优化请求深拷贝 (#7221)

* perf(relay): bulk-copy Responses raw JSON fields

* perf(common): share RawMessage deep-copy optimization

---------

Co-authored-by: CaIon <i@caion.me>
parent eb99ab1b
package common
import (
"bytes"
"encoding/json"
"fmt"
"github.com/jinzhu/copier"
......@@ -11,7 +13,18 @@ func DeepCopy[T any](src *T) (*T, error) {
return nil, fmt.Errorf("copy source cannot be nil")
}
var dst T
err := copier.CopyWithOption(&dst, src, copier.Option{DeepCopy: true, IgnoreEmpty: true})
err := copier.CopyWithOption(&dst, src, copier.Option{
DeepCopy: true, IgnoreEmpty: true,
Converters: []copier.TypeConverter{{
SrcType: json.RawMessage{},
DstType: json.RawMessage{},
Fn: func(src any) (any, error) {
// Copy raw JSON in bulk while retaining independent storage for
// request mutation and retries, instead of reflecting over each byte.
return json.RawMessage(bytes.Clone(src.(json.RawMessage))), nil
},
}},
})
if err != nil {
return nil, err
}
......
package relay
import (
"bytes"
"encoding/json"
"fmt"
"net/http/httptest"
"reflect"
"testing"
"github.com/QuantumNous/new-api/common"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relay/helper"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRequestDeepCopyResponses(t *testing.T) {
t.Run("nil source", func(t *testing.T) {
clone, err := common.DeepCopy[dto.OpenAIResponsesRequest](nil)
require.Error(t, err)
assert.Nil(t, clone)
})
for _, raw := range []json.RawMessage{nil, {}, []byte(`null`), []byte(`false`), []byte(`0`), []byte(`[]`), []byte(`{"x":1}`)} {
t.Run(fmt.Sprintf("raw_%q_nil_%t", raw, raw == nil), func(t *testing.T) {
src := &dto.OpenAIResponsesRequest{}
populateResponsesCloneFixture(t, reflect.ValueOf(src).Elem(), raw)
want := &dto.OpenAIResponsesRequest{}
populateResponsesCloneFixture(t, reflect.ValueOf(want).Elem(), raw)
clone, err := common.DeepCopy(src)
require.NoError(t, err)
assert.Equal(t, src, clone, "including internal state and explicit zero pointers")
// Mutate every reachable field, including nested pointers and raw bytes.
// A later channel attempt must still see the original request.
populateResponsesCloneFixture(t, reflect.ValueOf(clone).Elem(), []byte(`true`))
assert.Equal(t, want, src)
retry, err := common.DeepCopy(src)
require.NoError(t, err)
assert.Equal(t, want, retry)
})
}
t.Run("absent optional fields", func(t *testing.T) {
src := &dto.OpenAIResponsesRequest{Model: "gpt-4.1", Input: []byte(`"hello"`)}
clone, err := common.DeepCopy(src)
require.NoError(t, err)
assert.Equal(t, src, clone)
clone.Input[1] = 'H'
assert.Equal(t, json.RawMessage(`"hello"`), src.Input)
})
t.Run("large input", func(t *testing.T) {
input := bytes.Repeat([]byte("a"), 1<<20)
input[0], input[len(input)-1] = '"', '"'
src := &dto.OpenAIResponsesRequest{Model: "gpt-4.1", Input: input}
clone, err := common.DeepCopy(src)
require.NoError(t, err)
assert.Equal(t, src, clone)
clone.Input[len(input)/2] = 'b'
assert.Equal(t, byte('a'), src.Input[len(input)/2])
})
t.Run("model mapping on successive channel attempts", func(t *testing.T) {
src := &dto.OpenAIResponsesRequest{Model: "client-model", Input: []byte(`"hello"`)}
for _, model := range []string{"first-channel-model", "retry-channel-model"} {
request, err := common.DeepCopy(src)
require.NoError(t, err)
assert.Equal(t, json.RawMessage(`"hello"`), request.Input)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Set("model_mapping", fmt.Sprintf(`{"client-model":%q}`, model))
info := &relaycommon.RelayInfo{OriginModelName: src.Model}
require.NoError(t, helper.ModelMappedHelper(c, info, request))
assert.Equal(t, model, request.Model)
assert.Equal(t, "client-model", src.Model)
request.Input[1] = 'H'
assert.Equal(t, json.RawMessage(`"hello"`), src.Input)
}
})
}
// Populate all mutable fields so newly added request fields also exercise the
// retry-isolation contract, rather than silently escaping a hand-written list.
func populateResponsesCloneFixture(t *testing.T, value reflect.Value, raw json.RawMessage) {
t.Helper()
switch value.Kind() {
case reflect.Struct:
for i := 0; i < value.NumField(); i++ {
populateResponsesCloneFixture(t, value.Field(i), raw)
}
case reflect.Ptr:
if value.IsNil() {
value.Set(reflect.New(value.Type().Elem()))
}
populateResponsesCloneFixture(t, value.Elem(), raw)
case reflect.Slice:
require.Equal(t, reflect.TypeOf(json.RawMessage{}), value.Type())
if value.Len() > 0 {
value.Index(0).SetUint('!')
}
value.Set(reflect.ValueOf(json.RawMessage(bytes.Clone(raw))))
case reflect.String:
value.SetString(value.String() + "gpt-4.1")
case reflect.Bool:
value.SetBool(bytes.Equal(raw, []byte(`true`)))
case reflect.Int:
if bytes.Equal(raw, []byte(`true`)) {
value.SetInt(1)
}
case reflect.Uint:
if bytes.Equal(raw, []byte(`true`)) {
value.SetUint(1)
}
case reflect.Float64:
if bytes.Equal(raw, []byte(`true`)) {
value.SetFloat(1)
}
default:
require.FailNow(t, "extend clone fixture for new field type", "%s", value.Type())
}
}
func TestRequestDeepCopyRawMutation(t *testing.T) {
t.Run("edits before cloning are included", func(t *testing.T) {
src := &dto.OpenAIResponsesRequest{Input: json.RawMessage(`"hello"`)}
src.Input[1] = 'H'
clone, err := common.DeepCopy(src)
require.NoError(t, err)
assert.Equal(t, json.RawMessage(`"Hello"`), clone.Input)
})
t.Run("edits after cloning are isolated in both directions", func(t *testing.T) {
input := make(json.RawMessage, len(`"hello"`), 32)
copy(input, `"hello"`)
src := &dto.OpenAIResponsesRequest{Input: input}
clone, err := common.DeepCopy(src)
require.NoError(t, err)
clone.Input[1] = 'H'
assert.Equal(t, json.RawMessage(`"hello"`), src.Input)
src.Input[2] = 'A'
assert.Equal(t, json.RawMessage(`"Hello"`), clone.Input)
clone.Input = append(clone.Input, ' ')
src.Input = append(src.Input, '\n')
assert.Equal(t, json.RawMessage("\"Hello\" "), clone.Input)
assert.Equal(t, json.RawMessage("\"hAllo\"\n"), src.Input)
clone.Input = json.RawMessage(`"replacement"`)
assert.Equal(t, json.RawMessage("\"hAllo\"\n"), src.Input)
})
t.Run("raw map and slice entries retain nil and empty values", func(t *testing.T) {
src := &struct {
Fields map[string]json.RawMessage
Items []json.RawMessage
}{
Fields: map[string]json.RawMessage{"absent": nil, "empty": {}, "value": []byte(`false`)},
Items: []json.RawMessage{nil, {}, []byte(`0`)},
}
clone, err := common.DeepCopy(src)
require.NoError(t, err)
assert.Equal(t, src, clone)
clone.Fields["value"][0] = '!'
clone.Items[2][0] = '1'
delete(clone.Fields, "absent")
assert.Contains(t, src.Fields, "absent")
assert.Equal(t, json.RawMessage(`false`), src.Fields["value"])
assert.Equal(t, json.RawMessage(`0`), src.Items[2])
})
}
func TestRequestDeepCopyOtherProtocols(t *testing.T) {
t.Run("chat nested fields remain isolated", func(t *testing.T) {
assertRequestCloneIsolation(t, `{
"model":"gpt-4.1","max_tokens":0,"stream":false,"thinking_budget":128,
"messages":[{"role":"assistant","content":[{"type":"text","text":"hello"}],"tool_calls":[{"id":"call_1"}]}],
"response_format":{"type":"json_schema","json_schema":{"strict":false}}
}`, func(clone *dto.GeneralOpenAIRequest) {
clone.Messages[0].ToolCalls[0] = '!'
clone.Messages[0].Content.([]any)[0].(map[string]any)["text"] = "changed"
clone.ResponseFormat.JsonSchema[0] = '!'
*clone.MaxTokens = 1
*clone.Stream = true
})
})
t.Run("claude dynamic tools and raw fields remain isolated", func(t *testing.T) {
assertRequestCloneIsolation(t, `{
"model":"claude-sonnet-4-5","max_tokens":1024,"temperature":0,
"tools":[{"name":"lookup","input_schema":{"type":"object"}}],
"metadata":{"user_id":"test"},"messages":[{"role":"user","content":"hello"}]
}`, func(clone *dto.ClaudeRequest) {
clone.Metadata[0] = '!'
clone.Tools.([]any)[0].(map[string]any)["input_schema"].(map[string]any)["type"] = "array"
clone.Messages[0].Content = "changed"
*clone.Temperature = 1
})
})
t.Run("gemini nested parts and configuration remain isolated", func(t *testing.T) {
assertRequestCloneIsolation(t, `{
"contents":[{"role":"model","parts":[{"text":"hello","thoughtSignature":"signature","inlineData":{"mimeType":"text/plain","data":"aGk="}}]}],
"tools":[{"googleSearch":{}}],"generationConfig":{"responseJsonSchema":{"type":"object"}}
}`, func(clone *dto.GeminiChatRequest) {
clone.Contents[0].Parts[0].ThoughtSignature[1] = '!'
clone.Contents[0].Parts[0].InlineData.Data = "changed"
clone.Tools[0] = '!'
clone.GenerationConfig.ResponseJsonSchema[0] = '!'
})
})
t.Run("image extra parameters remain isolated", func(t *testing.T) {
assertRequestCloneIsolation(t, `{
"model":"gpt-image-1","prompt":"a cat","n":1,"watermark":false,
"image":["https://example.com/image.png"],"parameters":{"seed":0}
}`, func(clone *dto.ImageRequest) {
clone.Image[0] = '!'
clone.Extra["parameters"][0] = '!'
clone.Extra["new_field"] = json.RawMessage(`true`)
*clone.N = 2
*clone.Watermark = true
})
})
t.Run("audio raw parameters and speed remain isolated", func(t *testing.T) {
assertRequestCloneIsolation(t, `{
"model":"tts-1","input":"hello","voice":"alloy","speed":1,"metadata":{"tag":"test"},"ref_audio":"sample"
}`, func(clone *dto.AudioRequest) {
clone.Metadata[0] = '!'
clone.RefAudio[1] = '!'
*clone.Speed = 2
})
})
t.Run("embedding dynamic input and dimensions remain isolated", func(t *testing.T) {
assertRequestCloneIsolation(t, `{"model":"text-embedding-3-small","input":[[1,2],[3]],"dimensions":256}`,
func(clone *dto.EmbeddingRequest) {
clone.Input.([]any)[0].([]any)[0] = float64(9)
*clone.Dimensions = 512
})
})
t.Run("rerank documents and options remain isolated", func(t *testing.T) {
assertRequestCloneIsolation(t, `{
"model":"rerank-v3.5","query":"hello","documents":["first","second"],"top_n":1,"return_documents":false
}`, func(clone *dto.RerankRequest) {
clone.Documents[0] = "changed"
*clone.TopN = 2
*clone.ReturnDocuments = true
})
})
}
// A channel attempt may mutate its clone without changing the request used by
// later attempts. Decode the expected request independently to avoid aliases.
func assertRequestCloneIsolation[T any](t *testing.T, body string, mutate func(*T)) {
t.Helper()
var src, want T
require.NoError(t, common.UnmarshalJsonStr(body, &src))
require.NoError(t, common.UnmarshalJsonStr(body, &want))
clone, err := common.DeepCopy(&src)
require.NoError(t, err)
require.Equal(t, &want, clone)
wantJSON, err := common.Marshal(&want)
require.NoError(t, err)
cloneJSON, err := common.Marshal(clone)
require.NoError(t, err)
assert.Equal(t, string(wantJSON), string(cloneJSON), "preserve the outbound JSON")
mutate(clone)
assert.Equal(t, want, src, "a channel attempt must not mutate the original request")
retry, err := common.DeepCopy(&src)
require.NoError(t, err)
assert.Equal(t, &want, retry)
}
func BenchmarkRequestDeepCopy(b *testing.B) {
for _, size := range []int{256, 4 << 10, 1 << 20, 10 << 20, 40 << 20} {
input := bytes.Repeat([]byte("a"), size)
input[0], input[len(input)-1] = '"', '"'
src := &dto.OpenAIResponsesRequest{Model: "gpt-4.1", Input: input}
for _, method := range []struct {
name string
copy func(*dto.OpenAIResponsesRequest) (*dto.OpenAIResponsesRequest, error)
}{
{"reflect", func(src *dto.OpenAIResponsesRequest) (*dto.OpenAIResponsesRequest, error) {
var dst dto.OpenAIResponsesRequest
err := copier.CopyWithOption(&dst, src, copier.Option{DeepCopy: true, IgnoreEmpty: true})
return &dst, err
}},
{"raw_bytes", common.DeepCopy[dto.OpenAIResponsesRequest]},
} {
b.Run(fmt.Sprintf("%dB/%s", size, method.name), func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(size))
for b.Loop() {
clone, err := method.copy(src)
require.NoError(b, err)
require.Len(b, clone.Input, size)
}
})
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment