mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(web): address sogou search review feedback
This commit is contained in:
+47
-8
@@ -117,6 +117,21 @@ func extractSogouURL(href string) string {
|
||||
return decoded
|
||||
}
|
||||
|
||||
func applySogouRangeHint(query string, rangeCode string) string {
|
||||
switch rangeCode {
|
||||
case "d":
|
||||
return query + " 最近一天"
|
||||
case "w":
|
||||
return query + " 最近一周"
|
||||
case "m":
|
||||
return query + " 最近一个月"
|
||||
case "y":
|
||||
return query + " 最近一年"
|
||||
default:
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeSearchRange(raw string) (string, error) {
|
||||
rangeCode := strings.ToLower(strings.TrimSpace(raw))
|
||||
switch rangeCode {
|
||||
@@ -244,6 +259,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 != "" {
|
||||
@@ -343,6 +362,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"
|
||||
@@ -462,7 +485,7 @@ func (p *SogouSearchProvider) Search(
|
||||
|
||||
for page := 1; page <= maxPages && len(results) < count; page++ {
|
||||
params := url.Values{}
|
||||
params.Set("keyword", query)
|
||||
params.Set("keyword", applySogouRangeHint(query, rangeCode))
|
||||
params.Set("v", "5")
|
||||
params.Set("p", fmt.Sprintf("%d", page))
|
||||
|
||||
@@ -656,6 +679,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
|
||||
@@ -769,6 +796,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))
|
||||
@@ -843,6 +874,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"
|
||||
@@ -932,6 +967,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"
|
||||
@@ -1065,7 +1104,7 @@ func (opts WebSearchToolOptions) providerByName(name string) (SearchProvider, in
|
||||
client: client,
|
||||
}, maxResults, nil
|
||||
case "perplexity":
|
||||
if !opts.PerplexityEnabled || len(opts.PerplexityAPIKeys) == 0 {
|
||||
if !opts.PerplexityEnabled {
|
||||
return nil, 0, nil
|
||||
}
|
||||
client, err := utils.CreateHTTPClient(opts.Proxy, perplexityTimeout)
|
||||
@@ -1082,7 +1121,7 @@ func (opts WebSearchToolOptions) providerByName(name string) (SearchProvider, in
|
||||
client: client,
|
||||
}, maxResults, nil
|
||||
case "brave":
|
||||
if !opts.BraveEnabled || len(opts.BraveAPIKeys) == 0 {
|
||||
if !opts.BraveEnabled {
|
||||
return nil, 0, nil
|
||||
}
|
||||
client, err := utils.CreateHTTPClient(opts.Proxy, searchTimeout)
|
||||
@@ -1099,7 +1138,7 @@ func (opts WebSearchToolOptions) providerByName(name string) (SearchProvider, in
|
||||
client: client,
|
||||
}, maxResults, nil
|
||||
case "searxng":
|
||||
if !opts.SearXNGEnabled || opts.SearXNGBaseURL == "" {
|
||||
if !opts.SearXNGEnabled {
|
||||
return nil, 0, nil
|
||||
}
|
||||
maxResults := 10
|
||||
@@ -1110,7 +1149,7 @@ func (opts WebSearchToolOptions) providerByName(name string) (SearchProvider, in
|
||||
baseURL: opts.SearXNGBaseURL,
|
||||
}, maxResults, nil
|
||||
case "tavily":
|
||||
if !opts.TavilyEnabled || len(opts.TavilyAPIKeys) == 0 {
|
||||
if !opts.TavilyEnabled {
|
||||
return nil, 0, nil
|
||||
}
|
||||
client, err := utils.CreateHTTPClient(opts.Proxy, searchTimeout)
|
||||
@@ -1144,7 +1183,7 @@ func (opts WebSearchToolOptions) providerByName(name string) (SearchProvider, in
|
||||
client: client,
|
||||
}, maxResults, nil
|
||||
case "baidu_search":
|
||||
if !opts.BaiduSearchEnabled || opts.BaiduSearchAPIKey == "" {
|
||||
if !opts.BaiduSearchEnabled {
|
||||
return nil, 0, nil
|
||||
}
|
||||
client, err := utils.CreateHTTPClient(opts.Proxy, perplexityTimeout)
|
||||
@@ -1162,7 +1201,7 @@ func (opts WebSearchToolOptions) providerByName(name string) (SearchProvider, in
|
||||
client: client,
|
||||
}, maxResults, nil
|
||||
case "glm_search":
|
||||
if !opts.GLMSearchEnabled || opts.GLMSearchAPIKey == "" {
|
||||
if !opts.GLMSearchEnabled {
|
||||
return nil, 0, nil
|
||||
}
|
||||
client, err := utils.CreateHTTPClient(opts.Proxy, searchTimeout)
|
||||
@@ -1196,7 +1235,7 @@ func NewWebSearchTool(opts WebSearchToolOptions) (*WebSearchTool, error) {
|
||||
}
|
||||
|
||||
if provider == nil {
|
||||
for _, name := range []string{"sogou", "perplexity", "brave", "searxng", "tavily", "duckduckgo", "baidu_search", "glm_search"} {
|
||||
for _, name := range []string{"perplexity", "brave", "searxng", "tavily", "sogou", "duckduckgo", "baidu_search", "glm_search"} {
|
||||
provider, maxResults, err = opts.providerByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+54
-3
@@ -385,14 +385,24 @@ func TestWebFetchTool_PayloadTooLarge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebTool_WebSearch_NoApiKey verifies that no tool is created when API key is missing
|
||||
// TestWebTool_WebSearch_NoApiKey verifies missing credentials are surfaced at execution time.
|
||||
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.Errorf("Expected nil tool when Brave API key is empty")
|
||||
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)
|
||||
}
|
||||
|
||||
// Also nil when nothing is enabled
|
||||
@@ -1693,6 +1703,29 @@ func TestWebTool_SogouSearch_Success(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplySogouRangeHint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
rangeCode string
|
||||
want string
|
||||
}{
|
||||
{name: "empty range", query: "golang", rangeCode: "", want: "golang"},
|
||||
{name: "day", query: "golang", rangeCode: "d", want: "golang 最近一天"},
|
||||
{name: "week", query: "golang", rangeCode: "w", want: "golang 最近一周"},
|
||||
{name: "month", query: "golang", rangeCode: "m", want: "golang 最近一个月"},
|
||||
{name: "year", query: "golang", rangeCode: "y", want: "golang 最近一年"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := applySogouRangeHint(tt.query, tt.rangeCode); got != tt.want {
|
||||
t.Fatalf("applySogouRangeHint(%q, %q) = %q, want %q", tt.query, tt.rangeCode, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebTool_SogouPriorityAndExplicitProvider(t *testing.T) {
|
||||
tool, err := NewWebSearchTool(WebSearchToolOptions{
|
||||
SogouEnabled: true,
|
||||
@@ -1722,6 +1755,24 @@ func TestWebTool_SogouPriorityAndExplicitProvider(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebTool_AutoProviderPrefersConfiguredProvidersBeforeSogou(t *testing.T) {
|
||||
tool, err := NewWebSearchTool(WebSearchToolOptions{
|
||||
SogouEnabled: true,
|
||||
SogouMaxResults: 5,
|
||||
BraveEnabled: true,
|
||||
BraveAPIKeys: []string{"brave-key"},
|
||||
BraveMaxResults: 5,
|
||||
DuckDuckGoEnabled: true,
|
||||
DuckDuckGoMaxResults: 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewWebSearchTool() error: %v", err)
|
||||
}
|
||||
if _, ok := tool.provider.(*BraveSearchProvider); !ok {
|
||||
t.Fatalf("expected BraveSearchProvider, got %T", tool.provider)
|
||||
}
|
||||
}
|
||||
|
||||
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (fn roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
||||
Reference in New Issue
Block a user