[Bugfix][Frontend] Emit non-ASCII tool-call arguments without \uXXXX escapes (#46308)
Signed-off-by: EazyReal <8047065+EazyReal@users.noreply.github.com> Co-authored-by: EazyReal <8047065+EazyReal@users.noreply.github.com>
This commit is contained in:
@@ -177,3 +177,15 @@ def test_hunyuan_a13b_tool_parser_streaming(model_deltas, expected_tool_calls):
|
||||
reconstructor.tool_calls[idx].id = expected_tool_calls[idx].id
|
||||
|
||||
assert reconstructor.tool_calls == expected_tool_calls
|
||||
|
||||
|
||||
def test_hunyuan_a13b_tool_parser_non_ascii():
|
||||
mock_tokenizer = MagicMock()
|
||||
tool_parser: ToolParser = ToolParserManager.get_tool_parser("hunyuan_a13b")(
|
||||
mock_tokenizer
|
||||
)
|
||||
model_output = '<tool_calls>[{"name": "get_weather", "arguments": {"city": "北京"}}]</tool_calls>'
|
||||
_, tool_calls = run_tool_extraction(tool_parser, model_output, streaming=False)
|
||||
args = tool_calls[0].function.arguments
|
||||
assert "北京" in args
|
||||
assert "\\u" not in args
|
||||
|
||||
@@ -495,3 +495,28 @@ def test_streaming_tool_calls(
|
||||
actual_args = json.loads(arguments_str)
|
||||
expected_args = json.loads(expected_tool.function.arguments)
|
||||
assert actual_args == expected_args
|
||||
|
||||
|
||||
def test_streaming_tool_calls_non_ascii(
|
||||
seed_oss_tool_parser, seed_oss_tokenizer, sample_tools
|
||||
):
|
||||
request = ChatCompletionRequest(model=MODEL, messages=[], tools=sample_tools)
|
||||
model_output = (
|
||||
"""<seed:think>\n</seed:cot_budget_reflect>\n</seed:cot_budget_reflect>\n"""
|
||||
"""The current thinking budget is 0, so I will directly start answering the question.\n</seed:think>\n"""
|
||||
"""<seed:tool_call>\n<function=get_weather>\n"""
|
||||
"""<parameter=location>北京</parameter>\n</function>\n</seed:tool_call>"""
|
||||
)
|
||||
|
||||
args = "".join(
|
||||
tool_call.function.arguments
|
||||
for delta_message in stream_delta_message_generator(
|
||||
seed_oss_tool_parser, seed_oss_tokenizer, model_output, request
|
||||
)
|
||||
if delta_message.tool_calls
|
||||
for tool_call in delta_message.tool_calls
|
||||
if tool_call.function and tool_call.function.arguments is not None
|
||||
)
|
||||
|
||||
assert "北京" in args
|
||||
assert "\\u" not in args
|
||||
|
||||
@@ -532,3 +532,26 @@ def test_extract_tool_calls_streaming_incremental(
|
||||
parsed_args = json.loads(full_args)
|
||||
expected_args = json.loads(expected_first_tool.function.arguments)
|
||||
assert parsed_args == expected_args
|
||||
|
||||
|
||||
@pytest.mark.parametrize("streaming", [False, True])
|
||||
def test_extract_tool_calls_non_ascii(xlam_tool_parser, xlam_tokenizer, streaming):
|
||||
# Use parallel tool calls so the streaming path re-serializes arguments
|
||||
# (the ensure_ascii fix only runs when tool_count > 1).
|
||||
model_output = """[{"name": "get_current_weather", "arguments": {"city": "北京"}}, {"name": "get_current_weather", "arguments": {"city": "上海"}}]""" # noqa: E501
|
||||
|
||||
if streaming:
|
||||
request = ChatCompletionRequest(model=MODEL, messages=[])
|
||||
args = "".join(
|
||||
delta.tool_calls[0].function.arguments
|
||||
for delta in stream_delta_message_generator(
|
||||
xlam_tool_parser, xlam_tokenizer, model_output, request
|
||||
)
|
||||
if delta.tool_calls and delta.tool_calls[0].function.arguments
|
||||
)
|
||||
else:
|
||||
extracted = xlam_tool_parser.extract_tool_calls(model_output, request=None) # type: ignore[arg-type]
|
||||
args = "".join(tc.function.arguments for tc in extracted.tool_calls)
|
||||
|
||||
assert "北京" in args
|
||||
assert "\\u" not in args
|
||||
|
||||
@@ -144,7 +144,7 @@ class HunyuanA13BToolParser(ToolParser):
|
||||
function=FunctionCall(
|
||||
name=call["name"],
|
||||
arguments=(
|
||||
json.dumps(call["arguments"])
|
||||
json.dumps(call["arguments"], ensure_ascii=False)
|
||||
if isinstance(call["arguments"], dict)
|
||||
else call["arguments"]
|
||||
),
|
||||
|
||||
@@ -528,7 +528,7 @@ class SeedOssToolParser(ToolParser):
|
||||
'"'
|
||||
+ self.current_param_name
|
||||
+ '": "'
|
||||
+ json.dumps(param_value)[1:-1]
|
||||
+ json.dumps(param_value, ensure_ascii=False)[1:-1]
|
||||
+ '"'
|
||||
)
|
||||
else:
|
||||
@@ -536,7 +536,7 @@ class SeedOssToolParser(ToolParser):
|
||||
', "'
|
||||
+ self.current_param_name
|
||||
+ '": "'
|
||||
+ json.dumps(param_value)[1:-1]
|
||||
+ json.dumps(param_value, ensure_ascii=False)[1:-1]
|
||||
+ '"'
|
||||
)
|
||||
|
||||
@@ -571,11 +571,11 @@ class SeedOssToolParser(ToolParser):
|
||||
# Calculate incremental JSON
|
||||
full_value = self.current_param_value + value_chunk
|
||||
prev_escaped = (
|
||||
json.dumps(self.current_param_value)[1:-1]
|
||||
json.dumps(self.current_param_value, ensure_ascii=False)[1:-1]
|
||||
if self.current_param_value
|
||||
else ""
|
||||
)
|
||||
full_escaped = json.dumps(full_value)[1:-1]
|
||||
full_escaped = json.dumps(full_value, ensure_ascii=False)[1:-1]
|
||||
delta_escaped = full_escaped[len(prev_escaped) :]
|
||||
|
||||
self.in_param = False
|
||||
@@ -606,12 +606,16 @@ class SeedOssToolParser(ToolParser):
|
||||
if value_chunk:
|
||||
# Stream the escaped delta
|
||||
prev_escaped = (
|
||||
json.dumps(self.current_param_value)[1:-1]
|
||||
json.dumps(self.current_param_value, ensure_ascii=False)[
|
||||
1:-1
|
||||
]
|
||||
if self.current_param_value
|
||||
else ""
|
||||
)
|
||||
self.current_param_value += value_chunk
|
||||
full_escaped = json.dumps(self.current_param_value)[1:-1]
|
||||
full_escaped = json.dumps(
|
||||
self.current_param_value, ensure_ascii=False
|
||||
)[1:-1]
|
||||
delta_escaped = full_escaped[len(prev_escaped) :]
|
||||
|
||||
if delta_escaped:
|
||||
|
||||
@@ -165,7 +165,7 @@ class xLAMToolParser(ToolParser):
|
||||
function=FunctionCall(
|
||||
name=call["name"],
|
||||
arguments=(
|
||||
json.dumps(call["arguments"])
|
||||
json.dumps(call["arguments"], ensure_ascii=False)
|
||||
if isinstance(call["arguments"], dict)
|
||||
else call["arguments"]
|
||||
),
|
||||
@@ -473,7 +473,9 @@ class xLAMToolParser(ToolParser):
|
||||
):
|
||||
current_tool = parsed_tools[current_idx]
|
||||
if isinstance(current_tool.get("arguments"), dict):
|
||||
args_text = json.dumps(current_tool["arguments"])
|
||||
args_text = json.dumps(
|
||||
current_tool["arguments"], ensure_ascii=False
|
||||
)
|
||||
else:
|
||||
args_text = str(current_tool.get("arguments", "{}"))
|
||||
except (json.JSONDecodeError, KeyError, IndexError):
|
||||
|
||||
Reference in New Issue
Block a user