Skip to content

Commit

Permalink
chore: Improve documentation in source files
Browse files Browse the repository at this point in the history
  • Loading branch information
unfunco committed Apr 18, 2024
1 parent b49ea86 commit 544238c
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.editorconfig export-ignore
.github/ export-ignore
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Anthropic SDK for Go

> [!NOTE]
> This is not an official SDK and I have no affiliation with [Anthropic].
Client library for interacting with the [Anthropic] safety-first language model
REST APIs.

Expand Down Expand Up @@ -29,7 +32,7 @@ func main() {
claude := anthropic.NewClient(transport.Client())

if resp, httpResp, err := claude.Messages.Create(ctx, &anthropic.CreateMessageInput{
MaxTokens: 100,
MaxTokens: 1024,
Messages: []anthropic.Message{
{Content: "Hello, Claude!", Role: "user"},
},
Expand All @@ -53,6 +56,8 @@ git clone [email protected]:unfunco/anthropic-sdk-go.git
cd anthropic-sdk-go
```

## References

## License

© 2024 [Daniel Morris]\
Expand Down
44 changes: 33 additions & 11 deletions anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,40 @@ const (

// Client manages communication with the Anthropic REST API.
type Client struct {
baseURL *url.URL
client *http.Client
reusable service
baseURL *url.URL
httpClient *http.Client
reusable service

Messages *MessagesService
}

type service struct{ client *Client }

// NewClient returns a new Anthropic REST API client.
func NewClient(client *http.Client) *Client {
if client == nil {
client = &http.Client{}
// NewClient returns a new Anthropic REST API client. If a nil httpClient is
// provided, a new http.Client will be used.
// Adapted from go-github's NewClient method:
// https://github.com/google/go-github/blob/master/github/github.go
func NewClient(httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = &http.Client{}
}

claude := &Client{client: client}
claude := &Client{httpClient: httpClient}
claude.baseURL, _ = url.Parse(defaultBaseURL)
claude.reusable.client = claude

claude.Messages = (*MessagesService)(&claude.reusable)

return claude
}

// NewRequest creates an API request.
// NewRequest creates an API request. A relative URL can be provided in path,
// in which case it is resolved relative to the BaseURL of the Client.
// Paths should always be specified without a preceding slash.
// If specified, the value pointed to by body is JSON encoded and included as
// the request body.
// Adapted from go-github's Client.NewRequest method:
// https://github.com/google/go-github/blob/master/github/github.go
func (c *Client) NewRequest(method, path string, body any) (*http.Request, error) {
u, err := c.baseURL.Parse(path)
if err != nil {
Expand Down Expand Up @@ -75,10 +85,21 @@ func (c *Client) NewRequest(method, path string, body any) (*http.Request, error
return req, nil
}

// Do sends an API request and returns the API response.
// Do sends an API request and returns the API response. The API response is
// JSON decoded and stored in the value pointed to by v, or returned as an error
// if an API error has occurred. If v implements the io.Writer interface, the
// raw response body will be written to v without attempting to first decode it.
// If v is nil, and no error occurs, the response is returned as-is.
// Adapted from go-github's Client.BareDo and Client.Do methods:
// https://github.com/google/go-github/blob/master/github/github.go
func (c *Client) Do(ctx context.Context, req *http.Request, v any) (*http.Response, error) {
if ctx == nil {
ctx = context.Background()
}

req = req.WithContext(ctx)
resp, err := c.client.Do(req)

resp, err := c.httpClient.Do(req)
if err != nil {
select {
case <-ctx.Done():
Expand All @@ -88,6 +109,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v any) (*http.Respon

return nil, err
}

//goland:noinspection GoUnhandledErrorResult
defer resp.Body.Close()

Expand Down
21 changes: 18 additions & 3 deletions anthropic_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
package anthropic

import "testing"
import (
"net/http"
"testing"
)

func TestNewClient(t *testing.T) {
c1 := NewClient(nil)
c2 := NewClient(nil)

if c1.client == c2.client {
if c1.httpClient == c2.httpClient {
t.Error("NewClient returned equal http.Clients but they should differ")
}
}

func TestClient_NewRequest(t *testing.T) {
_ = NewClient(nil)
c := NewClient(nil)
req, err := c.NewRequest(http.MethodPost, "example", nil)
if err != nil {
t.Error(err)
}

if req.Method != http.MethodPost {
t.Errorf("req.Method = %q, want %q", req.Method, http.MethodPost)
}

if req.URL.String() != "https://api.anthropic.com/v1/example" {
t.Errorf("req.URL = %q, want %q", req.URL.String(), "https://api.anthropic.com/v1/example")
}
}
65 changes: 30 additions & 35 deletions messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,13 @@ type Message struct {
Role string `json:"role"`
}

// Usage defines billing and rate-limit usage information.
// Billing and rate-limiting are driven by token counts, since tokens represent
// the underlying cost to Anthropic.
type Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
}

type Content struct {
Type string `json:"type"`
Text string `json:"text"`
}

// CreateMessageInput defines a structured list of input messages.
type CreateMessageInput struct {
// MaxTokens defines the maximum number of tokens to generate before
// stopping. Token generation may stop before reaching this limit, this only
// specifies the absolute maximum number of tokens to generate. Different
// models have different maximum token limits.
MaxTokens int `json:"max_tokens"`
// Messages are the input messages, models are trained to operate on
// alternating user and assistant conversational turns. When creating a new
// message, prior conversational turns can be specified with this field.
Messages []Message `json:"messages"`
// Model defines the language model that will be used to complete the
// prompt. See model.go for a list of available models.
Model LanguageModel `json:"model"`
// StopSequences defines custom text sequences that will cause the model to
// stop generating.
StopSequences []string `json:"stop_sequences,omitempty"`
// System provides a means of specifying context and instructions to the
// model, such as specifying a particular goal or role.
System string `json:"system,omitempty"`
// Temperature defines the amount of randomness injected into the response.
// Note that even with a temperature of 0.0, results will not be fully
// deterministic.
Expand All @@ -54,23 +28,44 @@ type CreateMessageInput struct {
// Recommended for advanced use cases only. You usually only need to use
// Temperature.
TopK *int `json:"top_k,omitempty"`
// TopP (nucleus-sampling) defines the cumulative probability of the highest probability.
// TopP is the nucleus-sampling parameter.
// Recommended for advanced use cases only. You usually only need to use
// Temperature.
TopP *float64 `json:"top_p,omitempty"`
// Model defines the language model that will be used to complete the
// prompt. See model.go for a list of available models.
Model LanguageModel `json:"model"`
// System provides a means of specifying context and instructions to the
// model, such as specifying a particular goal or role.
System string `json:"system,omitempty"`
// Messages are the input messages, models are trained to operate on
// alternating user and assistant conversational turns. When creating a new
// message, prior conversational turns can be specified with this field.
Messages []Message `json:"messages"`
// StopSequences defines custom text sequences that will cause the model to
// stop generating.
StopSequences []string `json:"stop_sequences,omitempty"`
// MaxTokens defines the maximum number of tokens to generate before
// stopping. Token generation may stop before reaching this limit, this only
// specifies the absolute maximum number of tokens to generate. Different
// models have different maximum token limits.
MaxTokens int `json:"max_tokens"`
}

// CreateMessageOutput defines the response from creating a new message.
type CreateMessageOutput struct {
Id *string `json:"id"`
Type *string `json:"type"`
Role *string `json:"role"`
Model *string `json:"model"`
StopSequence *string `json:"stop_sequence"`
StopReason *string `json:"stop_reason"`
Content []*Content `json:"content"`
Usage *Usage `json:"usage"`
ID *string `json:"id"`
Type *string `json:"type"`
Role *string `json:"role"`
Model *string `json:"model"`
StopSequence *string `json:"stop_sequence"`
StopReason *string `json:"stop_reason"`
Usage *Usage `json:"usage"`
// Content is a list of generated messages.
Content []*Content `json:"content"`
}

// String implements the fmt.Stringer interface for CreateMessageOutput.
func (c *CreateMessageOutput) String() string {
return c.Content[0].Text
}
Expand Down
7 changes: 6 additions & 1 deletion transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ import (
"net/url"
)

// Transport ...
// Transport is a http.RoundTripper that includes an API key in each request.
type Transport struct {
APIKey string
}

// Client returns an HTTP client that will include the API key in the request,
// and is safe for concurrent use by multiple goroutines.
func (t *Transport) Client() *http.Client {
return &http.Client{
Transport: t,
}
}

// RoundTrip implements the http.RoundTripper interface.
// It makes a copy of the HTTP request so that it complies with the requirements
// of the interface and adds the API key to the new request before calling the
// default http.RoundTripper.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.APIKey == "" {
return nil, errors.New("API key is required")
Expand Down
15 changes: 15 additions & 0 deletions usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package anthropic

import "fmt"

// Usage defines billing and rate-limit usage information. Billing and
// rate-limiting are driven by token counts.
type Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
}

// String implements the fmt.Stringer interface for Usage.
func (u *Usage) String() string {
return fmt.Sprintf("Input: %d, Output: %d", u.InputTokens, u.OutputTokens)
}
39 changes: 39 additions & 0 deletions usage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package anthropic

import "testing"

func TestUsage_String(t *testing.T) {
tests := []struct {
name string
usage *Usage
want string
}{
{
name: "empty",
usage: &Usage{},
want: "Input: 0, Output: 0",
},
{
name: "input",
usage: &Usage{InputTokens: 1},
want: "Input: 1, Output: 0",
},
{
name: "output",
usage: &Usage{OutputTokens: 2},
want: "Input: 0, Output: 2",
},
{
name: "both",
usage: &Usage{InputTokens: 3, OutputTokens: 4},
want: "Input: 3, Output: 4",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.usage.String(); got != tt.want {
t.Errorf("Usage.String() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 544238c

Please sign in to comment.