mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 02:25:00 +00:00
feat: add parameter coverage for the operations: copy, trim_prefix, trim_suffix, ensure_prefix, ensure_suffix, trim_space, to_lower, to_upper, replace, and regex_replace
This commit is contained in:
@@ -23,7 +23,7 @@ type ConditionOperation struct {
|
||||
|
||||
type ParamOperation struct {
|
||||
Path string `json:"path"`
|
||||
Mode string `json:"mode"` // delete, set, move, prepend, append
|
||||
Mode string `json:"mode"` // delete, set, move, copy, prepend, append, trim_prefix, trim_suffix, ensure_prefix, ensure_suffix, trim_space, to_lower, to_upper, replace, regex_replace
|
||||
Value interface{} `json:"value"`
|
||||
KeepOrigin bool `json:"keep_origin"`
|
||||
From string `json:"from,omitempty"`
|
||||
@@ -330,8 +330,6 @@ func applyOperations(jsonStr string, operations []ParamOperation, conditionConte
|
||||
}
|
||||
// 处理路径中的负数索引
|
||||
opPath := processNegativeIndex(result, op.Path)
|
||||
opFrom := processNegativeIndex(result, op.From)
|
||||
opTo := processNegativeIndex(result, op.To)
|
||||
|
||||
switch op.Mode {
|
||||
case "delete":
|
||||
@@ -342,11 +340,38 @@ func applyOperations(jsonStr string, operations []ParamOperation, conditionConte
|
||||
}
|
||||
result, err = sjson.Set(result, opPath, op.Value)
|
||||
case "move":
|
||||
opFrom := processNegativeIndex(result, op.From)
|
||||
opTo := processNegativeIndex(result, op.To)
|
||||
result, err = moveValue(result, opFrom, opTo)
|
||||
case "copy":
|
||||
if op.From == "" || op.To == "" {
|
||||
return "", fmt.Errorf("copy from/to is required")
|
||||
}
|
||||
opFrom := processNegativeIndex(result, op.From)
|
||||
opTo := processNegativeIndex(result, op.To)
|
||||
result, err = copyValue(result, opFrom, opTo)
|
||||
case "prepend":
|
||||
result, err = modifyValue(result, opPath, op.Value, op.KeepOrigin, true)
|
||||
case "append":
|
||||
result, err = modifyValue(result, opPath, op.Value, op.KeepOrigin, false)
|
||||
case "trim_prefix":
|
||||
result, err = trimStringValue(result, opPath, op.Value, true)
|
||||
case "trim_suffix":
|
||||
result, err = trimStringValue(result, opPath, op.Value, false)
|
||||
case "ensure_prefix":
|
||||
result, err = ensureStringAffix(result, opPath, op.Value, true)
|
||||
case "ensure_suffix":
|
||||
result, err = ensureStringAffix(result, opPath, op.Value, false)
|
||||
case "trim_space":
|
||||
result, err = transformStringValue(result, opPath, strings.TrimSpace)
|
||||
case "to_lower":
|
||||
result, err = transformStringValue(result, opPath, strings.ToLower)
|
||||
case "to_upper":
|
||||
result, err = transformStringValue(result, opPath, strings.ToUpper)
|
||||
case "replace":
|
||||
result, err = replaceStringValue(result, opPath, op.From, op.To)
|
||||
case "regex_replace":
|
||||
result, err = regexReplaceStringValue(result, opPath, op.From, op.To)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown operation: %s", op.Mode)
|
||||
}
|
||||
@@ -369,6 +394,14 @@ func moveValue(jsonStr, fromPath, toPath string) (string, error) {
|
||||
return sjson.Delete(result, fromPath)
|
||||
}
|
||||
|
||||
func copyValue(jsonStr, fromPath, toPath string) (string, error) {
|
||||
sourceValue := gjson.Get(jsonStr, fromPath)
|
||||
if !sourceValue.Exists() {
|
||||
return jsonStr, fmt.Errorf("source path does not exist: %s", fromPath)
|
||||
}
|
||||
return sjson.Set(jsonStr, toPath, sourceValue.Value())
|
||||
}
|
||||
|
||||
func modifyValue(jsonStr, path string, value interface{}, keepOrigin, isPrepend bool) (string, error) {
|
||||
current := gjson.Get(jsonStr, path)
|
||||
switch {
|
||||
@@ -422,6 +455,88 @@ func modifyString(jsonStr, path string, value interface{}, isPrepend bool) (stri
|
||||
return sjson.Set(jsonStr, path, newStr)
|
||||
}
|
||||
|
||||
func trimStringValue(jsonStr, path string, value interface{}, isPrefix bool) (string, error) {
|
||||
current := gjson.Get(jsonStr, path)
|
||||
if current.Type != gjson.String {
|
||||
return jsonStr, fmt.Errorf("operation not supported for type: %v", current.Type)
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
return jsonStr, fmt.Errorf("trim value is required")
|
||||
}
|
||||
valueStr := fmt.Sprintf("%v", value)
|
||||
|
||||
var newStr string
|
||||
if isPrefix {
|
||||
newStr = strings.TrimPrefix(current.String(), valueStr)
|
||||
} else {
|
||||
newStr = strings.TrimSuffix(current.String(), valueStr)
|
||||
}
|
||||
return sjson.Set(jsonStr, path, newStr)
|
||||
}
|
||||
|
||||
func ensureStringAffix(jsonStr, path string, value interface{}, isPrefix bool) (string, error) {
|
||||
current := gjson.Get(jsonStr, path)
|
||||
if current.Type != gjson.String {
|
||||
return jsonStr, fmt.Errorf("operation not supported for type: %v", current.Type)
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
return jsonStr, fmt.Errorf("ensure value is required")
|
||||
}
|
||||
valueStr := fmt.Sprintf("%v", value)
|
||||
if valueStr == "" {
|
||||
return jsonStr, fmt.Errorf("ensure value is required")
|
||||
}
|
||||
|
||||
currentStr := current.String()
|
||||
if isPrefix {
|
||||
if strings.HasPrefix(currentStr, valueStr) {
|
||||
return jsonStr, nil
|
||||
}
|
||||
return sjson.Set(jsonStr, path, valueStr+currentStr)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(currentStr, valueStr) {
|
||||
return jsonStr, nil
|
||||
}
|
||||
return sjson.Set(jsonStr, path, currentStr+valueStr)
|
||||
}
|
||||
|
||||
func transformStringValue(jsonStr, path string, transform func(string) string) (string, error) {
|
||||
current := gjson.Get(jsonStr, path)
|
||||
if current.Type != gjson.String {
|
||||
return jsonStr, fmt.Errorf("operation not supported for type: %v", current.Type)
|
||||
}
|
||||
return sjson.Set(jsonStr, path, transform(current.String()))
|
||||
}
|
||||
|
||||
func replaceStringValue(jsonStr, path, from, to string) (string, error) {
|
||||
current := gjson.Get(jsonStr, path)
|
||||
if current.Type != gjson.String {
|
||||
return jsonStr, fmt.Errorf("operation not supported for type: %v", current.Type)
|
||||
}
|
||||
if from == "" {
|
||||
return jsonStr, fmt.Errorf("replace from is required")
|
||||
}
|
||||
return sjson.Set(jsonStr, path, strings.ReplaceAll(current.String(), from, to))
|
||||
}
|
||||
|
||||
func regexReplaceStringValue(jsonStr, path, pattern, replacement string) (string, error) {
|
||||
current := gjson.Get(jsonStr, path)
|
||||
if current.Type != gjson.String {
|
||||
return jsonStr, fmt.Errorf("operation not supported for type: %v", current.Type)
|
||||
}
|
||||
if pattern == "" {
|
||||
return jsonStr, fmt.Errorf("regex pattern is required")
|
||||
}
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return jsonStr, err
|
||||
}
|
||||
return sjson.Set(jsonStr, path, re.ReplaceAllString(current.String(), replacement))
|
||||
}
|
||||
|
||||
func mergeObjects(jsonStr, path string, value interface{}, keepOrigin bool) (string, error) {
|
||||
current := gjson.Get(jsonStr, path)
|
||||
var currentMap, newMap map[string]interface{}
|
||||
|
||||
791
relay/common/override_test.go
Normal file
791
relay/common/override_test.go
Normal file
@@ -0,0 +1,791 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestApplyParamOverrideTrimPrefix(t *testing.T) {
|
||||
// trim_prefix example:
|
||||
// {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
|
||||
input := []byte(`{"model":"openai/gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "trim_prefix",
|
||||
"value": "openai/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideTrimSuffix(t *testing.T) {
|
||||
// trim_suffix example:
|
||||
// {"operations":[{"path":"model","mode":"trim_suffix","value":"-latest"}]}
|
||||
input := []byte(`{"model":"gpt-4-latest","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "trim_suffix",
|
||||
"value": "-latest",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideTrimNoop(t *testing.T) {
|
||||
// trim_prefix no-op example:
|
||||
// {"operations":[{"path":"model","mode":"trim_prefix","value":"openai/"}]}
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "trim_prefix",
|
||||
"value": "openai/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideTrimRequiresValue(t *testing.T) {
|
||||
// trim_prefix requires value example:
|
||||
// {"operations":[{"path":"model","mode":"trim_prefix"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "trim_prefix",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideReplace(t *testing.T) {
|
||||
// replace example:
|
||||
// {"operations":[{"path":"model","mode":"replace","from":"openai/","to":""}]}
|
||||
input := []byte(`{"model":"openai/gpt-4o-mini","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "replace",
|
||||
"from": "openai/",
|
||||
"to": "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4o-mini","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
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]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "regex_replace",
|
||||
"from": "^gpt-",
|
||||
"to": "openai/gpt-",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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) {
|
||||
// replace requires from example:
|
||||
// {"operations":[{"path":"model","mode":"replace"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "replace",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideRegexReplaceRequiresPattern(t *testing.T) {
|
||||
// regex_replace requires from(pattern) example:
|
||||
// {"operations":[{"path":"model","mode":"regex_replace"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "regex_replace",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideDelete(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "delete",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
|
||||
var got map[string]interface{}
|
||||
if err := json.Unmarshal(out, &got); err != nil {
|
||||
t.Fatalf("failed to unmarshal output JSON: %v", err)
|
||||
}
|
||||
if _, exists := got["temperature"]; exists {
|
||||
t.Fatalf("expected temperature to be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideSet(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "set",
|
||||
"value": 0.1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideSetKeepOrigin(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "set",
|
||||
"value": 0.1,
|
||||
"keep_origin": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideMove(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4","meta":{"x":1}}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "move",
|
||||
"from": "model",
|
||||
"to": "meta.model",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"meta":{"x":1,"model":"gpt-4"}}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideMoveMissingSource(t *testing.T) {
|
||||
input := []byte(`{"meta":{"x":1}}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "move",
|
||||
"from": "model",
|
||||
"to": "meta.model",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverridePrependAppendString(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "prepend",
|
||||
"value": "openai/",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "append",
|
||||
"value": "-latest",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"openai/gpt-4-latest"}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverridePrependAppendArray(t *testing.T) {
|
||||
input := []byte(`{"arr":[1,2]}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "arr",
|
||||
"mode": "prepend",
|
||||
"value": 0,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"path": "arr",
|
||||
"mode": "append",
|
||||
"value": []interface{}{3, 4},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"arr":[0,1,2,3,4]}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideAppendObjectMergeKeepOrigin(t *testing.T) {
|
||||
input := []byte(`{"obj":{"a":1}}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "obj",
|
||||
"mode": "append",
|
||||
"keep_origin": true,
|
||||
"value": map[string]interface{}{
|
||||
"a": 2,
|
||||
"b": 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"obj":{"a":1,"b":3}}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideAppendObjectMergeOverride(t *testing.T) {
|
||||
input := []byte(`{"obj":{"a":1}}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "obj",
|
||||
"mode": "append",
|
||||
"value": map[string]interface{}{
|
||||
"a": 2,
|
||||
"b": 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"obj":{"a":2,"b":3}}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideConditionORDefault(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "set",
|
||||
"value": 0.1,
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "prefix",
|
||||
"value": "gpt",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "prefix",
|
||||
"value": "claude",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideConditionAND(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "set",
|
||||
"value": 0.1,
|
||||
"logic": "AND",
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "prefix",
|
||||
"value": "gpt",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "gt",
|
||||
"value": 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.1}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideConditionInvert(t *testing.T) {
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "set",
|
||||
"value": 0.1,
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "prefix",
|
||||
"value": "gpt",
|
||||
"invert": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideConditionPassMissingKey(t *testing.T) {
|
||||
input := []byte(`{"temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "set",
|
||||
"value": 0.1,
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "prefix",
|
||||
"value": "gpt",
|
||||
"pass_missing_key": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"temperature":0.1}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideConditionFromContext(t *testing.T) {
|
||||
input := []byte(`{"temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "temperature",
|
||||
"mode": "set",
|
||||
"value": 0.1,
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "prefix",
|
||||
"value": "gpt",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := map[string]interface{}{
|
||||
"model": "gpt-4",
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"temperature":0.1}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideNegativeIndexPath(t *testing.T) {
|
||||
input := []byte(`{"arr":[{"model":"a"},{"model":"b"}]}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "arr.-1.model",
|
||||
"mode": "set",
|
||||
"value": "c",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"arr":[{"model":"a"},{"model":"c"}]}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideRegexReplaceInvalidPattern(t *testing.T) {
|
||||
// regex_replace invalid pattern example:
|
||||
// {"operations":[{"path":"model","mode":"regex_replace","from":"(","to":"x"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "regex_replace",
|
||||
"from": "(",
|
||||
"to": "x",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideCopy(t *testing.T) {
|
||||
// copy example:
|
||||
// {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
|
||||
input := []byte(`{"model":"gpt-4","temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "copy",
|
||||
"from": "model",
|
||||
"to": "original_model",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4","original_model":"gpt-4","temperature":0.7}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideCopyMissingSource(t *testing.T) {
|
||||
// copy missing source example:
|
||||
// {"operations":[{"mode":"copy","from":"model","to":"original_model"}]}
|
||||
input := []byte(`{"temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "copy",
|
||||
"from": "model",
|
||||
"to": "original_model",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideCopyRequiresFromTo(t *testing.T) {
|
||||
// copy requires from/to example:
|
||||
// {"operations":[{"mode":"copy"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "copy",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideEnsurePrefix(t *testing.T) {
|
||||
// ensure_prefix example:
|
||||
// {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "ensure_prefix",
|
||||
"value": "openai/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideEnsurePrefixNoop(t *testing.T) {
|
||||
// ensure_prefix no-op example:
|
||||
// {"operations":[{"path":"model","mode":"ensure_prefix","value":"openai/"}]}
|
||||
input := []byte(`{"model":"openai/gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "ensure_prefix",
|
||||
"value": "openai/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"openai/gpt-4"}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideEnsureSuffix(t *testing.T) {
|
||||
// ensure_suffix example:
|
||||
// {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "ensure_suffix",
|
||||
"value": "-latest",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideEnsureSuffixNoop(t *testing.T) {
|
||||
// ensure_suffix no-op example:
|
||||
// {"operations":[{"path":"model","mode":"ensure_suffix","value":"-latest"}]}
|
||||
input := []byte(`{"model":"gpt-4-latest"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "ensure_suffix",
|
||||
"value": "-latest",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4-latest"}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideEnsureRequiresValue(t *testing.T) {
|
||||
// ensure_prefix requires value example:
|
||||
// {"operations":[{"path":"model","mode":"ensure_prefix"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "ensure_prefix",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideTrimSpace(t *testing.T) {
|
||||
// trim_space example:
|
||||
// {"operations":[{"path":"model","mode":"trim_space"}]}
|
||||
input := []byte("{\"model\":\" gpt-4 \\n\"}")
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "trim_space",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideToLower(t *testing.T) {
|
||||
// to_lower example:
|
||||
// {"operations":[{"path":"model","mode":"to_lower"}]}
|
||||
input := []byte(`{"model":"GPT-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "to_lower",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"gpt-4"}`, string(out))
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideToUpper(t *testing.T) {
|
||||
// to_upper example:
|
||||
// {"operations":[{"path":"model","mode":"to_upper"}]}
|
||||
input := []byte(`{"model":"gpt-4"}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"path": "model",
|
||||
"mode": "to_upper",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ApplyParamOverride(input, override, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
assertJSONEqual(t, `{"model":"GPT-4"}`, string(out))
|
||||
}
|
||||
|
||||
func assertJSONEqual(t *testing.T, want, got string) {
|
||||
t.Helper()
|
||||
|
||||
var wantObj interface{}
|
||||
var gotObj interface{}
|
||||
|
||||
if err := json.Unmarshal([]byte(want), &wantObj); err != nil {
|
||||
t.Fatalf("failed to unmarshal want JSON: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(got), &gotObj); err != nil {
|
||||
t.Fatalf("failed to unmarshal got JSON: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(wantObj, gotObj) {
|
||||
t.Fatalf("json not equal\nwant: %s\ngot: %s", want, got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user