A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 3, 17:45 UTC
[Bugfix] Fix Gemma4ToolParser streaming float corruption (#42128)
Signed-off-by: abinggo <107740309+abinggo@users.noreply.github.com>
longguo committed
May 14, 2026 at 09:03 UTC
665f9c42535c34e7d04b1c0424d34fe7677bb245
2 files changed
+41
tests/tool_parsers/test_gemma4_tool_parser.py
+29
@@ -135,6 +135,26 @@ class TestParseGemma4Args:
135
result = _parse_gemma4_args('name:<|"|>test<|"|>,flag:', partial=True)
136
assert result == {"name": "test"}
137
138
+ def test_trailing_dot_float_partial_withheld(self):
139
+ """Bare float ending with '.' is withheld in partial mode.
140
+
141
+ Regression test for #42047: float("108.") → 108.0 causes
142
+ streaming diff corruption (108.0 → 108.2 becomes 108.02).
143
+ """
144
+ # Single key with trailing dot — withheld entirely
145
+ result = _parse_gemma4_args("left:108.,right:22.8", partial=True)
146
+ assert result == {}
147
+
148
+ # Stable key before trailing-dot key — stable key is kept
149
+ result = _parse_gemma4_args(
150
+ 'name:<|"|>test<|"|>,score:3.,count:1', partial=True
151
+ )
152
+ assert result == {"name": "test"}
153
+
154
+ # Non-partial mode parses trailing dot normally
155
+ result = _parse_gemma4_args("left:108.,right:22.8", partial=False)
156
+ assert result == {"left": 108.0, "right": 22.8}
157
+
158
@pytest.mark.timeout(5)
159
def test_malformed_partial_array(self):
160
result = _parse_gemma4_args(":[t:[]")
@@ -164,6 +184,15 @@ class TestParseGemma4Array:
184
result = _parse_gemma4_array("42,]trailing")
185
assert result == [42]
186
187
+ def test_trailing_dot_float_partial_withheld(self):
188
+ """Array elements with trailing dot withheld in partial mode."""
189
+ result = _parse_gemma4_array("108.,22.8", partial=True)
190
+ assert result == []
191
+
192
+ # Stable elements before trailing-dot element are kept
193
+ result = _parse_gemma4_array("42,108.,3", partial=True)
194
+ assert result == [42]
195
+
196
197
# ---------------------------------------------------------------------------
198
# Non-streaming extraction tests
vllm/tool_parsers/gemma4_tool_parser.py
+12
@@ -211,6 +211,14 @@ def _parse_gemma4_args(args_str: str, *, partial: bool = False) -> dict:
211
i,
212
)
213
break
214
+ if partial:
215
+ raw_val = args_str[val_start:i].strip()
216
+ if raw_val.endswith("."):
217
+ # Trailing dot means decimal digits may still arrive
218
+ # (e.g. "108." may become "108.2"). Parsing now would
219
+ # yield float("108.") == 108.0, whose json repr "108.0"
220
+ # corrupts the streaming diff when the true digit lands.
221
+ break
222
result[key] = _parse_gemma4_value(args_str[val_start:i])
223
224
return result
@@ -294,6 +302,10 @@ def _parse_gemma4_array(arr_str: str, *, partial: bool = False) -> list:
302
i,
303
)
304
break
305
+ if partial:
306
+ raw_val = arr_str[val_start:i].strip()
307
+ if raw_val.endswith("."):
308
+ break
309
items.append(_parse_gemma4_value(arr_str[val_start:i]))
310
311
return items