mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-20 14:41:12 +08:00
perf(claude): prefilter JSON payloads before diagnostic walks
- Add ASCII pre-filter to skip expensive gjson traversals for title helper and 1h TTL checks - Check raw bytes for target keywords before initiating full JSON walks - Fall back to full inspection when printable ASCII Unicode escapes are detected Closes: #6291
This commit is contained in:
@@ -441,6 +441,12 @@ func isClaudeTitleHelperInstruction(body []byte) bool {
|
||||
}
|
||||
|
||||
func isClaudeTitleHelperRequest(body []byte) bool {
|
||||
// Without an output_config key the request is a helper only if one of the
|
||||
// system title instructions below appears, so skip the walks when none can.
|
||||
if !jsonMayContainASCII(body, "output_config", "naming a coding session", "Return a short title",
|
||||
"Write the title in the predominant language") {
|
||||
return false
|
||||
}
|
||||
props := gjson.GetBytes(body, "output_config.format.schema.properties")
|
||||
if props.Exists() {
|
||||
if props.Get("title").Exists() && len(props.Map()) == 1 {
|
||||
@@ -527,7 +533,9 @@ func IsClaudeSubagentRequest(headers http.Header, body []byte) bool {
|
||||
// ClaudePayloadHas1hTTL reports whether the request payload contains any cache_control
|
||||
// block with ttl set to "1h".
|
||||
func ClaudePayloadHas1hTTL(payload []byte) bool {
|
||||
if len(payload) == 0 || !gjson.ValidBytes(payload) {
|
||||
// A ttl of "1h" is the JSON string "1h"; its quotes are structural and never
|
||||
// escaped, so a payload without that token cannot match.
|
||||
if len(payload) == 0 || !jsonMayContainASCII(payload, `"1h"`) || !gjson.ValidBytes(payload) {
|
||||
return false
|
||||
}
|
||||
has1h := false
|
||||
|
||||
50
internal/runtime/executor/helps/claude_json_prefilter.go
Normal file
50
internal/runtime/executor/helps/claude_json_prefilter.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package helps
|
||||
|
||||
import "bytes"
|
||||
|
||||
// jsonMayContainASCII reports whether decoding the JSON document body could
|
||||
// yield a key or string value containing any of needles.
|
||||
//
|
||||
// It lets the request classifiers below skip their gjson walks: a Claude Code
|
||||
// request body carries the whole conversation (often several MB), and each
|
||||
// walk re-scans it. bytes.Contains is far cheaper than a gjson walk.
|
||||
//
|
||||
// Every needle must be printable ASCII without '\\' or '/' (JSON may spell
|
||||
// '/' as `\/`). A '"' may only stand at
|
||||
// either end of a needle, where it matches a structural string delimiter
|
||||
// (`"1h"` matches the whole string value 1h). JSON can only
|
||||
// spell such a character as itself or as a \u00XX escape, so when body holds
|
||||
// no \u escape of a printable ASCII character, a needle that is absent from
|
||||
// the raw bytes is absent from every decoded string too. If such an escape is
|
||||
// present the function answers true and the caller falls back to its full
|
||||
// walk, so the result never changes, only the cost.
|
||||
func jsonMayContainASCII(body []byte, needles ...string) bool {
|
||||
if hasPrintableASCIIUnicodeEscape(body) {
|
||||
return true
|
||||
}
|
||||
for _, needle := range needles {
|
||||
if bytes.Contains(body, []byte(needle)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPrintableASCIIUnicodeEscape reports whether body contains a \u00XX
|
||||
// escape whose first hex digit is 2-7, i.e. a printable ASCII character. It
|
||||
// errs only toward "yes": it also matches DEL (7F), malformed escapes such as
|
||||
// a 2-7 digit followed by a non-hex character, an escape truncated after that
|
||||
// digit, and an escaped backslash followed by literal text such as `\\u0041`.
|
||||
// Each of those only makes the caller fall back to a full walk.
|
||||
func hasPrintableASCIIUnicodeEscape(body []byte) bool {
|
||||
for rest := body; ; {
|
||||
i := bytes.Index(rest, []byte(`\u00`))
|
||||
if i < 0 || i+4 >= len(rest) {
|
||||
return false
|
||||
}
|
||||
if c := rest[i+4]; c >= '2' && c <= '7' {
|
||||
return true
|
||||
}
|
||||
rest = rest[i+4:]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package helps
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// referenceIsClaudeTitleHelperRequest is isClaudeTitleHelperRequest without
|
||||
// the jsonMayContainASCII pre-filter (the v8.0.11 implementation).
|
||||
func referenceIsClaudeTitleHelperRequest(body []byte) bool {
|
||||
props := gjson.GetBytes(body, "output_config.format.schema.properties")
|
||||
if props.Exists() {
|
||||
if props.Get("title").Exists() && len(props.Map()) == 1 {
|
||||
return isClaudeTitleHelperInstruction(body)
|
||||
}
|
||||
return false
|
||||
}
|
||||
matchesSystemTitleInstruction := func(t string) bool {
|
||||
return strings.Contains(t, "naming a coding session") ||
|
||||
strings.Contains(t, "Return a short title") ||
|
||||
strings.Contains(t, "Write the title in the predominant language")
|
||||
}
|
||||
system := gjson.GetBytes(body, "system")
|
||||
if system.IsArray() {
|
||||
for _, part := range system.Array() {
|
||||
if matchesSystemTitleInstruction(part.Get("text").String()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if matchesSystemTitleInstruction(system.String()) {
|
||||
return true
|
||||
}
|
||||
messages := gjson.GetBytes(body, "messages")
|
||||
if messages.IsArray() {
|
||||
for _, msg := range messages.Array() {
|
||||
if msg.Get("role").String() == "system" {
|
||||
content := msg.Get("content")
|
||||
if content.IsArray() {
|
||||
for _, part := range content.Array() {
|
||||
if matchesSystemTitleInstruction(part.Get("text").String()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if matchesSystemTitleInstruction(content.String()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// referenceClaudePayloadHas1hTTL is ClaudePayloadHas1hTTL without the
|
||||
// jsonMayContainASCII pre-filter (the v8.0.11 implementation).
|
||||
func referenceClaudePayloadHas1hTTL(payload []byte) bool {
|
||||
if len(payload) == 0 || !gjson.ValidBytes(payload) {
|
||||
return false
|
||||
}
|
||||
has1h := false
|
||||
checkBlock := func(item gjson.Result) bool {
|
||||
cc := item.Get("cache_control")
|
||||
if cc.IsObject() && cc.Get("ttl").String() == "1h" {
|
||||
has1h = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
for _, key := range []string{"tools", "system"} {
|
||||
if arr := gjson.GetBytes(payload, key); arr.IsArray() {
|
||||
arr.ForEach(func(_, item gjson.Result) bool {
|
||||
return checkBlock(item)
|
||||
})
|
||||
if has1h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if messages := gjson.GetBytes(payload, "messages"); messages.IsArray() {
|
||||
messages.ForEach(func(_, msg gjson.Result) bool {
|
||||
content := msg.Get("content")
|
||||
if content.IsArray() {
|
||||
content.ForEach(func(_, item gjson.Result) bool {
|
||||
return checkBlock(item)
|
||||
})
|
||||
}
|
||||
return !has1h
|
||||
})
|
||||
}
|
||||
return has1h
|
||||
}
|
||||
|
||||
// bs is one backslash; the seeds below build JSON escapes from it.
|
||||
const bs = `\`
|
||||
|
||||
var prefilterSeeds = []string{
|
||||
``,
|
||||
`{}`,
|
||||
`not json`,
|
||||
`{"system":"naming a coding session"}`,
|
||||
`{"system":"naming a` + bs + `u0020coding session"}`,
|
||||
`{"system":[{"text":"Return a short ` + bs + `u0074itle"}]}`,
|
||||
`{"system":"Return a short title`,
|
||||
`{"messages":[{"role":"system","content":[{"text":"Write the title in the predominant language"}]}]}`,
|
||||
`{"messages":[{"role":"system","content":"naming a coding session"}`,
|
||||
`{"output_config":{"format":{"schema":{"properties":{"title":{}}}}},"messages":[{"content":"<session>"}]}`,
|
||||
`{"output_` + bs + `u0063onfig":{"format":{"schema":{"properties":{"title":{}}}}},"system":"Return a short title"}`,
|
||||
`{"output_config":{"format":{"schema":{"properties":{"title":{},"x":{}}}}}}`,
|
||||
`{"system":"` + bs + `ud83d` + bs + `ude00 naming a coding session"}`,
|
||||
`{"system":"` + bs + `/naming a coding session` + bs + `"}`,
|
||||
`{"system":"` + bs + bs + `u0041 naming"}`,
|
||||
`{"system":"` + bs + `u00`,
|
||||
`{"messages":[{"content":[{"cache_control":{"ttl":"1h"}}]}]}`,
|
||||
`{"messages":[{"content":[{"cache_control":{"ttl":"` + bs + `u0031h"}}]}]}`,
|
||||
`{"tools":[{"cache_control":{"ttl":"1` + bs + `u0068"}}]}`,
|
||||
`{"system":[{"cache_control":{"ttl":"5m"}}],"note":"1h"}`,
|
||||
`{"tools":[{"cache_control":{"ttl":"1h"}}]}`,
|
||||
`{"messages":[{"content":[{"cache_control":{"ttl":1}}]}]}`,
|
||||
}
|
||||
|
||||
func checkPrefilterEquivalence(t *testing.T, body []byte) {
|
||||
t.Helper()
|
||||
if got, want := isClaudeTitleHelperRequest(body), referenceIsClaudeTitleHelperRequest(body); got != want {
|
||||
t.Fatalf("isClaudeTitleHelperRequest(%q) = %v, reference %v", body, got, want)
|
||||
}
|
||||
if got, want := ClaudePayloadHas1hTTL(body), referenceClaudePayloadHas1hTTL(body); got != want {
|
||||
t.Fatalf("ClaudePayloadHas1hTTL(%q) = %v, reference %v", body, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefilterMatchesReferenceOnSeeds(t *testing.T) {
|
||||
for _, seed := range prefilterSeeds {
|
||||
checkPrefilterEquivalence(t, []byte(seed))
|
||||
}
|
||||
}
|
||||
|
||||
// FuzzPrefilterMatchesReference checks the pre-filtered classifiers against
|
||||
// the full walks on arbitrary bytes: go test -fuzz FuzzPrefilterMatchesReference
|
||||
func FuzzPrefilterMatchesReference(f *testing.F) {
|
||||
for _, seed := range prefilterSeeds {
|
||||
f.Add([]byte(seed))
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, body []byte) {
|
||||
checkPrefilterEquivalence(t, body)
|
||||
})
|
||||
}
|
||||
137
internal/runtime/executor/helps/claude_json_prefilter_test.go
Normal file
137
internal/runtime/executor/helps/claude_json_prefilter_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package helps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJSONMayContainASCII(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
needles []string
|
||||
want bool
|
||||
}{
|
||||
{"absent", `{"system":"hello"}`, []string{"naming a coding session"}, false},
|
||||
{"present", `{"system":"naming a coding session"}`, []string{"naming a coding session"}, true},
|
||||
{"any needle", `{"output_config":{}}`, []string{"naming", "output_config"}, true},
|
||||
{"escaped space falls back", `{"system":"naming a\u0020coding session"}`, []string{"naming a coding session"}, true},
|
||||
{"escaped letter falls back", `{"s":"n\u0061ming"}`, []string{"naming"}, true},
|
||||
{"upper hex falls back", `{"s":"\u004E"}`, []string{"N"}, true},
|
||||
{"escaped backslash is conservative", `{"s":"\\u0041"}`, []string{"zzz"}, true},
|
||||
{"control escape keeps shortcut", `{"s":"a\u001bbé"}`, []string{"naming"}, false},
|
||||
{"truncated escape", `{"s":"\u00`, []string{"naming"}, false},
|
||||
{"quoted value", `{"ttl":"1h"}`, []string{`"1h"`}, true},
|
||||
{"quoted value absent", `{"ttl":"5m","note":"11h"}`, []string{`"1h"`}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := jsonMayContainASCII([]byte(tt.body), tt.needles...); got != tt.want {
|
||||
t.Fatalf("jsonMayContainASCII(%s, %q) = %v, want %v", tt.body, tt.needles, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The pre-filters must never change an answer, including when the matched
|
||||
// text is spelled with \u escapes.
|
||||
func TestPrefilteredClassifiersKeepEscapedMatches(t *testing.T) {
|
||||
escapedTitle := `{"model":"m","system":[{"type":"text","text":"You are naming a\u0020coding session."}],` +
|
||||
`"messages":[{"role":"user","content":"hi"}]}`
|
||||
if !isClaudeTitleHelperRequest([]byte(escapedTitle)) {
|
||||
t.Fatal("escaped system title instruction must still be detected")
|
||||
}
|
||||
plainTitle := `{"model":"m","system":"Return a short title for this.","messages":[{"role":"user","content":"hi"}]}`
|
||||
if !isClaudeTitleHelperRequest([]byte(plainTitle)) {
|
||||
t.Fatal("plain system title instruction must be detected")
|
||||
}
|
||||
schemaTitle := `{"model":"m","output_config":{"format":{"schema":{"properties":{"title":{"type":"string"}}}}},` +
|
||||
`"messages":[{"role":"user","content":"<session>x</session>"}]}`
|
||||
if !isClaudeTitleHelperRequest([]byte(schemaTitle)) {
|
||||
t.Fatal("title schema request must be detected")
|
||||
}
|
||||
ordinary := `{"model":"m","system":"You are Claude Code.","messages":[{"role":"user","content":"Return a short answer"}]}`
|
||||
if isClaudeTitleHelperRequest([]byte(ordinary)) {
|
||||
t.Fatal("ordinary request must not be a title helper")
|
||||
}
|
||||
escaped1h := `{"messages":[{"role":"user","content":[{"type":"text","text":"x","cache_control":{"type":"ephemeral","ttl":"\u0031h"}}]}]}`
|
||||
if !ClaudePayloadHas1hTTL([]byte(escaped1h)) {
|
||||
t.Fatal("escaped 1h ttl must still be detected")
|
||||
}
|
||||
fiveMinutes := `{"messages":[{"role":"user","content":[{"type":"text","text":"took 11h","cache_control":{"type":"ephemeral","ttl":"5m"}}]}]}`
|
||||
if ClaudePayloadHas1hTTL([]byte(fiveMinutes)) {
|
||||
t.Fatal("5m ttl must not count as 1h")
|
||||
}
|
||||
}
|
||||
|
||||
// largeClaudeCodeBody builds a Claude Code style request with n tool round
|
||||
// trips, about 2.2 KB each, ending with the top-level keys Claude Code sends
|
||||
// after messages.
|
||||
func largeClaudeCodeBody(n int) []byte {
|
||||
var b strings.Builder
|
||||
b.WriteString(`{"model":"claude-opus-5-5","messages":[`)
|
||||
output := strings.Repeat(`line of tool output with \"quotes\", tabs\t and a \u001b[0m reset `, 30)
|
||||
for i := 0; i < n; i++ {
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
fmt.Fprintf(&b, `{"role":"assistant","content":[{"type":"tool_use","id":"toolu_%d","name":"Bash","input":{"command":"ls %d"}}]},`, i, i)
|
||||
fmt.Fprintf(&b, `{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_%d","content":"%s"}]}`, i, output)
|
||||
}
|
||||
b.WriteString(`],"system":[{"type":"text","text":"x-anthropic-billing-header: cc_version=2.1.286;"},`)
|
||||
b.WriteString(`{"type":"text","text":"You are Claude Code.","cache_control":{"type":"ephemeral","ttl":"5m"}}],`)
|
||||
b.WriteString(`"tools":[{"name":"Bash","input_schema":{"type":"object"}}],"metadata":{"user_id":"u"},`)
|
||||
b.WriteString(`"max_tokens":64000,"thinking":{"type":"adaptive"},"stream":true}`)
|
||||
return []byte(b.String())
|
||||
}
|
||||
|
||||
func BenchmarkIsClaudeProbeOrHelperRequestLarge(b *testing.B) {
|
||||
body := largeClaudeCodeBody(1500)
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if IsClaudeProbeOrHelperRequest(body) {
|
||||
b.Fatal("ordinary request classified as probe or helper")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClaudePayloadHas1hTTLLarge(b *testing.B) {
|
||||
body := largeClaudeCodeBody(1500)
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if ClaudePayloadHas1hTTL(body) {
|
||||
b.Fatal("5m payload reported as 1h")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The worst cases: a printable-ASCII escape forces the full walk, so the
|
||||
// pre-filter's scan is pure overhead.
|
||||
func BenchmarkClaudePayloadHas1hTTLLargeEscapeFallback(b *testing.B) {
|
||||
body := largeClaudeCodeBody(1500)
|
||||
body = append([]byte(`{"note":"\u0041",`), body[1:]...)
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if ClaudePayloadHas1hTTL(body) {
|
||||
b.Fatal("5m payload reported as 1h")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClaudePayloadHas1hTTLLargeEscapeFallbackLate(b *testing.B) {
|
||||
body := largeClaudeCodeBody(1500)
|
||||
if len(body) > 1 && body[len(body)-1] == '}' {
|
||||
body = append(body[:len(body)-1], []byte(`,"late_escape":"\u0041"}`)...)
|
||||
}
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if ClaudePayloadHas1hTTL(body) {
|
||||
b.Fatal("5m payload reported as 1h")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user