736 lines
22 KiB
Python
736 lines
22 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
# ruff: noqa: E501
|
|
|
|
import json
|
|
import random
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from tests.tool_parsers.utils import run_tool_extraction_streaming
|
|
from vllm.entrypoints.openai.chat_completion.protocol import (
|
|
ChatCompletionRequest,
|
|
ChatCompletionToolsParam,
|
|
)
|
|
from vllm.entrypoints.openai.engine.protocol import FunctionCall, ToolCall
|
|
from vllm.tool_parsers import ToolParser, ToolParserManager
|
|
from vllm.tool_parsers.minicpm5xml_tool_parser import MiniCPM5XMLToolParser
|
|
|
|
|
|
def _tool(name: str, parameters: dict) -> ChatCompletionToolsParam:
|
|
return ChatCompletionToolsParam(
|
|
type="function",
|
|
function={
|
|
"name": name,
|
|
"parameters": parameters,
|
|
},
|
|
)
|
|
|
|
|
|
def make_tools_weather() -> list[ChatCompletionToolsParam]:
|
|
return [
|
|
_tool(
|
|
"get_weather",
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"city": {"type": "string"},
|
|
"date": {"type": "string"},
|
|
},
|
|
"required": ["city"],
|
|
},
|
|
)
|
|
]
|
|
|
|
|
|
def make_tools_sum() -> list[ChatCompletionToolsParam]:
|
|
return [
|
|
_tool(
|
|
"sum_values",
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"nums": {"type": "array"},
|
|
"exact": {"type": "boolean"},
|
|
},
|
|
"required": ["nums"],
|
|
},
|
|
)
|
|
]
|
|
|
|
|
|
def make_tools_no_required() -> list[ChatCompletionToolsParam]:
|
|
return [
|
|
_tool(
|
|
"noop",
|
|
{
|
|
"type": "object",
|
|
"properties": {"note": {"type": "string"}},
|
|
"required": [],
|
|
},
|
|
)
|
|
]
|
|
|
|
|
|
def make_request(
|
|
tools: list[ChatCompletionToolsParam],
|
|
tool_choice: str = "auto",
|
|
) -> ChatCompletionRequest:
|
|
return ChatCompletionRequest(
|
|
model="test-model",
|
|
messages=[{"role": "user", "content": "hi"}],
|
|
tools=tools,
|
|
tool_choice=tool_choice,
|
|
)
|
|
|
|
|
|
def make_tool_call(name: str, arguments: dict) -> ToolCall:
|
|
return ToolCall(
|
|
type="function",
|
|
function=FunctionCall(
|
|
name=name,
|
|
arguments=json.dumps(arguments, ensure_ascii=False),
|
|
),
|
|
)
|
|
|
|
|
|
def assert_tool_calls(
|
|
actual: list[ToolCall],
|
|
expected: list[ToolCall],
|
|
) -> None:
|
|
assert len(actual) == len(expected)
|
|
for act, exp in zip(actual, expected):
|
|
assert act.type == "function"
|
|
assert act.function.name == exp.function.name
|
|
assert json.loads(act.function.arguments) == json.loads(exp.function.arguments)
|
|
|
|
|
|
@pytest.fixture
|
|
def parser() -> ToolParser:
|
|
mock_tokenizer = MagicMock()
|
|
return MiniCPM5XMLToolParser(mock_tokenizer)
|
|
|
|
|
|
def test_registered_in_tool_parser_manager() -> None:
|
|
cls = ToolParserManager.get_tool_parser("minicpm5")
|
|
assert cls is MiniCPM5XMLToolParser
|
|
|
|
|
|
def test_adjust_request_skip_special_tokens(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
assert request.skip_special_tokens is True
|
|
adjusted = parser.adjust_request(request)
|
|
assert adjusted.skip_special_tokens is False
|
|
|
|
|
|
def test_adjust_request_tool_choice_none(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather(), tool_choice="none")
|
|
adjusted = parser.adjust_request(request)
|
|
assert adjusted.skip_special_tokens is True
|
|
|
|
|
|
def test_no_tool_call(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
out = parser.extract_tool_calls("How can I help you?", request)
|
|
assert not out.tools_called
|
|
assert out.tool_calls == []
|
|
assert out.content == "How can I help you?"
|
|
|
|
|
|
def test_single_call_with_surrounding_text(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
"Intro before.\n"
|
|
'<function name="get_weather">'
|
|
'<param name="city">上海</param>'
|
|
'<param name="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
"Outro after.\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert out.tools_called
|
|
assert_tool_calls(
|
|
out.tool_calls,
|
|
[
|
|
make_tool_call(
|
|
"get_weather",
|
|
{
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
},
|
|
)
|
|
],
|
|
)
|
|
assert out.content is None
|
|
|
|
|
|
def test_cdata_multiline(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function name="get_weather">'
|
|
'<param name="city"><![CDATA[北\n京]]></param>'
|
|
'<param name="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args["city"] == "北\n京"
|
|
assert args["date"] == "2024-06-27"
|
|
|
|
|
|
def test_tokenizer_space_marker(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function\u0120name="get_weather">'
|
|
'<param\u0120name="city">上海</param>'
|
|
'<param\u0120name="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert out.tools_called
|
|
assert_tool_calls(
|
|
out.tool_calls,
|
|
[
|
|
make_tool_call(
|
|
"get_weather",
|
|
{
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
},
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
def test_collapsed_function_and_param_tags(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<functionname="get_weather">'
|
|
'<paramname="city">上海</param>'
|
|
'<paramname="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert out.tools_called
|
|
assert_tool_calls(
|
|
out.tool_calls,
|
|
[
|
|
make_tool_call(
|
|
"get_weather",
|
|
{
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
},
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
def test_collapsed_param_tags_with_tokenizer_space_function(
|
|
parser: ToolParser,
|
|
) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function\u0120name="get_weather">'
|
|
'<paramname="city">上海</param>'
|
|
'<paramname="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert out.tools_called
|
|
assert_tool_calls(
|
|
out.tool_calls,
|
|
[
|
|
make_tool_call(
|
|
"get_weather",
|
|
{
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
},
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
def test_extract_tool_calls_streaming_partial_chunks(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
chunks = [
|
|
'<function name="get_weather">',
|
|
'<param name="city">',
|
|
'上海</param><param name="date">2024-06-27</param></function>\n',
|
|
]
|
|
reconstructor = run_tool_extraction_streaming(
|
|
parser,
|
|
chunks,
|
|
request,
|
|
assert_one_tool_per_delta=False,
|
|
)
|
|
assert len(reconstructor.tool_calls) == 1
|
|
assert reconstructor.tool_calls[0].function.name == "get_weather"
|
|
assert json.loads(reconstructor.tool_calls[0].function.arguments) == {
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
}
|
|
|
|
|
|
def test_extract_tool_calls_streaming_tokenizer_space_marker(
|
|
parser: ToolParser,
|
|
) -> None:
|
|
request = make_request(make_tools_weather())
|
|
chunks = [
|
|
'<function\u0120name="get_weather">',
|
|
'<param\u0120name="city">',
|
|
'上海</param><param\u0120name="date">2024-06-27</param></function>\n',
|
|
]
|
|
reconstructor = run_tool_extraction_streaming(
|
|
parser,
|
|
chunks,
|
|
request,
|
|
assert_one_tool_per_delta=False,
|
|
)
|
|
assert len(reconstructor.tool_calls) == 1
|
|
assert json.loads(reconstructor.tool_calls[0].function.arguments) == {
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
}
|
|
|
|
|
|
def test_extract_tool_calls_streaming_collapsed_tags_weather(
|
|
parser: ToolParser,
|
|
) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<functionname="get_weather">'
|
|
'<paramname="city">上海</param>'
|
|
'<paramname="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
)
|
|
random.seed(2)
|
|
reconstructor = run_tool_extraction_streaming(
|
|
parser,
|
|
_random_chunks(text, 1, 4),
|
|
request,
|
|
)
|
|
assert len(reconstructor.tool_calls) == 1
|
|
assert reconstructor.tool_calls[0].function.name == "get_weather"
|
|
assert json.loads(reconstructor.tool_calls[0].function.arguments) == {
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
}
|
|
|
|
|
|
def test_collapsed_tags_current_weather(parser: ToolParser) -> None:
|
|
request = make_request(
|
|
[
|
|
_tool(
|
|
"get_current_weather",
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"city": {"type": "string"},
|
|
"state": {"type": "string"},
|
|
"unit": {"type": "string"},
|
|
},
|
|
"required": ["city", "state", "unit"],
|
|
},
|
|
)
|
|
]
|
|
)
|
|
text = (
|
|
'<functionname="get_current_weather">'
|
|
'<paramname="city">Dallas</param>'
|
|
'<paramname="state">TX</param>'
|
|
'<paramname="unit">fahrenheit</param>'
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert out.tools_called
|
|
assert json.loads(out.tool_calls[0].function.arguments) == {
|
|
"city": "Dallas",
|
|
"state": "TX",
|
|
"unit": "fahrenheit",
|
|
}
|
|
|
|
|
|
def test_extract_tool_calls_streaming_incremental_arguments(
|
|
parser: ToolParser,
|
|
) -> None:
|
|
request = make_request(
|
|
[
|
|
_tool(
|
|
"get_current_weather",
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"city": {"type": "string"},
|
|
"state": {"type": "string"},
|
|
"unit": {"type": "string"},
|
|
},
|
|
"required": ["city", "state", "unit"],
|
|
},
|
|
)
|
|
]
|
|
)
|
|
text = (
|
|
'<function name="get_current_weather">'
|
|
'<param name="city">Dallas</param>'
|
|
'<param name="state">TX</param>'
|
|
'<param name="unit">fahrenheit</param>'
|
|
"</function>"
|
|
)
|
|
prev = ""
|
|
arguments = ""
|
|
for chunk in [text[:37], text[37:70], text[70:]]:
|
|
current = prev + chunk
|
|
delta = parser.extract_tool_calls_streaming(
|
|
prev,
|
|
current,
|
|
chunk,
|
|
[],
|
|
[],
|
|
[],
|
|
request,
|
|
)
|
|
if delta and delta.tool_calls:
|
|
arg_delta = delta.tool_calls[0].function.arguments
|
|
if arg_delta:
|
|
arguments += arg_delta
|
|
prev = current
|
|
|
|
assert json.loads(arguments) == {
|
|
"city": "Dallas",
|
|
"state": "TX",
|
|
"unit": "fahrenheit",
|
|
}
|
|
|
|
|
|
def test_unknown_tool_block_preserved(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = '<function name="unknown"><param name="x">1</param></function>\n'
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
assert "unknown" in (out.content or "")
|
|
|
|
|
|
def test_non_string_types(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_sum())
|
|
text = (
|
|
'<function name="sum_values">'
|
|
'<param name="nums">[1, 2, 3]</param>'
|
|
'<param name="exact">true</param>'
|
|
"</function>\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args["nums"] == [1, 2, 3]
|
|
assert args["exact"] is True
|
|
|
|
|
|
def test_multiple_calls_interleaved_text(parser: ToolParser) -> None:
|
|
tools = make_tools_weather() + make_tools_sum()
|
|
request = make_request(tools)
|
|
text = (
|
|
"Head\n"
|
|
'<function name="get_weather"><param name="city">北京</param></function>\n'
|
|
"TXT\n"
|
|
'<function name="sum_values"><param name="nums">[7,8,9]</param>'
|
|
'<param name="exact">false</param></function>\n'
|
|
"Tail\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 2
|
|
args0 = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args0["city"] == "北京"
|
|
args1 = json.loads(out.tool_calls[1].function.arguments)
|
|
assert args1["nums"] == [7, 8, 9]
|
|
assert args1["exact"] is False
|
|
assert out.content is None
|
|
|
|
|
|
def test_incomplete_missing_function_end(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = '<function name="get_weather"><param name="city">北京</param>'
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
assert "get_weather" in (out.content or "")
|
|
|
|
|
|
def test_param_missing_name_invalid(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function name="get_weather">'
|
|
"<param>北京</param>"
|
|
'<param name="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
assert "<param>北京</param>" in (out.content or "")
|
|
|
|
|
|
def test_duplicate_param_names_invalid(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function name="get_weather">'
|
|
'<param name="city">北京</param>'
|
|
'<param name="city">上海</param>'
|
|
"</function>\n"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
|
|
|
|
def test_case_sensitive_param_name_invalid(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = '<function name="get_weather"><param name="City">北京</param></function>\n'
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
|
|
|
|
def test_no_required_and_zero_param_valid(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_no_required())
|
|
text = '<function name="noop"></function>\n'
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {}
|
|
|
|
|
|
def test_thinking_only_sentencepiece_normalized_in_content(
|
|
parser: ToolParser,
|
|
) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = "\u010aFirst,\u0120I\u0120need\u0120to\u0120check\u0120the\u0120weather."
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
assert out.content is not None
|
|
assert "\u0120" not in out.content
|
|
assert "\u010a" not in out.content
|
|
assert "First, I need to check the weather." in out.content
|
|
|
|
|
|
def test_properties_wrapped_arguments(parser: ToolParser) -> None:
|
|
tools = [
|
|
_tool(
|
|
"get_customer_by_phone",
|
|
{
|
|
"type": "object",
|
|
"properties": {"phone_number": {"type": "string"}},
|
|
"required": ["phone_number"],
|
|
},
|
|
)
|
|
]
|
|
request = make_request(tools)
|
|
text = (
|
|
'<function name="get_customer_by_phone">'
|
|
"<param name=\"properties\">{'phone_number': '555-123-2002'}</param>"
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
assert out.tool_calls[0].function.name == "get_customer_by_phone"
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {"phone_number": "555-123-2002"}
|
|
|
|
|
|
def test_arguments_wrapped_arguments(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function name="get_weather">'
|
|
'<param name="arguments">{"city": "上海", "date": "2024-06-27"}</param>'
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {"city": "上海", "date": "2024-06-27"}
|
|
|
|
|
|
def test_wrapped_arguments_still_validate_schema(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function name="get_weather">'
|
|
'<param name="properties">{"unknown": "x"}</param>'
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
|
|
|
|
def test_extra_arguments_ignored_when_required_present(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function name="get_weather">'
|
|
'<param name="city">上海</param>'
|
|
'<param name="unknown">ignored</param>'
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {"city": "上海"}
|
|
|
|
|
|
def test_extra_arguments_do_not_satisfy_required(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
'<function name="get_weather"><param name="unknown">ignored</param></function>'
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert not out.tools_called
|
|
|
|
|
|
def test_zero_arg_tool_ignores_extra_arguments(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_no_required())
|
|
text = (
|
|
'<function name="noop">'
|
|
'<param name="note">ignored</param>'
|
|
'<param name="extra">ignored</param>'
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {"note": "ignored"}
|
|
|
|
|
|
def test_alias_get_details_by_phone(parser: ToolParser) -> None:
|
|
tools = [
|
|
_tool(
|
|
"get_customer_by_phone",
|
|
{
|
|
"type": "object",
|
|
"properties": {"phone_number": {"type": "string"}},
|
|
"required": ["phone_number"],
|
|
},
|
|
)
|
|
]
|
|
request = make_request(tools)
|
|
text = (
|
|
'<function name="get_details_by_phone">'
|
|
'<param name="phone_number">555-123-2002</param>'
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
assert out.tool_calls[0].function.name == "get_customer_by_phone"
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {"phone_number": "555-123-2002"}
|
|
|
|
|
|
def test_alias_get_line_details(parser: ToolParser) -> None:
|
|
tools = [
|
|
_tool(
|
|
"get_details_by_id",
|
|
{
|
|
"type": "object",
|
|
"properties": {"id": {"type": "string"}},
|
|
"required": ["id"],
|
|
},
|
|
)
|
|
]
|
|
request = make_request(tools)
|
|
text = (
|
|
'<function name="get_line_details">'
|
|
'<param name="customer_id">C1001</param>'
|
|
'<param name="line_id">L1001</param>'
|
|
"</function>"
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
assert out.tool_calls[0].function.name == "get_details_by_id"
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {"id": "L1001"}
|
|
|
|
|
|
def test_alias_enable_roaming(parser: ToolParser) -> None:
|
|
tools = [
|
|
_tool(
|
|
"toggle_roaming",
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"line_id": {"type": "string"},
|
|
"enabled": {"type": "boolean"},
|
|
},
|
|
"required": ["line_id", "enabled"],
|
|
},
|
|
)
|
|
]
|
|
request = make_request(tools)
|
|
text = (
|
|
'<function name="enable_roaming"><param name="line_id">L1001</param></function>'
|
|
)
|
|
out = parser.extract_tool_calls(text, request)
|
|
assert len(out.tool_calls) == 1
|
|
assert out.tool_calls[0].function.name == "toggle_roaming"
|
|
args = json.loads(out.tool_calls[0].function.arguments)
|
|
assert args == {"line_id": "L1001", "enabled": True}
|
|
|
|
|
|
def _random_chunks(text: str, min_len: int, max_len: int) -> list[str]:
|
|
chunks: list[str] = []
|
|
index = 0
|
|
while index < len(text):
|
|
size = random.randint(min_len, max_len)
|
|
chunks.append(text[index : index + size])
|
|
index += size
|
|
return chunks
|
|
|
|
|
|
def test_extract_tool_calls_streaming_single(parser: ToolParser) -> None:
|
|
request = make_request(make_tools_weather())
|
|
text = (
|
|
"Intro before.\n"
|
|
'<function name="get_weather">'
|
|
'<param name="city">上海</param>'
|
|
'<param name="date">2024-06-27</param>'
|
|
"</function>\n"
|
|
"Outro after.\n"
|
|
)
|
|
random.seed(0)
|
|
reconstructor = run_tool_extraction_streaming(
|
|
parser,
|
|
_random_chunks(text, 1, 4),
|
|
request,
|
|
)
|
|
assert "Intro before." in reconstructor.other_content
|
|
assert "Outro after." in reconstructor.other_content
|
|
assert len(reconstructor.tool_calls) == 1
|
|
assert reconstructor.tool_calls[0].function.name == "get_weather"
|
|
assert json.loads(reconstructor.tool_calls[0].function.arguments) == {
|
|
"city": "上海",
|
|
"date": "2024-06-27",
|
|
}
|
|
|
|
|
|
def test_extract_tool_calls_streaming_multiple(parser: ToolParser) -> None:
|
|
tools = make_tools_weather() + make_tools_sum()
|
|
request = make_request(tools)
|
|
text = (
|
|
"Head\n"
|
|
'<function name="get_weather"><param name="city">北京</param></function>\n'
|
|
"TXT\n"
|
|
'<function name="sum_values"><param name="nums">[7,8,9]</param>'
|
|
'<param name="exact">false</param></function>\n'
|
|
"Tail\n"
|
|
)
|
|
random.seed(1)
|
|
reconstructor = run_tool_extraction_streaming(
|
|
parser,
|
|
_random_chunks(text, 1, 5),
|
|
request,
|
|
)
|
|
assert "Head" in reconstructor.other_content
|
|
assert "TXT" in reconstructor.other_content
|
|
assert "Tail" in reconstructor.other_content
|
|
assert len(reconstructor.tool_calls) == 2
|
|
assert json.loads(reconstructor.tool_calls[0].function.arguments)["city"] == "北京"
|
|
assert json.loads(reconstructor.tool_calls[1].function.arguments) == {
|
|
"nums": [7, 8, 9],
|
|
"exact": False,
|
|
}
|