| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755 |
- package patch_test
- import (
- "testing"
- "github.com/bytedance/sonic"
- "github.com/labring/aiproxy/core/relay/meta"
- "github.com/labring/aiproxy/core/relay/plugin/patch"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func TestNew(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- assert.NotNil(t, plugin)
- assert.True(t, len(patch.DefaultPredefinedPatches) > 0)
- }
- func TestApplyPatches_DeepSeekMaxTokensLimit(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- config := &patch.Config{}
- testCases := []struct {
- name string
- input map[string]any
- actualModel string
- expectedMaxTokens int
- shouldModify bool
- }{
- {
- name: "deepseek model with high max_tokens",
- input: map[string]any{
- "model": "deepseek-chat",
- "max_tokens": 20000,
- },
- actualModel: "deepseek-chat",
- expectedMaxTokens: 8192,
- shouldModify: true,
- },
- {
- name: "deepseek model with high max_tokens",
- input: map[string]any{
- "model": "deepseek-v3",
- "max_tokens": 20000,
- },
- actualModel: "deepseek-v3",
- expectedMaxTokens: 16384,
- shouldModify: true,
- },
- {
- name: "deepseek model with low max_tokens",
- input: map[string]any{
- "model": "deepseek-chat",
- "max_tokens": 8000,
- },
- actualModel: "deepseek-chat",
- expectedMaxTokens: 8000,
- shouldModify: false,
- },
- {
- name: "non-deepseek model",
- input: map[string]any{
- "model": "gpt-4",
- "max_tokens": 20000,
- },
- actualModel: "gpt-4",
- expectedMaxTokens: 20000,
- shouldModify: false,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- inputBytes, err := sonic.Marshal(tc.input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: tc.actualModel}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, config)
- require.NoError(t, err)
- assert.Equal(t, tc.shouldModify, modified)
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- if maxTokens, exists := output["max_tokens"]; exists {
- maxTokensFloat, ok := maxTokens.(float64)
- require.True(t, ok, "max_tokens should be float64")
- assert.Equal(t, tc.expectedMaxTokens, int(maxTokensFloat))
- }
- })
- }
- }
- func TestApplyPatches_GPT5MaxTokensConversion(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- config := &patch.Config{}
- testCases := []struct {
- name string
- input map[string]any
- actualModel string
- expectedMaxCompletionTokens int
- shouldHaveMaxTokens bool
- shouldModify bool
- shouldHaveMaxCompletionTokens bool
- }{
- {
- name: "gpt-5 model with max_tokens",
- input: map[string]any{
- "model": "gpt-5",
- "max_tokens": 4000,
- "temperature": 0.7,
- },
- actualModel: "gpt-5",
- expectedMaxCompletionTokens: 4000,
- shouldHaveMaxTokens: false,
- shouldModify: true,
- shouldHaveMaxCompletionTokens: true,
- },
- {
- name: "gpt-5 model without max_tokens",
- input: map[string]any{
- "model": "gpt-5",
- "temperature": 0.7,
- },
- actualModel: "gpt-5",
- shouldHaveMaxTokens: false,
- shouldModify: true,
- shouldHaveMaxCompletionTokens: false,
- },
- {
- name: "gpt-4 model with max_tokens",
- input: map[string]any{
- "model": "gpt-4",
- "max_tokens": 4000,
- },
- actualModel: "gpt-4",
- shouldHaveMaxTokens: true,
- shouldModify: false,
- shouldHaveMaxCompletionTokens: false,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- inputBytes, err := sonic.Marshal(tc.input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: tc.actualModel}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, config)
- require.NoError(t, err)
- assert.Equal(t, tc.shouldModify, modified)
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- if tc.shouldHaveMaxCompletionTokens {
- maxCompletionTokens, ok := output["max_completion_tokens"].(float64)
- require.True(t, ok, "max_completion_tokens should be float64")
- assert.Equal(
- t,
- tc.expectedMaxCompletionTokens,
- int(maxCompletionTokens),
- )
- } else {
- _, hasMaxCompletionTokens := output["max_completion_tokens"]
- assert.False(t, hasMaxCompletionTokens, "max_completion_tokens should not exist")
- }
- _, hasMaxTokens := output["max_tokens"]
- assert.Equal(t, tc.shouldHaveMaxTokens, hasMaxTokens)
- })
- }
- }
- func TestCustomUserPatches(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- config := &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "test_temperature_limit",
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorContains,
- Value: "test",
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpLimit,
- Key: "temperature",
- Value: 1.0,
- },
- },
- },
- {
- Name: "add_default_top_p",
- Conditions: []patch.PatchCondition{
- {
- Key: "top_p",
- Operator: patch.OperatorNotExists,
- Value: "",
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpAdd,
- Key: "top_p",
- Value: 0.9,
- },
- },
- },
- },
- }
- // Test temperature limit
- input := map[string]any{
- "model": "test-model",
- "temperature": 1.5,
- }
- inputBytes, err := sonic.Marshal(input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: "test-model"}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, config)
- require.NoError(t, err)
- assert.True(t, modified)
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- assert.Equal(t, 1.0, output["temperature"])
- assert.Equal(t, 0.9, output["top_p"]) // Should be added
- }
- func TestNestedFieldOperations(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- config := &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "nested_operations",
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "parameters.max_tokens",
- Value: 2000,
- },
- {
- Op: patch.OpSet,
- Key: "metadata.version",
- Value: "1.0",
- },
- },
- },
- },
- }
- input := map[string]any{
- "model": "test",
- "parameters": map[string]any{
- "temperature": 0.7,
- },
- }
- inputBytes, err := sonic.Marshal(input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: "test"}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, config)
- require.NoError(t, err)
- assert.True(t, modified)
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- // Check nested field access
- params, ok := output["parameters"].(map[string]any)
- require.True(t, ok)
- maxTokens, ok := params["max_tokens"].(float64)
- require.True(t, ok, "max_tokens should be float64")
- assert.Equal(t, 2000, int(maxTokens))
- assert.Equal(t, 0.7, params["temperature"])
- metadata, ok := output["metadata"].(map[string]any)
- require.True(t, ok)
- assert.Equal(t, "1.0", metadata["version"])
- }
- func TestPlaceholderResolution(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- config := &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "placeholder_test",
- Conditions: []patch.PatchCondition{
- {
- Key: "max_tokens",
- Operator: patch.OperatorExists,
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "max_completion_tokens",
- Value: "{{max_tokens}}",
- },
- {
- Op: patch.OpDelete,
- Key: "max_tokens",
- },
- },
- },
- },
- }
- input := map[string]any{
- "model": "test",
- "max_tokens": 3000,
- }
- inputBytes, err := sonic.Marshal(input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: "test"}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, config)
- require.NoError(t, err)
- assert.True(t, modified)
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- maxCompletionTokens, ok := output["max_completion_tokens"].(float64)
- require.True(t, ok, "max_completion_tokens should be float64")
- assert.Equal(t, 3000, int(maxCompletionTokens))
- _, hasMaxTokens := output["max_tokens"]
- assert.False(t, hasMaxTokens)
- }
- func TestOperators(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- config := &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "operator_tests",
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorRegex,
- Value: "^gpt-[0-9]$",
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "matched",
- Value: true,
- },
- },
- },
- },
- }
- testCases := []struct {
- model string
- shouldMatch bool
- }{
- {"gpt-4", true},
- {"gpt-3", true},
- {"gpt-4o", false},
- {"claude-3", false},
- }
- for _, tc := range testCases {
- t.Run(tc.model, func(t *testing.T) {
- input := map[string]any{"model": tc.model}
- inputBytes, err := sonic.Marshal(input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: tc.model}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, config)
- require.NoError(t, err)
- assert.Equal(t, tc.shouldMatch, modified)
- if tc.shouldMatch {
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- matched, ok := output["matched"].(bool)
- require.True(t, ok, "matched should be bool")
- assert.True(t, matched)
- }
- })
- }
- }
- func TestInvalidJSON(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- config := &patch.Config{}
- invalidJSON := []byte(`{"invalid": json}`)
- meta := &meta.Meta{ActualModel: "test"}
- outputBytes, modified, err := plugin.ApplyPatches(invalidJSON, meta, config)
- require.NoError(t, err)
- assert.False(t, modified)
- assert.Equal(t, invalidJSON, outputBytes)
- }
- func TestConvertRequest(t *testing.T) {
- // Skip this test since it requires database initialization
- // The functionality is already tested in other unit tests
- t.Skip("Skipping integration test - requires database setup")
- }
- func TestToFloat64(t *testing.T) {
- testCases := []struct {
- input any
- expected float64
- hasError bool
- }{
- {float64(3.14), 3.14, false},
- {float32(2.5), 2.5, false},
- {int(42), 42.0, false},
- {int32(100), 100.0, false},
- {int64(200), 200.0, false},
- {"123.45", 123.45, false},
- {"invalid", 0, true},
- {true, 0, true},
- }
- for _, tc := range testCases {
- result, err := patch.ToFloat64(tc.input)
- if tc.hasError {
- assert.Error(t, err)
- } else {
- assert.NoError(t, err)
- assert.Equal(t, tc.expected, result)
- }
- }
- }
- func TestConditionLogicOperators(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- testCases := []struct {
- name string
- config *patch.Config
- input map[string]any
- actualModel string
- shouldModify bool
- }{
- {
- name: "OR logic - one condition matches",
- config: &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "or_logic_test",
- ConditionLogic: patch.LogicOr,
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorEquals,
- Value: "gpt-4",
- },
- {
- Key: "temperature",
- Operator: patch.OperatorGreaterThan,
- Value: "1.5",
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "modified",
- Value: true,
- },
- },
- },
- },
- },
- input: map[string]any{
- "model": "claude-3",
- "temperature": 2.0,
- },
- actualModel: "claude-3",
- shouldModify: true,
- },
- {
- name: "OR logic - no condition matches",
- config: &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "or_logic_test_no_match",
- ConditionLogic: patch.LogicOr,
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorEquals,
- Value: "gpt-4",
- },
- {
- Key: "temperature",
- Operator: patch.OperatorGreaterThan,
- Value: "1.5",
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "modified",
- Value: true,
- },
- },
- },
- },
- },
- input: map[string]any{
- "model": "claude-3",
- "temperature": 1.0,
- },
- actualModel: "claude-3",
- shouldModify: false,
- },
- {
- name: "AND logic (default) - all conditions match",
- config: &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "and_logic_test",
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorContains,
- Value: "gpt",
- },
- {
- Key: "temperature",
- Operator: patch.OperatorLessThan,
- Value: "1.5",
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "modified",
- Value: true,
- },
- },
- },
- },
- },
- input: map[string]any{
- "model": "gpt-4",
- "temperature": 1.0,
- },
- actualModel: "gpt-4",
- shouldModify: true,
- },
- {
- name: "AND logic (default) - one condition fails",
- config: &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "and_logic_test_fail",
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorContains,
- Value: "gpt",
- },
- {
- Key: "temperature",
- Operator: patch.OperatorLessThan,
- Value: "1.5",
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "modified",
- Value: true,
- },
- },
- },
- },
- },
- input: map[string]any{
- "model": "gpt-4",
- "temperature": 2.0,
- },
- actualModel: "gpt-4",
- shouldModify: false,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- inputBytes, err := sonic.Marshal(tc.input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: tc.actualModel}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, tc.config)
- require.NoError(t, err)
- assert.Equal(t, tc.shouldModify, modified)
- if tc.shouldModify {
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- assert.Equal(t, output["modified"], true)
- }
- })
- }
- }
- func TestConditionNegation(t *testing.T) {
- plugin := patch.NewPatchPlugin()
- testCases := []struct {
- name string
- config *patch.Config
- input map[string]any
- actualModel string
- shouldModify bool
- }{
- {
- name: "negate condition - should match when negated",
- config: &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "negate_test",
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorEquals,
- Value: "gpt-4",
- Negate: true,
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "modified",
- Value: true,
- },
- },
- },
- },
- },
- input: map[string]any{
- "model": "claude-3",
- },
- actualModel: "claude-3",
- shouldModify: true,
- },
- {
- name: "negate condition - should not match when negated",
- config: &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "negate_test_no_match",
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorEquals,
- Value: "gpt-4",
- Negate: true,
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "modified",
- Value: true,
- },
- },
- },
- },
- },
- input: map[string]any{
- "model": "gpt-4",
- },
- actualModel: "gpt-4",
- shouldModify: false,
- },
- {
- name: "OR with negation - complex logic",
- config: &patch.Config{
- UserPatches: []patch.PatchRule{
- {
- Name: "or_with_negate",
- ConditionLogic: patch.LogicOr,
- Conditions: []patch.PatchCondition{
- {
- Key: "model",
- Operator: patch.OperatorEquals,
- Value: "gpt-4",
- },
- {
- Key: "temperature",
- Operator: patch.OperatorExists,
- Negate: true, // NOT exists
- },
- },
- Operations: []patch.PatchOperation{
- {
- Op: patch.OpSet,
- Key: "modified",
- Value: true,
- },
- },
- },
- },
- },
- input: map[string]any{
- "model": "claude-3",
- // no temperature field
- },
- actualModel: "claude-3",
- shouldModify: true, // Should match because temperature doesn't exist (negated exists)
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- inputBytes, err := sonic.Marshal(tc.input)
- require.NoError(t, err)
- meta := &meta.Meta{ActualModel: tc.actualModel}
- outputBytes, modified, err := plugin.ApplyPatches(inputBytes, meta, tc.config)
- require.NoError(t, err)
- assert.Equal(t, tc.shouldModify, modified)
- if tc.shouldModify {
- var output map[string]any
- err = sonic.Unmarshal(outputBytes, &output)
- require.NoError(t, err)
- assert.Equal(t, output["modified"], true)
- }
- })
- }
- }
|