tools fix

This commit is contained in:
Luxferre
2026-09-07 18:05:32 +03:00
parent 2a0fd63676
commit c731b57dfc
2 changed files with 376 additions and 32 deletions
+133 -26
View File
@@ -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+[^>]*)?>|<function=[a-zA-Z0-9_-]+>|\[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
}