fix(heuristics): penalize user submit handlers and support history-only generator endpoints
This commit is contained in:
+207
@@ -1878,4 +1878,211 @@ func TestCallToPredictProtocolFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestEndpointScoringUserHandlerDepPenalization verifies that user submission helper
|
||||
// endpoints with shared input/output textboxes are penalized while streaming generators
|
||||
// and bot endpoints like Chat_Message are favored.
|
||||
func TestEndpointScoringUserHandlerDepPenalization(t *testing.T) {
|
||||
compMap := map[int]GradioComponent{
|
||||
5: {ID: 5, Type: "state", Props: map[string]interface{}{"label": "State"}},
|
||||
6: {ID: 6, Type: "chatbot", Props: map[string]interface{}{"label": "Chatbot"}},
|
||||
8: {ID: 8, Type: "textbox", Props: map[string]interface{}{"label": "Message"}},
|
||||
27: {ID: 27, Type: "chatbot", Props: map[string]interface{}{"label": "Chatbot 2"}},
|
||||
29: {ID: 29, Type: "textbox", Props: map[string]interface{}{"label": "Link"}},
|
||||
30: {ID: 30, Type: "textbox", Props: map[string]interface{}{"label": "User Message"}},
|
||||
}
|
||||
|
||||
// user: inputs [8, 6], outputs [8, 6] (clears textbox 8)
|
||||
userDep := GradioDependency{
|
||||
ID: 2,
|
||||
Inputs: []int{8, 6},
|
||||
Outputs: []int{8, 6},
|
||||
Types: GradioDependencyTypes{Generator: false},
|
||||
}
|
||||
|
||||
// user2: inputs [30, 27, 29], outputs [30, 27, 29] (clears textboxes 30 and 29)
|
||||
user2Dep := GradioDependency{
|
||||
ID: 23,
|
||||
Inputs: []int{30, 27, 29},
|
||||
Outputs: []int{30, 27, 29},
|
||||
Types: GradioDependencyTypes{Generator: false},
|
||||
}
|
||||
|
||||
// Chat_Message: inputs [6, 5], outputs [6, 5], generator: true
|
||||
chatMsgDep := GradioDependency{
|
||||
ID: 3,
|
||||
Inputs: []int{6, 5},
|
||||
Outputs: []int{6, 5},
|
||||
Types: GradioDependencyTypes{Generator: true},
|
||||
}
|
||||
|
||||
scoreUser := ScoreCandidateEndpoint("/user", nil, &userDep, compMap)
|
||||
scoreUser2 := ScoreCandidateEndpoint("/user2", nil, &user2Dep, compMap)
|
||||
scoreChatMsg := ScoreCandidateEndpoint("/Chat_Message", nil, &chatMsgDep, compMap)
|
||||
|
||||
if scoreChatMsg <= 0 {
|
||||
t.Errorf("expected positive score for Chat_Message, got %d", scoreChatMsg)
|
||||
}
|
||||
if scoreChatMsg <= scoreUser {
|
||||
t.Errorf("expected Chat_Message score > user score, got chatMsg=%d user=%d", scoreChatMsg, scoreUser)
|
||||
}
|
||||
if scoreChatMsg <= scoreUser2 {
|
||||
t.Errorf("expected Chat_Message score > user2 score, got chatMsg=%d user2=%d", scoreChatMsg, scoreUser2)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHistoryOnlyEndpointPayloadBuilding verifies that endpoints with no separate
|
||||
// message textbox (MessageIndex == -1, HistoryIndex == 0) correctly append the user prompt
|
||||
// into the history array without overwriting other slots.
|
||||
func TestHistoryOnlyEndpointPayloadBuilding(t *testing.T) {
|
||||
gw := NewGradioGateway("https://test-space.hf.space", "", 10*time.Second)
|
||||
|
||||
// 1. Gradio 6 messages format
|
||||
discG6 := NewDefaultSpaceDiscovery("https://test-space.hf.space")
|
||||
discG6.TotalInputs = 2
|
||||
discG6.MessageIndex = -1
|
||||
discG6.HistoryIndex = 0
|
||||
discG6.HistoryFormat = "gradio_messages"
|
||||
|
||||
req := ChatCompletionRequest{
|
||||
Messages: []ChatMessage{
|
||||
{Role: "user", Content: "Hello world"},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := gw.BuildGradioPayload(discG6, req)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildGradioPayload failed: %v", err)
|
||||
}
|
||||
if len(data) != 2 {
|
||||
t.Fatalf("expected 2 inputs, got %d", len(data))
|
||||
}
|
||||
|
||||
gMsgs, ok := data[0].([]map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected []map[string]interface{} for gradio_messages, got %T", data[0])
|
||||
}
|
||||
if len(gMsgs) != 1 {
|
||||
t.Fatalf("expected 1 message in history, got %d", len(gMsgs))
|
||||
}
|
||||
if gMsgs[0]["role"] != "user" {
|
||||
t.Errorf("expected user role, got %v", gMsgs[0]["role"])
|
||||
}
|
||||
contents, ok := gMsgs[0]["content"].([]map[string]string)
|
||||
if !ok || len(contents) != 1 || contents[0]["text"] != "Hello world" {
|
||||
t.Errorf("unexpected content structure: %v", gMsgs[0]["content"])
|
||||
}
|
||||
if data[1] != nil {
|
||||
t.Errorf("expected slot 1 (state) to remain nil, got %v", data[1])
|
||||
}
|
||||
|
||||
// 2. Pairs format with MessageIndex == -1
|
||||
discPairs := NewDefaultSpaceDiscovery("https://test-space.hf.space")
|
||||
discPairs.TotalInputs = 1
|
||||
discPairs.MessageIndex = -1
|
||||
discPairs.HistoryIndex = 0
|
||||
discPairs.HistoryFormat = "pairs"
|
||||
|
||||
dataPairs, err := gw.BuildGradioPayload(discPairs, req)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildGradioPayload failed: %v", err)
|
||||
}
|
||||
pairs, ok := dataPairs[0].([][]string)
|
||||
if !ok || len(pairs) != 1 {
|
||||
t.Fatalf("expected 1 pair in history, got %T (%v)", dataPairs[0], dataPairs[0])
|
||||
}
|
||||
if pairs[0][0] != "Hello world" {
|
||||
t.Errorf("expected user prompt in pair[0], got %v", pairs[0][0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestInspectSpaceBlocksChatbotStateResolution verifies InspectSpace selects /Chat_Message
|
||||
// when presented with a Blocks layout containing user handlers and generator functions.
|
||||
func TestInspectSpaceBlocksChatbotStateResolution(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/gradio_api/info" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"named_endpoints": map[string]interface{}{
|
||||
"/user": map[string]interface{}{
|
||||
"parameters": []map[string]interface{}{
|
||||
{"parameter_name": "user_message", "component": "Textbox", "label": "msg"},
|
||||
{"parameter_name": "history", "component": "Chatbot", "label": "chat"},
|
||||
},
|
||||
},
|
||||
"/Chat_Message": map[string]interface{}{
|
||||
"parameters": []map[string]interface{}{
|
||||
{
|
||||
"parameter_name": "history",
|
||||
"component": "Chatbot",
|
||||
"label": "chat",
|
||||
"type": map[string]interface{}{"title": "ChatbotDataMessages"},
|
||||
"python_type": map[string]interface{}{"type": "dict(text: str)"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/config" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"version": "6.20.0",
|
||||
"components": []map[string]interface{}{
|
||||
{"id": 5, "type": "state", "props": map[string]interface{}{"label": "State"}},
|
||||
{"id": 6, "type": "chatbot", "props": map[string]interface{}{"label": "Chatbot"}},
|
||||
{"id": 8, "type": "textbox", "props": map[string]interface{}{"label": "Message"}},
|
||||
},
|
||||
"dependencies": []map[string]interface{}{
|
||||
{
|
||||
"id": 2,
|
||||
"api_name": "user",
|
||||
"inputs": []int{8, 6},
|
||||
"outputs": []int{8, 6},
|
||||
"types": map[string]interface{}{"generator": false},
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"api_name": "Chat_Message",
|
||||
"inputs": []int{6, 5},
|
||||
"outputs": []int{6, 5},
|
||||
"types": map[string]interface{}{"generator": true},
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
disc, err := InspectSpace(client, ts.URL, "test-agent")
|
||||
if err != nil {
|
||||
t.Fatalf("InspectSpace failed: %v", err)
|
||||
}
|
||||
|
||||
if disc.Endpoint != "/Chat_Message" {
|
||||
t.Errorf("expected resolved endpoint /Chat_Message, got %s", disc.Endpoint)
|
||||
}
|
||||
if disc.FnIndex != 3 {
|
||||
t.Errorf("expected FnIndex 3, got %d", disc.FnIndex)
|
||||
}
|
||||
if disc.MessageIndex != -1 {
|
||||
t.Errorf("expected MessageIndex -1, got %d", disc.MessageIndex)
|
||||
}
|
||||
if disc.HistoryIndex != 0 {
|
||||
t.Errorf("expected HistoryIndex 0, got %d", disc.HistoryIndex)
|
||||
}
|
||||
if disc.HistoryFormat != "gradio_messages" {
|
||||
t.Errorf("expected HistoryFormat gradio_messages, got %s", disc.HistoryFormat)
|
||||
}
|
||||
if disc.TotalInputs != 2 {
|
||||
t.Errorf("expected TotalInputs 2, got %d", disc.TotalInputs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user