From 86800a8cd0740b6305ab74112c6106469f93c9b6 Mon Sep 17 00:00:00 2001 From: jjaw Date: Tue, 14 Jul 2026 15:39:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=86=85=E5=AE=B9=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E7=A7=8D=E5=AD=90=E5=AD=97=E6=AE=B5=E6=89=AB=E6=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/openai_content_session_seed.go | 78 +++++++- ...nai_content_session_seed_benchmark_test.go | 29 +++ .../openai_content_session_seed_test.go | 181 ++++++++++++++++++ 3 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 backend/internal/service/openai_content_session_seed_benchmark_test.go diff --git a/backend/internal/service/openai_content_session_seed.go b/backend/internal/service/openai_content_session_seed.go index fce85f11bd..5f8f1872e8 100644 --- a/backend/internal/service/openai_content_session_seed.go +++ b/backend/internal/service/openai_content_session_seed.go @@ -1,6 +1,7 @@ package service import ( + "bytes" "encoding/json" "strings" @@ -25,31 +26,96 @@ func deriveOpenAIContentSessionSeed(body []byte) string { return "" } + const ( + modelField = iota + toolsField + functionsField + instructionsField + messagesField + inputField + contentSessionSeedFieldCount + allContentSessionSeedFields = 1< len(root) { + return false + } + // Result.ForEach can continue after the root '}' on malformed input. + // The separator range excludes braces inside the preceding parsed value. + if bytes.IndexByte(root[nextKeyOffset:key.Index], '}') >= 0 { + return false + } + nextKeyOffset = value.Index + len(value.Raw) + + field := -1 + switch key.Str { + case "model": + field = modelField + case "tools": + field = toolsField + case "functions": + field = functionsField + case "instructions": + field = instructionsField + case "messages": + field = messagesField + case "input": + field = inputField + } + if field < 0 { + return true + } + mask := uint8(1 << field) + if seen&mask == 0 { + fields[field] = value + seen |= mask + } + return seen != allContentSessionSeedFields + }) + var b strings.Builder - if model := gjson.GetBytes(body, "model").String(); model != "" { + if model := fields[modelField].String(); model != "" { _, _ = b.WriteString("model=") _, _ = b.WriteString(model) } - if tools := gjson.GetBytes(body, "tools"); tools.Exists() && tools.IsArray() && tools.Raw != "[]" { + if tools := fields[toolsField]; tools.Exists() && tools.IsArray() && tools.Raw != "[]" { _, _ = b.WriteString("|tools=") _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(tools.Raw))) } - if funcs := gjson.GetBytes(body, "functions"); funcs.Exists() && funcs.IsArray() && funcs.Raw != "[]" { + if funcs := fields[functionsField]; funcs.Exists() && funcs.IsArray() && funcs.Raw != "[]" { _, _ = b.WriteString("|functions=") _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(funcs.Raw))) } - if instr := gjson.GetBytes(body, "instructions").String(); instr != "" { + if instr := fields[instructionsField].String(); instr != "" { _, _ = b.WriteString("|instructions=") _, _ = b.WriteString(instr) } firstUserCaptured := false - msgs := gjson.GetBytes(body, "messages") + msgs := fields[messagesField] if msgs.Exists() && msgs.IsArray() { msgs.ForEach(func(_, msg gjson.Result) bool { role := msg.Get("role").String() @@ -70,7 +136,7 @@ func deriveOpenAIContentSessionSeed(body []byte) string { } return true }) - } else if inp := gjson.GetBytes(body, "input"); inp.Exists() { + } else if inp := fields[inputField]; inp.Exists() { if inp.Type == gjson.String { _, _ = b.WriteString("|input=") _, _ = b.WriteString(inp.String()) diff --git a/backend/internal/service/openai_content_session_seed_benchmark_test.go b/backend/internal/service/openai_content_session_seed_benchmark_test.go new file mode 100644 index 0000000000..eda9deaa82 --- /dev/null +++ b/backend/internal/service/openai_content_session_seed_benchmark_test.go @@ -0,0 +1,29 @@ +package service + +import ( + "strings" + "testing" +) + +var benchmarkOpenAIContentSessionSeed string + +func BenchmarkDeriveOpenAIContentSessionSeedLargeBody(b *testing.B) { + largeHistory := strings.Repeat("payload", 1<<17) + tests := []struct { + name string + body []byte + }{ + {name: "ChatCompletions", body: []byte(`{"model":"gpt-5.4","tools":[{"type":"function","function":{"name":"lookup"}}],"messages":[{"role":"system","content":"Be concise."},{"role":"user","content":"Hello"},{"role":"assistant","content":"` + largeHistory + `"},{"role":"user","content":"Follow-up"}]}`)}, + {name: "Responses", body: []byte(`{"model":"gpt-5.4","instructions":"Be concise.","tools":[{"type":"function","name":"lookup"}],"input":[{"role":"system","content":"System prompt"},{"role":"user","content":"Hello"},{"role":"assistant","content":"` + largeHistory + `"}]}`)}, + } + + for _, test := range tests { + b.Run(test.name, func(b *testing.B) { + b.ReportAllocs() + b.SetBytes(int64(len(test.body))) + for range b.N { + benchmarkOpenAIContentSessionSeed = deriveOpenAIContentSessionSeed(test.body) + } + }) + } +} diff --git a/backend/internal/service/openai_content_session_seed_test.go b/backend/internal/service/openai_content_session_seed_test.go index 6dadc5cf53..0c6c63335c 100644 --- a/backend/internal/service/openai_content_session_seed_test.go +++ b/backend/internal/service/openai_content_session_seed_test.go @@ -1,9 +1,12 @@ package service import ( + "encoding/json" + "strings" "testing" "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" ) func TestDeriveOpenAIContentSessionSeed_EmptyInputs(t *testing.T) { @@ -197,6 +200,184 @@ func TestDeriveOpenAIContentSessionSeed_JSONCanonicalisation(t *testing.T) { require.Equal(t, s1, s2, "different formatting of identical JSON should produce the same seed") } +func TestDeriveOpenAIContentSessionSeed_SingleScanMatchesLegacyBytes(t *testing.T) { + largeValue := strings.Repeat("payload", 1<<17) + tests := []struct { + name string + body []byte + }{ + { + name: "large chat completions", + body: []byte(`{"metadata":"` + largeValue + `","model":"gpt-5.4","tools":[{"type":"function","function":{"name":"lookup"}}],"functions":[{"name":"legacy_lookup"}],"messages":[{"role":"system","content":"System prompt"},{"role":"developer","content":[{"type":"text","text":"Developer prompt"}]},{"role":"user","content":"Hello"}]}`), + }, + { + name: "large responses", + body: []byte(`{"metadata":"` + largeValue + `","model":"gpt-5.4","instructions":"Be concise.","tools":[{"type":"function","name":"lookup"}],"input":[{"role":"system","content":"System prompt"},{"role":"user","content":[{"type":"input_text","text":"Hello"}]}]}`), + }, + { + name: "fields in reverse order", + body: []byte(`{"input":"fallback input","messages":[{"role":"user","content":"chat wins"}],"instructions":"Be concise.","functions":[{"name":"lookup"}],"tools":[{"type":"function","name":"lookup"}],"model":"gpt-5.4"}`), + }, + { + name: "missing and wrong type fields", + body: []byte(`{"tools":[],"functions":null,"instructions":0,"messages":{},"input":[{"type":"input_text","text":"fallback"}]}`), + }, + { + name: "duplicate fields keep first value", + body: []byte(`{"model":"first","model":"second","tools":[{"name":"first"}],"tools":[{"name":"second"}],"functions":[],"functions":[{"name":"second"}],"instructions":"first","instructions":"second","messages":null,"messages":[{"role":"user","content":"second"}],"input":"first input","input":"second input"}`), + }, + { + name: "escaped field names", + body: []byte(`{"mo\u0064el":"gpt-5.4","mess\u0061ges":[{"role":"user","content":"Hello"}]}`), + }, + { + name: "trailing object fields are outside the root", + body: []byte(`{"foo":1}{"model":"trailing","input":"trailing input"}`), + }, + { + name: "trailing quoted fields are outside the root", + body: []byte(`{"model":"root"}"input":"trailing input"`), + }, + { + name: "leading garbage before the root", + body: []byte(`garbage{"model":"gpt-5.4","input":"Hello"}`), + }, + { + name: "escaped braces remain inside string values", + body: []byte(`{"metadata":"escaped } and [ and \" quote","model":"root","input":"Hello"}{"model":"trailing"}`), + }, + { + name: "nested braces do not end the root", + body: []byte(`{"metadata":{"nested":"} ]"},"model":"root","input":"Hello"}{"model":"trailing"}`), + }, + { + name: "root array does not expose nested or trailing object fields", + body: []byte(`[{"model":"nested"}]{"model":"trailing","input":"trailing input"}`), + }, + { + name: "trailing messages do not override root input", + body: []byte(`{"input":"root input"}{"messages":[{"role":"user","content":"trailing"}]}`), + }, + { + name: "truncated string containing a closing brace", + body: []byte(`{"model":"root","metadata":"still } inside`), + }, + { + name: "lenient truncated body", + body: []byte(`{"model":"gpt-5.4","messages":[{"role":"user","content":"Hello"}]`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, legacyDeriveOpenAIContentSessionSeed(test.body), deriveOpenAIContentSessionSeed(test.body)) + }) + } +} + +func TestDeriveOpenAIContentSessionSeed_AllTruncationOffsetsMatchLegacyBytes(t *testing.T) { + bodies := []string{ + `{"model":"gpt-5.4","tools":[{"type":"function","function":{"name":"lookup"}}],"functions":[{"name":"legacy"}],"instructions":"escaped \" } text","messages":[{"role":"system","content":"System"},{"role":"user","content":[{"type":"text","text":"Hello"}]}],"input":"fallback"}`, + `{"model":"gpt-5.4","instructions":"Be concise.","tools":[{"type":"function","name":"lookup"}],"input":[{"role":"system","content":"System"},{"role":"user","content":[{"type":"input_text","text":"Hello"}]}]}`, + } + for bodyIndex, body := range bodies { + for end := 1; end < len(body); end++ { + truncated := []byte(body[:end]) + require.Equalf(t, legacyDeriveOpenAIContentSessionSeed(truncated), deriveOpenAIContentSessionSeed(truncated), "body %d truncated at byte %d", bodyIndex, end) + } + } +} + +func legacyDeriveOpenAIContentSessionSeed(body []byte) string { + if len(body) == 0 { + return "" + } + + var b strings.Builder + + if model := gjson.GetBytes(body, "model").String(); model != "" { + _, _ = b.WriteString("model=") + _, _ = b.WriteString(model) + } + + if tools := gjson.GetBytes(body, "tools"); tools.Exists() && tools.IsArray() && tools.Raw != "[]" { + _, _ = b.WriteString("|tools=") + _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(tools.Raw))) + } + + if funcs := gjson.GetBytes(body, "functions"); funcs.Exists() && funcs.IsArray() && funcs.Raw != "[]" { + _, _ = b.WriteString("|functions=") + _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(funcs.Raw))) + } + + if instr := gjson.GetBytes(body, "instructions").String(); instr != "" { + _, _ = b.WriteString("|instructions=") + _, _ = b.WriteString(instr) + } + + firstUserCaptured := false + + msgs := gjson.GetBytes(body, "messages") + if msgs.Exists() && msgs.IsArray() { + msgs.ForEach(func(_, msg gjson.Result) bool { + role := msg.Get("role").String() + switch role { + case "system", "developer": + _, _ = b.WriteString("|system=") + if c := msg.Get("content"); c.Exists() { + _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(c.Raw))) + } + case "user": + if !firstUserCaptured { + _, _ = b.WriteString("|first_user=") + if c := msg.Get("content"); c.Exists() { + _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(c.Raw))) + } + firstUserCaptured = true + } + } + return true + }) + } else if inp := gjson.GetBytes(body, "input"); inp.Exists() { + if inp.Type == gjson.String { + _, _ = b.WriteString("|input=") + _, _ = b.WriteString(inp.String()) + } else if inp.IsArray() { + inp.ForEach(func(_, item gjson.Result) bool { + role := item.Get("role").String() + switch role { + case "system", "developer": + _, _ = b.WriteString("|system=") + if c := item.Get("content"); c.Exists() { + _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(c.Raw))) + } + case "user": + if !firstUserCaptured { + _, _ = b.WriteString("|first_user=") + if c := item.Get("content"); c.Exists() { + _, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(c.Raw))) + } + firstUserCaptured = true + } + } + if !firstUserCaptured && item.Get("type").String() == "input_text" { + _, _ = b.WriteString("|first_user=") + if text := item.Get("text").String(); text != "" { + _, _ = b.WriteString(text) + } + firstUserCaptured = true + } + return true + }) + } + } + + if b.Len() == 0 { + return "" + } + return contentSessionSeedPrefix + b.String() +} + func TestDeriveOpenAIContentSessionSeed_ResponsesAPI_InputTextTypedItem(t *testing.T) { body := []byte(`{ "model": "gpt-5.4",