@@ -560,13 +560,26 @@ func WebSearch(call ToolCall) ToolResult {
560560}
561561
562562func 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+
628694func 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