From f0b833dd97dce1bfc11bb9d1832341216abf5b00 Mon Sep 17 00:00:00 2001 From: Megumin Date: Tue, 7 Jan 2025 11:23:11 +0800 Subject: [PATCH] feat: claude --- components/model/claude/claude.go | 518 +++++++++++++++++++++ components/model/claude/claude_test.go | 249 ++++++++++ components/model/claude/examples/claude.go | 228 +++++++++ components/model/claude/examples/test.jpg | Bin 0 -> 8110 bytes components/model/claude/go.mod | 53 +++ components/model/claude/go.sum | 163 +++++++ components/model/claude/option.go | 31 ++ 7 files changed, 1242 insertions(+) create mode 100644 components/model/claude/claude.go create mode 100644 components/model/claude/claude_test.go create mode 100644 components/model/claude/examples/claude.go create mode 100644 components/model/claude/examples/test.jpg create mode 100644 components/model/claude/go.mod create mode 100644 components/model/claude/go.sum create mode 100644 components/model/claude/option.go diff --git a/components/model/claude/claude.go b/components/model/claude/claude.go new file mode 100644 index 0000000..5ae52fb --- /dev/null +++ b/components/model/claude/claude.go @@ -0,0 +1,518 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package claude + +import ( + "context" + "encoding/json" + "fmt" + "runtime/debug" + "strings" + + "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/option" + "github.com/cloudwego/eino/callbacks" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/schema" + "github.com/cloudwego/eino/utils/safe" +) + +// NewChatModel creates a new Claude chat model instance +// +// Parameters: +// - ctx: The context for the operation +// - conf: Configuration for the Claude model +// +// Returns: +// - model.ChatModel: A chat model interface implementation +// - error: Any error that occurred during creation +// +// Example: +// +// model, err := claude.NewChatModel(ctx, &claude.Config{ +// APIKey: "your-api-key", +// Model: "claude-3-opus-20240229", +// MaxTokens: 2000, +// }) +func NewChatModel(ctx context.Context, conf *Config) (model.ChatModel, error) { + var cli *anthropic.Client + if conf.BaseURL != nil { + cli = anthropic.NewClient(option.WithBaseURL(*conf.BaseURL), option.WithAPIKey(conf.APIKey)) + } else { + cli = anthropic.NewClient(option.WithAPIKey(conf.APIKey)) + } + return &claude{ + cli: cli, + maxTokens: conf.MaxTokens, + model: conf.Model, + stopSequences: conf.StopSequences, + temperature: conf.Temperature, + topK: conf.TopK, + topP: conf.TopP, + }, nil +} + +// Config contains the configuration options for the Claude model +type Config struct { + // BaseURL is the custom API endpoint URL + // Use this to specify a different API endpoint, e.g., for proxies or enterprise setups + // Optional. Example: baseURL := "https://custom-claude-api.example.com" + BaseURL *string + + // APIKey is your Anthropic API key + // Obtain from: https://console.anthropic.com/account/keys + APIKey string + + // Model specifies which Claude model to use + Model string + + // MaxTokens limits the maximum number of tokens in the response + // Range: 1 to model's context length + // Example: 2000 for a medium-length response + MaxTokens int + + // Temperature controls randomness in responses + // Range: [0.0, 1.0], where 0.0 is more focused and 1.0 is more creative + // Optional. Example: temperature := float32(0.7) + Temperature *float32 + + // TopP controls diversity via nucleus sampling + // Range: [0.0, 1.0], where 1.0 disables nucleus sampling + // Optional. Example: topP := float32(0.95) + TopP *float32 + + // TopK controls diversity by limiting the top K tokens to sample from + // Optional. Example: topK := int32(40) + TopK *int32 + + // StopSequences specifies custom stop sequences + // The model will stop generating when it encounters any of these sequences + // Optional. Example: []string{"\n\nHuman:", "\n\nAssistant:"} + StopSequences []string +} + +type claude struct { + cli *anthropic.Client + + maxTokens int + model string + stopSequences []string + temperature *float32 + topK *int32 + topP *float32 + tools []anthropic.ToolParam + origTools []*schema.ToolInfo +} + +func (c *claude) Generate(ctx context.Context, input []*schema.Message, opts ...model.Option) (message *schema.Message, err error) { + ctx = callbacks.OnStart(ctx, c.getCallbackInput(input)) + defer func() { + if err != nil { + callbacks.OnError(ctx, err) + } + }() + + param, err := c.genMessageNewParams(input, opts...) + if err != nil { + return nil, err + } + resp, err := c.cli.Messages.New(ctx, param) + if err != nil { + return nil, fmt.Errorf("create new message fail: %w", err) + } + message, err = convOutputMessage(resp) + if err != nil { + return nil, fmt.Errorf("convert response to schema message fail: %w", err) + } + callbacks.OnEnd(ctx, c.getCallbackOutput(message)) + return message, nil +} + +func (c *claude) Stream(ctx context.Context, input []*schema.Message, opts ...model.Option) (result *schema.StreamReader[*schema.Message], err error) { + ctx = callbacks.OnStart(ctx, c.getCallbackInput(input)) + defer func() { + if err != nil { + callbacks.OnError(ctx, err) + } + }() + + param, err := c.genMessageNewParams(input, opts...) + if err != nil { + return nil, err + } + stream := c.cli.Messages.NewStreaming(ctx, param) + + sr, sw := schema.Pipe[*model.CallbackOutput](1) + go func() { + defer func() { + panicErr := recover() + + if panicErr != nil { + _ = sw.Send(nil, safe.NewPanicErr(panicErr, debug.Stack())) + } + stream.Close() + sw.Close() + }() + var waitList []*schema.Message + streamCtx := &streamContext{} + for stream.Next() { + message, err_ := convStreamEvent(stream.Current(), streamCtx) + if err_ != nil { + _ = sw.Send(nil, fmt.Errorf("convert response chunk to schema message fail: %w", err_)) + return + } + if message == nil { + continue + } + if isMessageEmpty(message) { + waitList = append(waitList, message) + continue + } + if len(waitList) != 0 { + message, err = schema.ConcatMessages(append(waitList, message)) + if err != nil { + _ = sw.Send(nil, fmt.Errorf("concat empty message fail: %w", err)) + return + } + waitList = []*schema.Message{} + } + closed := sw.Send(c.getCallbackOutput(message), nil) + if closed { + return + } + } + if len(waitList) > 0 { + message, err_ := schema.ConcatMessages(waitList) + if err_ != nil { + _ = sw.Send(nil, fmt.Errorf("concat empty message fail: %w", err_)) + return + } else { + closed := sw.Send(c.getCallbackOutput(message), nil) + if closed { + return + } + } + } + if stream.Err() != nil { + _ = sw.Send(nil, stream.Err()) + } + }() + srList := sr.Copy(2) + callbacks.OnEndWithStreamOutput(ctx, srList[0]) + return schema.StreamReaderWithConvert(srList[1], func(t *model.CallbackOutput) (*schema.Message, error) { + return t.Message, nil + }), nil +} + +func (c *claude) BindTools(tools []*schema.ToolInfo) error { + result := make([]anthropic.ToolParam, 0, len(tools)) + for _, tool := range tools { + s, err := tool.ToOpenAPIV3() + if err != nil { + return fmt.Errorf("convert to openapi v3 schema fail: %w", err) + } + result = append(result, anthropic.ToolParam{ + Name: anthropic.F(tool.Name), + Description: anthropic.F(tool.Desc), + InputSchema: anthropic.F[any](s), + }) + } + c.tools = result + c.origTools = tools + return nil +} + +func (c *claude) genMessageNewParams(input []*schema.Message, opts ...model.Option) (anthropic.MessageNewParams, error) { + if len(input) == 0 { + return anthropic.MessageNewParams{}, fmt.Errorf("input is empty") + } + + commonOptions := model.GetCommonOptions(&model.Options{ + Model: &c.model, + Temperature: c.temperature, + MaxTokens: &c.maxTokens, + TopP: c.topP, + Stop: c.stopSequences, + }, opts...) + claudeOptions := model.GetImplSpecificOptions(&options{TopK: c.topK}, opts...) + + param := anthropic.MessageNewParams{} + if commonOptions.Model != nil { + param.Model = anthropic.F(*commonOptions.Model) + } + if commonOptions.MaxTokens != nil { + param.MaxTokens = anthropic.F(int64(*commonOptions.MaxTokens)) + } + if commonOptions.Temperature != nil { + param.Temperature = anthropic.F(float64(*commonOptions.Temperature)) + } + if commonOptions.TopP != nil { + param.TopP = anthropic.F(float64(*commonOptions.TopP)) + } + if len(commonOptions.Stop) > 0 { + param.StopSequences = anthropic.F(commonOptions.Stop) + } + if claudeOptions.TopK != nil { + param.TopK = anthropic.F(int64(*claudeOptions.TopK)) + } + if len(c.tools) > 0 { + param.Tools = anthropic.F(c.tools) + } + + // Convert messages + var systemTextBlocks []anthropic.TextBlockParam + for len(input) > 1 && input[0].Role == schema.System { + systemTextBlocks = append(systemTextBlocks, anthropic.NewTextBlock(input[0].Content)) + input = input[1:] + } + if len(systemTextBlocks) > 0 { + param.System = anthropic.F(systemTextBlocks) + } + + messages := make([]anthropic.MessageParam, 0, len(input)) + for _, msg := range input { + message, err := convSchemaMessage(msg) + if err != nil { + return anthropic.MessageNewParams{}, fmt.Errorf("convert schema message fail: %w", err) + } + messages = append(messages, *message) + } + param.Messages = anthropic.F(messages) + + return param, nil +} + +func (c *claude) getCallbackInput(input []*schema.Message) *model.CallbackInput { + result := &model.CallbackInput{ + Messages: input, + Tools: c.origTools, + Config: c.getConfig(), + } + return result +} + +func (c *claude) getCallbackOutput(output *schema.Message) *model.CallbackOutput { + result := &model.CallbackOutput{ + Message: output, + Config: c.getConfig(), + } + if output.ResponseMeta != nil && output.ResponseMeta.Usage != nil { + result.TokenUsage = &model.TokenUsage{ + PromptTokens: output.ResponseMeta.Usage.PromptTokens, + CompletionTokens: output.ResponseMeta.Usage.CompletionTokens, + TotalTokens: output.ResponseMeta.Usage.TotalTokens, + } + } + return result +} + +func (c *claude) getConfig() *model.Config { + result := &model.Config{ + Model: c.model, + MaxTokens: c.maxTokens, + Stop: c.stopSequences, + } + if c.temperature != nil { + result.Temperature = *c.temperature + } + if c.topP != nil { + result.TopP = *c.topP + } + return result +} + +func convSchemaMessage(message *schema.Message) (*anthropic.MessageParam, error) { + result := &anthropic.MessageParam{} + if message.Role == schema.Assistant { + result.Role = anthropic.F(anthropic.MessageParamRoleAssistant) + } else { + result.Role = anthropic.F(anthropic.MessageParamRoleUser) + } + + var messageParams []anthropic.ContentBlockParamUnion + if len(message.Content) > 0 { + if len(message.ToolCallID) > 0 { + messageParams = append(messageParams, anthropic.NewToolResultBlock(message.ToolCallID, message.Content, false)) + } else { + messageParams = append(messageParams, anthropic.NewTextBlock(message.Content)) + } + for i := range message.ToolCalls { + messageParams = append(messageParams, anthropic.NewToolUseBlockParam(message.ToolCalls[i].ID, message.ToolCalls[i].Function.Name, json.RawMessage(message.ToolCalls[i].Function.Arguments))) + } + result.Content = anthropic.F(messageParams) + return result, nil + } + + for i := range message.MultiContent { + switch message.MultiContent[i].Type { + case schema.ChatMessagePartTypeText: + messageParams = append(messageParams, anthropic.NewTextBlock(message.MultiContent[i].Text)) + case schema.ChatMessagePartTypeImageURL: + if message.MultiContent[i].ImageURL == nil { + continue + } + mediaType, data, err := convImageBase64(message.MultiContent[i].ImageURL.URL) + if err != nil { + return nil, fmt.Errorf("extract base64 image fail: %w", err) + } + messageParams = append(messageParams, anthropic.NewImageBlockBase64(mediaType, data)) + default: + return nil, fmt.Errorf("anthropic message type not supported: %s", message.MultiContent[i].Type) + } + } + result.Content = anthropic.F(messageParams) + + return result, nil +} + +func convOutputMessage(resp *anthropic.Message) (*schema.Message, error) { + message := &schema.Message{ + Role: schema.Assistant, + ResponseMeta: &schema.ResponseMeta{ + FinishReason: string(resp.StopReason), + Usage: &schema.TokenUsage{ + PromptTokens: int(resp.Usage.InputTokens), + CompletionTokens: int(resp.Usage.OutputTokens), + TotalTokens: int(resp.Usage.InputTokens + resp.Usage.OutputTokens), + }, + }, + } + + for _, item := range resp.Content { + switch item.Type { + case anthropic.ContentBlockTypeText: + message.Content += item.Text + case anthropic.ContentBlockTypeToolUse: + message.ToolCalls = append(message.ToolCalls, schema.ToolCall{ + ID: item.ID, + Function: schema.FunctionCall{ + Name: item.Name, + Arguments: string(item.Input), + }, + }) + default: + return nil, fmt.Errorf("unknown anthropic content block type: %s", item.Type) + } + } + + return message, nil +} + +type streamContext struct { + toolIndex *int +} + +func convStreamEvent(event anthropic.MessageStreamEvent, streamCtx *streamContext) (*schema.Message, error) { + result := &schema.Message{ + Role: schema.Assistant, + } + + switch e := event.AsUnion().(type) { + case anthropic.MessageStartEvent: + return convOutputMessage(&e.Message) + + case anthropic.MessageDeltaEvent: + result.ResponseMeta = &schema.ResponseMeta{ + FinishReason: string(e.Delta.StopReason), + Usage: &schema.TokenUsage{ + CompletionTokens: int(e.Usage.OutputTokens), + }, + } + return result, nil + + case anthropic.MessageStopEvent, anthropic.ContentBlockStopEvent: + return nil, nil + case anthropic.ContentBlockStartEvent: + content := &anthropic.ContentBlock{} + err := content.UnmarshalJSON([]byte(e.ContentBlock.JSON.RawJSON())) + if err != nil { + return nil, fmt.Errorf("unmarshal content block start event fail: %w", err) + } + switch content.Type { + case anthropic.ContentBlockTypeText: + result.Content = content.Text + case anthropic.ContentBlockTypeToolUse: + num := 0 + if streamCtx.toolIndex != nil { + num = *streamCtx.toolIndex + 1 + } + streamCtx.toolIndex = &num + + arguments := string(content.Input) + if arguments == "{}" { + arguments = "" + } + result.ToolCalls = append(result.ToolCalls, schema.ToolCall{ + Index: streamCtx.toolIndex, + ID: content.ID, + Function: schema.FunctionCall{ + Name: content.Name, + Arguments: arguments, + }, + }) + default: + return nil, fmt.Errorf("unknown anthropic content block type: %s", content.Type) + } + return result, nil + + case anthropic.ContentBlockDeltaEvent: + switch delta := e.Delta.AsUnion().(type) { + case anthropic.TextDelta: + result.Content = delta.Text + + case anthropic.InputJSONDelta: + result.ToolCalls = append(result.ToolCalls, schema.ToolCall{ + Index: streamCtx.toolIndex, + Function: schema.FunctionCall{ + Arguments: delta.PartialJSON, + }, + }) + } + return result, nil + + default: + return nil, fmt.Errorf("unknown stream event type: %T", e) + } +} + +func convImageBase64(data string) (string, string, error) { + if !strings.HasPrefix(data, "data:") { + return "", "", fmt.Errorf("invalid base64 image: %s", data) + } + contents := strings.SplitN(data[5:], ",", 2) + if len(contents) != 2 { + return "", "", fmt.Errorf("invalid base64 image: %s", data) + } + headParts := strings.Split(contents[0], ";") + bBase64 := false + for _, part := range headParts { + if part == "base64" { + bBase64 = true + } + } + if !bBase64 { + return "", "", fmt.Errorf("invalid base64 image: %s", data) + } + return headParts[0], contents[1], nil +} + +func isMessageEmpty(message *schema.Message) bool { + if len(message.Content) == 0 && len(message.ToolCalls) == 0 && len(message.MultiContent) == 0 { + return true + } + return false +} diff --git a/components/model/claude/claude_test.go b/components/model/claude/claude_test.go new file mode 100644 index 0000000..f661c3f --- /dev/null +++ b/components/model/claude/claude_test.go @@ -0,0 +1,249 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package claude + +import ( + "context" + "encoding/json" + "testing" + + "github.com/anthropics/anthropic-sdk-go" + "github.com/bytedance/mockey" + "github.com/cloudwego/eino/schema" + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/assert" +) + +func TestClaude(t *testing.T) { + ctx := context.Background() + model, err := NewChatModel(ctx, &Config{ + APIKey: "test-key", + Model: "claude-3-opus-20240229", + }) + assert.NoError(t, err) + + mockey.PatchConvey("basic chat", t, func() { + // Mock API response + defer mockey.Mock((*anthropic.MessageService).New).Return(&anthropic.Message{ + Content: []anthropic.ContentBlock{ + { + Type: anthropic.ContentBlockTypeText, + Text: "Hello, I'm Claude!", + }, + }, + Usage: anthropic.Usage{ + InputTokens: 10, + OutputTokens: 5, + }, + }, nil).Build().UnPatch() + + resp, err := model.Generate(ctx, []*schema.Message{ + { + Role: schema.User, + Content: "Hi, who are you?", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "Hello, I'm Claude!", resp.Content) + assert.Equal(t, schema.Assistant, resp.Role) + assert.Equal(t, 10, resp.ResponseMeta.Usage.PromptTokens) + assert.Equal(t, 5, resp.ResponseMeta.Usage.CompletionTokens) + }) + + mockey.PatchConvey("function calling", t, func() { + // Bind tool + err := model.BindTools([]*schema.ToolInfo{ + { + Name: "get_weather", + Desc: "Get weather information", + ParamsOneOf: schema.NewParamsOneOfByOpenAPIV3(&openapi3.Schema{ + Type: "object", + Properties: map[string]*openapi3.SchemaRef{ + "city": { + Value: &openapi3.Schema{ + Type: "string", + }, + }, + }, + }), + }, + }) + assert.NoError(t, err) + + // Mock function call response + defer mockey.Mock((*anthropic.MessageService).New).Return(&anthropic.Message{ + Content: []anthropic.ContentBlock{ + { + Type: anthropic.ContentBlockTypeToolUse, + ID: "call_1", + Name: "get_weather", + Input: []byte(`{"city":"Paris"}`), + }, + }, + }, nil).Build().UnPatch() + + resp, err := model.Generate(ctx, []*schema.Message{ + { + Role: schema.User, + Content: "What's the weather in Paris?", + }, + }) + + assert.NoError(t, err) + assert.Len(t, resp.ToolCalls, 1) + assert.Equal(t, "get_weather", resp.ToolCalls[0].Function.Name) + assert.Equal(t, `{"city":"Paris"}`, resp.ToolCalls[0].Function.Arguments) + }) + + mockey.PatchConvey("image processing", t, func() { + // Mock image response + defer mockey.Mock((*anthropic.MessageService).New).Return(&anthropic.Message{ + Content: []anthropic.ContentBlock{ + { + Type: anthropic.ContentBlockTypeText, + Text: "I see a beautiful sunset image", + }, + }, + }, nil).Build().UnPatch() + + resp, err := model.Generate(ctx, []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + { + Type: schema.ChatMessagePartTypeText, + Text: "What's in this image?", + }, + { + Type: schema.ChatMessagePartTypeImageURL, + ImageURL: &schema.ChatMessageImageURL{ + URL: "data:image/jpeg;base64,/9j/4AAQSkZ...", + MIMEType: "image/jpeg", + }, + }, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "I see a beautiful sunset image", resp.Content) + }) +} + +func TestConvStreamEvent(t *testing.T) { + streamCtx := &streamContext{} + + mockey.PatchConvey("message start event", t, func() { + event := anthropic.MessageStreamEvent{} + defer mockey.Mock(anthropic.MessageStreamEvent.AsUnion).Return(anthropic.MessageStartEvent{ + Message: anthropic.Message{ + Content: []anthropic.ContentBlock{ + { + Type: anthropic.ContentBlockTypeText, + Text: "Initial message", + }, + }, + Usage: anthropic.Usage{ + InputTokens: 5, + OutputTokens: 2, + }, + }, + }).Build().UnPatch() + + message, err := convStreamEvent(event, streamCtx) + assert.NoError(t, err) + assert.Equal(t, "Initial message", message.Content) + assert.Equal(t, schema.Assistant, message.Role) + assert.Equal(t, 5, message.ResponseMeta.Usage.PromptTokens) + assert.Equal(t, 2, message.ResponseMeta.Usage.CompletionTokens) + }) + + mockey.PatchConvey("content block delta event - text", t, func() { + event := anthropic.MessageStreamEvent{} + delta := anthropic.ContentBlockDeltaEventDelta{} + defer mockey.Mock(anthropic.ContentBlockDeltaEventDelta.AsUnion).Return(anthropic.TextDelta{ + Text: " world", + }).Build().UnPatch() + defer mockey.Mock(anthropic.MessageStreamEvent.AsUnion).Return(anthropic.ContentBlockDeltaEvent{ + Delta: delta, + Index: 0, + Type: "", + }).Build().UnPatch() + + message, err := convStreamEvent(event, streamCtx) + assert.NoError(t, err) + assert.Equal(t, " world", message.Content) + }) + + mockey.PatchConvey("content block delta event - tool input", t, func() { + streamCtx.toolIndex = new(int) + *streamCtx.toolIndex = 0 + + event := anthropic.MessageStreamEvent{} + delta := anthropic.ContentBlockDeltaEventDelta{} + defer mockey.Mock(anthropic.ContentBlockDeltaEventDelta.AsUnion).Return(anthropic.InputJSONDelta{ + PartialJSON: `,"temp":25`, + }).Build().UnPatch() + defer mockey.Mock(anthropic.MessageStreamEvent.AsUnion).Return(anthropic.ContentBlockDeltaEvent{ + Delta: delta, + Index: 0, + Type: "", + }).Build().UnPatch() + + message, err := convStreamEvent(event, streamCtx) + assert.NoError(t, err) + assert.Len(t, message.ToolCalls, 1) + assert.Equal(t, 0, *message.ToolCalls[0].Index) + assert.Equal(t, `,"temp":25`, message.ToolCalls[0].Function.Arguments) + }) + + mockey.PatchConvey("message delta event", t, func() { + event := anthropic.MessageStreamEvent{} + defer mockey.Mock(anthropic.MessageStreamEvent.AsUnion).Return(anthropic.MessageDeltaEvent{ + Delta: anthropic.MessageDeltaEventDelta{ + StopReason: "end_turn", + }, + Usage: anthropic.MessageDeltaUsage{ + OutputTokens: 10, + }, + }).Build().UnPatch() + + message, err := convStreamEvent(event, streamCtx) + assert.NoError(t, err) + assert.Equal(t, "end_turn", message.ResponseMeta.FinishReason) + assert.Equal(t, 10, message.ResponseMeta.Usage.CompletionTokens) + }) + + mockey.PatchConvey("content block start event", t, func() { + event := anthropic.MessageStreamEvent{} + defer mockey.Mock(anthropic.MessageStreamEvent.AsUnion).Return(anthropic.ContentBlockStartEvent{}).Build().UnPatch() + defer mockey.Mock((*anthropic.ContentBlock).UnmarshalJSON).When(func(r *anthropic.ContentBlock, data []byte) bool { + r.Type = anthropic.ContentBlockTypeToolUse + r.Name = "tool" + r.Input = json.RawMessage("") + return true + }).Return(nil).Build().UnPatch() + + message, err := convStreamEvent(event, streamCtx) + assert.NoError(t, err) + assert.Equal(t, len(message.ToolCalls), 1) + assert.Equal(t, *message.ToolCalls[0].Index, 1) + assert.Equal(t, message.ToolCalls[0].Function.Name, "tool") + assert.Equal(t, message.ToolCalls[0].Function.Arguments, "") + }) +} diff --git a/components/model/claude/examples/claude.go b/components/model/claude/examples/claude.go new file mode 100644 index 0000000..454ade9 --- /dev/null +++ b/components/model/claude/examples/claude.go @@ -0,0 +1,228 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + "encoding/base64" + "fmt" + "io" + "log" + "os" + + "github.com/cloudwego/eino-ext/components/model/claude" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/schema" + "github.com/getkin/kin-openapi/openapi3" +) + +func main() { + ctx := context.Background() + apiKey := os.Getenv("CLAUDE_API_KEY") + if apiKey == "" { + log.Fatal("CLAUDE_API_KEY environment variable is not set") + } + + // 创建 Claude 模型 + cm, err := claude.NewChatModel(ctx, &claude.Config{ + APIKey: apiKey, + Model: "claude-3-opus-20240229", + MaxTokens: 2000, + }) + if err != nil { + log.Fatal(err) + } + + fmt.Println("\n=== Basic Chat ===") + basicChat(ctx, cm) + + fmt.Println("\n=== Streaming Chat ===") + streamingChat(ctx, cm) + + fmt.Println("\n=== Function Calling ===") + functionCalling(ctx, cm) + + fmt.Println("\n=== Image Processing ===") + imageProcessing(ctx, cm) +} + +func basicChat(ctx context.Context, cm model.ChatModel) { + messages := []*schema.Message{ + { + Role: schema.System, + Content: "You are a helpful AI assistant. Be concise in your responses.", + }, + { + Role: schema.User, + Content: "What is the capital of France?", + }, + } + + resp, err := cm.Generate(ctx, messages) + if err != nil { + log.Printf("Generate error: %v", err) + return + } + + fmt.Printf("Assistant: %s\n", resp.Content) + if resp.ResponseMeta != nil && resp.ResponseMeta.Usage != nil { + fmt.Printf("Tokens used: %d (prompt) + %d (completion) = %d (total)\n", + resp.ResponseMeta.Usage.PromptTokens, + resp.ResponseMeta.Usage.CompletionTokens, + resp.ResponseMeta.Usage.TotalTokens) + } +} + +func streamingChat(ctx context.Context, cm model.ChatModel) { + messages := []*schema.Message{ + { + Role: schema.User, + Content: "Write a short poem about spring, word by word.", + }, + } + + stream, err := cm.Stream(ctx, messages) + if err != nil { + log.Printf("Stream error: %v", err) + return + } + + fmt.Print("Assistant: ") + for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Printf("Stream receive error: %v", err) + return + } + fmt.Print(resp.Content) + } + fmt.Println() +} + +func functionCalling(ctx context.Context, cm model.ChatModel) { + err := cm.BindTools([]*schema.ToolInfo{ + { + Name: "get_weather", + Desc: "Get current weather information for a city", + ParamsOneOf: schema.NewParamsOneOfByOpenAPIV3(&openapi3.Schema{ + Type: "object", + Properties: map[string]*openapi3.SchemaRef{ + "city": { + Value: &openapi3.Schema{ + Type: "string", + Description: "The city name", + }, + }, + "unit": { + Value: &openapi3.Schema{ + Type: "string", + Enum: []interface{}{"celsius", "fahrenheit"}, + }, + }, + }, + Required: []string{"city"}, + }), + }, + }) + if err != nil { + log.Printf("Bind tools error: %v", err) + return + } + + streamResp, err := cm.Stream(ctx, []*schema.Message{ + { + Role: schema.User, + Content: "What's the weather like in Paris today? Please use Celsius.", + }, + }) + if err != nil { + log.Printf("Generate error: %v", err) + return + } + + msgs := make([]*schema.Message, 0) + for { + msg, err := streamResp.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Fatalf("Stream receive error: %v", err) + } + msgs = append(msgs, msg) + } + resp, err := schema.ConcatMessages(msgs) + if err != nil { + log.Fatalf("Concat error: %v", err) + } + + if len(resp.ToolCalls) > 0 { + fmt.Printf("Function called: %s\n", resp.ToolCalls[0].Function.Name) + fmt.Printf("Arguments: %s\n", resp.ToolCalls[0].Function.Arguments) + + weatherResp, err := cm.Generate(ctx, []*schema.Message{ + { + Role: schema.User, + Content: "What's the weather like in Paris today? Please use Celsius.", + }, + resp, + { + Role: schema.Tool, + ToolCallID: resp.ToolCalls[0].ID, + Content: `{"temperature": 18, "condition": "sunny"}`, + }, + }) + if err != nil { + log.Printf("Generate error: %v", err) + return + } + fmt.Printf("Final response: %s\n", weatherResp.Content) + } +} + +func imageProcessing(ctx context.Context, cm model.ChatModel) { + imageBinary, err := os.ReadFile("./examples/test.jpg") + if err != nil { + log.Fatal(err) + } + resp, err := cm.Generate(ctx, []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + { + Type: schema.ChatMessagePartTypeText, + Text: "What do you see in this image?", + }, + { + Type: schema.ChatMessagePartTypeImageURL, + ImageURL: &schema.ChatMessageImageURL{ + URL: "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imageBinary), + MIMEType: "image/jpeg", + }, + }, + }, + }, + }) + if err != nil { + log.Printf("Generate error: %v", err) + return + } + fmt.Printf("Assistant: %s\n", resp.Content) +} diff --git a/components/model/claude/examples/test.jpg b/components/model/claude/examples/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..83f8ee10864c5808dc139952585a6af52539bb64 GIT binary patch literal 8110 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 literal 0 HcmV?d00001 diff --git a/components/model/claude/go.mod b/components/model/claude/go.mod new file mode 100644 index 0000000..61b7cb6 --- /dev/null +++ b/components/model/claude/go.mod @@ -0,0 +1,53 @@ +module github.com/cloudwego/eino-ext/components/model/claude + +go 1.18 + +require ( + github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.8 + github.com/bytedance/mockey v1.2.13 + github.com/cloudwego/eino v0.3.2 + github.com/getkin/kin-openapi v0.118.0 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/bytedance/sonic v1.12.2 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/swag v0.19.5 // indirect + github.com/goph/emperror v0.17.2 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/invopop/yaml v0.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/nikolalohinski/gonja v1.5.3 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect + github.com/smarty/assertions v1.15.0 // indirect + github.com/smartystreets/goconvey v1.8.1 // indirect + github.com/tidwall/gjson v1.18.0 // 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/yargevad/filepathx v1.0.0 // indirect + golang.org/x/arch v0.11.0 // indirect + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect + golang.org/x/sys v0.26.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/components/model/claude/go.sum b/components/model/claude/go.sum new file mode 100644 index 0000000..bea0f75 --- /dev/null +++ b/components/model/claude/go.sum @@ -0,0 +1,163 @@ +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.8 h1:ss/c/eeyILgoK2sMsTJdcdLdhY3wZSt//+nanM41B9w= +github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.8/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bytedance/mockey v1.2.13 h1:jokWZAm/pUEbD939Rhznz615MKUCZNuvCFQlJ2+ntoo= +github.com/bytedance/mockey v1.2.13/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY= +github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg= +github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/eino v0.3.2 h1:GaMqt3zJAee8ybN4qsATNgSIDAbNruzKCMeMKBH4F1E= +github.com/cloudwego/eino v0.3.2/go.mod h1:+kmJimGEcKuSI6OKhet7kBedkm1WUZS3H1QRazxgWUo= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM= +github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= +github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c= +github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= +github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/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 v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= +github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= +golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= +golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/components/model/claude/option.go b/components/model/claude/option.go new file mode 100644 index 0000000..ef512a6 --- /dev/null +++ b/components/model/claude/option.go @@ -0,0 +1,31 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package claude + +import ( + "github.com/cloudwego/eino/components/model" +) + +type options struct { + TopK *int32 +} + +func WithTopK(k int32) model.Option { + return model.WrapImplSpecificOptFn(func(o *options) { + o.TopK = &k + }) +}