feat: use Halvo78 playground as primary default endpoint with automatic failover

This commit is contained in:
Luxferre
2026-09-07 07:22:01 +03:00
parent 404779f8e8
commit f52e07f700
3 changed files with 146 additions and 32 deletions
+91
View File
@@ -10,6 +10,7 @@ import (
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestChatMessageGetContentString(t *testing.T) {
@@ -652,5 +653,95 @@ func TestResolveMaxTokens(t *testing.T) {
}
}
func TestQwenServiceSandboxFailover(t *testing.T) {
server1Hits := 0
server2Hits := 0
// Server 1: Returns Gradio chat_response sandbox simulation
server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server1Hits++
if r.URL.Path == "/gradio_api/call/chat_response" {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"event_id": "sandbox_event"}`))
return
}
if r.URL.Path == "/gradio_api/call/chat_response/sandbox_event" {
w.Header().Set("Content-Type", "text/event-stream")
flusher, _ := w.(http.Flusher)
chunk := "event: complete\n" +
"data: [[{\"role\": \"user\", \"content\": [{\"text\": \"hello\", \"type\": \"text\"}]}, {\"role\": \"assistant\", \"content\": [{\"text\": \"> 💭 **Thinking Process (QSA Micro-block Reasoning):**\\n\\n### Qwen3.8-Flash-Next Sandbox Response\\n\\nTo connect to a live inference engine, enter your endpoint credentials.\", \"type\": \"text\"}]}]]\n\n"
w.Write([]byte(chunk))
flusher.Flush()
return
}
http.NotFound(w, r)
}))
defer server1.Close()
// Server 2: Standard OpenAI server returning live completion
server2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server2Hits++
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":"Live completion from fallback server"},"finish_reason":"stop"}]}`))
}))
defer server2.Close()
svc := NewQwenService([]string{server1.URL, server2.URL}, "Qwen/Qwen3.8-Flash-Next", "auto", "", "", "", "", true, true)
// Explicitly assign modes for mock URLs
svc.endpoints[0].Mode = "chat_response"
svc.endpoints[1].Mode = "openai"
// 1. Non-streaming failover test
rec := httptest.NewRecorder()
req := ChatCompletionRequest{
Model: "qwen",
Messages: []ChatMessage{{Role: "user", Content: "hello"}},
Stream: false,
}
err := svc.Chat(rec, nil, req)
if err != nil {
t.Fatalf("expected failover to succeed, got error: %v", err)
}
var res ChatCompletionResponse
if err := json.Unmarshal(rec.Body.Bytes(), &res); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if res.Choices[0].Message.Content != "Live completion from fallback server" {
t.Fatalf("unexpected content from failover: %v", res.Choices[0].Message.Content)
}
if server1Hits != 2 { // 1 for /gradio_api/call, 1 for /gradio_api/call/sandbox_event
t.Fatalf("expected server1 to be attempted, got %d hits", server1Hits)
}
if server2Hits != 1 {
t.Fatalf("expected server2 to be reached on failover, got %d hits", server2Hits)
}
// 2. Verify server1 is in cooldown and next request goes directly to server2
rec2 := httptest.NewRecorder()
err2 := svc.Chat(rec2, nil, req)
if err2 != nil {
t.Fatalf("expected request during cooldown to succeed on server2: %v", err2)
}
if server2Hits != 2 {
t.Fatalf("expected server2 to receive the second request directly, got %d hits", server2Hits)
}
// 3. Test streaming failover when server1 is out of cooldown
svc.endpoints[0].CooldownUntil = time.Time{}
streamReq := ChatCompletionRequest{
Model: "qwen",
Messages: []ChatMessage{{Role: "user", Content: "hello"}},
Stream: true,
}
recStream := httptest.NewRecorder()
errStream := svc.Chat(recStream, nil, streamReq)
if errStream != nil {
t.Fatalf("expected streaming failover to succeed, got error: %v", errStream)
}
if server2Hits != 3 {
t.Fatalf("expected server2 to receive the streaming failover, got %d hits", server2Hits)
}
}