perf(relay): stop recompiling param override regexes on every request (#8061)

This commit is contained in:
Seefs authored and GitHub committed 2026-10-28 00:16:57 +08:00
1 parent 34003e9c2e
commit 01da3a55f8
2 files changed
+59 -27

No files matched your search

+22 -12
View File
@@ -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
}
+37 -15
View File
@@ -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) {