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
331 lines
9.2 KiB
Go
331 lines
9.2 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
// contentSessionSeedPrefix prevents collisions between content-derived seeds
|
|
// and explicit session IDs (e.g. "sess-xxx" or "compat_cc_xxx").
|
|
const contentSessionSeedPrefix = "compat_cs_"
|
|
|
|
// contentStablePrefixSessionSeedPrefix distinguishes cache identities derived
|
|
// only from request fields that remain stable across independent prompts.
|
|
const contentStablePrefixSessionSeedPrefix = "compat_csp_"
|
|
|
|
// deriveOpenAIContentSessionSeed builds a stable session seed from an
|
|
// OpenAI-format request body. Only fields constant across conversation turns
|
|
// are included: model, tools/functions definitions, system/developer prompts,
|
|
// instructions (Responses API), and the first user message.
|
|
// Supports both Chat Completions (messages) and Responses API (input).
|
|
func deriveOpenAIContentSessionSeed(body []byte) string {
|
|
if len(body) == 0 {
|
|
return ""
|
|
}
|
|
|
|
const (
|
|
modelField = iota
|
|
toolsField
|
|
functionsField
|
|
instructionsField
|
|
messagesField
|
|
inputField
|
|
contentSessionSeedFieldCount
|
|
allContentSessionSeedFields = 1<<contentSessionSeedFieldCount - 1
|
|
)
|
|
var fields [contentSessionSeedFieldCount]gjson.Result
|
|
var seen uint8
|
|
// Match gjson.GetBytes by starting at the first root container, even when
|
|
// malformed input has a non-JSON prefix.
|
|
root := body
|
|
for i := 0; i < len(body); i++ {
|
|
switch body[i] {
|
|
case '{':
|
|
root = body[i:]
|
|
goto scanRoot
|
|
case '[':
|
|
return ""
|
|
}
|
|
}
|
|
return ""
|
|
|
|
scanRoot:
|
|
nextKeyOffset := 1
|
|
parseRawJSONView(root).ForEach(func(key, value gjson.Result) bool {
|
|
if key.Index < nextKeyOffset || key.Index > 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 := fields[modelField].String(); model != "" {
|
|
_, _ = b.WriteString("model=")
|
|
_, _ = b.WriteString(model)
|
|
}
|
|
|
|
if tools := fields[toolsField]; tools.Exists() && tools.IsArray() && tools.Raw != "[]" {
|
|
_, _ = b.WriteString("|tools=")
|
|
_, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(tools.Raw)))
|
|
}
|
|
|
|
if funcs := fields[functionsField]; funcs.Exists() && funcs.IsArray() && funcs.Raw != "[]" {
|
|
_, _ = b.WriteString("|functions=")
|
|
_, _ = b.WriteString(normalizeCompatSeedJSON(json.RawMessage(funcs.Raw)))
|
|
}
|
|
|
|
if instr := fields[instructionsField].String(); instr != "" {
|
|
_, _ = b.WriteString("|instructions=")
|
|
_, _ = b.WriteString(instr)
|
|
}
|
|
|
|
firstUserCaptured := false
|
|
|
|
msgs := fields[messagesField]
|
|
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 := fields[inputField]; 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()
|
|
}
|
|
|
|
// deriveOpenAIAnchoredContentSessionSeed returns the legacy content-derived
|
|
// seed only when it contains a meaningful user/input anchor. This preserves
|
|
// the existing session derivation while preventing model-only requests from
|
|
// becoming a tenant-wide cache routing identity.
|
|
func deriveOpenAIAnchoredContentSessionSeed(body []byte) string {
|
|
if !hasOpenAIContentSessionUserAnchor(body) {
|
|
return ""
|
|
}
|
|
return deriveOpenAIContentSessionSeed(body)
|
|
}
|
|
|
|
func hasOpenAIContentSessionUserAnchor(body []byte) bool {
|
|
if len(body) == 0 {
|
|
return false
|
|
}
|
|
|
|
if messages := gjson.GetBytes(body, "messages"); messages.Exists() && messages.IsArray() {
|
|
anchored := false
|
|
messages.ForEach(func(_, message gjson.Result) bool {
|
|
if strings.TrimSpace(message.Get("role").String()) != "user" {
|
|
return true
|
|
}
|
|
anchored = hasMeaningfulOpenAIContent(message.Get("content"))
|
|
return false
|
|
})
|
|
return anchored
|
|
}
|
|
|
|
input := gjson.GetBytes(body, "input")
|
|
if !input.Exists() {
|
|
return false
|
|
}
|
|
if input.Type == gjson.String {
|
|
return strings.TrimSpace(input.String()) != ""
|
|
}
|
|
if !input.IsArray() {
|
|
return false
|
|
}
|
|
|
|
anchored := false
|
|
input.ForEach(func(_, item gjson.Result) bool {
|
|
if strings.TrimSpace(item.Get("role").String()) == "user" {
|
|
anchored = hasMeaningfulOpenAIContent(item.Get("content"))
|
|
return false
|
|
}
|
|
if strings.TrimSpace(item.Get("type").String()) == "input_text" {
|
|
anchored = strings.TrimSpace(item.Get("text").String()) != ""
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return anchored
|
|
}
|
|
|
|
func hasMeaningfulOpenAIContent(content gjson.Result) bool {
|
|
if !content.Exists() || content.Type == gjson.Null {
|
|
return false
|
|
}
|
|
if content.Type == gjson.String {
|
|
return strings.TrimSpace(content.String()) != ""
|
|
}
|
|
if !content.IsArray() {
|
|
normalized, ok := normalizeNonEmptyCompatSeedJSON(content)
|
|
return ok && strings.TrimSpace(normalized) != ""
|
|
}
|
|
|
|
meaningful := false
|
|
content.ForEach(func(_, item gjson.Result) bool {
|
|
if item.Type == gjson.String {
|
|
meaningful = strings.TrimSpace(item.String()) != ""
|
|
} else if text := item.Get("text"); text.Exists() {
|
|
meaningful = strings.TrimSpace(text.String()) != ""
|
|
} else {
|
|
_, meaningful = normalizeNonEmptyCompatSeedJSON(item)
|
|
}
|
|
return !meaningful
|
|
})
|
|
return meaningful
|
|
}
|
|
|
|
// deriveOpenAIStablePrefixSessionSeed builds a seed from the reusable prefix
|
|
// of an OpenAI-format request. User and assistant content are deliberately
|
|
// excluded so independent prompts with the same system/tool prefix can share
|
|
// an upstream prompt-cache routing identity.
|
|
//
|
|
// An empty result means the request has no meaningful stable prefix. Callers
|
|
// must then use a narrower fallback instead of grouping all requests by tenant
|
|
// and model alone.
|
|
func deriveOpenAIStablePrefixSessionSeed(body []byte) string {
|
|
if len(body) == 0 {
|
|
return ""
|
|
}
|
|
|
|
var b strings.Builder
|
|
hasStablePrefix := false
|
|
appendJSON := func(label string, value gjson.Result) {
|
|
normalized, ok := normalizeNonEmptyCompatSeedJSON(value)
|
|
if !ok {
|
|
return
|
|
}
|
|
_, _ = b.WriteString("|")
|
|
_, _ = b.WriteString(label)
|
|
_, _ = b.WriteString("=")
|
|
_, _ = b.WriteString(normalized)
|
|
hasStablePrefix = true
|
|
}
|
|
|
|
if tools := gjson.GetBytes(body, "tools"); tools.Exists() && tools.IsArray() {
|
|
appendJSON("tools", tools)
|
|
}
|
|
if funcs := gjson.GetBytes(body, "functions"); funcs.Exists() && funcs.IsArray() {
|
|
appendJSON("functions", funcs)
|
|
}
|
|
if instructions := gjson.GetBytes(body, "instructions"); strings.TrimSpace(instructions.String()) != "" {
|
|
appendJSON("instructions", instructions)
|
|
}
|
|
|
|
appendSystemMessages := func(items gjson.Result) {
|
|
items.ForEach(func(_, item gjson.Result) bool {
|
|
role := strings.TrimSpace(item.Get("role").String())
|
|
switch role {
|
|
case "system", "developer":
|
|
appendJSON(role, item.Get("content"))
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
if messages := gjson.GetBytes(body, "messages"); messages.Exists() && messages.IsArray() {
|
|
appendSystemMessages(messages)
|
|
} else if input := gjson.GetBytes(body, "input"); input.Exists() && input.IsArray() {
|
|
appendSystemMessages(input)
|
|
}
|
|
|
|
if !hasStablePrefix {
|
|
return ""
|
|
}
|
|
return contentStablePrefixSessionSeedPrefix + b.String()
|
|
}
|
|
|
|
func normalizeNonEmptyCompatSeedJSON(value gjson.Result) (string, bool) {
|
|
if !value.Exists() || value.Type == gjson.Null {
|
|
return "", false
|
|
}
|
|
normalized := normalizeCompatSeedJSON(json.RawMessage(value.Raw))
|
|
switch normalized {
|
|
case "", `""`, "[]", "{}", "null":
|
|
return "", false
|
|
default:
|
|
return normalized, true
|
|
}
|
|
}
|