package e2e_test import ( "context" "encoding/json" "github.com/anthropics/anthropic-sdk-go" "github.com/anthropics/anthropic-sdk-go/option" "github.com/anthropics/anthropic-sdk-go/shared/constant" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("Anthropic API E2E test", func() { var client anthropic.Client Context("API with Anthropic SDK", func() { BeforeEach(func() { client = anthropic.NewClient( option.WithBaseURL(anthropicBaseURL), option.WithAPIKey("test-api-key"), ) }) Context("Non-streaming responses", func() { It("generates a response for a simple message", func() { message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("How much is 2+2? Reply with just the number.")), }, }) Expect(err).ToNot(HaveOccurred()) Expect(message.Content).ToNot(BeEmpty()) Expect(string(message.Role)).To(Equal("assistant")) Expect(string(message.StopReason)).To(Equal("end_turn")) Expect(string(message.Type)).To(Equal("message")) Expect(len(message.Content)).To(BeNumerically(">=", 1)) textBlock := message.Content[0] Expect(string(textBlock.Type)).To(Equal("text")) Expect(textBlock.Text).To(ContainSubstring("mocked")) }) It("handles system prompts", func() { message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, System: []anthropic.TextBlockParam{ {Text: "You are a helpful assistant. Always respond in uppercase letters."}, }, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("Say hello")), }, }) Expect(err).ToNot(HaveOccurred()) Expect(message.Content).ToNot(BeEmpty()) Expect(len(message.Content)).To(BeNumerically(">=", 1)) }) It("returns usage information", func() { message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 100, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")), }, }) Expect(err).ToNot(HaveOccurred()) Expect(message.Usage.InputTokens).To(BeNumerically(">", 0)) Expect(message.Usage.OutputTokens).To(BeNumerically(">", 0)) }) }) Context("Streaming responses", func() { It("streams tokens for a simple message", func() { stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("Count from 1 to 5")), }, }) message := anthropic.Message{} eventCount := 0 hasContentDelta := false for stream.Next() { event := stream.Current() err := message.Accumulate(event) Expect(err).ToNot(HaveOccurred()) eventCount++ // Check for content block delta events switch event.AsAny().(type) { case anthropic.ContentBlockDeltaEvent: hasContentDelta = true } } Expect(stream.Err()).ToNot(HaveOccurred()) Expect(eventCount).To(BeNumerically(">", 0)) Expect(hasContentDelta).To(BeTrue()) // Check accumulated message Expect(message.Content).ToNot(BeEmpty()) // Role is a constant type that defaults to "assistant" Expect(string(message.Role)).To(Equal("assistant")) }) It("streams with system prompt", func() { stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, System: []anthropic.TextBlockParam{ {Text: "You are a helpful assistant."}, }, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("Say hello")), }, }) message := anthropic.Message{} for stream.Next() { event := stream.Current() err := message.Accumulate(event) Expect(err).ToNot(HaveOccurred()) } Expect(stream.Err()).ToNot(HaveOccurred()) Expect(message.Content).ToNot(BeEmpty()) }) }) Context("Tool calling", func() { It("handles tool calls in non-streaming mode", func() { message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather like in San Francisco?")), }, Tools: []anthropic.ToolUnionParam{ anthropic.ToolUnionParam{ OfTool: &anthropic.ToolParam{ Name: "get_weather", Description: anthropic.Opt("Get the current weather in a given location"), InputSchema: anthropic.ToolInputSchemaParam{ Type: constant.ValueOf[constant.Object](), Properties: map[string]any{ "location": map[string]any{ "type": "string", "description": "The city and state, e.g. San Francisco, CA", }, }, Required: []string{"location"}, }, }, }, }, }) Expect(err).ToNot(HaveOccurred()) Expect(message.Content).ToNot(BeEmpty()) // The model must use tools - find the tool use in the response hasToolUse := false for _, block := range message.Content { if block.Type == "tool_use" { hasToolUse = true Expect(block.Name).To(Equal("get_weather")) Expect(block.ID).ToNot(BeEmpty()) // Verify that input contains location var inputMap map[string]any err := json.Unmarshal(block.Input, &inputMap) Expect(err).ToNot(HaveOccurred()) _, hasLocation := inputMap["location"] Expect(hasLocation).To(BeTrue()) } } // Model must have called the tool Expect(hasToolUse).To(BeTrue(), "Model should have called the get_weather tool") Expect(string(message.StopReason)).To(Equal("tool_use")) }) It("handles tool_choice parameter", func() { message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("Tell me about the weather")), }, Tools: []anthropic.ToolUnionParam{ anthropic.ToolUnionParam{ OfTool: &anthropic.ToolParam{ Name: "get_weather", Description: anthropic.Opt("Get the current weather"), InputSchema: anthropic.ToolInputSchemaParam{ Type: constant.ValueOf[constant.Object](), Properties: map[string]any{ "location": map[string]any{ "type": "string", }, }, }, }, }, }, ToolChoice: anthropic.ToolChoiceUnionParam{ OfAuto: &anthropic.ToolChoiceAutoParam{ Type: constant.ValueOf[constant.Auto](), }, }, }) Expect(err).ToNot(HaveOccurred()) Expect(message.Content).ToNot(BeEmpty()) }) It("handles tool results in messages", func() { // First, make a request that should trigger a tool call firstMessage, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in SF?")), }, Tools: []anthropic.ToolUnionParam{ anthropic.ToolUnionParam{ OfTool: &anthropic.ToolParam{ Name: "get_weather", Description: anthropic.Opt("Get weather"), InputSchema: anthropic.ToolInputSchemaParam{ Type: constant.ValueOf[constant.Object](), Properties: map[string]any{ "location": map[string]any{"type": "string"}, }, }, }, }, }, }) Expect(err).ToNot(HaveOccurred()) // Find the tool use block - model must call the tool var toolUseID string var toolUseName string for _, block := range firstMessage.Content { if block.Type == "tool_use" { toolUseID = block.ID toolUseName = block.Name break } } // Model must have called the tool Expect(toolUseID).ToNot(BeEmpty(), "Model should have called the get_weather tool") // Convert ContentBlockUnion to ContentBlockParamUnion for NewAssistantMessage contentBlocks := make([]anthropic.ContentBlockParamUnion, len(firstMessage.Content)) for i, block := range firstMessage.Content { if block.Type == "tool_use" { var inputMap map[string]any if err := json.Unmarshal(block.Input, &inputMap); err == nil { contentBlocks[i] = anthropic.NewToolUseBlock(block.ID, inputMap, block.Name) } else { contentBlocks[i] = anthropic.NewToolUseBlock(block.ID, block.Input, block.Name) } } else if block.Type == "text" { contentBlocks[i] = anthropic.NewTextBlock(block.Text) } } // Send back a tool result and verify it's handled correctly secondMessage, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in SF?")), anthropic.NewAssistantMessage(contentBlocks...), anthropic.NewUserMessage( anthropic.NewToolResultBlock(toolUseID, "Sunny, 72°F", false), ), }, Tools: []anthropic.ToolUnionParam{ anthropic.ToolUnionParam{ OfTool: &anthropic.ToolParam{ Name: toolUseName, Description: anthropic.Opt("Get weather"), InputSchema: anthropic.ToolInputSchemaParam{ Type: constant.ValueOf[constant.Object](), Properties: map[string]any{ "location": map[string]any{"type": "string"}, }, }, }, }, }, }) Expect(err).ToNot(HaveOccurred()) Expect(secondMessage.Content).ToNot(BeEmpty()) }) It("handles tool calls in streaming mode", func() { stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather like in San Francisco?")), }, Tools: []anthropic.ToolUnionParam{ anthropic.ToolUnionParam{ OfTool: &anthropic.ToolParam{ Name: "get_weather", Description: anthropic.Opt("Get the current weather in a given location"), InputSchema: anthropic.ToolInputSchemaParam{ Type: constant.ValueOf[constant.Object](), Properties: map[string]any{ "location": map[string]any{ "type": "string", "description": "The city and state, e.g. San Francisco, CA", }, }, Required: []string{"location"}, }, }, }, }, }) message := anthropic.Message{} eventCount := 0 hasContentBlockStart := false hasContentBlockDelta := false hasContentBlockStop := false for stream.Next() { event := stream.Current() err := message.Accumulate(event) Expect(err).ToNot(HaveOccurred()) eventCount++ // Check for different event types related to tool use switch e := event.AsAny().(type) { case anthropic.ContentBlockStartEvent: hasContentBlockStart = true if e.ContentBlock.Type == "tool_use" { // Tool use block detected } case anthropic.ContentBlockDeltaEvent: hasContentBlockDelta = true case anthropic.ContentBlockStopEvent: hasContentBlockStop = true } } Expect(stream.Err()).ToNot(HaveOccurred()) Expect(eventCount).To(BeNumerically(">", 0)) // Verify streaming events were emitted Expect(hasContentBlockStart).To(BeTrue(), "Should have content_block_start event") Expect(hasContentBlockDelta).To(BeTrue(), "Should have content_block_delta event") Expect(hasContentBlockStop).To(BeTrue(), "Should have content_block_stop event") // Check accumulated message has tool use Expect(message.Content).ToNot(BeEmpty()) // Model must have called the tool foundToolUse := false for _, block := range message.Content { if block.Type == "tool_use" { foundToolUse = true Expect(block.Name).To(Equal("get_weather")) Expect(block.ID).ToNot(BeEmpty()) } } Expect(foundToolUse).To(BeTrue(), "Model should have called the get_weather tool in streaming mode") Expect(string(message.StopReason)).To(Equal("tool_use")) }) }) Context("ChatDeltas (C++ autoparser)", func() { It("streams tool calls via ChatDeltas", func() { stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model-autoparser", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("AUTOPARSER_TOOL_CALL What's the weather like in San Francisco?")), }, Tools: []anthropic.ToolUnionParam{ anthropic.ToolUnionParam{ OfTool: &anthropic.ToolParam{ Name: "get_weather", Description: anthropic.Opt("Get the current weather in a given location"), InputSchema: anthropic.ToolInputSchemaParam{ Type: constant.ValueOf[constant.Object](), Properties: map[string]any{ "location": map[string]any{ "type": "string", "description": "The city and state", }, }, Required: []string{"location"}, }, }, }, }, }) message := anthropic.Message{} hasToolUseStart := false for stream.Next() { event := stream.Current() err := message.Accumulate(event) Expect(err).ToNot(HaveOccurred()) if e, ok := event.AsAny().(anthropic.ContentBlockStartEvent); ok { if e.ContentBlock.Type == "tool_use" { hasToolUseStart = true } } } Expect(stream.Err()).ToNot(HaveOccurred()) Expect(hasToolUseStart).To(BeTrue(), "Should have tool_use content_block_start event from ChatDeltas") Expect(string(message.StopReason)).To(Equal("tool_use")) // Verify tool call is present in accumulated message foundToolUse := false for _, block := range message.Content { if block.Type == "tool_use" { foundToolUse = true Expect(block.ID).ToNot(BeEmpty()) } } Expect(foundToolUse).To(BeTrue(), "Accumulated message should contain tool_use block from ChatDeltas") }) It("streams content via ChatDeltas without duplication", func() { stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model-autoparser", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("AUTOPARSER_CONTENT Tell me about LocalAI")), }, }) message := anthropic.Message{} var textDeltas []string for stream.Next() { event := stream.Current() err := message.Accumulate(event) Expect(err).ToNot(HaveOccurred()) if e, ok := event.AsAny().(anthropic.ContentBlockDeltaEvent); ok { if e.Delta.Type == "text_delta" && e.Delta.Text != "" { textDeltas = append(textDeltas, e.Delta.Text) } } } Expect(stream.Err()).ToNot(HaveOccurred()) Expect(message.Content).ToNot(BeEmpty()) Expect(string(message.StopReason)).To(Equal("end_turn")) // Content should appear exactly once (no duplication) fullText := "" for _, block := range message.Content { if block.Type == "text" { fullText += block.Text } } Expect(fullText).To(ContainSubstring("LocalAI")) // Check that the content is not duplicated by counting occurrences Expect(len(fullText)).To(BeNumerically("<", 200), "Content should not be duplicated") }) It("handles tool calls via ChatDeltas in non-streaming mode", func() { message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{ Model: "mock-model-autoparser", MaxTokens: 1024, Messages: []anthropic.MessageParam{ anthropic.NewUserMessage(anthropic.NewTextBlock("AUTOPARSER_TOOL_CALL What's the weather like in San Francisco?")), }, Tools: []anthropic.ToolUnionParam{ anthropic.ToolUnionParam{ OfTool: &anthropic.ToolParam{ Name: "get_weather", Description: anthropic.Opt("Get the current weather"), InputSchema: anthropic.ToolInputSchemaParam{ Type: constant.ValueOf[constant.Object](), Properties: map[string]any{ "location": map[string]any{ "type": "string", }, }, Required: []string{"location"}, }, }, }, }, }) Expect(err).ToNot(HaveOccurred()) Expect(message.Content).ToNot(BeEmpty()) Expect(string(message.StopReason)).To(Equal("tool_use")) foundToolUse := false for _, block := range message.Content { if block.Type == "tool_use" { foundToolUse = true Expect(block.ID).ToNot(BeEmpty()) } } Expect(foundToolUse).To(BeTrue(), "Should have tool_use block from ChatDeltas") }) }) }) })