fix(tools): improve web search provider fallback (#2629)

- centralize web search provider readiness and resolution logic
- fall back when the configured provider is unavailable or invalid
- allow native-search-capable models to use built-in search without the client tool
- simplify the tools page and add direct access to web search settings
- add backend, agent, and integration tests for the new selection behavior
This commit is contained in:
wenjie
2026-04-23 15:39:16 +08:00
committed by GitHub
parent 451db2f5d8
commit cac4f21746
16 changed files with 633 additions and 222 deletions
+91 -13
View File
@@ -385,24 +385,14 @@ func TestWebFetchTool_PayloadTooLarge(t *testing.T) {
}
}
// TestWebTool_WebSearch_NoApiKey verifies missing credentials are surfaced at execution time.
// TestWebTool_WebSearch_NoApiKey verifies providers without required credentials are not registered.
func TestWebTool_WebSearch_NoApiKey(t *testing.T) {
tool, err := NewWebSearchTool(WebSearchToolOptions{BraveEnabled: true, BraveAPIKeys: nil})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if tool == nil {
t.Fatalf("Expected tool when Brave is enabled, even without API keys")
}
result := tool.Execute(context.Background(), map[string]any{
"query": "test query",
})
if !result.IsError {
t.Fatalf("Expected missing Brave API key to return error")
}
if !strings.Contains(result.ForLLM, "no API key provided") {
t.Fatalf("Unexpected error message: %s", result.ForLLM)
if tool != nil {
t.Fatalf("Expected nil tool when only enabled provider is missing credentials")
}
// Also nil when nothing is enabled
@@ -1878,6 +1868,94 @@ func TestWebTool_AutoProviderPrefersConfiguredProvidersBeforeSogou(t *testing.T)
}
}
func TestWebTool_ExplicitProviderFallsBackWhenMissingCredentials(t *testing.T) {
tool, err := NewWebSearchTool(WebSearchToolOptions{
Provider: "brave",
BraveEnabled: true,
SogouEnabled: true,
SogouMaxResults: 5,
})
if err != nil {
t.Fatalf("NewWebSearchTool() error: %v", err)
}
if _, ok := tool.provider.(*SogouSearchProvider); !ok {
t.Fatalf("expected SogouSearchProvider after fallback, got %T", tool.provider)
}
}
func TestWebTool_ExplicitProviderFallsBackWhenMissingBaseURL(t *testing.T) {
tool, err := NewWebSearchTool(WebSearchToolOptions{
Provider: "searxng",
SearXNGEnabled: true,
SogouEnabled: true,
SogouMaxResults: 5,
})
if err != nil {
t.Fatalf("NewWebSearchTool() error: %v", err)
}
if _, ok := tool.provider.(*SogouSearchProvider); !ok {
t.Fatalf("expected SogouSearchProvider after fallback, got %T", tool.provider)
}
}
func TestWebTool_AutoProviderSkipsEnabledButUnreadyProviders(t *testing.T) {
tool, err := NewWebSearchTool(WebSearchToolOptions{
Provider: "auto",
BraveEnabled: true,
SogouEnabled: true,
SogouMaxResults: 5,
})
if err != nil {
t.Fatalf("NewWebSearchTool() error: %v", err)
}
if _, ok := tool.provider.(*SogouSearchProvider); !ok {
t.Fatalf("expected SogouSearchProvider when Brave has no API key, got %T", tool.provider)
}
}
func TestResolveWebSearchProviderName_FallsBackFromExplicitUnavailableProvider(t *testing.T) {
got, err := ResolveWebSearchProviderName(WebSearchToolOptions{
Provider: "brave",
BraveEnabled: true,
SogouEnabled: true,
SogouMaxResults: 5,
}, "")
if err != nil {
t.Fatalf("ResolveWebSearchProviderName() error: %v", err)
}
if got != "sogou" {
t.Fatalf("ResolveWebSearchProviderName() = %q, want sogou", got)
}
}
func TestWebTool_UnknownExplicitProviderFallsBackToAuto(t *testing.T) {
tool, err := NewWebSearchTool(WebSearchToolOptions{
Provider: "totally_unknown",
SogouEnabled: true,
SogouMaxResults: 5,
})
if err != nil {
t.Fatalf("NewWebSearchTool() error: %v", err)
}
if _, ok := tool.provider.(*SogouSearchProvider); !ok {
t.Fatalf("expected SogouSearchProvider after fallback, got %T", tool.provider)
}
}
func TestResolveWebSearchProviderName_FallsBackFromUnknownProvider(t *testing.T) {
got, err := ResolveWebSearchProviderName(WebSearchToolOptions{
Provider: "totally_unknown",
SogouEnabled: true,
SogouMaxResults: 5,
}, "")
if err != nil {
t.Fatalf("ResolveWebSearchProviderName() error: %v", err)
}
if got != "sogou" {
t.Fatalf("ResolveWebSearchProviderName() = %q, want sogou", got)
}
}
type stubSearchProvider struct {
result string
calls []string