Skip to content

Commit d5dd51e

Browse files
committed
feat: Add the initial Anthropic SDK implementation
1 parent 1f87886 commit d5dd51e

File tree

11 files changed

+325
-2
lines changed

11 files changed

+325
-2
lines changed

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ trim_trailing_whitespace = true
1313
[*.{go,mod}]
1414
indent_size = 4
1515
tab_width = 4
16+
17+
[*.md]
18+
indent_size = 4
19+
tab_width = 4
20+
trim_trailing_whitespace = false

.github/workflows/pr.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: PR
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths-ignore:
8+
- .editorconfig
9+
- .gitignore
10+
- LICENSE.md
11+
- README.md
12+
13+
jobs:
14+
verify:
15+
name: Verify
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
- name: Setup Go
21+
uses: actions/setup-go@v5
22+
with:
23+
go-version-file: go.mod
24+
- name: Check formatting
25+
run: test -z "$(go fmt ./...)"
26+
27+
build:
28+
name: Build
29+
needs: [ verify ]
30+
runs-on: ubuntu-latest
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v4
34+
- name: Setup Go
35+
uses: actions/setup-go@v5
36+
with:
37+
go-version-file: go.mod

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
*.out
66
*.so
77
*.test
8+
/examples
89
go.work

README.md

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Anthropic SDK for Go
22

3+
Client library for interacting with the [Anthropic] safety-first language model
4+
REST APIs.
5+
36
## Getting started
47

58
### Requirements
@@ -8,17 +11,50 @@
811

912
### Installation and usage
1013

11-
TODO
14+
```go
15+
package main
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"os"
21+
22+
"github.com/unfunco/anthropic-sdk-go"
23+
)
24+
25+
func main() {
26+
ctx := context.Background()
27+
28+
transport := &anthropic.Transport{APIKey: os.Getenv("ANTHROPIC_API_KEY")}
29+
claude := anthropic.NewClient(transport.Client())
30+
31+
if resp, httpResp, err := claude.Messages.Create(ctx, &anthropic.CreateMessageInput{
32+
Model: anthropic.Claude3Opus20240229,
33+
}); err == nil {
34+
fmt.Println(httpResp.StatusCode)
35+
fmt.Println(resp)
36+
} else {
37+
_, _ = fmt.Fprintln(os.Stderr, err)
38+
os.Exit(1)
39+
}
40+
}
41+
```
1242

1343
### Development and testing
1444

15-
TODO
45+
Clone the repository and change into the `anthropic-sdk-go` directory.
46+
47+
```bash
48+
git clone git@github.com:unfunco/anthropic-sdk-go.git
49+
cd anthropic-sdk-go
50+
```
1651

1752
## License
1853

1954
© 2024 [Daniel Morris]\
2055
Made available under the terms of the [MIT License].
2156

57+
[anthropic]: https://www.anthropic.com
2258
[daniel morris]: https://unfun.co
2359
[go]: https://go.dev
2460
[mit license]: LICENSE.md

anthropic.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Package anthropic provides a client library for interacting with the
2+
// Anthropic safety-first language model REST APIs.
3+
package anthropic
4+
5+
import (
6+
"bytes"
7+
"context"
8+
"encoding/json"
9+
"io"
10+
"net/http"
11+
"net/url"
12+
)
13+
14+
const (
15+
defaultAPIVersion = "2023-06-01"
16+
defaultBaseURL = "https://api.anthropic.com/v1/"
17+
defaultUserAgent = "unfunco/anthropic-sdk-go@v0.0.0"
18+
)
19+
20+
// Client manages communication with the Anthropic REST API.
21+
type Client struct {
22+
baseURL *url.URL
23+
client *http.Client
24+
reusable service
25+
26+
Messages *MessagesService
27+
}
28+
29+
type service struct{ client *Client }
30+
31+
// NewClient returns a new Anthropic REST API client.
32+
func NewClient(client *http.Client) *Client {
33+
if client == nil {
34+
client = &http.Client{}
35+
}
36+
37+
claude := &Client{client: client}
38+
claude.baseURL, _ = url.Parse(defaultBaseURL)
39+
claude.reusable.client = claude
40+
claude.Messages = (*MessagesService)(&claude.reusable)
41+
42+
return claude
43+
}
44+
45+
// NewRequest creates an API request.
46+
func (c *Client) NewRequest(method, path string, body any) (*http.Request, error) {
47+
u, err := c.baseURL.Parse(path)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
var buf io.ReadWriter
53+
if body != nil {
54+
buf = &bytes.Buffer{}
55+
enc := json.NewEncoder(buf)
56+
enc.SetEscapeHTML(false)
57+
err = enc.Encode(body)
58+
if err != nil {
59+
return nil, err
60+
}
61+
}
62+
63+
req, err := http.NewRequest(method, u.String(), buf)
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
req.Header.Set("anthropic-version", defaultAPIVersion)
69+
req.Header.Set("user-agent", defaultUserAgent)
70+
71+
if body != nil {
72+
req.Header.Set("content-type", "application/json")
73+
}
74+
75+
return req, nil
76+
}
77+
78+
// Do sends an API request and returns the API response.
79+
func (c *Client) Do(ctx context.Context, req *http.Request, v any) (*http.Response, error) {
80+
req = req.WithContext(ctx)
81+
resp, err := c.client.Do(req)
82+
if err != nil {
83+
select {
84+
case <-ctx.Done():
85+
return nil, ctx.Err()
86+
default:
87+
}
88+
89+
return nil, err
90+
}
91+
//goland:noinspection GoUnhandledErrorResult
92+
defer resp.Body.Close()
93+
94+
switch v := v.(type) {
95+
case nil:
96+
case io.Writer:
97+
_, err = io.Copy(v, resp.Body)
98+
default:
99+
decErr := json.NewDecoder(resp.Body).Decode(v)
100+
if decErr == io.EOF {
101+
decErr = nil
102+
}
103+
if decErr != nil {
104+
err = decErr
105+
}
106+
}
107+
108+
return resp, nil
109+
}

