feat(translator): add benchmarking for convertSystemRoleToDeveloper with large inputs

- Introduced `BenchmarkConvertSystemRoleToDeveloperLargeInput` to evaluate performance on various input sizes and configurations.
- Improved `convertSystemRoleToDeveloper` logic to optimize JSON rebuilding and reduce unnecessary operations.
- Created helper `makeLargeResponsesInputForBenchmark` to generate test data for performance scenarios.

Closes: #3751
This commit is contained in:
Luis Pater
2026-06-20 11:10:53 +08:00
parent 28e2f9798c
commit 379167c992
2 changed files with 135 additions and 10 deletions
@@ -1,6 +1,7 @@
package responses
import (
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
@@ -71,18 +72,38 @@ func convertSystemRoleToDeveloper(rawJSON []byte) []byte {
return rawJSON
}
inputArray := inputResult.Array()
result := rawJSON
// Directly modify role values for items with "system" role
for i := 0; i < len(inputArray); i++ {
rolePath := fmt.Sprintf("input.%d.role", i)
if gjson.GetBytes(result, rolePath).String() == "system" {
result, _ = sjson.SetBytes(result, rolePath, "developer")
}
inputItems := inputResult.Array()
if len(inputItems) == 0 {
return rawJSON
}
return result
changed := false
rebuiltInput := make([]json.RawMessage, 0, len(inputItems))
for _, item := range inputItems {
itemRaw := []byte(item.Raw)
if item.IsObject() && item.Get("role").String() == "system" {
updatedItem, errSetItem := sjson.SetRawBytes(itemRaw, "role", []byte(`"developer"`))
if errSetItem != nil {
return rawJSON
}
itemRaw = updatedItem
changed = true
}
rebuiltInput = append(rebuiltInput, json.RawMessage(itemRaw))
}
if !changed {
return rawJSON
}
inputRaw, errMarshalInput := json.Marshal(rebuiltInput)
if errMarshalInput != nil {
return rawJSON
}
updated, errSetInput := sjson.SetRawBytes(rawJSON, "input", inputRaw)
if errSetInput != nil {
return rawJSON
}
return updated
}
// normalizeCodexBuiltinTools rewrites legacy/preview built-in tool variants to the
@@ -1,11 +1,17 @@
package responses
import (
"fmt"
"strconv"
"strings"
"testing"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
var benchmarkConvertSystemRoleOutput []byte
// TestConvertSystemRoleToDeveloper_BasicConversion tests the basic system -> developer role conversion
func TestConvertSystemRoleToDeveloper_BasicConversion(t *testing.T) {
inputJSON := []byte(`{
@@ -364,3 +370,101 @@ func TestTruncationRemovedForCodexCompatibility(t *testing.T) {
t.Fatalf("truncation should be removed for Codex compatibility")
}
}
func BenchmarkConvertSystemRoleToDeveloperLargeInput(b *testing.B) {
cases := []struct {
name string
inputJSON []byte
}{
{
name: "200_input_1_system",
inputJSON: makeLargeResponsesInputForBenchmark(200, 200),
},
{
name: "200_input_2_system",
inputJSON: makeLargeResponsesInputForBenchmark(200, 100),
},
{
name: "2000_input_20_system",
inputJSON: makeLargeResponsesInputForBenchmark(2000, 100),
},
}
benchmarks := []struct {
name string
fn func([]byte) []byte
}{
{
name: "previous_root_path_rewrite",
fn: convertSystemRoleToDeveloperPreviousRootPathRewriteForBenchmark,
},
{
name: "current_rebuilt_input_json_marshal",
fn: convertSystemRoleToDeveloper,
},
}
for _, testCase := range cases {
for _, benchmark := range benchmarks {
b.Run(testCase.name+"/"+benchmark.name, func(b *testing.B) {
output := benchmark.fn(testCase.inputJSON)
if got := gjson.GetBytes(output, "input.0.role").String(); got != "developer" {
b.Fatalf("input.0.role = %q, want %q", got, "developer")
}
if got := gjson.GetBytes(output, "input.1.role").String(); got != "user" {
b.Fatalf("input.1.role = %q, want %q", got, "user")
}
b.ReportAllocs()
b.SetBytes(int64(len(testCase.inputJSON)))
b.ResetTimer()
var benchmarkOutput []byte
for i := 0; i < b.N; i++ {
benchmarkOutput = benchmark.fn(testCase.inputJSON)
}
benchmarkConvertSystemRoleOutput = benchmarkOutput
})
}
}
}
func makeLargeResponsesInputForBenchmark(inputCount int, systemEvery int) []byte {
var builder strings.Builder
builder.Grow(inputCount * 96)
builder.WriteString(`{"model":"gpt-5.2","input":[`)
for i := 0; i < inputCount; i++ {
if i > 0 {
builder.WriteByte(',')
}
role := "user"
if i%systemEvery == 0 {
role = "system"
}
builder.WriteString(`{"type":"message","role":"`)
builder.WriteString(role)
builder.WriteString(`","content":[{"type":"input_text","text":"message `)
builder.WriteString(strconv.Itoa(i))
builder.WriteString(`"}]}`)
}
builder.WriteString(`]}`)
return []byte(builder.String())
}
func convertSystemRoleToDeveloperPreviousRootPathRewriteForBenchmark(rawJSON []byte) []byte {
inputResult := gjson.GetBytes(rawJSON, "input")
if !inputResult.IsArray() {
return rawJSON
}
inputArray := inputResult.Array()
result := rawJSON
for i := 0; i < len(inputArray); i++ {
rolePath := fmt.Sprintf("input.%d.role", i)
if gjson.GetBytes(result, rolePath).String() == "system" {
result, _ = sjson.SetBytes(result, rolePath, "developer")
}
}
return result
}