From 773a94c41437d21c7cb1fcc429cee1ac605dd509 Mon Sep 17 00:00:00 2001 From: lxowalle <83055338+lxowalle@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:55:05 +0800 Subject: [PATCH] fix(web_search): validate missing API key/URL directly in Search methods (#2517) --- pkg/tools/web.go | 36 ++++++++++++++++++++++++++++++------ pkg/tools/web_test.go | 9 +++++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/pkg/tools/web.go b/pkg/tools/web.go index 342f7458b..daf5140d4 100644 --- a/pkg/tools/web.go +++ b/pkg/tools/web.go @@ -218,6 +218,10 @@ func (p *BraveSearchProvider) Search( count int, rangeCode string, ) (string, error) { + if p.keyPool == nil || len(p.keyPool.keys) == 0 { + return "", errors.New("no API key provided") + } + searchURL := fmt.Sprintf("https://api.search.brave.com/res/v1/web/search?q=%s&count=%d", url.QueryEscape(query), count) if freshness := mapBraveFreshness(rangeCode); freshness != "" { @@ -317,6 +321,10 @@ func (p *TavilySearchProvider) Search( count int, rangeCode string, ) (string, error) { + if p.keyPool == nil || len(p.keyPool.keys) == 0 { + return "", errors.New("no API key provided") + } + searchURL := p.baseURL if searchURL == "" { searchURL = "https://api.tavily.com/search" @@ -532,6 +540,10 @@ func (p *PerplexitySearchProvider) Search( count int, rangeCode string, ) (string, error) { + if p.keyPool == nil || len(p.keyPool.keys) == 0 { + return "", errors.New("no API key provided") + } + searchURL := "https://api.perplexity.ai/chat/completions" var lastErr error @@ -645,6 +657,10 @@ func (p *SearXNGSearchProvider) Search( count int, rangeCode string, ) (string, error) { + if p.baseURL == "" { + return "", errors.New("no SearXNG URL provided") + } + searchURL := fmt.Sprintf("%s/search?q=%s&format=json&categories=general", strings.TrimSuffix(p.baseURL, "/"), url.QueryEscape(query)) @@ -719,6 +735,10 @@ func (p *GLMSearchProvider) Search( count int, rangeCode string, ) (string, error) { + if p.apiKey == "" { + return "", errors.New("no API key provided") + } + searchURL := p.baseURL if searchURL == "" { searchURL = "https://open.bigmodel.cn/api/paas/v4/web_search" @@ -808,6 +828,10 @@ func (p *BaiduSearchProvider) Search( count int, rangeCode string, ) (string, error) { + if p.apiKey == "" { + return "", errors.New("no API key provided") + } + searchURL := p.baseURL if searchURL == "" { searchURL = "https://qianfan.baidubce.com/v2/ai_search/web_search" @@ -921,7 +945,7 @@ func NewWebSearchTool(opts WebSearchToolOptions) (*WebSearchTool, error) { var provider SearchProvider maxResults := 10 // Priority: Perplexity > Brave > SearXNG > Tavily > DuckDuckGo > Baidu Search > GLM Search - if opts.PerplexityEnabled && len(opts.PerplexityAPIKeys) > 0 { + if opts.PerplexityEnabled { client, err := utils.CreateHTTPClient(opts.Proxy, perplexityTimeout) if err != nil { return nil, fmt.Errorf("failed to create HTTP client for Perplexity: %w", err) @@ -934,7 +958,7 @@ func NewWebSearchTool(opts WebSearchToolOptions) (*WebSearchTool, error) { if opts.PerplexityMaxResults > 0 { maxResults = min(opts.PerplexityMaxResults, 10) } - } else if opts.BraveEnabled && len(opts.BraveAPIKeys) > 0 { + } else if opts.BraveEnabled { client, err := utils.CreateHTTPClient(opts.Proxy, searchTimeout) if err != nil { return nil, fmt.Errorf("failed to create HTTP client for Brave: %w", err) @@ -943,12 +967,12 @@ func NewWebSearchTool(opts WebSearchToolOptions) (*WebSearchTool, error) { if opts.BraveMaxResults > 0 { maxResults = min(opts.BraveMaxResults, 10) } - } else if opts.SearXNGEnabled && opts.SearXNGBaseURL != "" { + } else if opts.SearXNGEnabled { provider = &SearXNGSearchProvider{baseURL: opts.SearXNGBaseURL} if opts.SearXNGMaxResults > 0 { maxResults = min(opts.SearXNGMaxResults, 10) } - } else if opts.TavilyEnabled && len(opts.TavilyAPIKeys) > 0 { + } else if opts.TavilyEnabled { client, err := utils.CreateHTTPClient(opts.Proxy, searchTimeout) if err != nil { return nil, fmt.Errorf("failed to create HTTP client for Tavily: %w", err) @@ -971,7 +995,7 @@ func NewWebSearchTool(opts WebSearchToolOptions) (*WebSearchTool, error) { if opts.DuckDuckGoMaxResults > 0 { maxResults = min(opts.DuckDuckGoMaxResults, 10) } - } else if opts.BaiduSearchEnabled && opts.BaiduSearchAPIKey != "" { + } else if opts.BaiduSearchEnabled { client, err := utils.CreateHTTPClient(opts.Proxy, perplexityTimeout) if err != nil { return nil, fmt.Errorf("failed to create HTTP client for Baidu Search: %w", err) @@ -985,7 +1009,7 @@ func NewWebSearchTool(opts WebSearchToolOptions) (*WebSearchTool, error) { if opts.BaiduSearchMaxResults > 0 { maxResults = min(opts.BaiduSearchMaxResults, 10) } - } else if opts.GLMSearchEnabled && opts.GLMSearchAPIKey != "" { + } else if opts.GLMSearchEnabled { client, err := utils.CreateHTTPClient(opts.Proxy, searchTimeout) if err != nil { return nil, fmt.Errorf("failed to create HTTP client for GLM Search: %w", err) diff --git a/pkg/tools/web_test.go b/pkg/tools/web_test.go index de6187cfa..2bdd01f6d 100644 --- a/pkg/tools/web_test.go +++ b/pkg/tools/web_test.go @@ -391,8 +391,13 @@ func TestWebTool_WebSearch_NoApiKey(t *testing.T) { if err != nil { t.Fatalf("Unexpected error: %v", err) } - if tool != nil { - t.Errorf("Expected nil tool when Brave API key is empty") + if tool == nil { + t.Fatalf("Expected tool to be created") + } + ctx := context.Background() + result := tool.Execute(ctx, map[string]any{"query": "test"}) + if !result.IsError { + t.Errorf("Expected error when API key is missing") } // Also nil when nothing is enabled