mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-10-28 08:35:33 +08:00
perf(relay): stop recompiling param override regexes on every request (#8061)
This commit is contained in:
1 parent
34003e9c2e
commit
01da3a55f8
2 files changed
+59
-27
No files matched your search
+22
-12
@@ -10,6 +10,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
kitreasoning "github.com/QuantumNous/new-api/relaykit/relayconvert/reasoning"
|
||||
@@ -21,6 +22,22 @@ import (
|
||||
|
||||
var negativeIndexRegexp = regexp.MustCompile(`\.(-\d+)`)
|
||||
|
||||
// Param override rules are re-parsed for every request, so compiled patterns are
|
||||
// shared by pattern text instead of being stored on the parsed operations.
|
||||
var paramOverrideRegexCache sync.Map // map[string]*regexp.Regexp
|
||||
|
||||
func getParamOverrideRegex(pattern string) (*regexp.Regexp, error) {
|
||||
if cached, ok := paramOverrideRegexCache.Load(pattern); ok {
|
||||
return cached.(*regexp.Regexp), nil
|
||||
}
|
||||
compiled, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actual, _ := paramOverrideRegexCache.LoadOrStore(pattern, compiled)
|
||||
return actual.(*regexp.Regexp), nil
|
||||
}
|
||||
|
||||
const (
|
||||
paramOverrideContextRequestHeaders = "request_headers"
|
||||
paramOverrideContextHeaderOverride = "header_override"
|
||||
@@ -60,10 +77,6 @@ type ConditionOperation struct {
|
||||
Value any `json:"value"` // 匹配的值
|
||||
Invert bool `json:"invert"` // 反选功能,true表示取反结果
|
||||
PassMissingKey bool `json:"pass_missing_key"` // 未获取到json key时的行为
|
||||
|
||||
// Conditions are parsed per request. Reuse the lazily compiled pattern when
|
||||
// pruning multiple objects; never compile before a matching key exists.
|
||||
regex *regexp.Regexp
|
||||
}
|
||||
|
||||
type ParamOperation struct {
|
||||
@@ -729,14 +742,11 @@ func (condition *ConditionOperation) compareGjsonValues(jsonValue, targetValue g
|
||||
if targetValue.Type != gjson.String {
|
||||
return false, fmt.Errorf("regex pattern must be a string")
|
||||
}
|
||||
if condition.regex == nil {
|
||||
re, err := regexp.Compile(targetValue.String())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid regex pattern: %w", err)
|
||||
}
|
||||
condition.regex = re
|
||||
re, err := getParamOverrideRegex(targetValue.String())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid regex pattern: %w", err)
|
||||
}
|
||||
return condition.regex.MatchString(jsonValue.String()), nil
|
||||
return re.MatchString(jsonValue.String()), nil
|
||||
case "gt":
|
||||
return compareNumeric(jsonValue, targetValue, "gt")
|
||||
case "gte":
|
||||
@@ -1914,7 +1924,7 @@ func regexReplaceStringValue(data []byte, path, pattern, replacement string) ([]
|
||||
if pattern == "" {
|
||||
return data, fmt.Errorf("regex pattern is required")
|
||||
}
|
||||
re, err := regexp.Compile(pattern)
|
||||
re, err := getParamOverrideRegex(pattern)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
@@ -165,23 +165,45 @@ func TestApplyParamOverrideReplace(t *testing.T) {
|
||||
func TestApplyParamOverrideRegexReplace(t *testing.T) {
|
||||
// regex_replace example:
|
||||
// {"operations":[{"path":"model","mode":"regex_replace","from":"^gpt-","to":"openai/gpt-"}]}
|
||||
input := []byte(`{"model":"gpt-4o-mini","temperature":0.7}`)
|
||||
override := map[string]any{
|
||||
"operations": []any{
|
||||
map[string]any{
|
||||
"path": "model",
|
||||
"mode": "regex_replace",
|
||||
"from": "^gpt-",
|
||||
"to": "openai/gpt-",
|
||||
},
|
||||
operation := map[string]any{"mode": "regex_replace"}
|
||||
override := map[string]any{"operations": []any{operation}}
|
||||
// Reusing a channel configuration must apply the current pattern on every request.
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
input string
|
||||
path string
|
||||
from string
|
||||
to string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "single path",
|
||||
input: `{"model":"gpt-4o-mini","temperature":0.7}`,
|
||||
path: "model", from: "^gpt-", to: "openai/gpt-",
|
||||
want: `{"model":"openai/gpt-4o-mini","temperature":0.7}`,
|
||||
},
|
||||
{
|
||||
name: "next request uses updated pattern",
|
||||
input: `{"model":"gpt-4o-mini","temperature":0.7}`,
|
||||
path: "model", from: "-mini$", to: "",
|
||||
want: `{"model":"gpt-4o","temperature":0.7}`,
|
||||
},
|
||||
{
|
||||
name: "wildcard path applies one pattern to every field",
|
||||
input: `{"messages":[{"content":"foo 1"},{"content":"foo 2"},{"content":"bar"}]}`,
|
||||
path: "messages.*.content", from: `^foo (\d)$`, to: "baz $1",
|
||||
want: `{"messages":[{"content":"baz 1"},{"content":"baz 2"},{"content":"bar"}]}`,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
operation["path"] = tt.path
|
||||
operation["from"] = tt.from
|
||||
operation["to"] = tt.to
|
||||
out, err := ApplyParamOverride([]byte(tt.input), override, nil)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, tt.want, string(out))
|
||||
})
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"openai/gpt-4o-mini","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideReplaceRequiresFrom(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user