feat: revert default to wanyamaelis Qwen3.8-27B and expand verified non-ZeroGPU endpoints
This commit is contained in:
@@ -5,15 +5,15 @@
|
|||||||
Qflash is a standalone, single-binary gateway that exposes Qwen3.8 model spaces on Hugging Face through a standard OpenAI-compatible API. It automatically detects and supports multiple upstream protocols:
|
Qflash is a standalone, single-binary gateway that exposes Qwen3.8 model spaces on Hugging Face through a standard OpenAI-compatible API. It automatically detects and supports multiple upstream protocols:
|
||||||
|
|
||||||
- gradio `/chat_response` endpoints (such as `https://halvo78-qwen3-8-flash-next-playground.hf.space`)
|
- gradio `/chat_response` endpoints (such as `https://halvo78-qwen3-8-flash-next-playground.hf.space`)
|
||||||
- direct OpenAI `/v1/chat/completions` endpoints (such as `https://wanyamaelis-qwen3-8-27b.hf.space` and `https://apathy-exe-qwen3-8-flash-next.hf.space`, running persistent `llama-server` instances on CPU with zero quota limits)
|
- direct OpenAI `/v1/chat/completions` endpoints (such as `https://wanyamaelis-qwen3-8-27b.hf.space`, `https://apathy-exe-qwen3-8-27b.hf.space`, and `https://mikelalda-qwen3-8-euskera-gguf-q4.hf.space`, running persistent `llama-server` instances on CPU with zero quota limits)
|
||||||
- gradio `/respond` endpoints (supported for custom spaces such as `https://microhero-qwen3-8-27b-uncensored-chat.hf.space`)
|
- gradio `/respond` endpoints (supported for spaces such as `https://jickman125-qwen3-8-27b-uncensored-demo.hf.space` and `https://microhero-qwen3-8-27b-uncensored-chat.hf.space`)
|
||||||
|
|
||||||
It translates standard `/v1/chat/completions` and `/v1/models` requests and Server-Sent Events (SSE) stream protocols into upstream formats, allowing any standard OpenAI-compatible client, agent, or IDE to interface with Qwen3.8 models without modification.
|
It translates standard `/v1/chat/completions` and `/v1/models` requests and Server-Sent Events (SSE) stream protocols into upstream formats, allowing any standard OpenAI-compatible client, agent, or IDE to interface with Qwen3.8 models without modification.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- openai-compatible chat completions (streaming and non-streaming)
|
- openai-compatible chat completions (streaming and non-streaming)
|
||||||
- automatic upstream failover from primary playground to non-ZeroGPU endpoints
|
- automatic upstream failover across persistent non-ZeroGPU endpoints
|
||||||
- smart endpoint cooldown (5 minutes on quota exhaustion, 30 seconds on network errors)
|
- smart endpoint cooldown (5 minutes on quota exhaustion, 30 seconds on network errors)
|
||||||
- automatic upstream endpoint detection (`respond`, `openai`, `chat_response`)
|
- automatic upstream endpoint detection (`respond`, `openai`, `chat_response`)
|
||||||
- deep reasoning extraction with thinking trace passthrough (`<think>` tags and blockquotes mapped to `reasoning_content`)
|
- deep reasoning extraction with thinking trace passthrough (`<think>` tags and blockquotes mapped to `reasoning_content`)
|
||||||
@@ -30,15 +30,17 @@ It translates standard `/v1/chat/completions` and `/v1/models` requests and Serv
|
|||||||
|
|
||||||
| Space | Model | Hardware | Protocol | Notes |
|
| Space | Model | Hardware | Protocol | Notes |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `Halvo78/qwen3-8-flash-next-playground` *(primary)* | Qwen3.8-Flash-Next | CPU basic | Gradio `/chat_response` | Default endpoint; auto-fails over to live endpoints if unavailable |
|
| `Wanyamaelis/Qwen3.8-27B` *(primary)* | Qwen3.8-27B (MTP) | CPU basic (8 vCPU) | Native OpenAI `/v1` | Default endpoint; non-ZeroGPU, no quota limits, fast speculative decoding |
|
||||||
| `Wanyamaelis/Qwen3.8-27B` *(fallback)* | Qwen3.8-27B (MTP) | CPU basic (8 vCPU) | Native OpenAI `/v1` | Non-ZeroGPU, no quota limits, fast speculative decoding (~5s) |
|
| `apathy-exe/Qwen3.8-27B` *(fallback)* | Qwen3.8-27B | CPU (AVX-512) | Native OpenAI `/v1` | Non-ZeroGPU, no quota limits, persistent `llama-server` |
|
||||||
| `apathy-exe/Qwen3.8-27B` *(fallback)* | Qwen3.8-27B (MTP) | CPU (OpenMP/AVX512) | Native OpenAI `/v1` | Non-ZeroGPU, no quota limits, speculative decoding |
|
| `apathy-exe/Qwen3.8-Flash-Next` *(fallback)* | Qwen3.8-Flash-Next (~177B) | CPU (AVX-512) | Native OpenAI `/v1` | Non-ZeroGPU, no quota limits, full 177B Flash-Next model |
|
||||||
| `apathy-exe/Qwen3.8-Flash-Next` *(fallback)* | Qwen3.8-Flash-Next (~177B) | CPU (OpenMP/AVX512) | Native OpenAI `/v1` | Non-ZeroGPU, no quota limits, full 177B Flash-Next model |
|
| `mikelalda/Qwen3.8-euskera-GGUF-Q4` *(fallback)* | Qwen3.8-27B | CPU basic | Native OpenAI `/v1` | Non-ZeroGPU, no quota limits, persistent `llama-server` (Q4_K_M) |
|
||||||
|
| `jickman125/Qwen3.8-27B-Uncensored-Demo` | Qwen3.8-27B Uncensored | ZeroGPU (A10G) | Gradio `/respond` | Fast live GPU inference, can be configured via custom `-endpoints` |
|
||||||
| `MicroHERO/qwen3.8-27b-uncensored-chat` | Qwen3.8-27B Uncensored | ZeroGPU (A10G) | Gradio `/respond` | Fast live GPU inference, can be configured via custom `-endpoints` |
|
| `MicroHERO/qwen3.8-27b-uncensored-chat` | Qwen3.8-27B Uncensored | ZeroGPU (A10G) | Gradio `/respond` | Fast live GPU inference, can be configured via custom `-endpoints` |
|
||||||
|
| `Halvo78/qwen3-8-flash-next-playground` | Qwen3.8-Flash-Next | CPU basic | Gradio `/chat_response` | Sandbox playground, supports BYOK upstream inference credentials |
|
||||||
|
|
||||||
### Auto-failover and quota handling
|
### Auto-failover and quota handling
|
||||||
|
|
||||||
By default, Qflash attempts the primary `Halvo78` playground endpoint. If the primary space is unavailable, offline, or lacks live inference credentials, it automatically fails over to the persistent CPU endpoints (`wanyamaelis` and `apathy-exe`), ensuring uninterrupted service:
|
By default, Qflash attempts the primary `wanyamaelis` endpoint with CPU MTP speculative decoding. If any endpoint is unavailable, offline, or exhausts resources, it automatically fails over across the persistent CPU endpoints (`apathy-exe` and `mikelalda`), ensuring uninterrupted service:
|
||||||
|
|
||||||
- with auto-failover enabled (default), when an endpoint encounters an error, timeout, or unavailable backend, it is placed on cooldown and the gateway automatically fails over to the next configured endpoint
|
- with auto-failover enabled (default), when an endpoint encounters an error, timeout, or unavailable backend, it is placed on cooldown and the gateway automatically fails over to the next configured endpoint
|
||||||
- transient network errors trigger a 30-second cooldown, while quota limits or unavailable sandbox spaces trigger a 5-minute cooldown
|
- transient network errors trigger a 30-second cooldown, while quota limits or unavailable sandbox spaces trigger a 5-minute cooldown
|
||||||
@@ -78,13 +80,13 @@ The gateway advertises the following models under `/v1/models`:
|
|||||||
|
|
||||||
| Model ID | Target model | Description |
|
| Model ID | Target model | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `Qwen/Qwen3.8-Flash-Next` | `Qwen/Qwen3.8-Flash-Next` | Default primary model identifier |
|
| `Qwen/Qwen3.8-27B` | `Qwen/Qwen3.8-27B` | Default primary model identifier |
|
||||||
| `Qwen/Qwen3.8-27B` | `Qwen/Qwen3.8-27B` | 27B model identifier |
|
| `Qwen/Qwen3.8-Flash-Next` | `Qwen/Qwen3.8-Flash-Next` | Flash-Next model identifier |
|
||||||
| `Qwen/Qwen3.8-27B-Uncensored` | `Qwen/Qwen3.8-27B-Uncensored` | Uncensored model identifier |
|
| `Qwen/Qwen3.8-27B-Uncensored` | `Qwen/Qwen3.8-27B-Uncensored` | Uncensored model identifier |
|
||||||
|
| `qwen3.8-27b` | `Qwen/Qwen3.8-27B` | Standard 27B lowercase alias |
|
||||||
| `qwen3.8-flash-next` | `Qwen/Qwen3.8-Flash-Next` | Flash-Next lowercase alias |
|
| `qwen3.8-flash-next` | `Qwen/Qwen3.8-Flash-Next` | Flash-Next lowercase alias |
|
||||||
| `qwen-flash-next` | `Qwen/Qwen3.8-Flash-Next` | Shorthand alias |
|
| `qwen-flash-next` | `Qwen/Qwen3.8-Flash-Next` | Shorthand alias |
|
||||||
| `qwen-flash` | `Qwen/Qwen3.8-Flash-Next` | Quick convenience alias |
|
| `qwen-flash` | `Qwen/Qwen3.8-Flash-Next` | Quick convenience alias |
|
||||||
| `qwen3.8-27b` | `Qwen/Qwen3.8-27B` | Standard 27B lowercase alias |
|
|
||||||
| `qwen3.8-27b-uncensored` | `Qwen/Qwen3.8-27B-Uncensored` | Uncensored lowercase alias |
|
| `qwen3.8-27b-uncensored` | `Qwen/Qwen3.8-27B-Uncensored` | Uncensored lowercase alias |
|
||||||
| `qwen` | Default model | Generic shorthand alias |
|
| `qwen` | Default model | Generic shorthand alias |
|
||||||
|
|
||||||
@@ -98,15 +100,15 @@ Run the gateway with default auto-failover endpoints:
|
|||||||
qflash
|
qflash
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, this listens on `http://127.0.0.1:8080` targeting `Halvo78` by default with automatic failover to `wanyamaelis` and `apathy-exe` persistent endpoints.
|
By default, this listens on `http://127.0.0.1:8080` targeting `wanyamaelis` by default with automatic failover to `apathy-exe` and `mikelalda` persistent endpoints.
|
||||||
|
|
||||||
Available flags:
|
Available flags:
|
||||||
|
|
||||||
- `-port` — tcp port to listen on (default `8080`)
|
- `-port` — tcp port to listen on (default `8080`)
|
||||||
- `-endpoints` / `-endpoint` — comma-separated upstream space or OpenAI URLs (default list of 4 endpoints, `QFLASH_ENDPOINTS` / `QFLASH_ENDPOINT` env)
|
- `-endpoints` / `-endpoint` — comma-separated upstream space or OpenAI URLs (default list of 4 persistent endpoints, `QFLASH_ENDPOINTS` / `QFLASH_ENDPOINT` env)
|
||||||
- `-failover` / `-auto-failover` — enable automatic failover across endpoints on quota exhaustion or error (default `true`, `QFLASH_FAILOVER` env)
|
- `-failover` / `-auto-failover` — enable automatic failover across endpoints on quota exhaustion or error (default `true`, `QFLASH_FAILOVER` env)
|
||||||
- `-mode` — upstream protocol mode: `auto`, `respond`, `chat_response`, `openai` (default `auto`, `QFLASH_MODE` env)
|
- `-mode` — upstream protocol mode: `auto`, `respond`, `chat_response`, `openai` (default `auto`, `QFLASH_MODE` env)
|
||||||
- `-model` — exposed model name (default `Qwen/Qwen3.8-Flash-Next`, `QFLASH_MODEL` env)
|
- `-model` — exposed model name (default `Qwen/Qwen3.8-27B`, `QFLASH_MODEL` env)
|
||||||
- `-thinking` / `-enable-thinking` — enable chain-of-thought reasoning by default (default `true`)
|
- `-thinking` / `-enable-thinking` — enable chain-of-thought reasoning by default (default `true`)
|
||||||
- `-hf-token` / `-token` — Hugging Face API token for ZeroGPU quota or private spaces (`HF_TOKEN` env)
|
- `-hf-token` / `-token` — Hugging Face API token for ZeroGPU quota or private spaces (`HF_TOKEN` env)
|
||||||
- `-api-key` — upstream inference engine API key for BYOK mode (`OPENAI_API_KEY` / `QWEN_API_KEY` env)
|
- `-api-key` — upstream inference engine API key for BYOK mode (`OPENAI_API_KEY` / `QWEN_API_KEY` env)
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ var (
|
|||||||
ConfiguredModel string
|
ConfiguredModel string
|
||||||
EnableThinkingDefault = true
|
EnableThinkingDefault = true
|
||||||
DefaultEndpoints = []string{
|
DefaultEndpoints = []string{
|
||||||
"https://halvo78-qwen3-8-flash-next-playground.hf.space",
|
|
||||||
"https://wanyamaelis-qwen3-8-27b.hf.space",
|
"https://wanyamaelis-qwen3-8-27b.hf.space",
|
||||||
"https://apathy-exe-qwen3-8-27b.hf.space",
|
"https://apathy-exe-qwen3-8-27b.hf.space",
|
||||||
"https://apathy-exe-qwen3-8-flash-next.hf.space",
|
"https://apathy-exe-qwen3-8-flash-next.hf.space",
|
||||||
|
"https://mikelalda-qwen3-8-euskera-gguf-q4.hf.space",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -517,7 +517,7 @@ func EffectiveModelID(reqModel string, defaultModel string) string {
|
|||||||
if defaultModel != "" {
|
if defaultModel != "" {
|
||||||
return defaultModel
|
return defaultModel
|
||||||
}
|
}
|
||||||
return "Qwen/Qwen3.8-Flash-Next"
|
return "Qwen/Qwen3.8-27B"
|
||||||
default:
|
default:
|
||||||
return clean
|
return clean
|
||||||
}
|
}
|
||||||
@@ -1154,6 +1154,27 @@ func (f *StreamToolCallFilter) HasEmittedCalls() bool {
|
|||||||
// Qwen3.8-Flash Service & Gradio Stream Parsing
|
// Qwen3.8-Flash Service & Gradio Stream Parsing
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func extractMessageContent(msgMap map[string]interface{}) string {
|
||||||
|
contentVal := msgMap["content"]
|
||||||
|
if contentStr, ok := contentVal.(string); ok {
|
||||||
|
return contentStr
|
||||||
|
}
|
||||||
|
if contentSlice, ok := contentVal.([]interface{}); ok {
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, item := range contentSlice {
|
||||||
|
if s, ok := item.(string); ok {
|
||||||
|
sb.WriteString(s)
|
||||||
|
} else if m, ok := item.(map[string]interface{}); ok {
|
||||||
|
if textVal, ok := m["text"].(string); ok {
|
||||||
|
sb.WriteString(textVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// parseAssistantText extracts the latest assistant message text from the Gradio output array.
|
// parseAssistantText extracts the latest assistant message text from the Gradio output array.
|
||||||
func parseAssistantText(dataJSON string) (string, bool) {
|
func parseAssistantText(dataJSON string) (string, bool) {
|
||||||
var raw []interface{}
|
var raw []interface{}
|
||||||
@@ -1172,9 +1193,12 @@ func parseAssistantText(dataJSON string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the last assistant message
|
var reasoningText string
|
||||||
for i := len(msgList) - 1; i >= 0; i-- {
|
var answerText string
|
||||||
msgMap, ok := msgList[i].(map[string]interface{})
|
var foundAssistant bool
|
||||||
|
|
||||||
|
for _, item := range msgList {
|
||||||
|
msgMap, ok := item.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1182,27 +1206,35 @@ func parseAssistantText(dataJSON string) (string, bool) {
|
|||||||
if role != "assistant" {
|
if role != "assistant" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
foundAssistant = true
|
||||||
|
text := extractMessageContent(msgMap)
|
||||||
|
|
||||||
contentVal := msgMap["content"]
|
isReasoning := false
|
||||||
if contentStr, ok := contentVal.(string); ok {
|
if meta, ok := msgMap["metadata"].(map[string]interface{}); ok {
|
||||||
return contentStr, true
|
if title, ok := meta["title"].(string); ok && strings.EqualFold(title, "reasoning") {
|
||||||
}
|
isReasoning = true
|
||||||
if contentSlice, ok := contentVal.([]interface{}); ok {
|
|
||||||
var sb strings.Builder
|
|
||||||
for _, item := range contentSlice {
|
|
||||||
if s, ok := item.(string); ok {
|
|
||||||
sb.WriteString(s)
|
|
||||||
} else if m, ok := item.(map[string]interface{}); ok {
|
|
||||||
if textVal, ok := m["text"].(string); ok {
|
|
||||||
sb.WriteString(textVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.String(), true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isReasoning {
|
||||||
|
reasoningText = text
|
||||||
|
} else {
|
||||||
|
answerText = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundAssistant {
|
||||||
return "", false
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if reasoningText != "" {
|
||||||
|
if answerText != "" {
|
||||||
|
return fmt.Sprintf("<think>\n%s\n</think>\n\n%s", reasoningText, answerText), true
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<think>\n%s\n</think>", reasoningText), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return answerText, true
|
||||||
}
|
}
|
||||||
|
|
||||||
type EndpointNode struct {
|
type EndpointNode struct {
|
||||||
@@ -1252,7 +1284,7 @@ func parseEndpointList(rawList []string) []*EndpointNode {
|
|||||||
|
|
||||||
func NewQwenService(endpoints []string, modelName, mode, token, apiKey, baseURL, socksProxy string, enableThinking, autoFailover bool) *QwenService {
|
func NewQwenService(endpoints []string, modelName, mode, token, apiKey, baseURL, socksProxy string, enableThinking, autoFailover bool) *QwenService {
|
||||||
if modelName == "" {
|
if modelName == "" {
|
||||||
modelName = "Qwen/Qwen3.8-Flash-Next"
|
modelName = "Qwen/Qwen3.8-27B"
|
||||||
}
|
}
|
||||||
if mode == "" {
|
if mode == "" {
|
||||||
mode = "auto"
|
mode = "auto"
|
||||||
@@ -1289,18 +1321,18 @@ func (s *QwenService) ListModels() []ModelItem {
|
|||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
primaryID := s.modelName
|
primaryID := s.modelName
|
||||||
if primaryID == "" {
|
if primaryID == "" {
|
||||||
primaryID = "Qwen/Qwen3.8-Flash-Next"
|
primaryID = "Qwen/Qwen3.8-27B"
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates := []string{
|
candidates := []string{
|
||||||
primaryID,
|
primaryID,
|
||||||
"Qwen/Qwen3.8-Flash-Next",
|
|
||||||
"Qwen/Qwen3.8-27B",
|
"Qwen/Qwen3.8-27B",
|
||||||
|
"Qwen/Qwen3.8-Flash-Next",
|
||||||
"Qwen/Qwen3.8-27B-Uncensored",
|
"Qwen/Qwen3.8-27B-Uncensored",
|
||||||
|
"qwen3.8-27b",
|
||||||
"qwen3.8-flash-next",
|
"qwen3.8-flash-next",
|
||||||
"qwen-flash-next",
|
"qwen-flash-next",
|
||||||
"qwen-flash",
|
"qwen-flash",
|
||||||
"qwen3.8-27b",
|
|
||||||
"qwen3.8-27b-uncensored",
|
"qwen3.8-27b-uncensored",
|
||||||
"qwen",
|
"qwen",
|
||||||
}
|
}
|
||||||
@@ -1314,7 +1346,7 @@ func (s *QwenService) ListModels() []ModelItem {
|
|||||||
ID: id,
|
ID: id,
|
||||||
Object: "model",
|
Object: "model",
|
||||||
Created: now,
|
Created: now,
|
||||||
OwnedBy: "qwen",
|
OwnedBy: "qwen-community",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1328,13 +1360,13 @@ func (s *QwenService) detectEndpointModeFor(epURL, defaultMode string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ep := strings.ToLower(epURL)
|
ep := strings.ToLower(epURL)
|
||||||
if strings.Contains(ep, "microhero") || strings.HasSuffix(ep, "/respond") {
|
if strings.Contains(ep, "microhero") || strings.Contains(ep, "jickman") || strings.HasSuffix(ep, "/respond") {
|
||||||
return "respond"
|
return "respond"
|
||||||
}
|
}
|
||||||
if strings.Contains(ep, "halvo78") || strings.HasSuffix(ep, "/chat_response") {
|
if strings.Contains(ep, "halvo78") || strings.HasSuffix(ep, "/chat_response") {
|
||||||
return "chat_response"
|
return "chat_response"
|
||||||
}
|
}
|
||||||
if strings.Contains(ep, "wanyamaelis") || strings.Contains(ep, "apathy-exe") || strings.HasSuffix(ep, "/v1") {
|
if strings.Contains(ep, "wanyamaelis") || strings.Contains(ep, "apathy-exe") || strings.Contains(ep, "mikelalda") || strings.HasSuffix(ep, "/v1") {
|
||||||
return "openai"
|
return "openai"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1587,13 +1619,13 @@ func (s *QwenService) chatDirectOpenAI(endpointURL string, w http.ResponseWriter
|
|||||||
|
|
||||||
func (s *QwenService) chatGradioRespond(endpointURL string, w http.ResponseWriter, r *http.Request, req ChatCompletionRequest, resolvedModel string, maxTokens int) error {
|
func (s *QwenService) chatGradioRespond(endpointURL string, w http.ResponseWriter, r *http.Request, req ChatCompletionRequest, resolvedModel string, maxTokens int) error {
|
||||||
var promptText string
|
var promptText string
|
||||||
|
var sysPrompt string
|
||||||
toolsPrompt := FormatToolsPrompt(req.Tools)
|
toolsPrompt := FormatToolsPrompt(req.Tools)
|
||||||
|
|
||||||
if len(req.Messages) == 1 && req.Messages[0].Role == "user" && len(req.Tools) == 0 {
|
if len(req.Messages) == 1 && req.Messages[0].Role == "user" && len(req.Tools) == 0 {
|
||||||
promptText = req.Messages[0].GetContentString()
|
promptText = req.Messages[0].GetContentString()
|
||||||
} else {
|
} else {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
var sysPrompt string
|
|
||||||
for _, m := range req.Messages {
|
for _, m := range req.Messages {
|
||||||
if m.Role == "system" {
|
if m.Role == "system" {
|
||||||
if sysPrompt != "" {
|
if sysPrompt != "" {
|
||||||
@@ -1679,7 +1711,26 @@ func (s *QwenService) chatGradioRespond(endpointURL string, w http.ResponseWrite
|
|||||||
effHFToken = s.token
|
effHFToken = s.token
|
||||||
}
|
}
|
||||||
|
|
||||||
gradioData := []interface{}{
|
var gradioData []interface{}
|
||||||
|
if strings.Contains(strings.ToLower(endpointURL), "jickman") {
|
||||||
|
jickmanSys := sysPrompt
|
||||||
|
if jickmanSys == "" {
|
||||||
|
jickmanSys = "You are a helpful assistant."
|
||||||
|
}
|
||||||
|
gradioData = []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"text": promptText,
|
||||||
|
"files": []interface{}{},
|
||||||
|
},
|
||||||
|
"low",
|
||||||
|
tempVal,
|
||||||
|
topPVal,
|
||||||
|
40,
|
||||||
|
clampedTokens,
|
||||||
|
jickmanSys,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gradioData = []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"text": promptText,
|
"text": promptText,
|
||||||
"files": []interface{}{},
|
"files": []interface{}{},
|
||||||
@@ -1688,6 +1739,7 @@ func (s *QwenService) chatGradioRespond(endpointURL string, w http.ResponseWrite
|
|||||||
tempVal,
|
tempVal,
|
||||||
topPVal,
|
topPVal,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gradioPayload := map[string]interface{}{"data": gradioData}
|
gradioPayload := map[string]interface{}{"data": gradioData}
|
||||||
jsonPayload, err := json.Marshal(gradioPayload)
|
jsonPayload, err := json.Marshal(gradioPayload)
|
||||||
@@ -2232,7 +2284,7 @@ func main() {
|
|||||||
autoFailover := flag.Bool("failover", true, "Enable automatic failover across endpoints on error or quota limit")
|
autoFailover := flag.Bool("failover", true, "Enable automatic failover across endpoints on error or quota limit")
|
||||||
flag.BoolVar(autoFailover, "auto-failover", true, "Alias for -failover")
|
flag.BoolVar(autoFailover, "auto-failover", true, "Alias for -failover")
|
||||||
|
|
||||||
defaultModelVal := "Qwen/Qwen3.8-Flash-Next"
|
defaultModelVal := "Qwen/Qwen3.8-27B"
|
||||||
if envModel := os.Getenv("QFLASH_MODEL"); envModel != "" {
|
if envModel := os.Getenv("QFLASH_MODEL"); envModel != "" {
|
||||||
defaultModelVal = envModel
|
defaultModelVal = envModel
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-5
@@ -66,13 +66,14 @@ func TestSOCKS5Parsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEffectiveModelID(t *testing.T) {
|
func TestEffectiveModelID(t *testing.T) {
|
||||||
def := "Qwen/Qwen3.8-Flash-Next"
|
def := "Qwen/Qwen3.8-27B"
|
||||||
cases := map[string]string{
|
cases := map[string]string{
|
||||||
"": def,
|
"": def,
|
||||||
"qwen3.8-flash-next": def,
|
"qwen": def,
|
||||||
"qwen-flash-next": def,
|
"qwen3.8-flash-next": "Qwen/Qwen3.8-Flash-Next",
|
||||||
"qwen-flash": def,
|
"qwen-flash-next": "Qwen/Qwen3.8-Flash-Next",
|
||||||
"Qwen/Qwen3.8-Flash-Next": def,
|
"qwen-flash": "Qwen/Qwen3.8-Flash-Next",
|
||||||
|
"Qwen/Qwen3.8-Flash-Next": "Qwen/Qwen3.8-Flash-Next",
|
||||||
"qwen3.8-27b": "Qwen/Qwen3.8-27B",
|
"qwen3.8-27b": "Qwen/Qwen3.8-27B",
|
||||||
"qwen-27b": "Qwen/Qwen3.8-27B",
|
"qwen-27b": "Qwen/Qwen3.8-27B",
|
||||||
"Qwen/Qwen3.8-27B": "Qwen/Qwen3.8-27B",
|
"Qwen/Qwen3.8-27B": "Qwen/Qwen3.8-27B",
|
||||||
@@ -206,6 +207,27 @@ func TestParseAssistantText(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseAssistantTextMultiMessageReasoning(t *testing.T) {
|
||||||
|
dataJSON := `[[
|
||||||
|
{"role": "user", "metadata": null, "content": [{"text": "Say hi", "type": "text"}]},
|
||||||
|
{"role": "assistant", "metadata": {"title": "Reasoning"}, "content": [{"text": "First, consider greeting politely.", "type": "text"}]},
|
||||||
|
{"role": "assistant", "metadata": null, "content": [{"text": "Hi there!", "type": "text"}]}
|
||||||
|
]]`
|
||||||
|
|
||||||
|
text, ok := parseAssistantText(dataJSON)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected successful parse of multi-message assistant text")
|
||||||
|
}
|
||||||
|
|
||||||
|
reasoning, content := SeparateReasoningAndContent(text)
|
||||||
|
if reasoning != "First, consider greeting politely." {
|
||||||
|
t.Fatalf("expected reasoning 'First, consider greeting politely.', got %q", reasoning)
|
||||||
|
}
|
||||||
|
if content != "Hi there!" {
|
||||||
|
t.Fatalf("expected content 'Hi there!', got %q", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestQwenServiceChatMock(t *testing.T) {
|
func TestQwenServiceChatMock(t *testing.T) {
|
||||||
// Mock upstream Gradio space server
|
// Mock upstream Gradio space server
|
||||||
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user