fix(DeepSeekAI): keep nameless arg fragments when grouping tool calls

The previous grouping pre-filtered tool-call updates on FunctionName
before grouping by index. In DeepSeek streaming only the FIRST fragment
of a call carries the name/id; the argument text arrives in later
fragments with FunctionName == null. The filter dropped them, so every
tool call got empty FunctionArgs (get_editor_state(), get_node() ->
'node_id is required').

Group ALL updates by Index first, then pick the first group that
contains a named fragment and concatenate every argument fragment in
that group. Verified via OpenPencil integration test: get_node now
carries {"node_id": "10"} intact instead of being empty or joined
across parallel calls.
This commit is contained in:
Vitali sharp8n 2026-09-09 11:06:12 +03:00
parent 9fe969a47c
commit aa132b1419

View file

@ -284,20 +284,28 @@ public class ChatCompletionProvider : IChatCompletion
if (choice.FinishReason == ChatFinishReason.ToolCalls || choice.FinishReason == ChatFinishReason.FunctionCall) if (choice.FinishReason == ChatFinishReason.ToolCalls || choice.FinishReason == ChatFinishReason.FunctionCall)
{ {
// The model may emit several tool calls in one response. The OpenAI SDK streams each // The model may emit several tool calls in one response. The OpenAI SDK streams
// call's arguments as interleaved fragments per call index, so we must accumulate them // arguments as interleaved fragments PER CALL INDEX: the first fragment of a call
// PER CALL (grouped by index), not concatenate every fragment of every call together // carries the name/id and an empty arguments payload, later fragments carry only
// (that would corrupt arguments, e.g. get_node({"node_id": "10"}{"maxDepth": 2})). // the argument text (FunctionName is null on them). We must accumulate fragments
var grouped = toolCalls // grouped by Index and must NOT pre-filter on FunctionName (that would drop the
.Where(x => !string.IsNullOrEmpty(x.FunctionName)) // argument-carrying fragments and yield empty FunctionArgs).
//
// Old bug: string.Join over ALL fragments of ALL calls produced corrupted
// arguments (get_node({"node_id": "10"}{"maxDepth": 2})), which made every
// tool call fail with e.g. "node_id is required".
var groups = toolCalls
.GroupBy(x => x.Index) .GroupBy(x => x.Index)
.OrderBy(g => g.Key) .OrderBy(g => g.Key)
.ToList(); .ToList();
var functionName = grouped.Count > 0 ? grouped[0].First().FunctionName : null; var namedGroup = groups.FirstOrDefault(g => g.Any(x => !string.IsNullOrEmpty(x.FunctionName)));
var toolCallId = grouped.Count > 0 ? grouped[0].First().ToolCallId : null; var namedUpdate = namedGroup?.FirstOrDefault(x => !string.IsNullOrEmpty(x.FunctionName));
var functionArgument = grouped.Count > 0
? string.Concat(grouped[0].Where(x => x.FunctionArgumentsUpdate != null).Select(x => x.FunctionArgumentsUpdate.ToString())) var functionName = namedUpdate?.FunctionName;
var toolCallId = namedUpdate?.ToolCallId;
var functionArgument = namedGroup != null
? string.Concat(namedGroup.Where(x => x.FunctionArgumentsUpdate != null).Select(x => x.FunctionArgumentsUpdate.ToString()))
: string.Empty; : string.Empty;
#if DEBUG #if DEBUG