Skip to content

Commit ee20129

Browse files
committed
Added anthropic (claude) chat
1 parent 55dfffa commit ee20129

File tree

8 files changed

+233
-1
lines changed

8 files changed

+233
-1
lines changed

cmd/api/anthropic.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/djthorpe/go-tablewriter"
8+
"github.com/mutablelogic/go-client"
9+
"github.com/mutablelogic/go-client/pkg/anthropic"
10+
)
11+
12+
///////////////////////////////////////////////////////////////////////////////
13+
// GLOBALS
14+
15+
var (
16+
anthropicName = "claude"
17+
anthropicClient *anthropic.Client
18+
)
19+
20+
///////////////////////////////////////////////////////////////////////////////
21+
// LIFECYCLE
22+
23+
func anthropicRegister(flags *Flags) {
24+
// Register flags required
25+
flags.String(anthropicName, "anthropic-api-key", "${ANTHROPIC_API_KEY}", "API Key")
26+
27+
flags.Register(Cmd{
28+
Name: anthropicName,
29+
Description: "Interact with Claude, from https://www.anthropic.com/api",
30+
Parse: anthropicParse,
31+
Fn: []Fn{
32+
{Name: "chat", Call: anthropicChat, Description: "Chat with Claude", MinArgs: 1},
33+
},
34+
})
35+
}
36+
37+
func anthropicParse(flags *Flags, opts ...client.ClientOpt) error {
38+
apiKey := flags.GetString("anthropic-api-key")
39+
if apiKey == "" {
40+
return fmt.Errorf("missing -anthropic-api-key flag")
41+
}
42+
if client, err := anthropic.New(flags.GetString("anthropic-api-key"), opts...); err != nil {
43+
return err
44+
} else {
45+
anthropicClient = client
46+
}
47+
48+
// Return success
49+
return nil
50+
}
51+
52+
///////////////////////////////////////////////////////////////////////////////
53+
// METHODS
54+
55+
func anthropicChat(w *tablewriter.Writer, args []string) error {
56+
// Request -> Response
57+
message := anthropic.NewMessage("user", strings.Join(args, " "))
58+
responses, err := anthropicClient.Messages(message)
59+
if err != nil {
60+
return err
61+
}
62+
63+
// Write table
64+
return w.Write(responses)
65+
}

