Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions tests/tool_parsers/test_glm47_moe_tool_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,34 @@ def test_whitespace_preserved_in_arg_values(self, glm47_tool_parser, mock_reques
assert r.tools_called
assert json.loads(r.tool_calls[0].function.arguments) == {"city": " Beijing "}

def test_missing_arg_value_open_tag(self, glm47_tool_parser, mock_request):
# Under load GLM-4.7-family models occasionally skip the opening
# <arg_value> tag while still emitting the value and closing tag.
out = (
"<tool_call>get_weather<arg_key>city</arg_key>"
"Beijing</arg_value></tool_call>"
)
r = glm47_tool_parser.extract_tool_calls(out, request=mock_request)
assert r.tools_called
assert r.tool_calls[0].function.name == "get_weather"
assert json.loads(r.tool_calls[0].function.arguments) == {"city": "Beijing"}

def test_missing_arg_value_open_tag_multiple_args(
self, glm47_tool_parser, mock_request
):
out = (
"<tool_call>get_weather"
"<arg_key>city</arg_key>Beijing</arg_value>"
"<arg_key>date</arg_key><arg_value>2026-07-21</arg_value>"
"</tool_call>"
)
r = glm47_tool_parser.extract_tool_calls(out, request=mock_request)
assert r.tools_called
assert json.loads(r.tool_calls[0].function.arguments) == {
"city": "Beijing",
"date": "2026-07-21",
}

def test_content_before(self, glm47_tool_parser, mock_request):
out = "Checking.<tool_call>get_current_date</tool_call>"
r = glm47_tool_parser.extract_tool_calls(out, request=mock_request)
Expand Down Expand Up @@ -266,3 +294,38 @@ def test_with_args(self, glm47_tool_parser, mock_request):
]
args = json.loads("".join(arguments))
assert args["city"] == "Beijing"

def test_missing_arg_value_open_tag(self, glm47_tool_parser, mock_request):
_reset(glm47_tool_parser)
chunks = [
"<tool_call>",
"get_weather\n",
"<arg_key>city</arg_key>",
"Bei",
"jing",
"</arg_value>",
"</tool_call>",
]
current_text = ""
deltas = []
for chunk in chunks:
current_text += chunk
delta = glm47_tool_parser.extract_tool_calls_streaming(
previous_text="",
current_text=current_text,
delta_text=chunk,
previous_token_ids=[],
current_token_ids=[],
delta_token_ids=[],
request=mock_request,
)
if delta:
deltas.append(delta)
arguments = [
tool_call.function.arguments
for delta in deltas
for tool_call in (delta.tool_calls or [])
if tool_call.function and tool_call.function.arguments
]
args = json.loads("".join(arguments))
assert args["city"] == "Beijing"
8 changes: 6 additions & 2 deletions vllm/parser/glm47_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,18 @@
("<|system|>", "<|user|>", "<|assistant|>", "<|observation|>")
)

# The opening <arg_value> tag is optional: under load GLM-4.7-family models
# occasionally skip it, emitting
# ``<arg_key>key</arg_key>value</arg_value>`` with the value fully intact.
# Requiring the tag would silently drop such arguments (yielding ``{}``).
_ARG_RE = re.compile(
r"<arg_key>(?P<key>.*?)</arg_key>\s*"
r"<arg_value>(?P<value>.*?)</arg_value>",
r"(?:<arg_value>)?(?P<value>.*?)</arg_value>",
re.DOTALL,
)
_PARTIAL_ARG_RE = re.compile(
r"<arg_key>(?P<key>.*?)</arg_key>\s*"
r"<arg_value>(?P<value>.*)$",
r"(?:<arg_value>)?(?P<value>.*)$",
re.DOTALL,
)

Expand Down
Loading