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>
102 lines
2.6 KiB
Go
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
|
|
},
|
|
)
|
|
}
|