From 487edfa64ece32b80fc638d2059b7ff5723defe1 Mon Sep 17 00:00:00 2001 From: PhotoPortfolio Developer Date: Thu, 19 Feb 2026 23:42:34 +0800 Subject: [PATCH] Enhance code as comment from best friends --- config/config.example.json | 10 +++--- pkg/agent/loop.go | 2 +- pkg/config/config.go | 19 +---------- pkg/tools/web.go | 67 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/config/config.example.json b/config/config.example.json index 17c0abcc7..89c554d76 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -3,7 +3,7 @@ "defaults": { "workspace": "~/.picoclaw/workspace", "restrict_to_workspace": true, - "model": "ollama/qwen2.5:14b-instruct ", + "model": "", "max_tokens": 8192, "temperature": 0.7, "max_tool_iterations": 20 @@ -119,11 +119,11 @@ "tools": { "web": { "search": { - "provider": "ollama", - "api_key": "Your Ollama API Key", + "provider": "ollama/brave/duckduckgo", + "api_key": "Your API Key", "endpoint": "https://ollama.com/api/web_search", - "rest_type": "POST", - "query_param": "query", + "rest_type": "POST for ollama, GET for others", + "query_param": "query for ollama, q for others", "max_results": 5 }, "perplexity": { diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index d41a36eba..aa2a71905 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -70,7 +70,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers Param: cfg.Tools.Web.Search.QueryParam, }, { - Provider: "duckduckgo", + Provider: "duckduckgo", //fallback to duckduckgo if not configured MaxResults: 5, }, } diff --git a/pkg/config/config.go b/pkg/config/config.go index a83f92ee3..8d29dd485 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -283,23 +283,6 @@ type GatewayConfig struct { Port int `json:"port" env:"PICOCLAW_GATEWAY_PORT"` } -type BraveConfig struct { - Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_BRAVE_ENABLED"` - APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_BRAVE_API_KEY"` - MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_BRAVE_MAX_RESULTS"` -} - -type DuckDuckGoConfig struct { - Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_DUCKDUCKGO_ENABLED"` - MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"` -} - -type OllamaConfig struct { - Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_OLLAMA_ENABLED"` - BaseURL string `json:"base_url" env:"PICOCLAW_TOOLS_WEB_OLLAMA_BASE_URL"` - MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_OLLAMA_MAX_RESULTS"` -} - // WebSearchConfig defines a single active web search provider // Falls back to DuckDuckGo if the primary provider fails type WebSearchConfig struct { @@ -429,7 +412,7 @@ func DefaultConfig() *Config { Web: WebToolsConfig{ Search: WebSearchConfig{ Provider: "ollama", - APIKey: "77b893700a1d4c8dad9a7326be9a76d6.7pl0DA9ojPa_6UCMMZ_Sk-Cn", + APIKey: "", Endpoint: "https://ollama.com/api/web_search", RestType: "POST", QueryParam: "query", diff --git a/pkg/tools/web.go b/pkg/tools/web.go index 3028cdec9..5894c86d1 100644 --- a/pkg/tools/web.go +++ b/pkg/tools/web.go @@ -10,6 +10,8 @@ import ( "regexp" "strings" "time" + + "github.com/sipeed/picoclaw/pkg/logger" ) const ( @@ -208,10 +210,22 @@ func (p *OllamaSearchProvider) Search(ctx context.Context, query string, count i defer resp.Body.Close() body, err := io.ReadAll(resp.Body) + if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } + if resp.StatusCode != http.StatusOK { + logger.ErrorCF("tool", "Ollama web search request failed", + map[string]interface{}{ + "provider": "ollama", + "status_code": resp.StatusCode, + "endpoint": p.baseURL, + "response": strings.TrimSpace(string(body)), + }) + return "", fmt.Errorf("Ollama API error: %s", string(body)) + } + // Return the raw JSON string for the agent/tool to process return string(body), nil } @@ -283,6 +297,7 @@ func (p *PerplexitySearchProvider) Search(ctx context.Context, query string, cou type WebSearchTool struct { provider SearchProvider + fallback SearchProvider maxResults int } @@ -298,11 +313,12 @@ type WebSearchTool struct { // opts := []WebSearchToolOptions{ // {Provider: "brave", APIKey: "key", MaxResults: 5}, // {Provider: "ollama", BaseURL: "http://localhost:11434", MaxResults: 5}, +// {Provider: "perplexity", APIKey: "key", MaxResults: 5}, // {Provider: "duckduckgo", MaxResults: 5}, // } // tool := NewWebSearchTool(opts...) type WebSearchToolOptions struct { - Provider string // "brave", "ollama", "duckduckgo" + Provider string // "brave", "ollama", "perplexity", "duckduckgo" APIKey string // For Brave API BaseURL string // For custom providers (e.g., Ollama) MaxResults int // Default: 5 @@ -311,8 +327,8 @@ type WebSearchToolOptions struct { } func NewWebSearchTool(opts ...WebSearchToolOptions) *WebSearchTool { - // Priority order: Brave > Ollama > DuckDuckGo - priorityOrder := []string{"brave", "ollama", "duckduckgo"} + // Priority order: Brave > Ollama > Perplexity > DuckDuckGo + priorityOrder := []string{"brave", "ollama", "perplexity", "duckduckgo"} optMap := make(map[string]WebSearchToolOptions) // Build map of enabled providers @@ -344,6 +360,15 @@ func NewWebSearchTool(opts ...WebSearchToolOptions) *WebSearchTool { return nil } + var fallbackProvider SearchProvider + if selectedOpt != nil && selectedOpt.Provider != "duckduckgo" { + if ddgOpt, exists := optMap["duckduckgo"]; exists { + if p, err := createProvider(&ddgOpt); err == nil && p != nil { + fallbackProvider = p + } + } + } + maxResults := 5 if selectedOpt != nil && selectedOpt.MaxResults > 0 { maxResults = selectedOpt.MaxResults @@ -351,6 +376,7 @@ func NewWebSearchTool(opts ...WebSearchToolOptions) *WebSearchTool { return &WebSearchTool{ provider: provider, + fallback: fallbackProvider, maxResults: maxResults, } } @@ -386,6 +412,12 @@ func createProvider(opt *WebSearchToolOptions) (SearchProvider, error) { case "duckduckgo": return &DuckDuckGoSearchProvider{}, nil + case "perplexity": + if opt.APIKey == "" { + return nil, fmt.Errorf("Perplexity API key required") + } + return &PerplexitySearchProvider{apiKey: opt.APIKey}, nil + default: return nil, fmt.Errorf("unknown provider: %s", opt.Provider) } @@ -433,6 +465,35 @@ func (t *WebSearchTool) Execute(ctx context.Context, args map[string]interface{} result, err := t.provider.Search(ctx, query, count) if err != nil { + if t.fallback != nil { + logger.WarnCF("tool", "Primary web search failed, attempting fallback", + map[string]interface{}{ + "primary_error": err.Error(), + "fallback": "duckduckgo", + "query": query, + }) + + fallbackResult, fallbackErr := t.fallback.Search(ctx, query, count) + if fallbackErr == nil { + logger.InfoCF("tool", "Fallback web search succeeded", + map[string]interface{}{ + "provider": "duckduckgo", + "query": query, + }) + + return &ToolResult{ + ForLLM: fmt.Sprintf("Primary search provider failed: %v\nFallback provider (duckduckgo) result:\n%s", err, fallbackResult), + ForUser: fallbackResult, + } + } + + logger.ErrorCF("tool", "Fallback web search failed", + map[string]interface{}{ + "provider": "duckduckgo", + "error": fallbackErr.Error(), + "query": query, + }) + } return ErrorResult(fmt.Sprintf("search failed: %v", err)) }