Bug Description
When the LLM calls multiple tools in parallel (e.g. ToolA + ToolB in the same response),
GetStreamingResponseAsync emits incorrect FunctionCallContent objects due to a logic
error in the RawContentBlockStopEvent handler.
Root Cause
In AnthropicClientExtensions.cs, the RawContentBlockStopEvent handler iterates over
all entries in streamingFunctions instead of only the entry at contentBlockStop.Index:
case RawContentBlockStopEvent contentBlockStop:
if (streamingFunctions is not null)
{
foreach (var sf in streamingFunctions) // ← should only emit sf at contentBlockStop.Index
{
contents.Add(FunctionCallContent.CreateFromParsedArguments(...));
}
streamingFunctions.Clear();
}
break;
What Actually Happens
Given a response with ToolA (index 1) and ToolB (index 2):
| Event |
streamingFunctions |
Emitted |
ToolA RawContentBlockStopEvent |
{1: ToolA, 2: ToolB} |
FCC(ToolA) + FCC(ToolB) ← ToolB emitted early with incomplete Arguments |
ToolB RawContentBlockStopEvent |
{} (already cleared) |
nothing ← ToolB's complete Arguments are lost |
This results in:
- ToolB is emitted before its
InputJsonDelta chunks are complete — Arguments may be incomplete
FunctionInvokingChatClient receives duplicate/wrong FunctionCallContent, causing it to
send a second API request with duplicate tool_use ids, which Anthropic API rejects with:
{"type":"error","error":{"type":"invalid_request_error","message":"messages.X.content.Y: tool_use ids must be unique"}}
Expected Behavior
RawContentBlockStopEvent should only emit the FunctionCallContent for the tool at
contentBlockStop.Index:
case RawContentBlockStopEvent contentBlockStop:
if (streamingFunctions is not null &&
streamingFunctions.TryGetValue(contentBlockStop.Index, out var sf))
{
contents.Add(FunctionCallContent.CreateFromParsedArguments(
sf.Arguments.ToString(),
sf.CallId,
sf.Name,
json => (Dictionary<string, object?>?)JsonSerializer.Deserialize(
json,
AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(Dictionary<string, object?>))
)
));
}
break;
Steps to Reproduce
- Use
AnthropicClient.AsIChatClient() with UseFunctionInvocation() middleware
- Provide a prompt that causes the LLM to call two or more tools in parallel in the same response turn
- Observe
AnthropicBadRequestException with tool_use ids must be unique
Environment
- Package:
Anthropic (official SDK)
- Affected versions: v11.0.0, confirmed still present in
main
Microsoft.Extensions.AI version: 10.4.1
Bug Description
When the LLM calls multiple tools in parallel (e.g. ToolA + ToolB in the same response),
GetStreamingResponseAsyncemits incorrectFunctionCallContentobjects due to a logicerror in the
RawContentBlockStopEventhandler.Root Cause
In
AnthropicClientExtensions.cs, theRawContentBlockStopEventhandler iterates overall entries in
streamingFunctionsinstead of only the entry atcontentBlockStop.Index:What Actually Happens
Given a response with ToolA (index 1) and ToolB (index 2):
RawContentBlockStopEvent{1: ToolA, 2: ToolB}RawContentBlockStopEvent{}(already cleared)This results in:
InputJsonDeltachunks are complete — Arguments may be incompleteFunctionInvokingChatClientreceives duplicate/wrongFunctionCallContent, causing it tosend a second API request with duplicate
tool_useids, which Anthropic API rejects with:{"type":"error","error":{"type":"invalid_request_error","message":"messages.X.content.Y: tool_use ids must be unique"}}
Expected Behavior
RawContentBlockStopEventshould only emit theFunctionCallContentfor the tool atcontentBlockStop.Index:Steps to Reproduce
AnthropicClient.AsIChatClient()withUseFunctionInvocation()middlewareAnthropicBadRequestExceptionwithtool_use ids must be uniqueEnvironment
Anthropic(official SDK)mainMicrosoft.Extensions.AIversion: 10.4.1