diff --git a/gr2gw.go b/gr2gw.go index 48372fa..5a26c09 100644 --- a/gr2gw.go +++ b/gr2gw.go @@ -1600,6 +1600,9 @@ func scrubToolMarkers(text string) string { reEmptyFences := regexp.MustCompile("(?s)```(?:xml|json|ya?ml)?\\s*```") s = reEmptyFences.ReplaceAllString(s, "") + reCitationDisclaimer := regexp.MustCompile(`(?i)\*?Web evidence was retrieved[^\n*]*\.\*?`) + s = reCitationDisclaimer.ReplaceAllString(s, "") + trimmed := strings.TrimSpace(s) if trimmed == "```xml" || trimmed == "```json" || trimmed == "```" { return "" @@ -1618,8 +1621,10 @@ func scrubToolMarkers(text string) string { return strings.TrimSpace(s) } -func ExtractToolCallBlocks(content string) (blocks []string, remaining string) { +func ExtractToolCallBlocks(content string) (blocks []string, preCallText string, remaining string) { remaining = content + var firstPreamble string + firstBlockFound := false reDynamicOpen := regexp.MustCompile(`(?i)<(?:tool_calls?|toolCalls?|function_calls?|functionCalls?|invoke|call|command|commands|action|function)(?:\s+[^>]*)?>|<(?:call|tool_call|function_call):[a-zA-Z0-9_-]+(?:\s+[^>]*)?>||\[TOOL_CALLS?\]`) @@ -1670,9 +1675,21 @@ func ExtractToolCallBlocks(content string) (blocks []string, remaining string) { before = reFenceOpen.ReplaceAllString(before, "") after = reFenceClose.ReplaceAllString(after, "") } + if !firstBlockFound { + preClean := reFenceOpen.ReplaceAllString(before, "") + firstPreamble = strings.TrimRight(preClean, "\r\n ") + firstBlockFound = true + } remaining = strings.TrimSpace(before + after) } else { nextLoc := reDynamicOpen.FindStringIndex(rest) + before := remaining[:sIdx] + reFenceOpen := regexp.MustCompile("(?s)\\n?\\s*```(?:xml|json|ya?ml)?\\s*$") + if !firstBlockFound { + preClean := reFenceOpen.ReplaceAllString(before, "") + firstPreamble = strings.TrimRight(preClean, "\r\n ") + firstBlockFound = true + } if nextLoc != nil { blockEndPos := loc[1] + nextLoc[0] blockText = remaining[sIdx:blockEndPos] @@ -1692,16 +1709,17 @@ func ExtractToolCallBlocks(content string) (blocks []string, remaining string) { blockText := remaining[loc[0]:loc[1]] before := remaining[:loc[0]] after := remaining[loc[1]:] + firstPreamble = strings.TrimSpace(before) remaining = strings.TrimSpace(before + " " + after) blocks = append(blocks, blockText) } } - return blocks, remaining + return blocks, firstPreamble, remaining } func DetectToolCalls(content string) ([]ToolCall, string, bool) { - blocks, remaining := ExtractToolCallBlocks(content) + blocks, preCallText, _ := ExtractToolCallBlocks(content) var calls []ToolCall for _, block := range blocks { @@ -1711,25 +1729,29 @@ func DetectToolCalls(content string) ([]ToolCall, string, bool) { } if len(calls) > 0 { - remaining = scrubToolMarkers(remaining) - return calls, remaining, true + return calls, scrubToolMarkers(preCallText), true } reFenced := regexp.MustCompile("(?s)```(?:json|xml)?\\s*([\\s\\S]*?)\\s*```") matches := reFenced.FindAllStringSubmatchIndex(content, -1) if len(matches) > 0 { var fencedCalls []ToolCall - rem := content + firstFenceStart := -1 for _, loc := range matches { - fenceTotal := content[loc[0]:loc[1]] fenceInner := strings.TrimSpace(content[loc[2]:loc[3]]) if tcs, ok := parseMultipleToolCalls(fenceInner); ok && len(tcs) > 0 { + if firstFenceStart == -1 { + firstFenceStart = loc[0] + } fencedCalls = append(fencedCalls, tcs...) - rem = strings.Replace(rem, fenceTotal, "", 1) } } if len(fencedCalls) > 0 { - return fencedCalls, scrubToolMarkers(rem), true + pre := "" + if firstFenceStart > 0 { + pre = content[:firstFenceStart] + } + return fencedCalls, scrubToolMarkers(pre), true } } @@ -1744,22 +1766,33 @@ func DetectToolCalls(content string) ([]ToolCall, string, bool) { callLocs := reCallInContent.FindAllStringIndex(content, -1) if len(callLocs) > 0 { var pyCalls []ToolCall - rem := content + firstCallStart := -1 for _, loc := range callLocs { callStr := strings.TrimSpace(content[loc[0]:loc[1]]) if tc, ok := parsePythonFunctionCall(callStr); ok { + if firstCallStart == -1 { + firstCallStart = loc[0] + } pyCalls = append(pyCalls, tc) - rem = strings.Replace(rem, content[loc[0]:loc[1]], "", 1) } } if len(pyCalls) > 0 { - return pyCalls, scrubToolMarkers(rem), true + pre := "" + if firstCallStart > 0 { + pre = content[:firstCallStart] + } + return pyCalls, scrubToolMarkers(pre), true } } if tc, ok := parseReActToolCall(content); ok { - rem := scrubToolMarkers(content) - return []ToolCall{tc}, rem, true + reAction := regexp.MustCompile(`(?i)(?:Action|Command):\s*[a-zA-Z0-9_.-]+`) + loc := reAction.FindStringIndex(content) + pre := "" + if loc != nil && loc[0] > 0 { + pre = content[:loc[0]] + } + return []ToolCall{tc}, scrubToolMarkers(pre), true } if tcs, ok := parseMultiplePythonFunctionCalls(trimmed); ok && len(tcs) > 0 { @@ -2363,7 +2396,7 @@ func (f *StreamToolCallFilter) Feed(chunk string, onContent func(string), onTool } else { before = strings.TrimRight(before, "\r\n") } - if before != "" { + if before != "" && !f.emittedCall { onContent(before) } f.inToolCall = true @@ -2385,7 +2418,7 @@ func (f *StreamToolCallFilter) Feed(chunk string, onContent func(string), onTool safe = reTrailingFence.ReplaceAllString(safe, "") safe = strings.TrimRight(safe, "\r\n") } - if strings.TrimSpace(safe) != "" { + if strings.TrimSpace(safe) != "" && !f.emittedCall { onContent(safe) } f.buf = f.buf[len(f.buf)-holdLen:] @@ -2393,7 +2426,9 @@ func (f *StreamToolCallFilter) Feed(chunk string, onContent func(string), onTool } else if len(f.buf) < 16 && strings.TrimSpace(f.buf) == "" { break } else { - onContent(f.buf) + if !f.emittedCall { + onContent(f.buf) + } f.buf = "" break } @@ -2432,7 +2467,7 @@ func (f *StreamToolCallFilter) Feed(chunk string, onContent func(string), onTool f.toolIndex++ f.emittedCall = true onToolCall(tc4) - } else { + } else if !f.emittedCall { onContent(f.activePair.Start + f.toolCallBuf + f.activePair.End) } f.toolCallBuf = "" @@ -2484,18 +2519,13 @@ func (f *StreamToolCallFilter) Flush(onContent func(string), onToolCall func(Too f.toolIndex++ f.emittedCall = true onToolCall(tc4) - } else { + } else if !f.emittedCall { onContent(f.activePair.Start + f.toolCallBuf) } f.toolCallBuf = "" } if len(f.buf) > 0 { - if f.emittedCall { - clean := scrubToolMarkers(f.buf) - if strings.TrimSpace(clean) != "" { - onContent(strings.TrimSpace(clean)) - } - } else { + if !f.emittedCall { onContent(f.buf) } f.buf = "" @@ -2576,8 +2606,9 @@ type SpaceParamMapping struct { 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" + ParamType string `json:"param_type"` // "message", "history", "system_prompt", "temperature", "max_tokens", "top_p", "think_level", "tools", "stream", "state", "web_search", "other" DefaultValue interface{} `json:"default_value,omitempty"` + Choices []string `json:"choices,omitempty"` } type SpaceDiscovery struct { @@ -2609,6 +2640,7 @@ type SpaceDiscovery struct { ThinkLevelIndex int `json:"think_level_index"` // -1 if none FunctionsJSONIndex int `json:"functions_json_index"` // -1 if none PreservedThinkingIndex int `json:"preserved_thinking_index"` // -1 if none + WebSearchIndex int `json:"web_search_index"` // -1 if none IsHunyuan3 bool `json:"is_hunyuan3"` HistoryFormat string `json:"history_format"` // "messages", "pairs", "gradio_messages", "none" ToolCallMode string `json:"tool_call_mode"` // "native_slot", "prompt_augmented_system", "prompt_augmented_first_turn", "prompt_augmented_single_prompt" @@ -2758,6 +2790,7 @@ func NewDefaultSpaceDiscovery(spaceURL string) *SpaceDiscovery { ThinkLevelIndex: -1, FunctionsJSONIndex: -1, PreservedThinkingIndex: -1, + WebSearchIndex: -1, HistoryFormat: "messages", ToolCallMode: "prompt_augmented_single_prompt", LastDiscovered: time.Now(), @@ -3293,6 +3326,29 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover discovery.DefaultInputs[idx] = val mapping.DefaultValue = val } + if chList, ok := comp.Props["choices"].([]interface{}); ok { + for _, ch := range chList { + if chStr, ok := ch.(string); ok { + mapping.Choices = append(mapping.Choices, chStr) + } else if chPair, ok := ch.([]interface{}); ok && len(chPair) > 0 { + if chStr, ok := chPair[0].(string); ok { + mapping.Choices = append(mapping.Choices, chStr) + } + } + } + } + } + if bestEndpointInfo != nil && idx < len(bestEndpointInfo.Parameters) && len(mapping.Choices) == 0 { + p := bestEndpointInfo.Parameters[idx] + if typeMap, ok := p.Type.(map[string]interface{}); ok { + if enumArr, ok := typeMap["enum"].([]interface{}); ok { + for _, e := range enumArr { + if s, ok := e.(string); ok { + mapping.Choices = append(mapping.Choices, s) + } + } + } + } } if strings.Contains(pName, "function") || strings.Contains(pName, "tool") || strings.Contains(pLabel, "tool") || strings.Contains(pLabel, "function") || strings.Contains(cLabel, "tool") || strings.Contains(cLabel, "function") { @@ -3335,6 +3391,9 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover } else if strings.Contains(pName, "stream") || strings.Contains(cLabel, "stream") { mapping.ParamType = "stream" discovery.StreamIndex = idx + } else if strings.Contains(pName, "search") || strings.Contains(pLabel, "search") || strings.Contains(cLabel, "search") || strings.Contains(pName, "browse") || strings.Contains(pLabel, "browse") || strings.Contains(cLabel, "browse") || strings.Contains(pName, "web") || strings.Contains(pLabel, "web") || strings.Contains(cLabel, "web") { + mapping.ParamType = "web_search" + discovery.WebSearchIndex = idx } else if cType == "state" { mapping.ParamType = "state" } @@ -3402,6 +3461,9 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover if discovery.StreamIndex >= discovery.TotalInputs { discovery.StreamIndex = -1 } + if discovery.WebSearchIndex >= discovery.TotalInputs { + discovery.WebSearchIndex = -1 + } } } } @@ -3501,6 +3563,19 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover } else if strings.Contains(pName, "stream") || strings.Contains(pLabel, "stream") { discovery.StreamIndex = idx mapping.ParamType = "stream" + } else if strings.Contains(pName, "search") || strings.Contains(pLabel, "search") || strings.Contains(pName, "browse") || strings.Contains(pLabel, "browse") || strings.Contains(pName, "web") || strings.Contains(pLabel, "web") { + discovery.WebSearchIndex = idx + mapping.ParamType = "web_search" + } + + if typeMap, ok := p.Type.(map[string]interface{}); ok { + if enumArr, ok := typeMap["enum"].([]interface{}); ok { + for _, e := range enumArr { + if s, ok := e.(string); ok { + mapping.Choices = append(mapping.Choices, s) + } + } + } } discovery.ParamMappings = append(discovery.ParamMappings, mapping) @@ -4043,6 +4118,38 @@ func (g *GradioGateway) BuildGradioPayload(disc *SpaceDiscovery, req ChatComplet data[disc.FunctionsJSONIndex] = functionsJSONStr } + if disc.WebSearchIndex >= 0 && disc.WebSearchIndex != disc.MessageIndex && disc.WebSearchIndex != disc.HistoryIndex && disc.WebSearchIndex != disc.SystemIndex && disc.WebSearchIndex < len(data) && len(req.Tools) > 0 { + var mapping *SpaceParamMapping + for i := range disc.ParamMappings { + if disc.ParamMappings[i].InputIndex == disc.WebSearchIndex { + mapping = &disc.ParamMappings[i] + break + } + } + disabledSet := false + if mapping != nil && len(mapping.Choices) > 0 { + for _, choice := range mapping.Choices { + cLower := strings.ToLower(choice) + if cLower == "direct" || cLower == "off" || cLower == "disabled" || cLower == "none" || cLower == "false" || cLower == "no" || strings.Contains(cLower, "direct") || strings.Contains(cLower, "no search") || strings.Contains(cLower, "disable") { + data[disc.WebSearchIndex] = choice + disabledSet = true + break + } + } + } + if !disabledSet { + switch data[disc.WebSearchIndex].(type) { + case bool: + data[disc.WebSearchIndex] = false + case string: + strVal := strings.ToLower(data[disc.WebSearchIndex].(string)) + if strings.Contains(strVal, "search") { + data[disc.WebSearchIndex] = "Direct" + } + } + } + } + return data, nil } diff --git a/gr2gw_test.go b/gr2gw_test.go index 2713d56..55bcdcf 100644 --- a/gr2gw_test.go +++ b/gr2gw_test.go @@ -149,8 +149,8 @@ func TestStreamToolCallFilter(t *testing.T) { t.Errorf("expected tool name 'search_web', got %q", toolCalls[0].Function.Name) } fullContent := strings.Join(contentParts, "") - if fullContent != "Searching now: Done." { - t.Errorf("expected 'Searching now: Done.', got %q", fullContent) + if fullContent != "Searching now: " { + t.Errorf("expected 'Searching now: ' (post-call text suppressed), got %q", fullContent) } } @@ -639,8 +639,11 @@ Some postamble.` if strings.Contains(rem2, "function_call") { t.Errorf("expected tag stripped from remaining, got %q", rem2) } - if !strings.Contains(rem2, "Some preamble") || !strings.Contains(rem2, "Some postamble") { - t.Errorf("expected surrounding text preserved in remaining, got %q", rem2) + if !strings.Contains(rem2, "Some preamble") { + t.Errorf("expected preamble text preserved in remaining, got %q", rem2) + } + if strings.Contains(rem2, "Some postamble") { + t.Errorf("expected postamble text discarded from remaining on tool call, got %q", rem2) } // 3. [TOOL_CALLS] bracket syntax @@ -723,8 +726,8 @@ func TestUniversalStreamToolCallFilterVariants(t *testing.T) { if strings.Contains(fullContent, "TOOL_CALLS") { t.Errorf("tag leaked into stream content: %q", fullContent) } - if fullContent != "Preamble text: Completed." { - t.Errorf("unexpected streamed content: %q", fullContent) + if fullContent != "Preamble text: " { + t.Errorf("unexpected streamed content (expected post-call text suppressed): %q", fullContent) } } @@ -3367,3 +3370,237 @@ func TestHunyuan3CallAndStreamingNoInterleaving(t *testing.T) { } } +func TestDetectToolCallsDiscardsPostambleAndCitationDisclaimer(t *testing.T) { + raw := ` +{"name": "get_current_weather", "arguments": {"location": "Tokyo"}} + + +*Web evidence was retrieved, but the response did not include a valid source citation.*` + + tcs, rem, ok := DetectToolCalls(raw) + if !ok || len(tcs) != 1 { + t.Fatalf("expected 1 tool call, got %d (ok: %v)", len(tcs), ok) + } + if tcs[0].Function.Name != "get_current_weather" { + t.Errorf("expected function get_current_weather, got %q", tcs[0].Function.Name) + } + if rem != "" { + t.Errorf("expected empty remaining content, got %q", rem) + } + + frame := GradioOutputFrame{Content: raw} + finalContent, reasoning, finalCalls, finishReason := finalizeOutput(frame) + if finishReason != "tool_calls" { + t.Errorf("expected finish_reason 'tool_calls', got %q", finishReason) + } + if finalContent != nil { + t.Errorf("expected finalContent to be nil, got %v", finalContent) + } + if reasoning != "" { + t.Errorf("expected empty reasoning, got %q", reasoning) + } + if len(finalCalls) != 1 { + t.Fatalf("expected 1 final tool call, got %d", len(finalCalls)) + } +} + +func TestDetectToolCallsPreservesPreambleBeforeToolCall(t *testing.T) { + raw := "I will check the weather in Tokyo for you.\n" + + "```xml\n" + + "\n" + + "{\"name\": \"get_current_weather\", \"arguments\": {\"location\": \"Tokyo\"}}\n" + + "\n" + + "```\n" + + "*Web evidence was retrieved, but the response did not include a valid source citation.*" + + tcs, rem, ok := DetectToolCalls(raw) + if !ok || len(tcs) != 1 { + t.Fatalf("expected 1 tool call, got %d (ok: %v)", len(tcs), ok) + } + if tcs[0].Function.Name != "get_current_weather" { + t.Errorf("expected function get_current_weather, got %q", tcs[0].Function.Name) + } + if rem != "I will check the weather in Tokyo for you." { + t.Errorf("expected preamble preserved without postamble, got %q", rem) + } + + frame := GradioOutputFrame{Content: raw} + finalContent, _, finalCalls, finishReason := finalizeOutput(frame) + if finishReason != "tool_calls" { + t.Errorf("expected finish_reason 'tool_calls', got %q", finishReason) + } + if finalContentStr, ok := finalContent.(string); !ok || finalContentStr != "I will check the weather in Tokyo for you." { + t.Errorf("expected finalContent 'I will check the weather in Tokyo for you.', got %v", finalContent) + } + if len(finalCalls) != 1 { + t.Fatalf("expected 1 tool call, got %d", len(finalCalls)) + } +} + +func TestStreamToolCallFilterPostCallLeakSuppression(t *testing.T) { + // Test 1: Tool call followed by citation disclaimer + filter1 := NewStreamToolCallFilter() + var contentParts1 []string + var toolCalls1 []ToolCall + + onContent1 := func(s string) { contentParts1 = append(contentParts1, s) } + onToolCall1 := func(tc ToolCall) { toolCalls1 = append(toolCalls1, tc) } + + chunks1 := []string{ + "\n", + "{\"name\": \"get_current_weather\", \"arguments\": {\"location\": \"Tokyo\"}}\n", + "\n", + "\n*Web evidence was retrieved, but the response did not include a valid source citation.*", + } + + for _, c := range chunks1 { + filter1.Feed(c, onContent1, onToolCall1) + } + filter1.Flush(onContent1, onToolCall1) + + if len(toolCalls1) != 1 { + t.Fatalf("expected 1 tool call, got %d", len(toolCalls1)) + } + if toolCalls1[0].Function.Name != "get_current_weather" { + t.Errorf("expected get_current_weather, got %q", toolCalls1[0].Function.Name) + } + if len(contentParts1) > 0 { + t.Errorf("expected 0 content parts leaked after tool call, got %v", contentParts1) + } + + // Test 2: Parallel tool calls followed by trailing residue + filter2 := NewStreamToolCallFilter() + var contentParts2 []string + var toolCalls2 []ToolCall + + onContent2 := func(s string) { contentParts2 = append(contentParts2, s) } + onToolCall2 := func(tc ToolCall) { toolCalls2 = append(toolCalls2, tc) } + + chunks2 := []string{ + "{\"name\": \"call_a\", \"arguments\": {}}\n", + "{\"name\": \"call_b\", \"arguments\": {}}\n", + "Residual text after parallel calls that should be suppressed.", + } + + for _, c := range chunks2 { + filter2.Feed(c, onContent2, onToolCall2) + } + filter2.Flush(onContent2, onToolCall2) + + if len(toolCalls2) != 2 { + t.Fatalf("expected 2 tool calls, got %d", len(toolCalls2)) + } + if toolCalls2[0].Function.Name != "call_a" || toolCalls2[1].Function.Name != "call_b" { + t.Errorf("unexpected tool calls: %+v", toolCalls2) + } + if len(contentParts2) > 0 { + t.Errorf("expected 0 content parts leaked, got %v", contentParts2) + } + + // Test 3: Preamble + Tool call + Postamble + filter3 := NewStreamToolCallFilter() + var contentParts3 []string + var toolCalls3 []ToolCall + + onContent3 := func(s string) { contentParts3 = append(contentParts3, s) } + onToolCall3 := func(tc ToolCall) { toolCalls3 = append(toolCalls3, tc) } + + chunks3 := []string{ + "Thinking about your query: ", + "{\"name\": \"search\", \"arguments\": {\"q\": \"go\"}}", + " Trailing hallucinated answer that must not be emitted.", + } + + for _, c := range chunks3 { + filter3.Feed(c, onContent3, onToolCall3) + } + filter3.Flush(onContent3, onToolCall3) + + if len(toolCalls3) != 1 { + t.Fatalf("expected 1 tool call, got %d", len(toolCalls3)) + } + fullContent3 := strings.Join(contentParts3, "") + if fullContent3 != "Thinking about your query: " { + t.Errorf("expected preamble 'Thinking about your query: ', got %q", fullContent3) + } +} + +func TestWebSearchParameterMappingAndSuppression(t *testing.T) { + gw := &GradioGateway{} + + // 1. Radio search_mode with choices ["Auto search", "Always search", "Direct"] + discRadio := NewDefaultSpaceDiscovery("https://test-search-space.hf.space") + discRadio.TotalInputs = 3 + discRadio.MessageIndex = 0 + discRadio.HistoryIndex = 1 + discRadio.WebSearchIndex = 2 + discRadio.ParamMappings = []SpaceParamMapping{ + {InputIndex: 0, ComponentType: "textbox", Label: "message", ParamType: "message"}, + {InputIndex: 1, ComponentType: "chatbot", Label: "history", ParamType: "history"}, + { + InputIndex: 2, + ComponentType: "radio", + Label: "search_mode", + ParamName: "search_mode", + ParamType: "web_search", + DefaultValue: "Auto search", + Choices: []string{"Auto search", "Always search", "Direct"}, + }, + } + discRadio.DefaultInputs = []interface{}{"", nil, "Auto search"} + + // Normal request without tools retains space default ("Auto search") + reqNoTools := ChatCompletionRequest{ + Model: "test-model", + Messages: []ChatMessage{ + {Role: "user", Content: "Hello world"}, + }, + } + dataNoTools, err := gw.BuildGradioPayload(discRadio, reqNoTools) + if err != nil { + t.Fatalf("failed to build payload without tools: %v", err) + } + if dataNoTools[2] != "Auto search" { + t.Errorf("expected 'Auto search' default retained, got %v", dataNoTools[2]) + } + + // Request with explicit tools disables web search ("Direct") + reqWithTools := ChatCompletionRequest{ + Model: "test-model", + Tools: []Tool{ + {Type: "function", Function: map[string]interface{}{"name": "get_weather"}}, + }, + Messages: []ChatMessage{ + {Role: "user", Content: "Weather in Tokyo?"}, + }, + } + dataWithTools, err := gw.BuildGradioPayload(discRadio, reqWithTools) + if err != nil { + t.Fatalf("failed to build payload with tools: %v", err) + } + if dataWithTools[2] != "Direct" { + t.Errorf("expected search_mode disabled to 'Direct', got %v", dataWithTools[2]) + } + + // 2. Checkbox web_search with bool default + discCheck := NewDefaultSpaceDiscovery("https://test-check-space.hf.space") + discCheck.TotalInputs = 2 + discCheck.MessageIndex = 0 + discCheck.WebSearchIndex = 1 + discCheck.ParamMappings = []SpaceParamMapping{ + {InputIndex: 0, ComponentType: "textbox", Label: "message", ParamType: "message"}, + {InputIndex: 1, ComponentType: "checkbox", Label: "enable_web_search", ParamType: "web_search", DefaultValue: true}, + } + discCheck.DefaultInputs = []interface{}{"", true} + + dataCheckNoTools, _ := gw.BuildGradioPayload(discCheck, reqNoTools) + if dataCheckNoTools[1] != true { + t.Errorf("expected bool true retained without tools, got %v", dataCheckNoTools[1]) + } + + dataCheckWithTools, _ := gw.BuildGradioPayload(discCheck, reqWithTools) + if dataCheckWithTools[1] != false { + t.Errorf("expected bool false set with tools, got %v", dataCheckWithTools[1]) + } +} +