switched to fib backoff
This commit is contained in:
+301
-2
@@ -10,6 +10,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var httpClient = &http.Client{}
|
||||
@@ -96,7 +97,12 @@ func handleModels(cm *ConfigManager, expectedTokens []string) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func handleChatCompletions(cm *ConfigManager, expectedTokens []string) http.HandlerFunc {
|
||||
func handleChatCompletions(cm *ConfigManager, expectedTokens []string, retryBaseDelay ...time.Duration) http.HandlerFunc {
|
||||
baseDelay := 100 * time.Millisecond
|
||||
if len(retryBaseDelay) > 0 {
|
||||
baseDelay = retryBaseDelay[0]
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !checkAuth(expectedTokens, r) {
|
||||
sendUnauthorized(w)
|
||||
@@ -141,6 +147,8 @@ func handleChatCompletions(cm *ConfigManager, expectedTokens []string) http.Hand
|
||||
return
|
||||
}
|
||||
|
||||
autodetectAndNormalizeMessages(bodyMap)
|
||||
|
||||
var requestedModel string
|
||||
if m, ok := bodyMap["model"]; ok {
|
||||
if s, ok := m.(string); ok {
|
||||
@@ -186,6 +194,25 @@ func handleChatCompletions(cm *ConfigManager, expectedTokens []string) http.Hand
|
||||
log.Printf("Received completion request for model %q (stream=%t). Found %d config trials.", requestedModel, isStream, len(trialConfigs))
|
||||
|
||||
for i, trial := range trialConfigs {
|
||||
if i > 0 && baseDelay > 0 {
|
||||
delay := time.Duration(fibonacci(i)) * baseDelay
|
||||
log.Printf("Trial %d/%d: Fibonacci backoff delay of %v before retry...", i+1, len(trialConfigs), delay)
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
log.Printf("Request context cancelled during retry delay before trial %d", i+1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(499)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"error": map[string]any{
|
||||
"message": "Client closed request",
|
||||
"type": "gateway_error",
|
||||
},
|
||||
})
|
||||
return
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Trial %d/%d: model=%s endpoint=%s key_len=%d", i+1, len(trialConfigs), trial.Model, trial.Endpoint, len(trial.Key))
|
||||
|
||||
bodyMap["model"] = trial.Model
|
||||
@@ -329,7 +356,12 @@ func handleChatCompletions(cm *ConfigManager, expectedTokens []string) http.Hand
|
||||
}
|
||||
}
|
||||
|
||||
func handleImageGenerations(cm *ConfigManager, expectedTokens []string) http.HandlerFunc {
|
||||
func handleImageGenerations(cm *ConfigManager, expectedTokens []string, retryBaseDelay ...time.Duration) http.HandlerFunc {
|
||||
baseDelay := 100 * time.Millisecond
|
||||
if len(retryBaseDelay) > 0 {
|
||||
baseDelay = retryBaseDelay[0]
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !checkAuth(expectedTokens, r) {
|
||||
sendUnauthorized(w)
|
||||
@@ -374,6 +406,10 @@ func handleImageGenerations(cm *ConfigManager, expectedTokens []string) http.Han
|
||||
return
|
||||
}
|
||||
|
||||
if p, ok := bodyMap["prompt"]; ok && p != nil {
|
||||
bodyMap["prompt"] = normalizeContent(p)
|
||||
}
|
||||
|
||||
var requestedModel string
|
||||
if m, ok := bodyMap["model"]; ok {
|
||||
if s, ok := m.(string); ok {
|
||||
@@ -412,6 +448,25 @@ func handleImageGenerations(cm *ConfigManager, expectedTokens []string) http.Han
|
||||
log.Printf("Received image generation request for model %q. Found %d config trials.", requestedModel, len(trialConfigs))
|
||||
|
||||
for i, trial := range trialConfigs {
|
||||
if i > 0 && baseDelay > 0 {
|
||||
delay := time.Duration(fibonacci(i)) * baseDelay
|
||||
log.Printf("Trial %d/%d: Fibonacci backoff delay of %v before retry...", i+1, len(trialConfigs), delay)
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
log.Printf("Request context cancelled during retry delay before trial %d", i+1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(499)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"error": map[string]any{
|
||||
"message": "Client closed request",
|
||||
"type": "gateway_error",
|
||||
},
|
||||
})
|
||||
return
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Trial %d/%d: model=%s endpoint=%s key_len=%d", i+1, len(trialConfigs), trial.Model, trial.Endpoint, len(trial.Key))
|
||||
|
||||
bodyMap["model"] = trial.Model
|
||||
@@ -555,3 +610,247 @@ func buildURL(endpoint string, reqPath string) string {
|
||||
|
||||
return endpoint + "/" + reqPath
|
||||
}
|
||||
|
||||
func isMediaContent(m map[string]any) bool {
|
||||
if t, ok := m["type"].(string); ok {
|
||||
tLower := strings.ToLower(t)
|
||||
switch tLower {
|
||||
case "image_url", "image", "input_audio", "audio", "file", "document", "video":
|
||||
return true
|
||||
}
|
||||
}
|
||||
for k := range m {
|
||||
kLower := strings.ToLower(k)
|
||||
switch kLower {
|
||||
case "image_url", "input_audio", "inline_data", "file_data":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeContent(contentAny any) any {
|
||||
if contentAny == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := contentAny.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []any:
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
}
|
||||
hasMedia := false
|
||||
for _, item := range v {
|
||||
if m, ok := item.(map[string]any); ok {
|
||||
if isMediaContent(m) {
|
||||
hasMedia = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasMedia {
|
||||
var normSlice []any
|
||||
for _, item := range v {
|
||||
if m, ok := item.(map[string]any); ok {
|
||||
if t, ok := m["type"].(string); ok && strings.ToLower(t) == "text" {
|
||||
textVal, _ := m["text"].(string)
|
||||
normSlice = append(normSlice, map[string]any{
|
||||
"type": "text",
|
||||
"text": textVal,
|
||||
})
|
||||
} else {
|
||||
normSlice = append(normSlice, m)
|
||||
}
|
||||
} else {
|
||||
normSlice = append(normSlice, item)
|
||||
}
|
||||
}
|
||||
return normSlice
|
||||
}
|
||||
|
||||
var textParts []string
|
||||
for _, item := range v {
|
||||
switch elem := item.(type) {
|
||||
case string:
|
||||
textParts = append(textParts, elem)
|
||||
case map[string]any:
|
||||
if txt, ok := elem["text"].(string); ok {
|
||||
textParts = append(textParts, txt)
|
||||
} else if txt, ok := elem["content"].(string); ok {
|
||||
textParts = append(textParts, txt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, part := range textParts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if sb.Len() > 0 {
|
||||
lastChar := sb.String()[sb.Len()-1]
|
||||
firstChar := part[0]
|
||||
if lastChar != '\n' && lastChar != ' ' && firstChar != '\n' && firstChar != ' ' {
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
sb.WriteString(part)
|
||||
}
|
||||
return sb.String()
|
||||
|
||||
case map[string]any:
|
||||
if isMediaContent(v) {
|
||||
return []any{v}
|
||||
}
|
||||
if txt, ok := v["text"].(string); ok {
|
||||
return txt
|
||||
}
|
||||
if txt, ok := v["content"].(string); ok {
|
||||
return txt
|
||||
}
|
||||
return v
|
||||
default:
|
||||
return contentAny
|
||||
}
|
||||
}
|
||||
|
||||
func autodetectAndNormalizeMessages(bodyMap map[string]any) {
|
||||
if bodyMap == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var systemMsg map[string]any
|
||||
if sysVal, ok := bodyMap["system"]; ok && sysVal != nil {
|
||||
sysText := normalizeContent(sysVal)
|
||||
if sysStr, isStr := sysText.(string); isStr && sysStr != "" {
|
||||
systemMsg = map[string]any{
|
||||
"role": "system",
|
||||
"content": sysStr,
|
||||
}
|
||||
} else if sysSlice, isSlice := sysText.([]any); isSlice && len(sysSlice) > 0 {
|
||||
systemMsg = map[string]any{
|
||||
"role": "system",
|
||||
"content": sysSlice,
|
||||
}
|
||||
}
|
||||
delete(bodyMap, "system")
|
||||
}
|
||||
|
||||
var rawMessages any
|
||||
var sourceKey string
|
||||
|
||||
if msgs, ok := bodyMap["messages"]; ok && msgs != nil {
|
||||
rawMessages = msgs
|
||||
sourceKey = "messages"
|
||||
} else if prompt, ok := bodyMap["prompt"]; ok && prompt != nil {
|
||||
rawMessages = prompt
|
||||
sourceKey = "prompt"
|
||||
delete(bodyMap, "prompt")
|
||||
} else if contents, ok := bodyMap["contents"]; ok && contents != nil {
|
||||
rawMessages = contents
|
||||
sourceKey = "contents"
|
||||
delete(bodyMap, "contents")
|
||||
} else if input, ok := bodyMap["input"]; ok && input != nil {
|
||||
rawMessages = input
|
||||
sourceKey = "input"
|
||||
delete(bodyMap, "input")
|
||||
}
|
||||
|
||||
if rawMessages == nil && systemMsg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var msgList []map[string]any
|
||||
|
||||
switch v := rawMessages.(type) {
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
switch elem := item.(type) {
|
||||
case map[string]any:
|
||||
msgList = append(msgList, elem)
|
||||
case string:
|
||||
msgList = append(msgList, map[string]any{
|
||||
"role": "user",
|
||||
"content": elem,
|
||||
})
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
msgList = append(msgList, v)
|
||||
case string:
|
||||
if v != "" {
|
||||
msgList = append(msgList, map[string]any{
|
||||
"role": "user",
|
||||
"content": v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var normalizedList []any
|
||||
hasSystemInList := false
|
||||
|
||||
for _, msg := range msgList {
|
||||
role, _ := msg["role"].(string)
|
||||
if role == "" {
|
||||
role = "user"
|
||||
}
|
||||
if role == "system" {
|
||||
hasSystemInList = true
|
||||
}
|
||||
|
||||
normMsg := make(map[string]any)
|
||||
for k, val := range msg {
|
||||
normMsg[k] = val
|
||||
}
|
||||
normMsg["role"] = role
|
||||
|
||||
if cnt, ok := msg["content"]; ok {
|
||||
normMsg["content"] = normalizeContent(cnt)
|
||||
} else if parts, ok := msg["parts"]; ok {
|
||||
normMsg["content"] = normalizeContent(parts)
|
||||
delete(normMsg, "parts")
|
||||
}
|
||||
|
||||
normalizedList = append(normalizedList, normMsg)
|
||||
}
|
||||
|
||||
if systemMsg != nil {
|
||||
if !hasSystemInList {
|
||||
normalizedList = append([]any{systemMsg}, normalizedList...)
|
||||
} else if len(normalizedList) > 0 {
|
||||
if firstMsg, ok := normalizedList[0].(map[string]any); ok && firstMsg["role"] == "system" {
|
||||
existingSys := normalizeContent(firstMsg["content"])
|
||||
if sysStr, ok := systemMsg["content"].(string); ok {
|
||||
if exStr, ok := existingSys.(string); ok && exStr != "" {
|
||||
firstMsg["content"] = sysStr + "\n" + exStr
|
||||
} else {
|
||||
firstMsg["content"] = sysStr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(normalizedList) > 0 || sourceKey != "" {
|
||||
bodyMap["messages"] = normalizedList
|
||||
}
|
||||
}
|
||||
|
||||
func fibonacci(n int) int64 {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
}
|
||||
if n == 1 || n == 2 {
|
||||
return 1
|
||||
}
|
||||
var a, b int64 = 1, 1
|
||||
for i := 3; i <= n; i++ {
|
||||
a, b = b, a+b
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user