anthropic_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package anthropic
2+
3+
import "testing"
4+
5+
func TestNewClient(t *testing.T) {
6+
c1 := NewClient(nil)
7+
c2 := NewClient(nil)
8+
9+
if c1.client == c2.client {
10+
t.Error("NewClient returned equal http.Clients but they should differ")
11+
}
12+
}
13+
14+
func TestClient_NewRequest(t *testing.T) {
15+
_ = NewClient(nil)
16+
}

messages.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package anthropic
2+
3+
import (
4+
"context"
5+
"net/http"
6+
)
7+
8+
type MessagesService service
9+
10+
// Message defines ...
11+
type Message struct {
12+
Content string `json:"content"`
13+
Role string `json:"role"`
14+
}
15+
16+
// CreateMessageInput defines ...
17+
type CreateMessageInput struct {
18+
// MaxTokens defines the maximum number of tokens to generate before
19+
// stopping. Token generation may stop before reaching this limit, this only
20+
// specifies the absolute maximum number of tokens to generate. Different
21+
// models have different maximum token limits.
22+
MaxTokens int `json:"max_tokens"`
23+
// Messages ...
24+
Messages []Message `json:"messages"`
25+
// Model defines the language model that will be used to complete the
26+
// prompt. See model.go for a list of available models.
27+
Model LanguageModel `json:"model"`
28+
// StopSequences defines custom text sequences that will cause the model to
29+
// stop generating.
30+
StopSequences []string `json:"stop_sequences,omitempty"`
31+
// System provides a means of specifying context and instructions to the
32+
// model, such as specifying a particular goal or role.
33+
System string `json:"system,omitempty"`
34+
// Temperature defines the amount of randomness injected into the response.
35+
// Note that even with a temperature of 0.0, results will not be fully
36+
// deterministic.
37+
Temperature *float64 `json:"temperature,omitempty"`
38+
// TopK is used to remove long tail low probability responses.
39+
// Recommended for advanced use cases only. You usually only need to use
40+
// Temperature.
41+
TopK *int `json:"top_k,omitempty"`
42+
// TopP (nucleus-sampling) defines the cumulative probability of the highest probability.
43+
// Recommended for advanced use cases only. You usually only need to use
44+
// Temperature.
45+
TopP *float64 `json:"top_p,omitempty"`
46+
}
47+
48+
type CreateMessageOutput struct {
49+
Id string `json:"id"`
50+
Model string `json:"model"`
51+
}
52+
53+
// Create creates a new message using the provided options.
54+
func (c *MessagesService) Create(
55+
ctx context.Context,
56+
in *CreateMessageInput,
57+
) (*CreateMessageOutput, *http.Response, error) {
58+
req, err := c.client.NewRequest(http.MethodPost, "messages", in)
59+
if err != nil {
60+
return nil, nil, err
61+
}
62+
63+
out := new(CreateMessageOutput)
64+
resp, err := c.client.Do(ctx, req, out)
65+
if err != nil {
66+
return nil, resp, err
67+
}
68+
69+
return out, resp, nil
70+
}

messages_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package anthropic

model.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package anthropic
2+
3+
// LanguageModel represents a language model that can be used to complete
4+
// a prompt.
5+
// https://docs.anthropic.com/claude/docs/models-overview
6+
type LanguageModel string
7+
8+
const (
9+
Claude3Opus20240229 LanguageModel = "claude-3-opus-20240229"
10+
Claude3Sonnet20240229 LanguageModel = "claude-3-sonnet-20240229"
11+
Claude3Haiku20240229 LanguageModel = "claude-3-haiku-20240307"
12+
Claude21 LanguageModel = "claude-2.1"
13+
Claude20 LanguageModel = "claude-2.0"
14+
ClaudeInstant12 LanguageModel = "claude-instant-1.2"
15+
)

transport.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package anthropic
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"net/url"
7+
)
8+
9+
// Transport ...
10+
type Transport struct {
11+
APIKey string
12+
}
13+
14+
func (t *Transport) Client() *http.Client {
15+
return &http.Client{
16+
Transport: t,
17+
}
18+
}
19+
20+
// RoundTrip implements the http.RoundTripper interface.
21+
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
22+
if t.APIKey == "" {
23+
return nil, errors.New("API key is required")
24+
}
25+
26+
newReq := new(http.Request)
27+
*newReq = *req
28+
newReq.URL = new(url.URL)
29+
*newReq.URL = *req.URL
30+
31+
return http.DefaultTransport.RoundTrip(newReq)
32+
}

transport_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package anthropic

0 commit comments

Comments
 (0)