[BugFix][Platform] Fix extra function name in final chunk of streaming tool calls (#8178)
### What this PR does / why we need it? Fix a bug in the GLM tool call parser where the `function.name` field was incorrectly included in the final (non-first) chunks of streaming tool calls. Per OpenAI streaming semantics, `id`, `type`, and `function.name` must only appear in the **first** chunk for a given tool call index. When `_create_remaining_args_delta` was called for continuing/finishing chunks, it was incorrectly reading the function name from `delta_message.tool_calls` and re-emitting it, causing clients to see a duplicate/extra function name in the final chunk. **Root cause**: The original code always looked up the tool call in `delta_message.tool_calls` to get the name, id, and type — even when this was not the first chunk being streamed. This caused the function name to appear again in the final argument-completion chunk. **Fix**: - Track whether arguments have already been streamed (`already_streamed_args`) for each tool call index. - Only populate `fallback_tool_call_id`, `fallback_tool_call_type`, and `fallback_tool_call_name` when `already_streamed_args` is empty (i.e., this is genuinely the first chunk). - Refactored `_create_remaining_args_delta` to omit header fields entirely when all fallback values are `None`, which is the correct behavior for continuing/finishing chunks. ### Does this PR introduce _any_ user-facing change? Yes. Clients consuming the streaming tool call response will no longer receive a duplicate `function.name` in the final chunk. This fixes incorrect behavior visible in the OpenAI-compatible streaming API output for GLM models using tool calls. ### How was this patch tested? - Code review and logic analysis of the streaming tool call path in `patch_glm_tool_call_parser.py`. - Existing unit tests in `tests/ut/platform/test_patch_glm_tool_call_parser.py`. --------- Signed-off-by: chen-weipeng12 <chen-weipeng12@noreply.gitcode.com> Signed-off-by: chenweiqiang11 <chenweiqiang11@noreply.github.com> Co-authored-by: chen-weipeng12 <chen-weipeng12@noreply.gitcode.com>
This commit is contained in:
@@ -70,7 +70,11 @@ def test_create_remaining_args_delta_uses_fallback_metadata_for_args_only_delta(
|
||||
assert tc.function.arguments == ('{"files":[{"filepath":"HumanEval-X/README.md"}]}')
|
||||
|
||||
|
||||
def test_create_remaining_args_delta_prefers_current_metadata_over_fallback():
|
||||
def test_create_remaining_args_delta_uses_fallback_over_original_delta():
|
||||
# _create_remaining_args_delta ignores original_delta metadata and uses
|
||||
# the explicit fallback_* parameters instead. The caller is responsible
|
||||
# for passing non-None fallback values only for the first chunk of a
|
||||
# tool call (when the header has not yet been streamed).
|
||||
original_delta = DeltaMessage(
|
||||
tool_calls=[
|
||||
DeltaToolCall(
|
||||
@@ -95,9 +99,9 @@ def test_create_remaining_args_delta_prefers_current_metadata_over_fallback():
|
||||
)
|
||||
|
||||
tc = result.tool_calls[0]
|
||||
assert tc.id == "call_current"
|
||||
assert tc.id == "call_fallback"
|
||||
assert tc.type == "function"
|
||||
assert tc.function.name == "current_name"
|
||||
assert tc.function.name == "fallback_name"
|
||||
assert tc.function.arguments == "]}"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user