Skip to content

Commit 292730a

Browse files
author
Test
committed
Add Tavily support for web search
- Support both Tavily and Exa APIs - Tavily takes priority (more popular for AI agents) - Update fallback message with both options - Update docs with Tavily setup
1 parent 57b3fd7 commit 292730a

File tree

2 files changed

+91
-15
lines changed

2 files changed

+91
-15
lines changed

docs/reference/commands.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -649,16 +649,23 @@ Available tools in autonomous modes (`gt do`, `gt run`, `gt chat`):
649649
650650
### Web Search Configuration
651651
652-
To enable web search:
652+
To enable web search, set one of these API keys:
653653
654654
```bash
655-
# Get free API key from https://exa.ai
655+
# Option 1: Tavily (recommended - $1/month for 1k searches)
656+
export TAVILY_API_KEY="your-key"
657+
658+
# Option 2: Exa (1k free/month)
656659
export EXA_API_KEY="your-key"
657660
658-
# Or use SEARCH_API_KEY env var
661+
# Or use a generic key
659662
export SEARCH_API_KEY="your-key"
660663
```
661664
665+
Get free API keys:
666+
- **Tavily**: https://tavily.com
667+
- **Exa**: https://exa.ai
668+
662669
Without API key, the tool shows setup instructions.
663670
664671
---

internal/tools/tools.go

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -560,13 +560,26 @@ func WebSearch(call ToolCall) ToolResult {
560560
}
561561

562562
func searchWeb(query string, numResults int) (string, error) {
563-
apiKey := os.Getenv("EXA_API_KEY")
564-
if apiKey == "" {
565-
apiKey = os.Getenv("SEARCH_API_KEY")
563+
// Check for Tavily first (more popular for AI agents)
564+
tavilyKey := os.Getenv("TAVILY_API_KEY")
565+
if tavilyKey != "" {
566+
return searchTavily(query, numResults, tavilyKey)
566567
}
567568

568-
if apiKey != "" {
569-
return searchExa(query, numResults, apiKey)
569+
// Check for Exa
570+
exaKey := os.Getenv("EXA_API_KEY")
571+
if exaKey != "" {
572+
return searchExa(query, numResults, exaKey)
573+
}
574+
575+
// Check generic search API key
576+
searchKey := os.Getenv("SEARCH_API_KEY")
577+
if searchKey != "" {
578+
// Try Exa first, fall back to Tavily
579+
if result, err := searchExa(query, numResults, searchKey); err == nil {
580+
return result, nil
581+
}
582+
return searchTavily(query, numResults, searchKey)
570583
}
571584

572585
return searchFallback(query, numResults)
@@ -625,15 +638,71 @@ func searchExa(query string, numResults int, apiKey string) (string, error) {
625638
return output.String(), nil
626639
}
627640

641+
func searchTavily(query string, numResults int, apiKey string) (string, error) {
642+
reqBody := fmt.Sprintf(`{"query": %q, "max_results": %d}`, query, numResults)
643+
req, err := http.NewRequest("POST", "https://api.tavily.com/search", strings.NewReader(reqBody))
644+
if err != nil {
645+
return "", err
646+
}
647+
648+
req.Header.Set("Content-Type", "application/json")
649+
650+
client := &http.Client{Timeout: 30 * time.Second}
651+
resp, err := client.Do(req)
652+
if err != nil {
653+
return "", err
654+
}
655+
defer resp.Body.Close()
656+
657+
if resp.StatusCode != 200 {
658+
return "", fmt.Errorf("tavily API returned status %d", resp.StatusCode)
659+
}
660+
661+
var result struct {
662+
Results []struct {
663+
Title string `json:"title"`
664+
URL string `json:"url"`
665+
Content string `json:"content"`
666+
Score float64 `json:"score"`
667+
} `json:"results"`
668+
}
669+
670+
body, _ := io.ReadAll(resp.Body)
671+
if err := json.Unmarshal(body, &result); err != nil {
672+
return "", err
673+
}
674+
675+
if len(result.Results) == 0 {
676+
return "No results found", nil
677+
}
678+
679+
var output strings.Builder
680+
output.WriteString("Search Results:\n\n")
681+
for i, r := range result.Results {
682+
output.WriteString(fmt.Sprintf("%d. %s\n", i+1, r.Title))
683+
output.WriteString(fmt.Sprintf(" URL: %s\n", r.URL))
684+
truncated := r.Content
685+
if len(truncated) > 300 {
686+
truncated = truncated[:300] + "..."
687+
}
688+
output.WriteString(fmt.Sprintf(" %s\n\n", truncated))
689+
}
690+
691+
return output.String(), nil
692+
}
693+
628694
func searchFallback(query string, numResults int) (string, error) {
629-
return fmt.Sprintf(`Web search not configured. To enable:
695+
return fmt.Sprintf(`Web search not configured. To enable, get a free API key:
630696
631-
1. Get a free API key from https://exa.ai (or use SEARCH_API_KEY env var)
632-
2. Set: export EXA_API_KEY="your-key"
697+
# Option 1: Tavily (recommended - $1/month for 1k searches)
698+
https://tavily.com
699+
export TAVILY_API_KEY="your-key"
633700
634-
For now, here's a suggestion for manual search:
635-
- Query: %s
636-
- Search manually at: https://www.google.com/search?q=%s
701+
# Option 2: Exa (1k free/month)
702+
https://exa.ai
703+
export EXA_API_KEY="your-key"
637704
638-
Alternatively, use DuckDuckGo (no API key needed in browser).`, query, query), nil
705+
# Then retry your search.
706+
- Query: %s
707+
- Search manually: https://www.google.com/search?q=%s`, query, query), nil
639708
}

0 commit comments

Comments
 (0)