Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
144
backend/examples/plugin/claude-web-search-router/go/tavily.go
Normal file
144
backend/examples/plugin/claude-web-search-router/go/tavily.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const tavilySearchURL = "https://api.tavily.com/search"
|
||||
|
||||
type tavilyClient struct {
|
||||
keys []string
|
||||
idx atomic.Uint64
|
||||
http *http.Client
|
||||
baseURL string // empty → https://api.tavily.com/search
|
||||
}
|
||||
|
||||
func newTavilyClient(keys []string) *tavilyClient {
|
||||
return newTavilyClientWithOptions(keys, nil, "")
|
||||
}
|
||||
|
||||
func newTavilyClientWithOptions(keys []string, httpClient *http.Client, baseURL string) *tavilyClient {
|
||||
trimmed := make([]string, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
if k := strings.TrimSpace(key); k != "" {
|
||||
trimmed = append(trimmed, k)
|
||||
}
|
||||
}
|
||||
if httpClient == nil {
|
||||
httpClient = &http.Client{}
|
||||
}
|
||||
return &tavilyClient{
|
||||
keys: trimmed,
|
||||
http: httpClient,
|
||||
baseURL: strings.TrimSpace(baseURL),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *tavilyClient) searchEndpoint() string {
|
||||
if c != nil && c.baseURL != "" {
|
||||
return c.baseURL
|
||||
}
|
||||
return tavilySearchURL
|
||||
}
|
||||
|
||||
func (c *tavilyClient) available() bool {
|
||||
return c != nil && len(c.keys) > 0
|
||||
}
|
||||
|
||||
func (c *tavilyClient) nextKey() string {
|
||||
if len(c.keys) == 0 {
|
||||
return ""
|
||||
}
|
||||
n := c.idx.Add(1)
|
||||
return c.keys[int(n-1)%len(c.keys)]
|
||||
}
|
||||
|
||||
type tavilySearchRequest struct {
|
||||
APIKey string `json:"api_key"`
|
||||
Query string `json:"query"`
|
||||
SearchDepth string `json:"search_depth,omitempty"`
|
||||
MaxResults int `json:"max_results,omitempty"`
|
||||
IncludeAnswer bool `json:"include_answer,omitempty"`
|
||||
}
|
||||
|
||||
type tavilySearchResponse struct {
|
||||
Answer string `json:"answer"`
|
||||
Results []struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Content string `json:"content"`
|
||||
} `json:"results"`
|
||||
}
|
||||
|
||||
type claudeWebSearchHit struct {
|
||||
Title string
|
||||
URL string
|
||||
Snippet string
|
||||
}
|
||||
|
||||
func (c *tavilyClient) search(ctx context.Context, query string, maxResults int) ([]claudeWebSearchHit, string, error) {
|
||||
if !c.available() {
|
||||
return nil, "", fmt.Errorf("tavily_api_keys is empty")
|
||||
}
|
||||
query = strings.TrimSpace(query)
|
||||
if query == "" {
|
||||
return nil, "", fmt.Errorf("web search query is empty")
|
||||
}
|
||||
if maxResults <= 0 {
|
||||
maxResults = 5
|
||||
}
|
||||
payload, errMarshal := json.Marshal(tavilySearchRequest{
|
||||
APIKey: c.nextKey(),
|
||||
Query: query,
|
||||
SearchDepth: "basic",
|
||||
MaxResults: maxResults,
|
||||
IncludeAnswer: true,
|
||||
})
|
||||
if errMarshal != nil {
|
||||
return nil, "", errMarshal
|
||||
}
|
||||
req, errNew := http.NewRequestWithContext(ctx, http.MethodPost, c.searchEndpoint(), bytes.NewReader(payload))
|
||||
if errNew != nil {
|
||||
return nil, "", errNew
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, errDo := c.http.Do(req)
|
||||
if errDo != nil {
|
||||
return nil, "", errDo
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
body, errRead := io.ReadAll(resp.Body)
|
||||
if errRead != nil {
|
||||
return nil, "", errRead
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return nil, "", fmt.Errorf("tavily http %d: %s", resp.StatusCode, truncate(string(body), 512))
|
||||
}
|
||||
var parsed tavilySearchResponse
|
||||
if errDecode := json.Unmarshal(body, &parsed); errDecode != nil {
|
||||
return nil, "", errDecode
|
||||
}
|
||||
hits := make([]claudeWebSearchHit, 0, len(parsed.Results))
|
||||
for _, r := range parsed.Results {
|
||||
hits = append(hits, claudeWebSearchHit{
|
||||
Title: strings.TrimSpace(r.Title),
|
||||
URL: strings.TrimSpace(r.URL),
|
||||
Snippet: strings.TrimSpace(r.Content),
|
||||
})
|
||||
}
|
||||
return hits, strings.TrimSpace(parsed.Answer), nil
|
||||
}
|
||||
|
||||
func truncate(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
return s[:max] + "..."
|
||||
}
|
||||
Loading…
Reference in a new issue