Files
go-llm/v2/tools/websearch.go
Steve Dudenhoeffer a4cb4baab5 Add go-llm v2: redesigned API for simpler LLM abstraction
v2 is a new Go module (v2/) with a dramatically simpler API:
- Unified Message type (no more Input marker interface)
- Define[T] for ergonomic tool creation with standard context.Context
- Chat session with automatic tool-call loop (agent loop)
- Streaming via pull-based StreamReader
- MCP one-call connect (MCPStdioServer, MCPHTTPServer, MCPSSEServer)
- Middleware support (logging, retry, timeout, usage tracking)
- Decoupled JSON Schema (map[string]any, no provider coupling)
- Sample tools: WebSearch, Browser, Exec, ReadFile, WriteFile, HTTP
- Providers: OpenAI, Anthropic, Google (all with streaming)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:00:08 -05:00

102 lines
2.6 KiB
Go

// 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
},
)
}