From a3afcd96a057d68b4a19a1681f1ee6c9938b67fd Mon Sep 17 00:00:00 2001 From: Ting Sun Date: Fri, 3 Jul 2026 04:54:13 +0700 Subject: [PATCH] Fix serve tool parsing for legacy response schemas --- src/transformers/cli/serving/utils.py | 5 +++-- tests/cli/test_serve.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/transformers/cli/serving/utils.py b/src/transformers/cli/serving/utils.py index b95f03bc6423..78da55112dc0 100644 --- a/src/transformers/cli/serving/utils.py +++ b/src/transformers/cli/serving/utils.py @@ -216,7 +216,8 @@ def parse_tool_calls(processor, generated_ids, schema: dict) -> list[dict] | Non Returns a list of ``{"name": str, "arguments": str}`` dicts, or ``None`` if none found. """ - parsed = processor.parse_response(generated_ids, schema, prefix="") + parse_kwargs = {"prefix": ""} if isinstance(schema, dict) and ("version" in schema or "fields" in schema) else {} + parsed = processor.parse_response(generated_ids, schema, **parse_kwargs) # The new response_template path returns a dict like {"tool_calls": [...]}; unwrap. if isinstance(parsed, dict) and "tool_calls" in parsed: parsed = parsed["tool_calls"] @@ -225,7 +226,7 @@ def parse_tool_calls(processor, generated_ids, schema: dict) -> list[dict] | Non if not isinstance(parsed, list): parsed = [parsed] tool_calls = [_normalize_tool_call(tool_call) for tool_call in parsed] - return tool_calls if tool_calls else None + return tool_calls or None # Default start/end tokens + schema. The opening token is optional so prefilled diff --git a/tests/cli/test_serve.py b/tests/cli/test_serve.py index a4dac1fea152..bd87edd5f999 100644 --- a/tests/cli/test_serve.py +++ b/tests/cli/test_serve.py @@ -1826,6 +1826,17 @@ def test_parse_multiple_tool_calls_from_text(self): calls = parse_tool_calls(processor, text, schema) self.assertEqual(len(calls), 2) + def test_parse_tool_calls_with_legacy_schema_does_not_pass_prefix(self): + def parse_legacy(response, schema): + return [{"name": "get_weather", "arguments": {"city": "Paris"}}] + + processor = MagicMock() + processor.parse_response.side_effect = parse_legacy + schema = next(v["schema"] for k, v in _TOOL_CALL_FALLBACKS.items() if "qwen3_5" in k) + calls = parse_tool_calls(processor, "unused", schema) + + self.assertEqual(calls, [{"name": "get_weather", "arguments": '{"city": "Paris"}'}]) + class TestCBWorkerDeadServerIntegration(unittest.TestCase): """End-to-end FastAPI behavior when the CB worker has died.