Support Gradio 6 Chatbot format and multi-output spaces like tdecae-personal-chatbot
This commit is contained in:
@@ -73,14 +73,14 @@ type ChatMessage struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ChatMessage) GetContentString() string {
|
||||
if m.Content == nil {
|
||||
func extractContentString(val interface{}) string {
|
||||
if val == nil {
|
||||
return ""
|
||||
}
|
||||
if str, ok := m.Content.(string); ok {
|
||||
if str, ok := val.(string); ok {
|
||||
return str
|
||||
}
|
||||
if parts, ok := m.Content.([]interface{}); ok {
|
||||
if parts, ok := val.([]interface{}); ok {
|
||||
var sb strings.Builder
|
||||
for _, p := range parts {
|
||||
if str, ok := p.(string); ok {
|
||||
@@ -88,16 +88,29 @@ func (m *ChatMessage) GetContentString() string {
|
||||
} else if itemMap, ok := p.(map[string]interface{}); ok {
|
||||
if textVal, ok := itemMap["text"].(string); ok {
|
||||
sb.WriteString(textVal)
|
||||
} else if valStr, ok := itemMap["value"].(string); ok {
|
||||
sb.WriteString(valStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
b, err := json.Marshal(m.Content)
|
||||
if itemMap, ok := val.(map[string]interface{}); ok {
|
||||
if textVal, ok := itemMap["text"].(string); ok {
|
||||
return textVal
|
||||
} else if valStr, ok := itemMap["value"].(string); ok {
|
||||
return valStr
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(val)
|
||||
if err == nil {
|
||||
return string(b)
|
||||
}
|
||||
return fmt.Sprintf("%v", m.Content)
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
|
||||
func (m *ChatMessage) GetContentString() string {
|
||||
return extractContentString(m.Content)
|
||||
}
|
||||
|
||||
type ChatCompletionRequest struct {
|
||||
@@ -1520,6 +1533,8 @@ type GradioParamInfo struct {
|
||||
ParameterName string `json:"parameter_name"`
|
||||
ParameterDefault interface{} `json:"parameter_default,omitempty"`
|
||||
Component string `json:"component"`
|
||||
Type interface{} `json:"type,omitempty"`
|
||||
PythonType interface{} `json:"python_type,omitempty"`
|
||||
}
|
||||
|
||||
type GradioEndpointInfo struct {
|
||||
@@ -1828,20 +1843,32 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
|
||||
score := 0
|
||||
lowerName := strings.ToLower(epName)
|
||||
|
||||
if strings.Contains(lowerName, "chat") {
|
||||
// Penalize non-generation/reset/clear/init endpoints or 0-parameter endpoints
|
||||
if len(epInfo.Parameters) == 0 {
|
||||
score -= 500
|
||||
}
|
||||
if strings.Contains(lowerName, "clear") || strings.Contains(lowerName, "reset") || strings.Contains(lowerName, "init") || strings.Contains(lowerName, "undo") || strings.Contains(lowerName, "delete") {
|
||||
score -= 500
|
||||
}
|
||||
|
||||
if strings.Contains(lowerName, "chat") || strings.Contains(lowerName, "conversation") || strings.Contains(lowerName, "dialogue") {
|
||||
score += 100
|
||||
}
|
||||
if strings.Contains(lowerName, "predict") || strings.Contains(lowerName, "respond") || strings.Contains(lowerName, "generate") {
|
||||
score += 50
|
||||
if strings.Contains(lowerName, "answer") || strings.Contains(lowerName, "respond") || strings.Contains(lowerName, "generate") || strings.Contains(lowerName, "predict") || strings.Contains(lowerName, "completion") {
|
||||
score += 60
|
||||
}
|
||||
if strings.Contains(lowerName, "ask") || strings.Contains(lowerName, "query") || strings.Contains(lowerName, "question") || strings.Contains(lowerName, "talk") {
|
||||
score += 40
|
||||
}
|
||||
|
||||
for _, p := range epInfo.Parameters {
|
||||
pLower := strings.ToLower(p.ParameterName)
|
||||
if strings.Contains(pLower, "message") || strings.Contains(pLower, "text") || strings.Contains(pLower, "prompt") {
|
||||
pComp := strings.ToLower(p.Component)
|
||||
if strings.Contains(pLower, "message") || strings.Contains(pLower, "text") || strings.Contains(pLower, "prompt") || strings.Contains(pLower, "query") || strings.Contains(pLower, "question") || strings.Contains(pLower, "input") || pComp == "textbox" {
|
||||
score += 40
|
||||
}
|
||||
if strings.Contains(pLower, "history") || strings.Contains(pLower, "chat") {
|
||||
score += 20
|
||||
if strings.Contains(pLower, "history") || strings.Contains(pLower, "chat") || strings.Contains(pLower, "messages") || strings.Contains(pLower, "conversation") || pComp == "chatbot" {
|
||||
score += 30
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1943,6 +1970,7 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
|
||||
}
|
||||
for idx, p := range bestEndpointInfo.Parameters {
|
||||
pName := strings.ToLower(p.ParameterName)
|
||||
pComp := strings.ToLower(p.Component)
|
||||
if strings.Contains(pName, "system") {
|
||||
discovery.SystemIndex = idx
|
||||
if p.ParameterDefault != nil && discovery.DefaultSystemPrompt == "" {
|
||||
@@ -1950,9 +1978,20 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
|
||||
discovery.DefaultSystemPrompt = strings.TrimSpace(defStr)
|
||||
}
|
||||
}
|
||||
} else if strings.Contains(pName, "history") || strings.Contains(pName, "chat") {
|
||||
} else if strings.Contains(pName, "history") || strings.Contains(pName, "chat") || strings.Contains(pName, "conversation") || strings.Contains(pName, "messages") || pComp == "chatbot" {
|
||||
discovery.HistoryIndex = idx
|
||||
} else if strings.Contains(pName, "message") || (strings.Contains(pName, "prompt") && !strings.Contains(pName, "system")) || strings.Contains(pName, "text") {
|
||||
bType, _ := json.Marshal(p.Type)
|
||||
bPyType, _ := json.Marshal(p.PythonType)
|
||||
pPyType := strings.ToLower(string(bPyType))
|
||||
bTypeStr := strings.ToLower(string(bType))
|
||||
if strings.Contains(pPyType, "list[tuple[") || strings.Contains(pPyType, "list[list[") || strings.Contains(bTypeStr, "tuple") {
|
||||
discovery.HistoryFormat = "pairs"
|
||||
} else if strings.Contains(pPyType, "textmessage") || strings.Contains(pPyType, "dict(text: str") || strings.Contains(bTypeStr, "textmessage") || strings.Contains(bTypeStr, "chatbotdatamessages") {
|
||||
discovery.HistoryFormat = "gradio_messages"
|
||||
} else if strings.HasPrefix(configResp.Version, "5.") || strings.HasPrefix(configResp.Version, "6.") {
|
||||
discovery.HistoryFormat = "gradio_messages"
|
||||
}
|
||||
} else if strings.Contains(pName, "message") || (strings.Contains(pName, "prompt") && !strings.Contains(pName, "system")) || strings.Contains(pName, "text") || strings.Contains(pName, "query") || strings.Contains(pName, "question") || strings.Contains(pName, "input") || pComp == "textbox" {
|
||||
discovery.MessageIndex = idx
|
||||
} else if strings.Contains(pName, "think_level") || strings.Contains(pName, "thinking_level") {
|
||||
discovery.ThinkLevelIndex = idx
|
||||
@@ -1972,6 +2011,7 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
|
||||
|
||||
if discovery.FunctionsJSONIndex != -1 || discovery.ThinkLevelIndex != -1 || strings.Contains(cleanURL, "hy3") || strings.Contains(cleanURL, "hunyuan") {
|
||||
discovery.IsHunyuan3 = true
|
||||
discovery.HistoryFormat = "messages"
|
||||
discovery.Models = append(discovery.Models, "hy3", "hunyuan3", "tencent/Hy3")
|
||||
if discovery.PrimaryModel == "gradio-chat" || discovery.PrimaryModel == "" {
|
||||
discovery.PrimaryModel = "hy3"
|
||||
@@ -2301,6 +2341,26 @@ func (g *GradioGateway) BuildGradioPayload(disc *SpaceDiscovery, req ChatComplet
|
||||
pairs = append(pairs, []string{u, a})
|
||||
}
|
||||
data[disc.HistoryIndex] = pairs
|
||||
} else if disc.HistoryFormat == "gradio_messages" {
|
||||
var gMsgs []map[string]interface{}
|
||||
for _, item := range historyArray {
|
||||
role, _ := item["role"].(string)
|
||||
cStr := ""
|
||||
if s, ok := item["content"].(string); ok {
|
||||
cStr = s
|
||||
}
|
||||
gMsg := map[string]interface{}{
|
||||
"role": role,
|
||||
"content": []map[string]string{
|
||||
{"text": cStr, "type": "text"},
|
||||
},
|
||||
}
|
||||
gMsgs = append(gMsgs, gMsg)
|
||||
}
|
||||
if gMsgs == nil {
|
||||
gMsgs = []map[string]interface{}{}
|
||||
}
|
||||
data[disc.HistoryIndex] = gMsgs
|
||||
} else {
|
||||
data[disc.HistoryIndex] = historyArray
|
||||
}
|
||||
@@ -2393,107 +2453,182 @@ func ParseGradioStreamOutput(rawJSON string) GradioOutputFrame {
|
||||
return frame
|
||||
}
|
||||
|
||||
// Check if v[0] is an inner slice (e.g. Hy3: [[content, reasoning, tool_calls, history]])
|
||||
if inner, ok := v[0].([]interface{}); ok {
|
||||
if len(inner) >= 3 {
|
||||
s0, _ := inner[0].(string)
|
||||
s1, _ := inner[1].(string)
|
||||
frame.Content = s0
|
||||
frame.Reasoning = s1
|
||||
if inner[2] != nil {
|
||||
b, err := json.Marshal(inner[2])
|
||||
if err == nil {
|
||||
var tcs []ToolCall
|
||||
if json.Unmarshal(b, &tcs) == nil && len(tcs) > 0 {
|
||||
frame.ToolCalls = tcs
|
||||
}
|
||||
}
|
||||
}
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
|
||||
if len(inner) == 2 {
|
||||
s0, ok0 := inner[0].(string)
|
||||
s1, ok1 := inner[1].(string)
|
||||
if ok0 && ok1 {
|
||||
frame.Content = s0
|
||||
frame.Reasoning = s1
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
|
||||
// Check if inner is a chat pair: ["user msg", "assistant msg"]
|
||||
if ok1 {
|
||||
frame.Content = s1
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
}
|
||||
|
||||
// Check if inner is a list of chat message maps: [{"role":..., "content":...}, ...]
|
||||
// or list of pairs: [["u", "a"], ...]
|
||||
if len(inner) > 0 {
|
||||
lastItem := inner[len(inner)-1]
|
||||
if m, ok := lastItem.(map[string]interface{}); ok {
|
||||
if c, ok := m["content"].(string); ok {
|
||||
frame.Content = c
|
||||
frame.OK = true
|
||||
}
|
||||
if r, ok := m["reasoning_content"].(string); ok {
|
||||
frame.Reasoning = r
|
||||
}
|
||||
if tcsRaw, ok := m["tool_calls"].([]interface{}); ok && len(tcsRaw) > 0 {
|
||||
b, err := json.Marshal(tcsRaw)
|
||||
if err == nil {
|
||||
var tcs []ToolCall
|
||||
if err := json.Unmarshal(b, &tcs); err == nil {
|
||||
frame.ToolCalls = tcs
|
||||
}
|
||||
}
|
||||
}
|
||||
if frame.OK {
|
||||
return frame
|
||||
}
|
||||
} else if pair, ok := lastItem.([]interface{}); ok && len(pair) >= 2 {
|
||||
if aStr, ok := pair[1].(string); ok {
|
||||
frame.Content = aStr
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if v[0] is string (standard single output e.g. ["content", null])
|
||||
if s, ok := v[0].(string); ok {
|
||||
frame.Content = s
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
|
||||
// Check if v is a flat list of messages: [{"role": "assistant", ...}]
|
||||
lastItem := v[len(v)-1]
|
||||
if m, ok := lastItem.(map[string]interface{}); ok {
|
||||
if c, ok := m["content"].(string); ok {
|
||||
frame.Content = c
|
||||
frame.OK = true
|
||||
}
|
||||
if r, ok := m["reasoning_content"].(string); ok {
|
||||
frame.Reasoning = r
|
||||
}
|
||||
if tcsRaw, ok := m["tool_calls"].([]interface{}); ok && len(tcsRaw) > 0 {
|
||||
b, err := json.Marshal(tcsRaw)
|
||||
// 1. Check if v[0] is an inner slice with len >= 3 (e.g. Hy3: [[content, reasoning, tool_calls, history]])
|
||||
if inner, ok := v[0].([]interface{}); ok && len(inner) >= 3 {
|
||||
s0, _ := inner[0].(string)
|
||||
s1, _ := inner[1].(string)
|
||||
frame.Content = s0
|
||||
frame.Reasoning = s1
|
||||
if inner[2] != nil {
|
||||
b, err := json.Marshal(inner[2])
|
||||
if err == nil {
|
||||
var tcs []ToolCall
|
||||
if err := json.Unmarshal(b, &tcs); err == nil {
|
||||
if json.Unmarshal(b, &tcs) == nil && len(tcs) > 0 {
|
||||
frame.ToolCalls = tcs
|
||||
}
|
||||
}
|
||||
}
|
||||
if frame.OK {
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
|
||||
// 2. Check if any element of v is a Chatbot message list or Chatbot pair list
|
||||
for _, elem := range v {
|
||||
if msgList, ok := elem.([]interface{}); ok && len(msgList) > 0 {
|
||||
allMaps := true
|
||||
var maps []map[string]interface{}
|
||||
for _, item := range msgList {
|
||||
if m, ok := item.(map[string]interface{}); ok {
|
||||
if _, hasRole := m["role"]; hasRole {
|
||||
maps = append(maps, m)
|
||||
continue
|
||||
}
|
||||
}
|
||||
allMaps = false
|
||||
break
|
||||
}
|
||||
if allMaps && len(maps) > 0 {
|
||||
var targetMsg map[string]interface{}
|
||||
for i := len(maps) - 1; i >= 0; i-- {
|
||||
if r, _ := maps[i]["role"].(string); r == "assistant" {
|
||||
targetMsg = maps[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if targetMsg == nil {
|
||||
targetMsg = maps[len(maps)-1]
|
||||
}
|
||||
|
||||
cText := extractContentString(targetMsg["content"])
|
||||
frame.Content = cText
|
||||
|
||||
if r, ok := targetMsg["reasoning_content"].(string); ok {
|
||||
frame.Reasoning = r
|
||||
} else if meta, ok := targetMsg["metadata"].(map[string]interface{}); ok {
|
||||
if logStr, ok := meta["log"].(string); ok && logStr != "" {
|
||||
frame.Reasoning = logStr
|
||||
}
|
||||
}
|
||||
|
||||
if tcsRaw, ok := targetMsg["tool_calls"].([]interface{}); ok && len(tcsRaw) > 0 {
|
||||
b, err := json.Marshal(tcsRaw)
|
||||
if err == nil {
|
||||
var tcs []ToolCall
|
||||
if json.Unmarshal(b, &tcs) == nil {
|
||||
frame.ToolCalls = tcs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(frame.ToolCalls) == 0 {
|
||||
tcs, clean, has := DetectToolCalls(cText)
|
||||
if has && len(tcs) > 0 {
|
||||
frame.ToolCalls = tcs
|
||||
frame.Content = clean
|
||||
}
|
||||
}
|
||||
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
|
||||
// Check if elements are pairs [user, assistant]
|
||||
allPairs := true
|
||||
var pairs [][]interface{}
|
||||
for _, item := range msgList {
|
||||
if p, ok := item.([]interface{}); ok && len(p) == 2 {
|
||||
pairs = append(pairs, p)
|
||||
continue
|
||||
}
|
||||
allPairs = false
|
||||
break
|
||||
}
|
||||
if allPairs && len(pairs) > 0 {
|
||||
lastPair := pairs[len(pairs)-1]
|
||||
cText := extractContentString(lastPair[1])
|
||||
frame.Content = cText
|
||||
tcs, clean, has := DetectToolCalls(cText)
|
||||
if has && len(tcs) > 0 {
|
||||
frame.ToolCalls = tcs
|
||||
frame.Content = clean
|
||||
}
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check if v itself is a list of message maps: [{"role": "assistant", ...}]
|
||||
allMaps := true
|
||||
var maps []map[string]interface{}
|
||||
for _, item := range v {
|
||||
if m, ok := item.(map[string]interface{}); ok {
|
||||
if _, hasRole := m["role"]; hasRole {
|
||||
maps = append(maps, m)
|
||||
continue
|
||||
}
|
||||
}
|
||||
allMaps = false
|
||||
break
|
||||
}
|
||||
if allMaps && len(maps) > 0 {
|
||||
var targetMsg map[string]interface{}
|
||||
for i := len(maps) - 1; i >= 0; i-- {
|
||||
if r, _ := maps[i]["role"].(string); r == "assistant" {
|
||||
targetMsg = maps[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if targetMsg == nil {
|
||||
targetMsg = maps[len(maps)-1]
|
||||
}
|
||||
cText := extractContentString(targetMsg["content"])
|
||||
frame.Content = cText
|
||||
if r, ok := targetMsg["reasoning_content"].(string); ok {
|
||||
frame.Reasoning = r
|
||||
}
|
||||
if tcsRaw, ok := targetMsg["tool_calls"].([]interface{}); ok && len(tcsRaw) > 0 {
|
||||
b, err := json.Marshal(tcsRaw)
|
||||
if err == nil {
|
||||
var tcs []ToolCall
|
||||
if json.Unmarshal(b, &tcs) == nil {
|
||||
frame.ToolCalls = tcs
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(frame.ToolCalls) == 0 {
|
||||
tcs, clean, has := DetectToolCalls(cText)
|
||||
if has && len(tcs) > 0 {
|
||||
frame.ToolCalls = tcs
|
||||
frame.Content = clean
|
||||
}
|
||||
}
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
|
||||
// 4. Check if v[0] is an inner pair [content, reasoning]
|
||||
if inner, ok := v[0].([]interface{}); ok && len(inner) == 2 {
|
||||
s0, ok0 := inner[0].(string)
|
||||
s1, ok1 := inner[1].(string)
|
||||
if ok0 && ok1 {
|
||||
frame.Content = s0
|
||||
frame.Reasoning = s1
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
if ok1 {
|
||||
frame.Content = s1
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Check if v[0] is a non-empty string or single output
|
||||
if s, ok := v[0].(string); ok && (s != "" || len(v) == 1) {
|
||||
frame.Content = s
|
||||
frame.OK = true
|
||||
return frame
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
|
||||
Reference in New Issue
Block a user