mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-05 15:20:30 +08:00
fix(go-agent): normalize canvas tool names (#17768)
## Summary - Normalize Canvas component names before resolving Go Agent tools and parameters. - Add regression coverage for CodeExec and other Canvas tool mappings. ## Testing - `CGO_ENABLED=0 go test -count=1 ./internal/agent/tool ./internal/agent/component`
This commit is contained in:
@@ -654,6 +654,43 @@ func TestAgent_NewAcceptsCanvasToolObjects(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_NewAcceptsCodeExecCanvasToolObject(t *testing.T) {
|
||||
cmp, err := New("Agent", map[string]any{
|
||||
"model_id": "stub",
|
||||
"user_prompt": "x",
|
||||
"tools": []any{
|
||||
map[string]any{
|
||||
"component_name": "CodeExec",
|
||||
"params": map[string]any{},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New(Agent): %v", err)
|
||||
}
|
||||
agent, ok := cmp.(*AgentComponent)
|
||||
if !ok {
|
||||
t.Fatalf("New(Agent) returned %T, want *AgentComponent", cmp)
|
||||
}
|
||||
if len(agent.param.Tools) != 1 || agent.param.Tools[0] != "CodeExec" {
|
||||
t.Fatalf("agent.param.Tools = %#v, want [CodeExec]", agent.param.Tools)
|
||||
}
|
||||
tools, err := buildAgentTools(t.Context(), agent.param)
|
||||
if err != nil {
|
||||
t.Fatalf("buildAgentTools: %v", err)
|
||||
}
|
||||
if len(tools) != 1 {
|
||||
t.Fatalf("len(tools) = %d, want 1", len(tools))
|
||||
}
|
||||
info, err := tools[0].Info(t.Context())
|
||||
if err != nil {
|
||||
t.Fatalf("CodeExec tool Info: %v", err)
|
||||
}
|
||||
if info.Name != "execute_code" {
|
||||
t.Errorf("CodeExec tool Info().Name = %q, want execute_code", info.Name)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeToolCallingChatModel struct {
|
||||
tools []*schema.ToolInfo
|
||||
}
|
||||
|
||||
@@ -55,23 +55,32 @@ var registry = map[string]Factory{
|
||||
"qweather": noConfig("qweather", func() einotool.BaseTool { return NewQWeatherTool() }),
|
||||
"querit": buildQueritTool,
|
||||
"querit_search": buildQueritTool,
|
||||
"queritsearch": buildQueritTool,
|
||||
"retrieval": buildRetrievalTool,
|
||||
"search_my_dataset": buildRetrievalTool,
|
||||
"search_my_dateset": buildRetrievalTool,
|
||||
"searxng": buildSearXNGTool,
|
||||
"tavily": buildTavilyTool,
|
||||
// Agent DSL tool lists carry the Python Canvas component_name verbatim.
|
||||
// BuildByName lower-cases names, so register those component names too.
|
||||
"tavilysearch": buildTavilyTool,
|
||||
"tavily_extract": buildTavilyExtractTool,
|
||||
"tavilyextract": buildTavilyExtractTool,
|
||||
"tushare": noConfig("tushare", func() einotool.BaseTool { return NewTushareTool() }),
|
||||
"wencai": buildWencaiTool,
|
||||
"web_crawler": noConfig("web_crawler", func() einotool.BaseTool { return NewCrawlerTool() }),
|
||||
"wikipedia": buildWikipediaTool,
|
||||
"wikipedia_search": buildWikipediaTool,
|
||||
"yahoo_finance": buildYahooFinanceTool,
|
||||
"tavily_extract": buildTavilyExtractTool,
|
||||
"tushare": noConfig("tushare", func() einotool.BaseTool { return NewTushareTool() }),
|
||||
"wencai": buildWencaiTool,
|
||||
"web_crawler": noConfig("web_crawler", func() einotool.BaseTool { return NewCrawlerTool() }),
|
||||
"wikipedia": buildWikipediaTool,
|
||||
"wikipedia_search": buildWikipediaTool,
|
||||
"yahoo_finance": buildYahooFinanceTool,
|
||||
}
|
||||
|
||||
// canvasToolNames maps lower-cased Canvas component names to the canonical
|
||||
// registry keys used by the Go Agent tool layer. Canvas preserves component
|
||||
// names such as "CodeExec" and "GoogleScholar", while the tool registry uses
|
||||
// snake_case names for several tools.
|
||||
var canvasToolNames = map[string]string{
|
||||
"codeexec": "code_exec",
|
||||
"googlescholar": "google_scholar",
|
||||
"keenablesearch": "keenable",
|
||||
"queritsearch": "querit_search",
|
||||
"tavilyextract": "tavily_extract",
|
||||
"tavilysearch": "tavily",
|
||||
"yahoofinance": "yahoo_finance",
|
||||
}
|
||||
|
||||
func noConfig(name string, fn func() einotool.BaseTool) Factory {
|
||||
@@ -85,7 +94,7 @@ func noConfig(name string, fn func() einotool.BaseTool) Factory {
|
||||
|
||||
// BuildByName resolves a tool name into an Eino BaseTool.
|
||||
func BuildByName(name string, params map[string]any) (einotool.BaseTool, error) {
|
||||
key := strings.ToLower(strings.TrimSpace(name))
|
||||
key := normalizeToolName(name)
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("agent tool: empty tool name")
|
||||
}
|
||||
@@ -109,7 +118,14 @@ func BuildAll(names []string, perToolParams map[string]map[string]any) ([]einoto
|
||||
for _, name := range names {
|
||||
var params map[string]any
|
||||
if perToolParams != nil {
|
||||
params = perToolParams[strings.ToLower(strings.TrimSpace(name))]
|
||||
// Prefer the canonical key so callers can provide params using the
|
||||
// Go registry name even when the tool list uses a Canvas name.
|
||||
params = perToolParams[normalizeToolName(name)]
|
||||
if params == nil {
|
||||
// Canvas DSL extraction currently keys object params by the
|
||||
// lower-cased component name, so retain that lookup as a fallback.
|
||||
params = perToolParams[strings.ToLower(strings.TrimSpace(name))]
|
||||
}
|
||||
if params == nil {
|
||||
params = perToolParams[name]
|
||||
}
|
||||
@@ -123,6 +139,17 @@ func BuildAll(names []string, perToolParams map[string]map[string]any) ([]einoto
|
||||
return tools, nil
|
||||
}
|
||||
|
||||
// normalizeToolName returns the canonical registry key for a DSL or Agent
|
||||
// tool name. It lower-cases ordinary names and translates Canvas component
|
||||
// names whose spelling differs from the Go registry key.
|
||||
func normalizeToolName(name string) string {
|
||||
key := strings.ToLower(strings.TrimSpace(name))
|
||||
if canonical, ok := canvasToolNames[key]; ok {
|
||||
return canonical
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func buildAkShareTool(params map[string]any) (einotool.BaseTool, error) {
|
||||
topN := defaultAkShareTopN
|
||||
if len(params) != 0 {
|
||||
|
||||
@@ -40,27 +40,67 @@ func TestBuildAll_UnknownTool(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildByName_TavilyCanvasComponentNames(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
func TestBuildByName_CanvasComponentNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wantToolName string
|
||||
}{
|
||||
{name: "TavilySearch"},
|
||||
{name: "TavilyExtract"},
|
||||
{name: "CodeExec", wantToolName: "execute_code"},
|
||||
{name: "GoogleScholar", wantToolName: "google_scholar_search"},
|
||||
{name: "KeenableSearch", wantToolName: "keenable_search"},
|
||||
{name: "QueritSearch", wantToolName: "querit_search"},
|
||||
{name: "TavilyExtract", wantToolName: "tavily_extract"},
|
||||
{name: "TavilySearch", wantToolName: "tavily_search"},
|
||||
{name: "YahooFinance", wantToolName: "yahoo_finance"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
built, err := BuildByName(tc.name, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildByName(%q): %v", tc.name, err)
|
||||
}
|
||||
info, err := built.Info(t.Context())
|
||||
if err != nil {
|
||||
t.Fatalf("BuildByName(%q).Info: %v", tc.name, err)
|
||||
}
|
||||
if info.Name != tc.wantToolName {
|
||||
t.Errorf("BuildByName(%q).Info().Name = %q, want %q", tc.name, info.Name, tc.wantToolName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAll_CanvasComponentNameUsesCanonicalParams(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
paramsKey string
|
||||
topN int
|
||||
}{
|
||||
{name: "canonical key", paramsKey: "google_scholar", topN: 7},
|
||||
{name: "canvas key", paramsKey: "googlescholar", topN: 9},
|
||||
} {
|
||||
built, err := BuildByName(tc.name, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildByName(%q): %v", tc.name, err)
|
||||
}
|
||||
switch tc.name {
|
||||
case "TavilySearch":
|
||||
if _, ok := built.(*TavilyTool); !ok {
|
||||
t.Errorf("BuildByName(%q) returned %T, want *TavilyTool", tc.name, built)
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tools, err := BuildAll(
|
||||
[]string{"GoogleScholar"},
|
||||
map[string]map[string]any{
|
||||
tc.paramsKey: {"top_n": tc.topN},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildAll: %v", err)
|
||||
}
|
||||
case "TavilyExtract":
|
||||
if _, ok := built.(*TavilyExtractTool); !ok {
|
||||
t.Errorf("BuildByName(%q) returned %T, want *TavilyExtractTool", tc.name, built)
|
||||
if len(tools) != 1 {
|
||||
t.Fatalf("len(tools) = %d, want 1", len(tools))
|
||||
}
|
||||
}
|
||||
scholar, ok := tools[0].(*GoogleScholarTool)
|
||||
if !ok {
|
||||
t.Fatalf("tools[0] = %T, want *GoogleScholarTool", tools[0])
|
||||
}
|
||||
if scholar.defaults.TopN != tc.topN {
|
||||
t.Errorf("GoogleScholar defaults.TopN = %d, want %d", scholar.defaults.TopN, tc.topN)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +140,7 @@ func TestBuildAll_AllRegisteredTools(t *testing.T) {
|
||||
"akshare", "arxiv", "bgpt", "code_exec", "crawler", "deepl",
|
||||
"duckduckgo", "email", "exesql", "execute_sql", "github", "google",
|
||||
"google_scholar", "google_scholar_search", "jin10", "keenable", "pubmed", "qweather",
|
||||
"querit", "querit_search", "queritsearch",
|
||||
"querit", "querit_search",
|
||||
"retrieval", "search_my_dataset", "search_my_dateset", "searxng",
|
||||
"tavily", "tavily_extract", "tushare", "web_crawler", "wencai", "wikipedia", "wikipedia_search",
|
||||
"yahoo_finance",
|
||||
@@ -164,7 +204,7 @@ func TestToolRegistry_SchemasAreComplete(t *testing.T) {
|
||||
"akshare", "arxiv", "bgpt", "code_exec", "crawler", "deepl",
|
||||
"duckduckgo", "email", "execute_sql", "exesql", "github", "google",
|
||||
"google_scholar", "google_scholar_search", "jin10", "keenable", "pubmed", "qweather",
|
||||
"querit", "querit_search", "queritsearch",
|
||||
"querit", "querit_search",
|
||||
"retrieval", "search_my_dataset", "search_my_dateset", "searxng",
|
||||
"tavily", "tavily_extract", "tushare", "web_crawler", "wencai", "wikipedia", "wikipedia_search",
|
||||
"yahoo_finance",
|
||||
@@ -236,7 +276,6 @@ func TestToolRegistry_SchemasAreComplete(t *testing.T) {
|
||||
"wikipedia_search": "wikipedia_search",
|
||||
"querit": "querit_search",
|
||||
"querit_search": "querit_search",
|
||||
"queritsearch": "querit_search",
|
||||
}
|
||||
for _, name := range names {
|
||||
canonical, ok := canonicalByAlias[name]
|
||||
|
||||
Reference in New Issue
Block a user