Skip to content

Commit 2132b65

Browse files
HavenDVclaude
andcommitted
fix: accumulate streaming tool call arguments across chunks
Replace ProcessStreamingDelta with inline accumulation logic. First delta (ToolCallDeltaStart) initializes Id/Name, subsequent deltas (ToolCallDelta) append argument fragments. Emit complete FunctionCallContent only when finish_reason arrives. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1be95a4 commit 2132b65

1 file changed

Lines changed: 50 additions & 46 deletions

File tree

src/libs/AI21/Extensions/Ai21Client.ChatClient.cs

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Runtime.CompilerServices;
2+
using System.Text;
23
using System.Text.Json;
34
using Microsoft.Extensions.AI;
45

@@ -55,6 +56,8 @@ async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync
5556
yield break;
5657
}
5758

59+
var toolCallBuilders = new Dictionary<int, (string Id, string Name, StringBuilder Args)>();
60+
5861
foreach (var streamMsg in streamMessages)
5962
{
6063
foreach (var choice in streamMsg.Choices)
@@ -63,11 +66,56 @@ async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync
6366
{
6467
ResponseId = streamMsg.Id,
6568
Role = ChatRole.Assistant,
66-
FinishReason = ToFinishReason(choice.FinishReason),
6769
RawRepresentation = streamMsg,
6870
};
6971

70-
ProcessStreamingDelta(update, choice.Delta);
72+
var delta = choice.Delta;
73+
if (delta.IsValue2 && delta.Value2 is { } contentDelta)
74+
{
75+
if (!string.IsNullOrEmpty(contentDelta.Content))
76+
{
77+
update.Contents.Add(new TextContent(contentDelta.Content)
78+
{
79+
RawRepresentation = contentDelta,
80+
});
81+
}
82+
}
83+
else if (delta.IsValue3 && delta.Value3 is { } toolCallsFirst)
84+
{
85+
foreach (var toolCall in toolCallsFirst.ToolCalls)
86+
{
87+
toolCallBuilders[toolCall.Index] = (
88+
Id: toolCall.Id,
89+
Name: toolCall.Function.Name,
90+
Args: new StringBuilder());
91+
}
92+
}
93+
else if (delta.IsValue4 && delta.Value4 is { } toolCallsDelta)
94+
{
95+
foreach (var toolCall in toolCallsDelta.ToolCalls)
96+
{
97+
if (!string.IsNullOrEmpty(toolCall.Function.Arguments) &&
98+
toolCallBuilders.TryGetValue(toolCall.Index, out var existing))
99+
{
100+
existing.Args.Append(toolCall.Function.Arguments);
101+
}
102+
}
103+
}
104+
105+
if (choice.FinishReason is not null)
106+
{
107+
update.FinishReason = ToFinishReason(choice.FinishReason);
108+
if (toolCallBuilders.Count > 0)
109+
{
110+
foreach (var (_, builder) in toolCallBuilders)
111+
{
112+
update.Contents.Add(new FunctionCallContent(
113+
builder.Id, builder.Name,
114+
ParseArguments(builder.Args.ToString())));
115+
}
116+
toolCallBuilders.Clear();
117+
}
118+
}
71119

72120
yield return update;
73121
}
@@ -87,50 +135,6 @@ async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync
87135
}
88136
}
89137

90-
private static void ProcessStreamingDelta(
91-
ChatResponseUpdate update,
92-
AnyOf<ChatStreamingFirstDelta, ChatStreamingContentDelta, ChatStreamingToolCallsFirstDelta, ChatStreamingToolCallsDelta> delta)
93-
{
94-
if (delta.IsValue2 && delta.Value2 is { } contentDelta)
95-
{
96-
if (!string.IsNullOrEmpty(contentDelta.Content))
97-
{
98-
update.Contents.Add(new TextContent(contentDelta.Content)
99-
{
100-
RawRepresentation = contentDelta,
101-
});
102-
}
103-
}
104-
else if (delta.IsValue3 && delta.Value3 is { } toolCallsFirst)
105-
{
106-
foreach (var toolCall in toolCallsFirst.ToolCalls)
107-
{
108-
update.Contents.Add(new FunctionCallContent(
109-
callId: toolCall.Id,
110-
name: toolCall.Function.Name,
111-
arguments: null)
112-
{
113-
RawRepresentation = toolCall,
114-
});
115-
}
116-
}
117-
else if (delta.IsValue4 && delta.Value4 is { } toolCallsDelta)
118-
{
119-
foreach (var toolCall in toolCallsDelta.ToolCalls)
120-
{
121-
if (!string.IsNullOrEmpty(toolCall.Function.Arguments))
122-
{
123-
update.Contents.Add(new FunctionCallContent(
124-
callId: null,
125-
name: null,
126-
arguments: ParseArguments(toolCall.Function.Arguments))
127-
{
128-
RawRepresentation = toolCall,
129-
});
130-
}
131-
}
132-
}
133-
}
134138

135139
private ChatRequest CreateChatRequest(
136140
IEnumerable<ChatMessage> messages,

0 commit comments

Comments
 (0)