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
9 changes: 5 additions & 4 deletions lightllm/server/function_call_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1717,15 +1717,16 @@ def _get_param_config(self, func_name: str, tools: List[Tool]) -> Dict:

def _convert_param_value(self, value: str, param_name: str, param_config: Dict, func_name: str) -> Any:
"""Convert parameter value based on schema type. Safe alternative to eval()."""
if value.lower() == "null":
prop = param_config.get(param_name, {})
param_type = str(prop.get("type", "string")).strip().lower() if isinstance(prop, dict) else "string"
if value.lower() == "null" and not (
isinstance(prop, dict) and "type" in prop and param_type in ("string", "str", "enum")
):
return None

if param_name not in param_config:
return value

prop = param_config.get(param_name, {})
param_type = str(prop.get("type", "string")).strip().lower() if isinstance(prop, dict) else "string"

if param_type in ("string", "str", "enum"):
return value
elif param_type.startswith("int") or param_type == "integer":
Expand Down
68 changes: 68 additions & 0 deletions unit_tests/server/test_qwen3_coder_param_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Schema-aware parameter conversion through the public Qwen3-Coder parser."""

import json

import pytest

from lightllm.server.api_models import Function, Tool
from lightllm.server.function_call_parser import FunctionCallParser


@pytest.mark.parametrize("chunk_size", [None, 1, 7, 1024], ids=["nonstream", "stream-1", "stream-7", "stream-whole"])
@pytest.mark.parametrize(
("schema", "value", "expected"),
[
({"type": "string"}, "null", "null"),
({"type": "string"}, "NULL", "NULL"),
({"type": "string"}, "NuLl", "NuLl"),
({"type": "string"}, "hello", "hello"),
({"type": "string"}, "true", "true"),
({"type": "string"}, "42", "42"),
({"type": "string"}, "None", "None"),
({"type": "integer"}, "42", 42),
({"type": "boolean"}, "true", True),
({"type": "object"}, '{"x":1}', {"x": 1}),
({"type": "array"}, "[1,2]", [1, 2]),
({"type": "integer"}, "null", None),
({"type": "boolean"}, "NULL", None),
({"type": "object"}, "null", None),
({"type": "array"}, "null", None),
({"type": ["string", "null"]}, "null", None),
({"type": ["object", "null"]}, "null", None),
({"type": ["integer", "null"]}, "null", None),
({}, "null", None),
(None, "null", None),
],
)
def test_qwen3_coder_parameter_schema_values(schema, value, expected, chunk_size):
properties = {} if schema is None else {"value": schema}
tool = Tool(
type="function",
function=Function(name="echo", parameters={"type": "object", "properties": properties}),
)
parser = FunctionCallParser([tool], "qwen3_coder")
text = f"<tool_call>\n<function=echo>\n<parameter=value>{value}</parameter>\n</function>\n</tool_call>"

if chunk_size is None:
normal_text, calls = parser.parse_non_stream(text)
assert normal_text == ""
assert len(calls) == 1
assert calls[0].name == "echo"
arguments = calls[0].parameters
else:
names = []
argument_parts = []
for offset in range(0, len(text), chunk_size):
normal_text, calls = parser.parse_stream_chunk(text[offset : offset + chunk_size])
assert normal_text == ""
for call in calls:
assert call.tool_index == 0
if call.name is not None:
names.append(call.name)
argument_parts.append(call.parameters)
assert names == ["echo"]
arguments = "".join(argument_parts)

result = json.loads(arguments)["value"]
assert type(result) is type(expected)
assert result == expected