-
Notifications
You must be signed in to change notification settings - Fork 3
/
client.go
182 lines (150 loc) · 4.57 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package anthrogo
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"os"
"time"
)
const (
DefaultMaxRetries = 3
DefaultTimeout = time.Minute
DefaultVersion = "2023-06-01"
jitterFactor = 0.5
RequestTypeComplete = "complete"
RequestTypeMessages = "messages"
)
// ErrorResponse holds the error details in the response.
type ErrorResponse struct {
Error ErrorDetail `json:"error"`
}
// ErrorDetail describes the error type and message.
type ErrorDetail struct {
Type string `json:"type"`
Message string `json:"message"`
}
type HttpClient interface {
Do(req *http.Request) (*http.Response, error)
}
// Client is a structure holding all necessary fields for making requests to the API.
type Client struct {
baseURL string
version string
maxRetries int
timeout time.Duration
customHeaders map[string]string
httpClient HttpClient
apiKey string
}
// NewClient creates and returns a new Client. It applies the provided options to the client.
// If no API key is provided as an option, it looks for the API key in the environment variable ANTHROPIC_API_KEY.
func NewClient(options ...func(*Client)) (*Client, error) {
client := &Client{
version: DefaultVersion,
maxRetries: DefaultMaxRetries,
timeout: DefaultTimeout,
httpClient: &http.Client{},
apiKey: "",
baseURL: "https://api.anthropic.com/v1/",
}
for _, option := range options {
option(client)
}
if client.apiKey == "" {
apiKey, exists := os.LookupEnv("ANTHROPIC_API_KEY")
if !exists {
return nil, errors.New("ANTHROPIC_API_KEY not found in environment and not provided as option")
}
client.apiKey = apiKey
}
return client, nil
}
// Optional settings
// WithApiKey is an option to provide an API key for the Client.
func WithApiKey(apiKey string) func(*Client) {
return func(c *Client) {
c.apiKey = apiKey
}
}
// WithMaxRetries is an option to set the maximum number of retries for the Client.
func WithMaxRetries(maxRetries int) func(*Client) {
return func(c *Client) {
c.maxRetries = maxRetries
}
}
// WithTimeout is an option to set the timeout for the Client.
func WithTimeout(timeout time.Duration) func(*Client) {
return func(c *Client) {
c.timeout = timeout
}
}
// WithCustomHeaders is an option to set custom headers for the Client.
func WithCustomHeaders(headers map[string]string) func(*Client) {
return func(c *Client) {
c.customHeaders = headers
}
}
// WithVersion is an option to set the API version for the Client.
func WithVersion(version string) func(*Client) {
return func(c *Client) {
c.version = version
}
}
// setRequestHeaders sets the necessary headers for the HTTP request.
func (c *Client) setRequestHeaders(req *http.Request) {
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Anthropic-Version", c.version)
req.Header.Set("x-api-key", c.apiKey)
for key, value := range c.customHeaders {
req.Header.Set(key, value)
}
}
// createRequest creates and returns a new HTTP request with necessary headers.
func (c *Client) createRequest(ctx context.Context, payload any, requestType string) (*http.Request, context.CancelFunc, error) {
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, nil, err
}
req, err := http.NewRequest("POST", c.baseURL+requestType, bytes.NewBuffer(jsonData))
if err != nil {
return nil, nil, err
}
c.setRequestHeaders(req)
ctx, cancel := context.WithTimeout(ctx, c.timeout)
req = req.WithContext(ctx)
return req, cancel, nil
}
// doRequest sends an HTTP request and returns the response.
func (c *Client) doRequest(req *http.Request) (*http.Response, error) {
res, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
return res, nil
}
// doRequestWithRetries sends the HTTP request and retries upon failure up to the maximum retry limit.
func (c *Client) doRequestWithRetries(req *http.Request) (*http.Response, error) {
for i := 0; i < c.maxRetries; i++ {
response, err := c.doRequest(req)
if err != nil {
if i == c.maxRetries-1 {
return nil, err
}
time.Sleep(c.getSleepDuration(i))
continue
}
return response, nil
}
return nil, fmt.Errorf("failed to complete request after %d retries", c.maxRetries)
}
// getSleepDuration calculates and returns the sleep duration based on the retry attempt with added jitter.
func (c *Client) getSleepDuration(retry int) time.Duration {
sleepDuration := time.Duration(retry) * time.Second
jitter := time.Duration(rand.Float64() * jitterFactor * float64(sleepDuration))
return sleepDuration + jitter
}