Support Gradio 6 call_v2 protocol with named parameters and fallback

This commit is contained in:
Luxferre
2026-09-07 14:10:18 +03:00
parent 03825fecfd
commit defc727c95
2 changed files with 198 additions and 11 deletions
+72 -11
View File
@@ -1179,6 +1179,10 @@ func extractFailedGeneration(raw string) (string, bool) {
}
func extractGradioErrorMessage(dataStr string) string {
var strVal string
if err := json.Unmarshal([]byte(dataStr), &strVal); err == nil && strVal != "" {
return strVal
}
var errObj map[string]interface{}
if err := json.Unmarshal([]byte(dataStr), &errObj); err == nil {
if e, ok := errObj["error"].(string); ok && e != "" {
@@ -1541,10 +1545,11 @@ type GradioParamInfo struct {
}
type GradioEndpointInfo struct {
Parameters []GradioParamInfo `json:"parameters"`
Returns []GradioParamInfo `json:"returns"`
APIVisibility string `json:"api_visibility"`
Description string `json:"description"`
Parameters []GradioParamInfo `json:"parameters"`
Returns []GradioParamInfo `json:"returns"`
APIVisibility string `json:"api_visibility"`
Description string `json:"description"`
CodeSnippets map[string]interface{} `json:"code_snippets,omitempty"`
}
type GradioAPIInfoResponse struct {
@@ -1599,6 +1604,7 @@ type SpaceParamMapping struct {
ComponentID int `json:"component_id"`
ComponentType string `json:"component_type,omitempty"`
Label string `json:"label,omitempty"`
ParamName string `json:"param_name,omitempty"`
ParamType string `json:"param_type"` // "message", "history", "system_prompt", "temperature", "max_tokens", "top_p", "think_level", "tools", "stream", "state", "other"
DefaultValue interface{} `json:"default_value,omitempty"`
}
@@ -2230,11 +2236,29 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
}
// Select protocol
isCallV2 := false
if strings.HasPrefix(discovery.GradioVersion, "6.") {
isCallV2 = true
}
if bestEndpointInfo != nil && bestEndpointInfo.CodeSnippets != nil {
if bashSnippet, ok := bestEndpointInfo.CodeSnippets["bash"].(string); ok {
if strings.Contains(bashSnippet, "/call/v2/") {
isCallV2 = true
}
}
}
if strings.HasPrefix(discovery.GradioVersion, "3.") {
discovery.Protocol = "predict"
discovery.APIPrefix = ""
discovery.Endpoint = "/run/predict"
discovery.CleanEndpoint = "run/predict"
} else if isCallV2 {
discovery.Protocol = "call_v2"
if bestEndpoint != "" {
discovery.Endpoint = bestEndpoint
discovery.CleanEndpoint = strings.TrimPrefix(bestEndpoint, "/")
}
} else {
discovery.Protocol = "call"
if bestEndpoint != "" {
@@ -2268,6 +2292,7 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
mapping.DefaultValue = p.ParameterDefault
}
mapping.Label = p.ParameterName
mapping.ParamName = p.ParameterName
if p.Component != "" {
mapping.ComponentType = p.Component
}
@@ -2440,6 +2465,7 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
InputIndex: idx,
ComponentType: p.Component,
Label: p.Label,
ParamName: p.ParameterName,
ParamType: "other",
DefaultValue: p.ParameterDefault,
}
@@ -3463,17 +3489,44 @@ func (g *GradioGateway) ExecuteChatCompletion(w http.ResponseWriter, r *http.Req
return g.executePredictCompletion(w, r, disc, gradioData, req, completionID, createdTime, modelName, effUA)
}
payloadMap := map[string]interface{}{
"data": gradioData,
"session_hash": GenerateUUID(),
isV2 := disc.Protocol == "call_v2"
buildPayload := func(v2 bool) ([]byte, error) {
if v2 {
v2Payload := make(map[string]interface{})
for idx, val := range gradioData {
pName := ""
if idx < len(disc.ParamMappings) {
if disc.ParamMappings[idx].ParamName != "" {
pName = disc.ParamMappings[idx].ParamName
} else if disc.ParamMappings[idx].Label != "" {
pName = disc.ParamMappings[idx].Label
}
}
if pName == "" {
pName = fmt.Sprintf("param_%d", idx)
}
v2Payload[pName] = val
}
return json.Marshal(v2Payload)
}
payloadMap := map[string]interface{}{
"data": gradioData,
"session_hash": GenerateUUID(),
}
return json.Marshal(payloadMap)
}
jsonPayload, err := json.Marshal(payloadMap)
callPath := "call"
if isV2 {
callPath = "call/v2"
}
jsonPayload, err := buildPayload(isV2)
if err != nil {
return fmt.Errorf("failed to encode request: %w", err)
}
// 1. Submit to /call/{endpoint}
callURL := fmt.Sprintf("%s%s/call/%s", disc.SpaceURL, disc.APIPrefix, disc.CleanEndpoint)
// 1. Submit to /call/{endpoint} or /call/v2/{endpoint}
callURL := fmt.Sprintf("%s%s/%s/%s", disc.SpaceURL, disc.APIPrefix, callPath, disc.CleanEndpoint)
makeCallReq := func() (*http.Request, error) {
r, err := http.NewRequest("POST", callURL, bytes.NewBuffer(jsonPayload))
if err != nil {
@@ -3485,6 +3538,14 @@ func (g *GradioGateway) ExecuteChatCompletion(w http.ResponseWriter, r *http.Req
}
resp, err := DoWithFibonacciRetry(g.client, makeCallReq, 5)
if err != nil && isV2 {
log.Printf("Gradio /call/v2 endpoint failed (%v), falling back to /call...", err)
isV2 = false
callPath = "call"
jsonPayload, _ = buildPayload(false)
callURL = fmt.Sprintf("%s%s/%s/%s", disc.SpaceURL, disc.APIPrefix, callPath, disc.CleanEndpoint)
resp, err = DoWithFibonacciRetry(g.client, makeCallReq, 3)
}
if err != nil {
if fg, ok := extractFailedGeneration(err.Error()); ok {
if tcs, _, has := DetectToolCalls(fg); has && len(tcs) > 0 {
@@ -3508,7 +3569,7 @@ func (g *GradioGateway) ExecuteChatCompletion(w http.ResponseWriter, r *http.Req
}
}
// If call failed, try without APIPrefix
altCallURL := fmt.Sprintf("%s/call/%s", disc.SpaceURL, disc.CleanEndpoint)
altCallURL := fmt.Sprintf("%s/%s/%s", disc.SpaceURL, callPath, disc.CleanEndpoint)
makeAltReq := func() (*http.Request, error) {
r, err := http.NewRequest("POST", altCallURL, bytes.NewBuffer(jsonPayload))
if err != nil {