Files
vllm-project--vllm/tests/tool_parsers/test_glm4_moe_tool_parser.py
T
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

229 lines
6.9 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Compatibility tests for GLM-4.5 using the shared GLM XML parser."""
import json
from typing import Any, TypedDict
from tests.parser.engine.replay_harness import MockTokenizer
from vllm.entrypoints.openai.chat_completion.protocol import (
ChatCompletionRequest,
ChatCompletionToolsParam,
FunctionDefinition,
)
from vllm.tool_parsers import ToolParserManager
from vllm.tool_parsers.glm47_moe_tool_parser import Glm47MoeModelToolParser
MODEL = "zai-org/GLM-4.5"
_GLM_VOCAB = {
"<think>": 50,
"</think>": 51,
"<tool_call>": 60,
"</tool_call>": 61,
"<arg_key>": 62,
"</arg_key>": 63,
"<arg_value>": 64,
"</arg_value>": 65,
}
class _CollectedToolDelta(TypedDict):
name: str | None
args_fragments: list[str]
def _mock_tokenizer() -> MockTokenizer:
return MockTokenizer(vocab=_GLM_VOCAB, tokens=[])
def _tools() -> list[ChatCompletionToolsParam]:
return [
ChatCompletionToolsParam(
function=FunctionDefinition(
name="get_current_weather",
parameters={
"type": "object",
"properties": {
"city": {"type": "string"},
"state": {"type": "string"},
"unit": {"type": "string"},
},
},
),
),
ChatCompletionToolsParam(
function=FunctionDefinition(
name="calculate",
parameters={
"type": "object",
"properties": {
"operation": {"type": "string"},
"a": {"type": "number"},
"b": {"type": "number"},
"enabled": {"type": "boolean"},
},
},
),
),
ChatCompletionToolsParam(
function=FunctionDefinition(name="get_time", parameters={}),
),
]
def _request(tools: list[ChatCompletionToolsParam]) -> ChatCompletionRequest:
return ChatCompletionRequest(model=MODEL, messages=[], tools=tools)
def _parser(tools: list[ChatCompletionToolsParam] | None = None):
return Glm47MoeModelToolParser(_mock_tokenizer(), tools=tools)
def _collect_tool_deltas(deltas: Any) -> dict[int, _CollectedToolDelta]:
calls: dict[int, _CollectedToolDelta] = {}
for delta in deltas:
if delta is None or not delta.tool_calls:
continue
for tool_call in delta.tool_calls:
entry = calls.setdefault(
tool_call.index,
{"name": None, "args_fragments": []},
)
function = tool_call.function
if function is None:
continue
if isinstance(function, dict):
name = function.get("name")
arguments = function.get("arguments")
else:
name = function.name
arguments = function.arguments
if isinstance(name, str) and name:
entry["name"] = name
if isinstance(arguments, str) and arguments:
entry["args_fragments"].append(arguments)
return calls
def test_glm45_uses_shared_glm47_parser():
assert ToolParserManager.get_tool_parser("glm45") is Glm47MoeModelToolParser
assert ToolParserManager.get_tool_parser("glm47") is Glm47MoeModelToolParser
def test_extract_tool_calls_with_glm45_newline_format():
tools = _tools()
parser = _parser(tools)
model_output = """I'll check it. <tool_call>get_current_weather
<arg_key>city</arg_key>
<arg_value>Dallas</arg_value>
<arg_key>state</arg_key>
<arg_value>TX</arg_value>
<arg_key>unit</arg_key>
<arg_value>fahrenheit</arg_value>
</tool_call>"""
extracted = parser.extract_tool_calls(model_output, request=_request(tools))
assert extracted.tools_called
assert extracted.content == "I'll check it."
assert len(extracted.tool_calls) == 1
tool_call = extracted.tool_calls[0]
assert tool_call.function.name == "get_current_weather"
assert json.loads(tool_call.function.arguments) == {
"city": "Dallas",
"state": "TX",
"unit": "fahrenheit",
}
def test_extract_multiple_tool_calls_with_glm45_newline_format():
tools = _tools()
parser = _parser(tools)
model_output = """<tool_call>get_current_weather
<arg_key>city</arg_key><arg_value>Dallas</arg_value>
</tool_call>
<tool_call>get_current_weather
<arg_key>city</arg_key><arg_value>Orlando</arg_value>
</tool_call>"""
extracted = parser.extract_tool_calls(model_output, request=_request(tools))
assert extracted.tools_called
assert [tc.function.name for tc in extracted.tool_calls] == [
"get_current_weather",
"get_current_weather",
]
assert [
json.loads(tc.function.arguments)["city"] for tc in extracted.tool_calls
] == ["Dallas", "Orlando"]
def test_extract_tool_calls_coerces_schema_types():
tools = _tools()
parser = _parser(tools)
model_output = """<tool_call>calculate
<arg_key>operation</arg_key><arg_value>add</arg_value>
<arg_key>a</arg_key><arg_value>42</arg_value>
<arg_key>b</arg_key><arg_value>3.14</arg_value>
<arg_key>enabled</arg_key><arg_value>true</arg_value>
</tool_call>"""
extracted = parser.extract_tool_calls(model_output, request=_request(tools))
assert extracted.tools_called
assert json.loads(extracted.tool_calls[0].function.arguments) == {
"operation": "add",
"a": 42,
"b": 3.14,
"enabled": True,
}
def test_extract_zero_argument_tool_call_with_glm45_newline_format():
tools = _tools()
parser = _parser(tools)
extracted = parser.extract_tool_calls(
"<tool_call>get_time\n</tool_call>",
request=_request(tools),
)
assert extracted.tools_called
assert extracted.tool_calls[0].function.name == "get_time"
assert json.loads(extracted.tool_calls[0].function.arguments) == {}
def test_streaming_tool_call_with_glm45_newline_format():
tools = _tools()
parser = _parser(tools)
request = _request(tools)
chunks = [
"<tool_call>",
"get_current_weather\n",
"<arg_key>city</arg_key>",
"<arg_value>Bei",
"jing</arg_value>",
"</tool_call>",
]
deltas = []
current_text = ""
for chunk in chunks:
current_text += chunk
deltas.append(
parser.extract_tool_calls_streaming(
previous_text="",
current_text=current_text,
delta_text=chunk,
previous_token_ids=[],
current_token_ids=[],
delta_token_ids=[],
request=request,
)
)
calls = _collect_tool_deltas(deltas)
assert calls[0]["name"] == "get_current_weather"
assert json.loads("".join(calls[0]["args_fragments"])) == {"city": "Beijing"}