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,341 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||
)
|
||||
|
||||
var (
|
||||
benchmarkStringSink string
|
||||
benchmarkIntSink int
|
||||
)
|
||||
|
||||
// BenchmarkGenerateSessionHash_Metadata 关注 JSON 解析与正则匹配开销。
|
||||
func BenchmarkGenerateSessionHash_Metadata(b *testing.B) {
|
||||
svc := &GatewayService{}
|
||||
body := []byte(`{"metadata":{"user_id":"session_123e4567-e89b-12d3-a456-426614174000"},"messages":[{"content":"hello"}]}`)
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parsed, err := ParseGatewayRequest(NewRequestBodyRef(body), "")
|
||||
if err != nil {
|
||||
b.Fatalf("解析请求失败: %v", err)
|
||||
}
|
||||
benchmarkStringSink = svc.GenerateSessionHash(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseGatewayRequest_LargeAnthropicMessages(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeAnthropicMessagesBody(size.bytes, false)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parsed, err := ParseGatewayRequest(NewRequestBodyRef(body), domain.PlatformAnthropic)
|
||||
if err != nil {
|
||||
b.Fatalf("解析 Anthropic 请求失败: %v", err)
|
||||
}
|
||||
benchmarkIntSink = len(parsed.MessagesRaw())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseGatewayRequest_LargeGeminiContents(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeGeminiContentsBody(size.bytes)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parsed, err := ParseGatewayRequest(NewRequestBodyRef(body), domain.PlatformGemini)
|
||||
if err != nil {
|
||||
b.Fatalf("解析 Gemini 请求失败: %v", err)
|
||||
}
|
||||
benchmarkIntSink = len(parsed.MessagesRaw())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenerateSessionHash_LargeAnthropicMessages(b *testing.B) {
|
||||
svc := &GatewayService{}
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeAnthropicMessagesBody(size.bytes, true)
|
||||
parsed, err := ParseGatewayRequest(NewRequestBodyRef(body), domain.PlatformAnthropic)
|
||||
if err != nil {
|
||||
b.Fatalf("解析请求失败: %v", err)
|
||||
}
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchmarkStringSink = svc.GenerateSessionHash(parsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpenAIResponses_LargeInputMeta(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeOpenAIResponsesBody(size.bytes)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
model, stream, promptCacheKey := extractOpenAIRequestMetaFromBody(body)
|
||||
benchmarkStringSink = model + promptCacheKey
|
||||
if stream {
|
||||
benchmarkIntSink++
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpenAIResponses_LargeInputDecodeMap(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeOpenAIResponsesBody(size.bytes)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
reqBody, err := getOpenAIRequestBodyMap(nil, body)
|
||||
if err != nil {
|
||||
b.Fatalf("解析 OpenAI 请求失败: %v", err)
|
||||
}
|
||||
benchmarkIntSink = len(reqBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpenAIResponses_LargeInputRawPatch(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeOpenAIResponsesBody(size.bytes)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
view := newOpenAIRequestView(body)
|
||||
view.MarkPatchSet("instructions", "You are a helpful coding assistant.")
|
||||
view.MarkPatchSet("reasoning.effort", "none")
|
||||
patched, err := view.ApplyPatches()
|
||||
if err != nil {
|
||||
b.Fatalf("应用 OpenAI raw patch 失败: %v", err)
|
||||
}
|
||||
benchmarkIntSink = len(patched)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpenAIResponses_LargeInputImageBillingRaw(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeOpenAIResponsesImageToolBody(size.bytes)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cfg, err := resolveOpenAIResponsesImageBillingConfigDetailedFromBody(body, "gpt-5.4")
|
||||
if err != nil {
|
||||
b.Fatalf("解析 OpenAI 图片计费配置失败: %v", err)
|
||||
}
|
||||
benchmarkStringSink = cfg.Model + cfg.SizeTier + cfg.InputSize
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpenAIResponses_LargeInputEmptyBase64Guard(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeOpenAIResponsesBody(size.bytes)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if openAIRequestBodyMayContainEmptyBase64InputImage(body) {
|
||||
benchmarkIntSink++
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpenAIResponses_LargeInputFunctionCallValidation(b *testing.B) {
|
||||
for _, size := range benchmarkBodySizes() {
|
||||
b.Run(size.name, func(b *testing.B) {
|
||||
body := buildLargeOpenAIResponsesToolContinuationBody(size.bytes)
|
||||
|
||||
b.SetBytes(int64(len(body)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
validation := ValidateFunctionCallOutputContextBytes(body)
|
||||
if !validation.HasFunctionCallOutput || !validation.HasItemReferenceForAllCallIDs {
|
||||
b.Fatalf("工具续链校验结果异常: %+v", validation)
|
||||
}
|
||||
benchmarkIntSink++
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkExtractCacheableContent_System 关注字符串拼接路径的性能。
|
||||
func BenchmarkExtractCacheableContent_System(b *testing.B) {
|
||||
svc := &GatewayService{}
|
||||
req := buildSystemCacheableRequest(12)
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchmarkStringSink = svc.extractCacheableContent(req)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkBodySizes() []struct {
|
||||
name string
|
||||
bytes int
|
||||
} {
|
||||
return []struct {
|
||||
name string
|
||||
bytes int
|
||||
}{
|
||||
{name: "4MB", bytes: 4 << 20},
|
||||
{name: "8MB", bytes: 8 << 20},
|
||||
{name: "16MB", bytes: 16 << 20},
|
||||
{name: "32MB", bytes: 32 << 20},
|
||||
}
|
||||
}
|
||||
|
||||
func buildSystemCacheableRequest(parts int) *ParsedRequest {
|
||||
var builder strings.Builder
|
||||
_, _ = builder.WriteString(`{"system":[`)
|
||||
for i := 0; i < parts; i++ {
|
||||
if i > 0 {
|
||||
_ = builder.WriteByte(',')
|
||||
}
|
||||
_, _ = builder.WriteString(`{"text":"system_part_`)
|
||||
_, _ = builder.WriteString(strconv.Itoa(i))
|
||||
_, _ = builder.WriteString(`","cache_control":{"type":"ephemeral"}}`)
|
||||
}
|
||||
_, _ = builder.WriteString(`]}`)
|
||||
parsed, err := ParseGatewayRequest(NewRequestBodyRef([]byte(builder.String())), "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func buildLargeAnthropicMessagesBody(targetBytes int, includeCacheControl bool) []byte {
|
||||
var builder strings.Builder
|
||||
builder.Grow(targetBytes + 1024)
|
||||
_, _ = builder.WriteString(`{"model":"claude-sonnet-4-5","stream":true,"system":[{"type":"text","text":"system seed"}],"messages":[`)
|
||||
for i := 0; builder.Len() < targetBytes; i++ {
|
||||
if i > 0 {
|
||||
_ = builder.WriteByte(',')
|
||||
}
|
||||
_, _ = builder.WriteString(`{"role":"user","content":[{"type":"text","text":"`)
|
||||
_, _ = builder.WriteString(strings.Repeat("anthropic payload ", 64))
|
||||
_, _ = builder.WriteString(strconv.Itoa(i))
|
||||
_ = builder.WriteByte('"')
|
||||
if includeCacheControl && i%32 == 0 {
|
||||
_, _ = builder.WriteString(`,"cache_control":{"type":"ephemeral"}`)
|
||||
}
|
||||
_, _ = builder.WriteString(`}]}`)
|
||||
}
|
||||
_, _ = builder.WriteString(`]}`)
|
||||
return []byte(builder.String())
|
||||
}
|
||||
|
||||
func buildLargeGeminiContentsBody(targetBytes int) []byte {
|
||||
var builder strings.Builder
|
||||
builder.Grow(targetBytes + 1024)
|
||||
_, _ = builder.WriteString(`{"model":"gemini-2.5-pro","systemInstruction":{"parts":[{"text":"system seed"}]},"contents":[`)
|
||||
for i := 0; builder.Len() < targetBytes; i++ {
|
||||
if i > 0 {
|
||||
_ = builder.WriteByte(',')
|
||||
}
|
||||
_, _ = builder.WriteString(`{"role":"user","parts":[{"text":"`)
|
||||
_, _ = builder.WriteString(strings.Repeat("gemini payload ", 64))
|
||||
_, _ = builder.WriteString(strconv.Itoa(i))
|
||||
_, _ = builder.WriteString(`"}]}`)
|
||||
}
|
||||
_, _ = builder.WriteString(`]}`)
|
||||
return []byte(builder.String())
|
||||
}
|
||||
|
||||
func buildLargeOpenAIResponsesBody(targetBytes int) []byte {
|
||||
var builder strings.Builder
|
||||
builder.Grow(targetBytes + 1024)
|
||||
_, _ = builder.WriteString(`{"model":"gpt-5.4","stream":true,"prompt_cache_key":"session-benchmark","input":[`)
|
||||
for i := 0; builder.Len() < targetBytes; i++ {
|
||||
if i > 0 {
|
||||
_ = builder.WriteByte(',')
|
||||
}
|
||||
_, _ = builder.WriteString(`{"type":"message","role":"user","content":[{"type":"input_text","text":"`)
|
||||
_, _ = builder.WriteString(strings.Repeat("openai responses payload ", 48))
|
||||
_, _ = builder.WriteString(strconv.Itoa(i))
|
||||
_, _ = builder.WriteString(`"}]}`)
|
||||
}
|
||||
_, _ = builder.WriteString(`],"tools":[{"type":"function","name":"lookup","parameters":{"type":"object","properties":{"query":{"type":"string"}}}}]}`)
|
||||
return []byte(builder.String())
|
||||
}
|
||||
|
||||
func buildLargeOpenAIResponsesToolContinuationBody(targetBytes int) []byte {
|
||||
var builder strings.Builder
|
||||
builder.Grow(targetBytes + 1024)
|
||||
_, _ = builder.WriteString(`{"model":"gpt-5.4","stream":true,"previous_response_id":"resp_benchmark","input":[`)
|
||||
for i := 0; builder.Len() < targetBytes; i++ {
|
||||
if i > 0 {
|
||||
_ = builder.WriteByte(',')
|
||||
}
|
||||
callID := "call_" + strconv.Itoa(i)
|
||||
_, _ = builder.WriteString(`{"type":"item_reference","id":"`)
|
||||
_, _ = builder.WriteString(callID)
|
||||
_, _ = builder.WriteString(`"},{"type":"function_call_output","call_id":"`)
|
||||
_, _ = builder.WriteString(callID)
|
||||
_, _ = builder.WriteString(`","output":"`)
|
||||
_, _ = builder.WriteString(strings.Repeat("tool output payload ", 48))
|
||||
_, _ = builder.WriteString(strconv.Itoa(i))
|
||||
_, _ = builder.WriteString(`"}`)
|
||||
}
|
||||
_, _ = builder.WriteString(`]}`)
|
||||
return []byte(builder.String())
|
||||
}
|
||||
|
||||
func buildLargeOpenAIResponsesImageToolBody(targetBytes int) []byte {
|
||||
var builder strings.Builder
|
||||
builder.Grow(targetBytes + 1024)
|
||||
_, _ = builder.WriteString(`{"model":"gpt-5.4","stream":false,"tools":[{"type":"image_generation","model":"gpt-image-2","size":"2048x1152"}],"input":[`)
|
||||
for i := 0; builder.Len() < targetBytes; i++ {
|
||||
if i > 0 {
|
||||
_ = builder.WriteByte(',')
|
||||
}
|
||||
_, _ = builder.WriteString(`{"type":"message","role":"user","content":[{"type":"input_text","text":"`)
|
||||
_, _ = builder.WriteString(strings.Repeat("openai image billing payload ", 48))
|
||||
_, _ = builder.WriteString(strconv.Itoa(i))
|
||||
_, _ = builder.WriteString(`"}]}`)
|
||||
}
|
||||
_, _ = builder.WriteString(`]}`)
|
||||
return []byte(builder.String())
|
||||
}
|
||||
Reference in New Issue
Block a user