// Package tools provides ready-to-use tool implementations for common agent patterns. package tools import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" llm "gitea.stevedudenhoeffer.com/steve/go-llm/v2" ) // WebSearchParams defines parameters for the web search tool. type WebSearchParams struct { Query string `json:"query" description:"The search query"` Count *int `json:"count,omitempty" description:"Number of results to return (default 5, max 20)"` } // WebSearch creates a web search tool using the Brave Search API. // // Get a free API key at https://brave.com/search/api/ // // Example: // // tools := llm.NewToolBox(tools.WebSearch("your-brave-api-key")) func WebSearch(apiKey string) llm.Tool { return llm.Define[WebSearchParams]("web_search", "Search the web for information using Brave Search", func(ctx context.Context, p WebSearchParams) (string, error) { count := 5 if p.Count != nil && *p.Count > 0 { count = *p.Count if count > 20 { count = 20 } } u := fmt.Sprintf("https://api.search.brave.com/res/v1/web/search?q=%s&count=%d", url.QueryEscape(p.Query), count) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return "", fmt.Errorf("creating request: %w", err) } req.Header.Set("Accept", "application/json") req.Header.Set("X-Subscription-Token", apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { return "", fmt.Errorf("search request failed: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("reading response: %w", err) } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("search API returned %d: %s", resp.StatusCode, string(body)) } // Parse and simplify the response var raw map[string]any if err := json.Unmarshal(body, &raw); err != nil { return string(body), nil } type result struct { Title string `json:"title"` URL string `json:"url"` Description string `json:"description"` } var results []result if web, ok := raw["web"].(map[string]any); ok { if items, ok := web["results"].([]any); ok { for _, item := range items { if m, ok := item.(map[string]any); ok { r := result{} if t, ok := m["title"].(string); ok { r.Title = t } if u, ok := m["url"].(string); ok { r.URL = u } if d, ok := m["description"].(string); ok { r.Description = d } results = append(results, r) } } } } out, _ := json.MarshalIndent(results, "", " ") return string(out), nil }, ) }