Skip to content

Commit

Permalink
feat: support Tool use
Browse files Browse the repository at this point in the history
  • Loading branch information
liushuangls committed Apr 7, 2024
1 parent e8a5397 commit 2a70431
Show file tree
Hide file tree
Showing 10 changed files with 640 additions and 52 deletions.
89 changes: 88 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ func main() {
}
```

### Messages Vision example usage:
### Other examples:

<details>
<summary>Messages Vision example</summary>

```go
package main
Expand Down Expand Up @@ -151,6 +154,90 @@ func main() {
fmt.Println(resp.Content[0].Text)
}
```
</details>

<details>

<summary>Messages Tool use example</summary>

```go
package main

import (
"context"
"fmt"

"github.com/liushuangls/go-anthropic"
"github.com/liushuangls/go-anthropic/jsonschema"
)

func main() {
client := anthropic.NewClient(
"your anthropic apikey",
)

request := anthropic.MessagesRequest{
Model: anthropic.ModelClaude3Haiku20240307,
Messages: []anthropic.Message{
anthropic.NewUserTextMessage("What is the weather like in San Francisco?"),
},
MaxTokens: 1000,
Tools: []anthropic.ToolDefinition{
{
Name: "get_weather",
Description: "Get the current weather in a given location",
InputSchema: jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Description: "The city and state, e.g. San Francisco, CA",
},
"unit": {
Type: jsonschema.String,
Enum: []string{"celsius", "fahrenheit"},
Description: "The unit of temperature, either 'celsius' or 'fahrenheit'",
},
},
Required: []string{"location"},
},
},
},
}

resp, err := client.CreateMessages(context.Background(), request)
if err != nil {
panic(err)
}

request.Messages = append(request.Messages, anthropic.Message{
Role: anthropic.RoleAssistant,
Content: resp.Content,
})

var toolUse *anthropic.MessageContentToolUse

for _, c := range resp.Content {
if c.Type == anthropic.MessagesContentTypeToolUse {
toolUse = c.MessageContentToolUse
}
}

if toolUse == nil {
panic("tool use not found")
}

request.Messages = append(request.Messages, anthropic.NewToolResultsMessage(toolUse.ID, "65 degrees", false))

resp, err = client.CreateMessages(context.Background(), request)
if err != nil {
panic(err)
}
fmt.Printf("Response: %+v\n", resp)
}
```

</details>

## Acknowledgments
The following project had particular influence on go-anthropic is design.
Expand Down
14 changes: 13 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ func (c *Client) fullURL(suffix string) string {
return fmt.Sprintf("%s%s", c.config.BaseURL, suffix)
}

func (c *Client) newRequest(ctx context.Context, method, urlSuffix string, body any) (req *http.Request, err error) {
type requestSetter func(req *http.Request)

func withBetaVersion(version string) requestSetter {
return func(req *http.Request) {
req.Header.Set("anthropic-beta", version)
}
}

func (c *Client) newRequest(ctx context.Context, method, urlSuffix string, body any, requestSetters ...requestSetter) (req *http.Request, err error) {
var reqBody []byte
if body != nil {
reqBody, err = json.Marshal(body)
Expand All @@ -79,6 +87,10 @@ func (c *Client) newRequest(ctx context.Context, method, urlSuffix string, body
req.Header.Set("X-Api-Key", c.config.apikey)
req.Header.Set("Anthropic-Version", c.config.APIVersion)

for _, setter := range requestSetters {
setter(req)
}

return req, nil
}

Expand Down
24 changes: 18 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ const (
APIVersion20230601 = "2023-06-01"
)

const (
BetaTools20240404 = "tools-2024-04-04"
)

// ClientConfig is a configuration of a client.
type ClientConfig struct {
apikey string

BaseURL string
APIVersion string
HTTPClient *http.Client
BaseURL string
APIVersion string
BetaVersion string
HTTPClient *http.Client

EmptyMessagesLimit uint
}
Expand All @@ -30,9 +35,10 @@ func newConfig(apikey string, opts ...ClientOption) ClientConfig {
c := ClientConfig{
apikey: apikey,

BaseURL: anthropicAPIURLv1,
APIVersion: APIVersion20230601,
HTTPClient: &http.Client{},
BaseURL: anthropicAPIURLv1,
APIVersion: APIVersion20230601,
BetaVersion: BetaTools20240404,
HTTPClient: &http.Client{},

EmptyMessagesLimit: defaultEmptyMessagesLimit,
}
Expand Down Expand Up @@ -67,3 +73,9 @@ func WithEmptyMessagesLimit(limit uint) ClientOption {
c.EmptyMessagesLimit = limit
}
}

func WithBetaVersion(betaVersion string) ClientOption {
return func(c *ClientConfig) {
c.BetaVersion = betaVersion
}
}
9 changes: 8 additions & 1 deletion error.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package anthropic

import "fmt"
import (
"errors"
"fmt"
)

type ErrType string

Expand All @@ -21,6 +24,10 @@ const (
ErrTypeOverloaded ErrType = "overloaded_error"
)

var (
ErrSteamingNotSupportTools = errors.New("streaming is not yet supported tools")
)

// APIError provides error information returned by the Anthropic API.
type APIError struct {
Type ErrType `json:"type"`
Expand Down
49 changes: 49 additions & 0 deletions jsonschema/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Package jsonschema provides very simple functionality for representing a JSON schema as a
// (nested) struct. This struct can be used with the messages "tool use" feature.
// For more complicated schemas, it is recommended to use a dedicated JSON schema library
// and/or pass in the schema in []byte format.
package jsonschema

import "encoding/json"

type DataType string

const (
Object DataType = "object"
Number DataType = "number"
Integer DataType = "integer"
String DataType = "string"
Array DataType = "array"
Null DataType = "null"
Boolean DataType = "boolean"
)

// Definition is a struct for describing a JSON Schema.
// It is fairly limited, and you may have better luck using a third-party library.
type Definition struct {
// Type specifies the data type of the schema.
Type DataType `json:"type,omitempty"`
// Description is the description of the schema.
Description string `json:"description,omitempty"`
// Enum is used to restrict a value to a fixed set of values. It must be an array with at least
// one element, where each element is unique. You will probably only use this with strings.
Enum []string `json:"enum,omitempty"`
// Properties describes the properties of an object, if the schema type is Object.
Properties map[string]Definition `json:"properties"`
// Required specifies which properties are required, if the schema type is Object.
Required []string `json:"required,omitempty"`
// Items specifies which data type an array contains, if the schema type is Array.
Items *Definition `json:"items,omitempty"`
}

func (d Definition) MarshalJSON() ([]byte, error) {
if d.Properties == nil {
d.Properties = make(map[string]Definition)
}
type Alias Definition
return json.Marshal(struct {
Alias
}{
Alias: (Alias)(d),
})
}
Loading

0 comments on commit 2a70431

Please sign in to comment.