From 3684031f440c357c11c984b39c936f41bed0404e Mon Sep 17 00:00:00 2001 From: Eugen Eisler Date: Tue, 19 Nov 2024 13:12:10 +0100 Subject: [PATCH] feat: migrate to official anthropics Go SDK --- go.mod | 5 ++ go.sum | 12 +++ plugins/ai/anthropic/anthropic.go | 123 ++++++++++++++---------------- 3 files changed, 76 insertions(+), 64 deletions(-) diff --git a/go.mod b/go.mod index 6fd6a509..85463019 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.2 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4 // indirect github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/bytedance/sonic v1.12.4 // indirect github.com/bytedance/sonic/loader v0.2.1 // indirect @@ -75,6 +76,10 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect diff --git a/go.sum b/go.sum index 8455fa52..0fc0507a 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsVi github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4 h1:TdGQS+RoR4AUO6gqUL74yK1dz/Arrt/WG+dxOj6Yo6A= +github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -209,6 +211,16 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= diff --git a/plugins/ai/anthropic/anthropic.go b/plugins/ai/anthropic/anthropic.go index b13e6d0c..ff3f4d3a 100644 --- a/plugins/ai/anthropic/anthropic.go +++ b/plugins/ai/anthropic/anthropic.go @@ -2,17 +2,16 @@ package anthropic import ( "context" - "errors" "fmt" + "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/option" + "github.com/danielmiessler/fabric/common" "github.com/danielmiessler/fabric/plugins" goopenai "github.com/sashabaranov/go-openai" - - "github.com/danielmiessler/fabric/common" - "github.com/liushuangls/go-anthropic/v2" ) -const baseUrl = "https://api.anthropic.com/v1" +//const baseUrl = "https://api.anthropic.com/" func NewClient() (ret *Client) { vendorName := "Anthropic" @@ -24,17 +23,20 @@ func NewClient() (ret *Client) { ConfigureCustom: ret.configure, } - ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false) - ret.ApiBaseURL.Value = baseUrl + //ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false) + //ret.ApiBaseURL.Value = baseUrl ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true) // we could provide a setup question for the following settings ret.maxTokens = 4096 ret.defaultRequiredUserMessage = "Hi" ret.models = []string{ - string(anthropic.ModelClaude3Dot5HaikuLatest), string(anthropic.ModelClaude3Opus20240229), - string(anthropic.ModelClaude3Opus20240229), string(anthropic.ModelClaude2Dot0), string(anthropic.ModelClaude2Dot1), - string(anthropic.ModelClaude3Dot5SonnetLatest), string(anthropic.ModelClaude3Dot5HaikuLatest), + anthropic.ModelClaude3_5HaikuLatest, anthropic.ModelClaude3_5Haiku20241022, + anthropic.ModelClaude3_5SonnetLatest, anthropic.ModelClaude3_5Sonnet20241022, + anthropic.ModelClaude_3_5_Sonnet_20240620, anthropic.ModelClaude3OpusLatest, + anthropic.ModelClaude_3_Opus_20240229, anthropic.ModelClaude_3_Sonnet_20240229, + anthropic.ModelClaude_3_Haiku_20240307, anthropic.ModelClaude_2_1, + anthropic.ModelClaude_2_0, anthropic.ModelClaude_Instant_1_2, } return @@ -42,8 +44,8 @@ func NewClient() (ret *Client) { type Client struct { *plugins.PluginBase - ApiBaseURL *plugins.SetupQuestion - ApiKey *plugins.SetupQuestion + //ApiBaseURL *plugins.SetupQuestion + ApiKey *plugins.SetupQuestion maxTokens int defaultRequiredUserMessage string @@ -53,11 +55,14 @@ type Client struct { } func (an *Client) configure() (err error) { - if an.ApiBaseURL.Value != "" { - an.client = anthropic.NewClient(an.ApiKey.Value, anthropic.WithBaseURL(an.ApiBaseURL.Value)) + /*if an.ApiBaseURL.Value != "" { + an.client = anthropic.NewClient( + option.WithAPIKey(an.ApiKey.Value), option.WithBaseURL(an.ApiBaseURL.Value), + ) } else { - an.client = anthropic.NewClient(an.ApiKey.Value) - } + */ + an.client = anthropic.NewClient(option.WithAPIKey(an.ApiKey.Value)) + //} return } @@ -68,75 +73,65 @@ func (an *Client) ListModels() (ret []string, err error) { func (an *Client) SendStream( msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string, ) (err error) { - ctx := context.Background() - req := an.buildMessagesRequest(msgs, opts) - req.Stream = true - if _, err = an.client.CreateMessagesStream(ctx, anthropic.MessagesStreamRequest{ - MessagesRequest: req, - OnContentBlockDelta: func(data anthropic.MessagesEventContentBlockDeltaData) { - // fmt.Printf("Stream Content: %s\n", data.Delta.Text) - channel <- *data.Delta.Text - }, - }); err != nil { - var e *anthropic.APIError - if errors.As(err, &e) { - fmt.Printf("Messages stream error, type: %s, message: %s", e.Type, e.Message) - } else { - fmt.Printf("Messages stream error: %v\n", err) + messages := an.toMessages(msgs) + + ctx := context.Background() + stream := an.client.Messages.NewStreaming(ctx, anthropic.MessageNewParams{ + Model: anthropic.F(opts.Model), + MaxTokens: anthropic.F(int64(an.maxTokens)), + TopP: anthropic.F(opts.TopP), + Temperature: anthropic.F(opts.Temperature), + Messages: anthropic.F(messages), + }) + + for stream.Next() { + event := stream.Current() + + switch delta := event.Delta.(type) { + case anthropic.ContentBlockDeltaEventDelta: + if delta.Text != "" { + channel <- delta.Text + } } - } else { - close(channel) } + + if stream.Err() != nil { + fmt.Printf("Messages stream error: %v\n", stream.Err()) + } + close(channel) return } func (an *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (ret string, err error) { - req := an.buildMessagesRequest(msgs, opts) - req.Stream = false - - var resp anthropic.MessagesResponse - if resp, err = an.client.CreateMessages(ctx, req); err == nil { - ret = *resp.Content[0].Text - } else { - var e *anthropic.APIError - if errors.As(err, &e) { - fmt.Printf("Messages error, type: %s, message: %s", e.Type, e.Message) - } else { - fmt.Printf("Messages error: %v\n", err) - } - } - return -} - -func (an *Client) buildMessagesRequest(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (ret anthropic.MessagesRequest) { - temperature := float32(opts.Temperature) - topP := float32(opts.TopP) - messages := an.toMessages(msgs) - ret = anthropic.MessagesRequest{ - Model: anthropic.Model(opts.Model), - Temperature: &temperature, - TopP: &topP, - Messages: messages, - MaxTokens: an.maxTokens, + var message *anthropic.Message + if message, err = an.client.Messages.New(ctx, anthropic.MessageNewParams{ + Model: anthropic.F(opts.Model), + MaxTokens: anthropic.F(int64(an.maxTokens)), + TopP: anthropic.F(opts.TopP), + Temperature: anthropic.F(opts.Temperature), + Messages: anthropic.F(messages), + }); err != nil { + return } + ret = message.Content[0].Text return } -func (an *Client) toMessages(msgs []*goopenai.ChatCompletionMessage) (ret []anthropic.Message) { +func (an *Client) toMessages(msgs []*goopenai.ChatCompletionMessage) (ret []anthropic.MessageParam) { // we could call the method before calling the specific vendor normalizedMessages := common.NormalizeMessages(msgs, an.defaultRequiredUserMessage) // Iterate over the incoming session messages and process them for _, msg := range normalizedMessages { - var message anthropic.Message + var message anthropic.MessageParam switch msg.Role { case goopenai.ChatMessageRoleUser: - message = anthropic.NewUserTextMessage(msg.Content) + message = anthropic.NewUserMessage(anthropic.NewTextBlock(msg.Content)) default: - message = anthropic.NewAssistantTextMessage(msg.Content) + message = anthropic.NewAssistantMessage(anthropic.NewTextBlock(msg.Content)) } ret = append(ret, message) }