diff --git a/internal/multiagent/eino_middleware.go b/internal/multiagent/eino_middleware.go index 1a1d2106..640fba38 100644 --- a/internal/multiagent/eino_middleware.go +++ b/internal/multiagent/eino_middleware.go @@ -51,14 +51,7 @@ func splitToolsForToolSearch(all []tool.BaseTool, alwaysVisible int) (static []t } func splitToolsForToolSearchByNames(all []tool.BaseTool, names []string, fallbackAlwaysVisible int) (static []tool.BaseTool, dynamic []tool.BaseTool, ok bool) { - nameSet := make(map[string]struct{}, len(names)) - for _, n := range names { - n = strings.TrimSpace(strings.ToLower(n)) - if n == "" { - continue - } - nameSet[n] = struct{}{} - } + nameSet := expandAlwaysVisibleNameSet(names) if len(nameSet) == 0 { return splitToolsForToolSearch(all, fallbackAlwaysVisible) } @@ -71,9 +64,9 @@ func splitToolsForToolSearchByNames(all []tool.BaseTool, names []string, fallbac info, err := t.Info(context.Background()) name := "" if err == nil && info != nil { - name = strings.TrimSpace(strings.ToLower(info.Name)) + name = info.Name } - if _, keep := nameSet[name]; keep { + if toolMatchesAlwaysVisible(name, nameSet) { static = append(static, t) continue } diff --git a/internal/multiagent/tool_always_visible.go b/internal/multiagent/tool_always_visible.go new file mode 100644 index 00000000..151cccc2 --- /dev/null +++ b/internal/multiagent/tool_always_visible.go @@ -0,0 +1,72 @@ +package multiagent + +import ( + "strings" +) + +// expandAlwaysVisibleNameSet 将配置中的常驻工具名展开为可匹配运行时工具名的集合。 +// 支持:内置短名 read_file;外部 mcp::tool;运行时 mcp__tool(OpenAI/Eino 命名)。 +func expandAlwaysVisibleNameSet(names []string) map[string]struct{} { + set := make(map[string]struct{}, len(names)*3) + add := func(name string) { + n := strings.TrimSpace(strings.ToLower(name)) + if n == "" { + return + } + set[n] = struct{}{} + } + for _, raw := range names { + n := strings.TrimSpace(strings.ToLower(raw)) + if n == "" { + continue + } + add(n) + if mcp, tool, ok := strings.Cut(n, "::"); ok && mcp != "" && tool != "" { + // 外部工具用 mcp::tool 配置时只展开运行时 mcp__tool,避免短名误伤其它 MCP 同名工具。 + add(mcp + "__" + tool) + continue + } + if idx := strings.LastIndex(n, "__"); idx > 0 { + mcp, tool := n[:idx], n[idx+2:] + if mcp != "" && tool != "" { + add(mcp + "::" + tool) + } + continue + } + } + return set +} + +// toolMatchesAlwaysVisible 判断运行时工具名是否命中常驻白名单(含别名)。 +func toolMatchesAlwaysVisible(runtimeName string, nameSet map[string]struct{}) bool { + if len(nameSet) == 0 { + return false + } + name := strings.TrimSpace(strings.ToLower(runtimeName)) + if name == "" { + return false + } + if _, ok := nameSet[name]; ok { + return true + } + if mcp, tool, ok := strings.Cut(name, "::"); ok && mcp != "" && tool != "" { + if _, ok := nameSet[mcp+"__"+tool]; ok { + return true + } + if _, ok := nameSet[tool]; ok { + return true + } + } + if idx := strings.LastIndex(name, "__"); idx > 0 { + mcp, tool := name[:idx], name[idx+2:] + if mcp != "" && tool != "" { + if _, ok := nameSet[mcp+"::"+tool]; ok { + return true + } + if _, ok := nameSet[tool]; ok { + return true + } + } + } + return false +} diff --git a/internal/multiagent/tool_always_visible_test.go b/internal/multiagent/tool_always_visible_test.go new file mode 100644 index 00000000..00c9eaa0 --- /dev/null +++ b/internal/multiagent/tool_always_visible_test.go @@ -0,0 +1,32 @@ +package multiagent + +import "testing" + +func TestToolMatchesAlwaysVisible_ExternalAliases(t *testing.T) { + t.Parallel() + set := expandAlwaysVisibleNameSet([]string{"zhidemai::discount_search", "read_file"}) + + cases := []struct { + runtime string + want bool + }{ + {"zhidemai__discount_search", true}, + {"zhidemai::discount_search", true}, + {"read_file", true}, + {"zhidemai__product_search_pro", false}, + {"github__discount_search", false}, + } + for _, tc := range cases { + if got := toolMatchesAlwaysVisible(tc.runtime, set); got != tc.want { + t.Fatalf("toolMatchesAlwaysVisible(%q) = %v, want %v", tc.runtime, got, tc.want) + } + } +} + +func TestExpandAlwaysVisibleNameSet_LegacyShortName(t *testing.T) { + t.Parallel() + set := expandAlwaysVisibleNameSet([]string{"discount_search"}) + if !toolMatchesAlwaysVisible("zhidemai__discount_search", set) { + t.Fatal("legacy short name should match external runtime tool") + } +}