Enhance code as comment from best friends

This commit is contained in:
PhotoPortfolio Developer 2026-02-19 23:42:34 +08:00
parent c3967a560d
commit 487edfa64e
4 changed files with 71 additions and 27 deletions

View file

@ -3,7 +3,7 @@
"defaults": { "defaults": {
"workspace": "~/.picoclaw/workspace", "workspace": "~/.picoclaw/workspace",
"restrict_to_workspace": true, "restrict_to_workspace": true,
"model": "ollama/qwen2.5:14b-instruct ", "model": "",
"max_tokens": 8192, "max_tokens": 8192,
"temperature": 0.7, "temperature": 0.7,
"max_tool_iterations": 20 "max_tool_iterations": 20
@ -119,11 +119,11 @@
"tools": { "tools": {
"web": { "web": {
"search": { "search": {
"provider": "ollama", "provider": "ollama/brave/duckduckgo",
"api_key": "Your Ollama API Key", "api_key": "Your API Key",
"endpoint": "https://ollama.com/api/web_search", "endpoint": "https://ollama.com/api/web_search",
"rest_type": "POST", "rest_type": "POST for ollama, GET for others",
"query_param": "query", "query_param": "query for ollama, q for others",
"max_results": 5 "max_results": 5
}, },
"perplexity": { "perplexity": {

View file

@ -70,7 +70,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
Param: cfg.Tools.Web.Search.QueryParam, Param: cfg.Tools.Web.Search.QueryParam,
}, },
{ {
Provider: "duckduckgo", Provider: "duckduckgo", //fallback to duckduckgo if not configured
MaxResults: 5, MaxResults: 5,
}, },
} }

View file

@ -283,23 +283,6 @@ type GatewayConfig struct {
Port int `json:"port" env:"PICOCLAW_GATEWAY_PORT"` 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 // WebSearchConfig defines a single active web search provider
// Falls back to DuckDuckGo if the primary provider fails // Falls back to DuckDuckGo if the primary provider fails
type WebSearchConfig struct { type WebSearchConfig struct {
@ -429,7 +412,7 @@ func DefaultConfig() *Config {
Web: WebToolsConfig{ Web: WebToolsConfig{
Search: WebSearchConfig{ Search: WebSearchConfig{
Provider: "ollama", Provider: "ollama",
APIKey: "77b893700a1d4c8dad9a7326be9a76d6.7pl0DA9ojPa_6UCMMZ_Sk-Cn", APIKey: "",
Endpoint: "https://ollama.com/api/web_search", Endpoint: "https://ollama.com/api/web_search",
RestType: "POST", RestType: "POST",
QueryParam: "query", QueryParam: "query",

View file

@ -10,6 +10,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/sipeed/picoclaw/pkg/logger"
) )
const ( const (
@ -208,10 +210,22 @@ func (p *OllamaSearchProvider) Search(ctx context.Context, query string, count i
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read response: %w", err) 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 the raw JSON string for the agent/tool to process
return string(body), nil return string(body), nil
} }
@ -283,6 +297,7 @@ func (p *PerplexitySearchProvider) Search(ctx context.Context, query string, cou
type WebSearchTool struct { type WebSearchTool struct {
provider SearchProvider provider SearchProvider
fallback SearchProvider
maxResults int maxResults int
} }
@ -298,11 +313,12 @@ type WebSearchTool struct {
// opts := []WebSearchToolOptions{ // opts := []WebSearchToolOptions{
// {Provider: "brave", APIKey: "key", MaxResults: 5}, // {Provider: "brave", APIKey: "key", MaxResults: 5},
// {Provider: "ollama", BaseURL: "http://localhost:11434", MaxResults: 5}, // {Provider: "ollama", BaseURL: "http://localhost:11434", MaxResults: 5},
// {Provider: "perplexity", APIKey: "key", MaxResults: 5},
// {Provider: "duckduckgo", MaxResults: 5}, // {Provider: "duckduckgo", MaxResults: 5},
// } // }
// tool := NewWebSearchTool(opts...) // tool := NewWebSearchTool(opts...)
type WebSearchToolOptions struct { type WebSearchToolOptions struct {
Provider string // "brave", "ollama", "duckduckgo" Provider string // "brave", "ollama", "perplexity", "duckduckgo"
APIKey string // For Brave API APIKey string // For Brave API
BaseURL string // For custom providers (e.g., Ollama) BaseURL string // For custom providers (e.g., Ollama)
MaxResults int // Default: 5 MaxResults int // Default: 5
@ -311,8 +327,8 @@ type WebSearchToolOptions struct {
} }
func NewWebSearchTool(opts ...WebSearchToolOptions) *WebSearchTool { func NewWebSearchTool(opts ...WebSearchToolOptions) *WebSearchTool {
// Priority order: Brave > Ollama > DuckDuckGo // Priority order: Brave > Ollama > Perplexity > DuckDuckGo
priorityOrder := []string{"brave", "ollama", "duckduckgo"} priorityOrder := []string{"brave", "ollama", "perplexity", "duckduckgo"}
optMap := make(map[string]WebSearchToolOptions) optMap := make(map[string]WebSearchToolOptions)
// Build map of enabled providers // Build map of enabled providers
@ -344,6 +360,15 @@ func NewWebSearchTool(opts ...WebSearchToolOptions) *WebSearchTool {
return nil 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 maxResults := 5
if selectedOpt != nil && selectedOpt.MaxResults > 0 { if selectedOpt != nil && selectedOpt.MaxResults > 0 {
maxResults = selectedOpt.MaxResults maxResults = selectedOpt.MaxResults
@ -351,6 +376,7 @@ func NewWebSearchTool(opts ...WebSearchToolOptions) *WebSearchTool {
return &WebSearchTool{ return &WebSearchTool{
provider: provider, provider: provider,
fallback: fallbackProvider,
maxResults: maxResults, maxResults: maxResults,
} }
} }
@ -386,6 +412,12 @@ func createProvider(opt *WebSearchToolOptions) (SearchProvider, error) {
case "duckduckgo": case "duckduckgo":
return &DuckDuckGoSearchProvider{}, nil return &DuckDuckGoSearchProvider{}, nil
case "perplexity":
if opt.APIKey == "" {
return nil, fmt.Errorf("Perplexity API key required")
}
return &PerplexitySearchProvider{apiKey: opt.APIKey}, nil
default: default:
return nil, fmt.Errorf("unknown provider: %s", opt.Provider) 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) result, err := t.provider.Search(ctx, query, count)
if err != nil { 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)) return ErrorResult(fmt.Sprintf("search failed: %v", err))
} }