cmd/api/cmd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ func (c *Cmd) Get(args []string) (*Fn, []string, error) {
4848
// Check number of arguments
4949
if fn.MinArgs != 0 && nargs < fn.MinArgs {
5050
return nil, nil, fmt.Errorf("not enough arguments for %q", fn.Name)
51-
} else if nargs > fn.MaxArgs {
51+
}
52+
if fn.MaxArgs != 0 && nargs > fn.MaxArgs {
5253
return nil, nil, fmt.Errorf("too many arguments for %q", fn.Name)
5354
}
5455

cmd/api/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func main() {
1717
ipifyRegister(flags)
1818
bwRegister(flags)
1919
newsapiRegister(flags)
20+
anthropicRegister(flags)
2021

2122
// Parse
2223
if err := flags.Parse(os.Args[1:]); errors.Is(err, ErrHelp) {

pkg/anthropic/client.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
anthropic implements an API client for anthropic (https://docs.anthropic.com/en/api/getting-started)
3+
*/
4+
package anthropic
5+
6+
import (
7+
// Packages
8+
"github.com/mutablelogic/go-client"
9+
)
10+
11+
///////////////////////////////////////////////////////////////////////////////
12+
// TYPES
13+
14+
type Client struct {
15+
*client.Client
16+
}
17+
18+
///////////////////////////////////////////////////////////////////////////////
19+
// GLOBALS
20+
21+
const (
22+
endPoint = "https://api.anthropic.com/v1"
23+
defaultVersion = "2023-06-01"
24+
defaultMessageModel = "claude-3-haiku-20240307"
25+
defaultMaxTokens = 4096
26+
)
27+
28+
///////////////////////////////////////////////////////////////////////////////
29+
// LIFECYCLE
30+
31+
// Create a new client
32+
func New(ApiKey string, opts ...client.ClientOpt) (*Client, error) {
33+
// Create client
34+
opts = append(opts, client.OptEndpoint(endPoint))
35+
opts = append(opts, client.OptHeader("x-api-key", ApiKey), client.OptHeader("anthropic-version", defaultVersion))
36+
client, err := client.New(opts...)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
// Return the client
42+
return &Client{client}, nil
43+
}

pkg/anthropic/client_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package anthropic_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
// Packages
8+
opts "github.com/mutablelogic/go-client"
9+
anthropic "github.com/mutablelogic/go-client/pkg/anthropic"
10+
assert "github.com/stretchr/testify/assert"
11+
)
12+
13+
func Test_client_001(t *testing.T) {
14+
assert := assert.New(t)
15+
client, err := anthropic.New(GetApiKey(t), opts.OptTrace(os.Stderr, true))
16+
assert.NoError(err)
17+
assert.NotNil(client)
18+
t.Log(client)
19+
}
20+
21+
///////////////////////////////////////////////////////////////////////////////
22+
// ENVIRONMENT
23+
24+
func GetApiKey(t *testing.T) string {
25+
key := os.Getenv("ANTHROPIC_API_KEY")
26+
if key == "" {
27+
t.Skip("ANTHROPIC_API_KEY not set")
28+
t.SkipNow()
29+
}
30+
return key
31+
}

pkg/anthropic/message.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package anthropic
2+
3+
import (
4+
// Packages
5+
"github.com/mutablelogic/go-client"
6+
schema "github.com/mutablelogic/go-client/pkg/openai/schema"
7+
)
8+
9+
///////////////////////////////////////////////////////////////////////////////
10+
// TYPES
11+
12+
type reqMessage struct {
13+
Model string `json:"model"`
14+
Messages []schema.Message `json:"messages,omitempty"`
15+
MaxTokens int `json:"max_tokens,omitempty"`
16+
}
17+
18+
type respMessage struct {
19+
Id string `json:"id"`
20+
Type string `json:"type,omitempty"`
21+
Role string `json:"role"`
22+
Model string `json:"model"`
23+
StopReason string `json:"stop_reason"`
24+
StopSequence string `json:"stop_sequence"`
25+
Content []schema.Content `json:"content"`
26+
Usage struct {
27+
InputTokens int `json:"input_tokens"`
28+
OuputTokens int `json:"output_tokens"`
29+
} `json:"usage"`
30+
}
31+
32+
///////////////////////////////////////////////////////////////////////////////
33+
// PUBLIC METHODS
34+
35+
func NewMessage(role, text string) schema.Message {
36+
return schema.Message{
37+
Role: role,
38+
Content: text,
39+
}
40+
}
41+
42+
///////////////////////////////////////////////////////////////////////////////
43+
// API CALLS
44+
45+
// Send a structured list of input messages with text and/or image content,
46+
// and the model will generate the next message in the conversation.
47+
func (c *Client) Messages(message schema.Message) ([]schema.Content, error) {
48+
var request reqMessage
49+
var response respMessage
50+
51+
request.Model = defaultMessageModel
52+
request.MaxTokens = defaultMaxTokens
53+
request.Messages = []schema.Message{message}
54+
55+
// Return the response
56+
if payload, err := client.NewJSONRequest(request); err != nil {
57+
return nil, err
58+
} else if err := c.Do(payload, &response, client.OptPath("messages")); err != nil {
59+
return nil, err
60+
} else {
61+
return response.Content, nil
62+
}
63+
}

pkg/anthropic/message_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package anthropic_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
opts "github.com/mutablelogic/go-client"
8+
anthropic "github.com/mutablelogic/go-client/pkg/anthropic"
9+
schema "github.com/mutablelogic/go-client/pkg/openai/schema"
10+
assert "github.com/stretchr/testify/assert"
11+
)
12+
13+
func Test_message_001(t *testing.T) {
14+
assert := assert.New(t)
15+
client, err := anthropic.New(GetApiKey(t), opts.OptTrace(os.Stderr, true))
16+
assert.NoError(err)
17+
assert.NotNil(client)
18+
_, err = client.Messages(schema.Message{
19+
Role: "user",
20+
Content: "What is the weather today",
21+
})
22+
assert.NoError(err)
23+
}

pkg/openai/schema/chat.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ type MessageChoice struct {
1212
Index int `json:"index"`
1313
FinishReason string `json:"finish_reason"`
1414
}
15+
16+
type Content struct {
17+
Type string `json:"type,width:4"`
18+
Text string `json:"text,wrap,width:60"`
19+
}

0 commit comments

Comments
 (0)