diff --git a/Anthropic.SDK.Tests/Tools.cs b/Anthropic.SDK.Tests/Tools.cs index cd6dcc2..00c67ff 100644 --- a/Anthropic.SDK.Tests/Tools.cs +++ b/Anthropic.SDK.Tests/Tools.cs @@ -840,5 +840,52 @@ public async Task TestClaude3ImageJsonModeMessage() } + [TestMethod] + public async Task TestMultipleCallsSameFunction() + { + var client = new AnthropicClient(); + var messages = new List + { + new Message(RoleType.User, "Calculate 3 times 2, then 7 times 2, then 11 times 2. Use the multiply function for each calculation. Make all three function calls in a single response.") + }; + var tools = new List + { + Common.Tool.FromFunc("Multiply_By_Two", + ([FunctionParameter("Number to multiply by 2", true)]int number) => (number * 2).ToString()) + }; + + var parameters = new MessageParameters() + { + Messages = messages, + MaxTokens = 2048, + Model = AnthropicModels.Claude3Sonnet, + Stream = false, + Temperature = 1.0m, + Tools = tools + }; + var res = await client.Messages.GetClaudeMessageAsync(parameters); + + messages.Add(res.Message); + + // This tests the bug fix - multiple calls to same function should work correctly + Assert.IsTrue(res.ToolCalls.Count >= 2, "Should have multiple tool calls"); + + // Verify that each tool call has unique ID and different arguments + var ids = new HashSet(); + var args = new HashSet(); + + foreach (var toolCall in res.ToolCalls) + { + Assert.IsTrue(ids.Add(toolCall.Id), $"Tool call ID {toolCall.Id} should be unique"); + + var argString = toolCall.Arguments?.ToJsonString() ?? ""; + Assert.IsTrue(args.Add(argString), $"Tool call arguments {argString} should be different"); + + var response = toolCall.Invoke(); + messages.Add(new Message(toolCall, response)); + } + + } + } } diff --git a/Anthropic.SDK/Messaging/MessagesEndpoint.cs b/Anthropic.SDK/Messaging/MessagesEndpoint.cs index c6b6ad1..28f47c6 100644 --- a/Anthropic.SDK/Messaging/MessagesEndpoint.cs +++ b/Anthropic.SDK/Messaging/MessagesEndpoint.cs @@ -38,12 +38,14 @@ public async Task GetClaudeMessageAsync(MessageParameters param if (message.Type == ContentType.tool_use) { var tool = parameters.Tools?.FirstOrDefault(t => t.Function.Name == (message as ToolUseContent).Name); - + if (tool != null) { - tool.Function.Arguments = (message as ToolUseContent).Input; - tool.Function.Id = (message as ToolUseContent).Id; - toolCalls.Add(tool.Function); + var copiedTool = new Common.Tool(tool); + copiedTool.Function.Arguments = (message as ToolUseContent).Input; + copiedTool.Function.Id = (message as ToolUseContent).Id; + + toolCalls.Add(copiedTool.Function); } } } @@ -117,9 +119,11 @@ public async IAsyncEnumerable StreamClaudeMessageAsync(MessageP if (tool != null) { - tool.Function.Arguments = arguments; - tool.Function.Id = id; - toolCalls.Add(tool.Function); + var copiedTool = new Common.Tool(tool); + copiedTool.Function.Arguments = arguments; + copiedTool.Function.Id = id; + + toolCalls.Add(copiedTool.Function); } captureTool = false; result.ToolCalls = toolCalls;