diff --git a/gr2gw.go b/gr2gw.go
index bf8437f..505f3a7 100644
--- a/gr2gw.go
+++ b/gr2gw.go
@@ -441,23 +441,44 @@ func BuildToolInstruction(tools []Tool) string {
return ""
}
toolsBytes, _ := json.MarshalIndent(tools, "", " ")
+
+ sampleFnName := ""
+ for _, t := range tools {
+ if fnMap, ok := t.Function.(map[string]interface{}); ok {
+ if n, ok := fnMap["name"].(string); ok && n != "" {
+ sampleFnName = n
+ break
+ }
+ }
+ }
+ if sampleFnName == "" {
+ sampleFnName = "example_tool"
+ }
+
return fmt.Sprintf(`# Tool Calling Instructions
+You are equipped with external tools to assist with user queries.
You have access to the following tools:
%s
-To call a tool, you MUST output a block directly in your text response formatted exactly as follows:
+When the user asks a question or makes a request that can be fulfilled, assisted, or answered using any of the tools above, you MUST call the appropriate tool.
+DO NOT refuse to answer, and DO NOT claim that you lack real-time access, live data, or tool capabilities when a tool is provided for that purpose.
+
+## Tool Calling Syntax
+To call a tool, you MUST output a block formatted as:
-{"name": "", "arguments": {}}
+{"name": "%s", "arguments": {...}}
-Rules:
-- If you need to call a tool, respond ONLY with the block. Do not include introductory text, explanations, or commentary around the block.
-- If you need to call multiple tools, provide each tool call in its own block.
-- If no tool call is needed, answer the user's request directly and normally without using tool tags.
-- When you receive a , answer the user's request using the information provided in the response, or call another tool if additional information is required.`, string(toolsBytes))
+## Rules:
+1. If an available tool is relevant to the user's request, invoking the tool is MANDATORY.
+2. When calling a tool, your ENTIRE output must consist ONLY of the block. Do not add introductory text, commentary, or conversational filler.
+3. If multiple tools are required, output each in its own block.
+4. Arguments must be a valid JSON object strictly matching the tool's parameter definitions.
+5. If no tools are relevant to the user's inquiry, respond normally with plain text.
+6. When tool execution results are provided to you in subsequent turns (via blocks or tool role messages), formulate your final answer to the user based on those results.`, string(toolsBytes), sampleFnName)
}
func TransformMessages(req ChatCompletionRequest) (processed []ChatMessage, toolInstruction string, hasSystem bool) {
@@ -1494,9 +1515,10 @@ func (f *StreamToolCallFilter) Flush(onContent func(string), onToolCall func(Too
// ---------------------------------------------------------------------------
type GradioParamInfo struct {
- Label string `json:"label"`
- ParameterName string `json:"parameter_name"`
- Component string `json:"component"`
+ Label string `json:"label"`
+ ParameterName string `json:"parameter_name"`
+ ParameterDefault interface{} `json:"parameter_default,omitempty"`
+ Component string `json:"component"`
}
type GradioEndpointInfo struct {
@@ -1574,6 +1596,7 @@ type SpaceDiscovery struct {
HistoryIndex int // -1 if none
MessageIndex int // index for user message text
SystemIndex int // -1 if none
+ DefaultSystemPrompt string // default space system prompt if present
TempIndex int // -1 if none
MaxTokensIndex int // -1 if none
TopPIndex int // -1 if none
@@ -1639,6 +1662,7 @@ func NewDefaultSpaceDiscovery(spaceURL string) *SpaceDiscovery {
HistoryIndex: -1,
MessageIndex: 0,
SystemIndex: -1,
+ DefaultSystemPrompt: "",
TempIndex: -1,
MaxTokensIndex: -1,
TopPIndex: -1,
@@ -1863,6 +1887,11 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
} else if discovery.SystemIndex == -1 {
mapping.ParamType = "system_prompt"
discovery.SystemIndex = idx
+ if comp.Props != nil {
+ if val, ok := comp.Props["value"].(string); ok && strings.TrimSpace(val) != "" {
+ discovery.DefaultSystemPrompt = strings.TrimSpace(val)
+ }
+ }
}
case "state":
mapping.ParamType = "state"
@@ -1905,6 +1934,11 @@ func InspectSpace(client *http.Client, rawURL, userAgent string) (*SpaceDiscover
pName := strings.ToLower(p.ParameterName)
if strings.Contains(pName, "system") {
discovery.SystemIndex = idx
+ if p.ParameterDefault != nil && discovery.DefaultSystemPrompt == "" {
+ if defStr, ok := p.ParameterDefault.(string); ok && strings.TrimSpace(defStr) != "" {
+ discovery.DefaultSystemPrompt = strings.TrimSpace(defStr)
+ }
+ }
} else if strings.Contains(pName, "history") || strings.Contains(pName, "chat") {
discovery.HistoryIndex = idx
} else if strings.Contains(pName, "message") || (strings.Contains(pName, "prompt") && !strings.Contains(pName, "system")) || strings.Contains(pName, "text") {
@@ -2038,6 +2072,7 @@ func (g *GradioGateway) GetDiscovery(spaceURL, userAgent string) *SpaceDiscovery
// BuildGradioPayload packages OpenAI messages and parameters into the target Gradio input array.
func (g *GradioGateway) BuildGradioPayload(disc *SpaceDiscovery, req ChatCompletionRequest) ([]interface{}, error) {
var transformed []ChatMessage
+ var toolInstruction string
if disc.IsHunyuan3 && disc.FunctionsJSONIndex != -1 {
for _, msg := range req.Messages {
transformed = append(transformed, ChatMessage{
@@ -2050,7 +2085,16 @@ func (g *GradioGateway) BuildGradioPayload(disc *SpaceDiscovery, req ChatComplet
})
}
} else {
- transformed, _, _ = TransformMessages(req)
+ // When no native tool calling support is detected, augment system prompt
+ transformed, toolInstruction, _ = TransformMessages(req)
+ }
+
+ hasClientSystem := false
+ for _, m := range req.Messages {
+ if m.Role == "system" {
+ hasClientSystem = true
+ break
+ }
}
var systemPromptStr string
@@ -2071,13 +2115,22 @@ func (g *GradioGateway) BuildGradioPayload(disc *SpaceDiscovery, req ChatComplet
}
}
+ // If the space has a DefaultSystemPrompt and client provided no system message,
+ // retain and augment the default system prompt:
+ if !hasClientSystem && disc.DefaultSystemPrompt != "" {
+ if systemPromptStr != "" {
+ systemPromptStr = disc.DefaultSystemPrompt + "\n\n" + systemPromptStr
+ } else {
+ systemPromptStr = disc.DefaultSystemPrompt
+ }
+ }
+
// If the space has NO native system prompt input (disc.SystemIndex == -1),
// but we have system instructions (from system message or tool instructions):
if disc.SystemIndex == -1 && systemPromptStr != "" && len(nonSystem) > 0 {
// If the space supports conversation history, prepend system instructions to the first turn
if disc.HistoryIndex != -1 {
nonSystem[0].Content = systemPromptStr + "\n\n" + nonSystem[0].GetContentString()
- systemPromptStr = ""
}
}
@@ -2156,7 +2209,11 @@ func (g *GradioGateway) BuildGradioPayload(disc *SpaceDiscovery, req ChatComplet
var promptMessageText string
if disc.HistoryIndex != -1 {
- promptMessageText = lastUserMessage
+ if disc.SystemIndex == -1 && len(nonSystem) > 1 && toolInstruction != "" {
+ promptMessageText = fmt.Sprintf("[System Directive: Tool calling mode active. If relevant, output a block.]\n\n%s", lastUserMessage)
+ } else {
+ promptMessageText = lastUserMessage
+ }
} else {
// Single message space: compose multi-turn history into the prompt
if len(nonSystem) <= 1 {
diff --git a/gr2gw_test.go b/gr2gw_test.go
index c0ab1ac..dd61744 100644
--- a/gr2gw_test.go
+++ b/gr2gw_test.go
@@ -1204,4 +1204,97 @@ func TestToolUseFailedImmediateCallRecovery(t *testing.T) {
}
}
+func TestSystemPromptAugmentationWithoutNativeToolCalling(t *testing.T) {
+ gw := NewGradioGateway("https://generic-space.hf.space", "", 10*time.Second)
+
+ tools := []Tool{
+ {
+ Type: "function",
+ Function: map[string]interface{}{
+ "name": "search_docs",
+ "description": "Search local documentation",
+ "parameters": map[string]interface{}{
+ "type": "object",
+ "properties": map[string]interface{}{
+ "query": map[string]interface{}{"type": "string"},
+ },
+ "required": []string{"query"},
+ },
+ },
+ },
+ }
+
+ // Case 1: Space with SystemIndex and space default prompt, client sends no system message
+ discWithSys := NewDefaultSpaceDiscovery("https://generic-space.hf.space")
+ discWithSys.TotalInputs = 2
+ discWithSys.MessageIndex = 0
+ discWithSys.SystemIndex = 1
+ discWithSys.DefaultSystemPrompt = "You are a specialized documentation bot."
+
+ req1 := ChatCompletionRequest{
+ Tools: tools,
+ Messages: []ChatMessage{
+ {Role: "user", Content: "How to configure SSL?"},
+ },
+ }
+
+ payload1, err := gw.BuildGradioPayload(discWithSys, req1)
+ if err != nil {
+ t.Fatalf("BuildGradioPayload failed: %v", err)
+ }
+ sysStr, ok := payload1[1].(string)
+ if !ok {
+ t.Fatalf("expected string at SystemIndex 1, got %T", payload1[1])
+ }
+ if !strings.Contains(sysStr, "You are a specialized documentation bot.") {
+ t.Errorf("expected default system prompt to be retained, got: %s", sysStr)
+ }
+ if !strings.Contains(sysStr, "Tool Calling Instructions") || !strings.Contains(sysStr, "search_docs") {
+ t.Errorf("expected tool calling instructions and function name in system prompt, got: %s", sysStr)
+ }
+
+ // Case 2: Space with SystemIndex, client sends their own system message
+ req2 := ChatCompletionRequest{
+ Tools: tools,
+ Messages: []ChatMessage{
+ {Role: "system", Content: "You are an expert developer assistant."},
+ {Role: "user", Content: "How to configure SSL?"},
+ },
+ }
+ payload2, err := gw.BuildGradioPayload(discWithSys, req2)
+ if err != nil {
+ t.Fatalf("BuildGradioPayload failed: %v", err)
+ }
+ sysStr2, ok := payload2[1].(string)
+ if !ok {
+ t.Fatalf("expected string at SystemIndex 1, got %T", payload2[1])
+ }
+ if !strings.Contains(sysStr2, "You are an expert developer assistant.") {
+ t.Errorf("expected client system message, got: %s", sysStr2)
+ }
+ if !strings.Contains(sysStr2, "search_docs") {
+ t.Errorf("expected search_docs tool instruction, got: %s", sysStr2)
+ }
+
+ // Case 3: Space without SystemIndex, single input textbox
+ discSingle := NewDefaultSpaceDiscovery("https://single-input.hf.space")
+ discSingle.TotalInputs = 1
+ discSingle.MessageIndex = 0
+ discSingle.SystemIndex = -1
+ discSingle.HistoryIndex = -1
+
+ payload3, err := gw.BuildGradioPayload(discSingle, req1)
+ if err != nil {
+ t.Fatalf("BuildGradioPayload failed: %v", err)
+ }
+ singleMsg, ok := payload3[0].(string)
+ if !ok {
+ t.Fatalf("expected string at index 0, got %T", payload3[0])
+ }
+ if !strings.Contains(singleMsg, "Tool Calling Instructions") || !strings.Contains(singleMsg, "How to configure SSL?") {
+ t.Errorf("expected single message to contain augmented instructions and user query, got: %s", singleMsg)
+ }
+}
+
+