Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,550 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_EmptyInputs(t *testing.T) {
|
||||
require.Empty(t, deriveOpenAIContentSessionSeed(nil))
|
||||
require.Empty(t, deriveOpenAIContentSessionSeed([]byte{}))
|
||||
require.Empty(t, deriveOpenAIContentSessionSeed([]byte(`{}`)))
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ModelOnly(t *testing.T) {
|
||||
seed := deriveOpenAIContentSessionSeed([]byte(`{"model":"gpt-5.4"}`))
|
||||
require.Contains(t, seed, contentSessionSeedPrefix)
|
||||
require.Contains(t, seed, "model=gpt-5.4")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_StableAcrossTurns(t *testing.T) {
|
||||
turn1 := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are helpful."},
|
||||
{"role": "user", "content": "Hello"}
|
||||
]
|
||||
}`)
|
||||
turn2 := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are helpful."},
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Hi there!"},
|
||||
{"role": "user", "content": "How are you?"}
|
||||
]
|
||||
}`)
|
||||
s1 := deriveOpenAIContentSessionSeed(turn1)
|
||||
s2 := deriveOpenAIContentSessionSeed(turn2)
|
||||
require.Equal(t, s1, s2, "seed should be stable across later turns")
|
||||
require.NotEmpty(t, s1)
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_DifferentFirstUserDiffers(t *testing.T) {
|
||||
req1 := []byte(`{"model":"gpt-5.4","messages":[{"role":"user","content":"Question A"}]}`)
|
||||
req2 := []byte(`{"model":"gpt-5.4","messages":[{"role":"user","content":"Question B"}]}`)
|
||||
s1 := deriveOpenAIContentSessionSeed(req1)
|
||||
s2 := deriveOpenAIContentSessionSeed(req2)
|
||||
require.NotEqual(t, s1, s2)
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_DifferentSystemDiffers(t *testing.T) {
|
||||
req1 := []byte(`{"model":"gpt-5.4","messages":[{"role":"system","content":"A"},{"role":"user","content":"Hi"}]}`)
|
||||
req2 := []byte(`{"model":"gpt-5.4","messages":[{"role":"system","content":"B"},{"role":"user","content":"Hi"}]}`)
|
||||
s1 := deriveOpenAIContentSessionSeed(req1)
|
||||
s2 := deriveOpenAIContentSessionSeed(req2)
|
||||
require.NotEqual(t, s1, s2)
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_DifferentModelDiffers(t *testing.T) {
|
||||
req1 := []byte(`{"model":"gpt-5.4","messages":[{"role":"user","content":"Hi"}]}`)
|
||||
req2 := []byte(`{"model":"gpt-4o","messages":[{"role":"user","content":"Hi"}]}`)
|
||||
s1 := deriveOpenAIContentSessionSeed(req1)
|
||||
s2 := deriveOpenAIContentSessionSeed(req2)
|
||||
require.NotEqual(t, s1, s2)
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_WithTools(t *testing.T) {
|
||||
withTools := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"tools": [{"type":"function","function":{"name":"get_weather"}}],
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
}`)
|
||||
withoutTools := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
}`)
|
||||
s1 := deriveOpenAIContentSessionSeed(withTools)
|
||||
s2 := deriveOpenAIContentSessionSeed(withoutTools)
|
||||
require.NotEqual(t, s1, s2, "tools should affect the seed")
|
||||
require.Contains(t, s1, "|tools=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_WithFunctions(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"functions": [{"name":"get_weather","parameters":{}}],
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|functions=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_DeveloperRole(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"messages": [
|
||||
{"role": "developer", "content": "You are helpful."},
|
||||
{"role": "user", "content": "Hello"}
|
||||
]
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|system=")
|
||||
require.Contains(t, seed, "|first_user=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ChatCompletions_StructuredContent(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type":"text","text":"Hello"}]}
|
||||
]
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.NotEmpty(t, seed)
|
||||
require.Contains(t, seed, "|first_user=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ResponsesAPI_InputString(t *testing.T) {
|
||||
body := []byte(`{"model":"gpt-5.4","input":"Hello, how are you?"}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|input=Hello, how are you?")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ResponsesAPI_InputArray(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"input": [
|
||||
{"role": "system", "content": "You are helpful."},
|
||||
{"role": "user", "content": "Hello"}
|
||||
]
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|system=")
|
||||
require.Contains(t, seed, "|first_user=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ResponsesAPI_WithInstructions(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"instructions": "You are a coding assistant.",
|
||||
"input": "Write a hello world"
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|instructions=You are a coding assistant.")
|
||||
require.Contains(t, seed, "|input=Write a hello world")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_Deterministic(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are helpful."},
|
||||
{"role": "user", "content": "Hello"}
|
||||
]
|
||||
}`)
|
||||
s1 := deriveOpenAIContentSessionSeed(body)
|
||||
s2 := deriveOpenAIContentSessionSeed(body)
|
||||
require.Equal(t, s1, s2, "seed must be deterministic")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_PrefixPresent(t *testing.T) {
|
||||
body := []byte(`{"model":"gpt-5.4","messages":[{"role":"user","content":"Hi"}]}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.True(t, len(seed) > len(contentSessionSeedPrefix))
|
||||
require.Equal(t, contentSessionSeedPrefix, seed[:len(contentSessionSeedPrefix)])
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_EmptyToolsIgnored(t *testing.T) {
|
||||
body := []byte(`{"model":"gpt-5.4","tools":[],"messages":[{"role":"user","content":"Hi"}]}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.NotContains(t, seed, "|tools=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_MessagesPreferredOverInput(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"messages": [{"role": "user", "content": "from messages"}],
|
||||
"input": "from input"
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|first_user=")
|
||||
require.NotContains(t, seed, "|input=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_JSONCanonicalisation(t *testing.T) {
|
||||
compact := []byte(`{"model":"gpt-5.4","tools":[{"type":"function","function":{"name":"get_weather","description":"Get weather"}}],"messages":[{"role":"user","content":"Hi"}]}`)
|
||||
spaced := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"tools": [
|
||||
{ "type" : "function", "function": { "description": "Get weather", "name": "get_weather" } }
|
||||
],
|
||||
"messages": [ { "role": "user", "content": "Hi" } ]
|
||||
}`)
|
||||
s1 := deriveOpenAIContentSessionSeed(compact)
|
||||
s2 := deriveOpenAIContentSessionSeed(spaced)
|
||||
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",
|
||||
"input": [{"type": "input_text", "text": "Hello world"}]
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|first_user=")
|
||||
require.Contains(t, seed, "Hello world")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIContentSessionSeed_ResponsesAPI_TypedMessageItem(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"model": "gpt-5.4",
|
||||
"input": [{"type": "message", "role": "user", "content": "Hello from typed message"}]
|
||||
}`)
|
||||
seed := deriveOpenAIContentSessionSeed(body)
|
||||
require.Contains(t, seed, "|first_user=")
|
||||
require.Contains(t, seed, "Hello from typed message")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIStablePrefixSessionSeed_IgnoresUserContent(t *testing.T) {
|
||||
first := []byte(`{
|
||||
"model": "grok",
|
||||
"instructions": "Be concise.",
|
||||
"tools": [{"type":"function","name":"lookup","parameters":{"type":"object"}}],
|
||||
"input": [{"role":"user","content":"Question A"}]
|
||||
}`)
|
||||
second := []byte(`{
|
||||
"model": "grok",
|
||||
"instructions": "Be concise.",
|
||||
"tools": [{"parameters":{"type":"object"},"name":"lookup","type":"function"}],
|
||||
"input": [{"role":"user","content":"Question B"}]
|
||||
}`)
|
||||
|
||||
firstSeed := deriveOpenAIStablePrefixSessionSeed(first)
|
||||
secondSeed := deriveOpenAIStablePrefixSessionSeed(second)
|
||||
|
||||
require.NotEmpty(t, firstSeed)
|
||||
require.Equal(t, firstSeed, secondSeed)
|
||||
require.NotContains(t, firstSeed, "Question A")
|
||||
require.NotContains(t, firstSeed, "first_user")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIStablePrefixSessionSeed_IsolatesStablePrefixFields(t *testing.T) {
|
||||
base := []byte(`{
|
||||
"instructions":"Be concise.",
|
||||
"tools":[{"type":"function","name":"lookup"}],
|
||||
"input":[{"role":"system","content":"System A"},{"role":"user","content":"Question"}]
|
||||
}`)
|
||||
differentInstructions := []byte(`{
|
||||
"instructions":"Be detailed.",
|
||||
"tools":[{"type":"function","name":"lookup"}],
|
||||
"input":[{"role":"system","content":"System A"},{"role":"user","content":"Question"}]
|
||||
}`)
|
||||
differentTools := []byte(`{
|
||||
"instructions":"Be concise.",
|
||||
"tools":[{"type":"function","name":"search"}],
|
||||
"input":[{"role":"system","content":"System A"},{"role":"user","content":"Question"}]
|
||||
}`)
|
||||
differentSystem := []byte(`{
|
||||
"instructions":"Be concise.",
|
||||
"tools":[{"type":"function","name":"lookup"}],
|
||||
"input":[{"role":"system","content":"System B"},{"role":"user","content":"Question"}]
|
||||
}`)
|
||||
|
||||
baseSeed := deriveOpenAIStablePrefixSessionSeed(base)
|
||||
require.NotEqual(t, baseSeed, deriveOpenAIStablePrefixSessionSeed(differentInstructions))
|
||||
require.NotEqual(t, baseSeed, deriveOpenAIStablePrefixSessionSeed(differentTools))
|
||||
require.NotEqual(t, baseSeed, deriveOpenAIStablePrefixSessionSeed(differentSystem))
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIStablePrefixSessionSeed_ChatSystemAndDeveloper(t *testing.T) {
|
||||
first := []byte(`{
|
||||
"messages":[
|
||||
{"role":"system","content":"System prompt"},
|
||||
{"role":"developer","content":[{"type":"text","text":"Developer prompt"}]},
|
||||
{"role":"user","content":"Question A"}
|
||||
]
|
||||
}`)
|
||||
second := []byte(`{
|
||||
"messages":[
|
||||
{"role":"system","content":"System prompt"},
|
||||
{"role":"developer","content":[{"text":"Developer prompt","type":"text"}]},
|
||||
{"role":"user","content":"Question B"}
|
||||
]
|
||||
}`)
|
||||
|
||||
firstSeed := deriveOpenAIStablePrefixSessionSeed(first)
|
||||
require.Equal(t, firstSeed, deriveOpenAIStablePrefixSessionSeed(second))
|
||||
require.Contains(t, firstSeed, "System prompt")
|
||||
require.Contains(t, firstSeed, "Developer prompt")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIStablePrefixSessionSeed_EncodesSystemAndDeveloperRoles(t *testing.T) {
|
||||
systemThenDeveloper := []byte(`{
|
||||
"messages":[
|
||||
{"role":"system","content":"Prompt A"},
|
||||
{"role":"developer","content":"Prompt B"}
|
||||
]
|
||||
}`)
|
||||
developerThenSystem := []byte(`{
|
||||
"messages":[
|
||||
{"role":"developer","content":"Prompt A"},
|
||||
{"role":"system","content":"Prompt B"}
|
||||
]
|
||||
}`)
|
||||
|
||||
firstSeed := deriveOpenAIStablePrefixSessionSeed(systemThenDeveloper)
|
||||
secondSeed := deriveOpenAIStablePrefixSessionSeed(developerThenSystem)
|
||||
|
||||
require.NotEqual(t, firstSeed, secondSeed)
|
||||
require.Contains(t, firstSeed, "|system=")
|
||||
require.Contains(t, firstSeed, "|developer=")
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIStablePrefixSessionSeed_EncodesInstructionDelimiters(t *testing.T) {
|
||||
instructionOnly := []byte(`{
|
||||
"instructions":"foo|system=\"bar\""
|
||||
}`)
|
||||
instructionAndSystem := []byte(`{
|
||||
"instructions":"foo",
|
||||
"input":[{"role":"system","content":"bar"}]
|
||||
}`)
|
||||
|
||||
firstSeed := deriveOpenAIStablePrefixSessionSeed(instructionOnly)
|
||||
secondSeed := deriveOpenAIStablePrefixSessionSeed(instructionAndSystem)
|
||||
|
||||
require.NotEmpty(t, firstSeed)
|
||||
require.NotEmpty(t, secondSeed)
|
||||
require.NotEqual(t, firstSeed, secondSeed)
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIAnchoredContentSessionSeed_RequiresMeaningfulAnchor(t *testing.T) {
|
||||
emptyAnchors := [][]byte{
|
||||
nil,
|
||||
[]byte(`{"model":"grok"}`),
|
||||
[]byte(`{"model":"grok","messages":[{"role":"assistant","content":"answer"}]}`),
|
||||
[]byte(`{"model":"grok","messages":[{"role":"user","content":" "}]}`),
|
||||
[]byte(`{"model":"grok","messages":[{"role":"user","content":[{"type":"text","text":""}]}]}`),
|
||||
[]byte(`{"model":"grok","input":" "}`),
|
||||
[]byte(`{"model":"grok","input":[{"type":"input_text","text":""}]}`),
|
||||
}
|
||||
for _, body := range emptyAnchors {
|
||||
require.Empty(t, deriveOpenAIAnchoredContentSessionSeed(body))
|
||||
}
|
||||
|
||||
meaningfulAnchors := [][]byte{
|
||||
[]byte(`{"model":"grok","messages":[{"role":"user","content":"question"}]}`),
|
||||
[]byte(`{"model":"grok","messages":[{"role":"user","content":[{"type":"text","text":"question"}]}]}`),
|
||||
[]byte(`{"model":"grok","input":"question"}`),
|
||||
[]byte(`{"model":"grok","input":[{"type":"input_text","text":"question"}]}`),
|
||||
}
|
||||
for _, body := range meaningfulAnchors {
|
||||
require.NotEmpty(t, deriveOpenAIAnchoredContentSessionSeed(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveOpenAIStablePrefixSessionSeed_RequiresMeaningfulPrefix(t *testing.T) {
|
||||
tests := [][]byte{
|
||||
nil,
|
||||
[]byte(`{}`),
|
||||
[]byte(`{"model":"grok","input":"Question A"}`),
|
||||
[]byte(`{"model":"grok","tools":[],"input":"Question A"}`),
|
||||
[]byte(`{"model":"grok","functions":[],"instructions":" ","messages":[{"role":"system","content":""},{"role":"user","content":"Question A"}]}`),
|
||||
}
|
||||
|
||||
for _, body := range tests {
|
||||
require.Empty(t, deriveOpenAIStablePrefixSessionSeed(body))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user