# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import json import pytest from openai_harmony import (Conversation, DeveloperContent, HarmonyEncodingName, Message, Role, SystemContent, load_harmony_encoding) from vllm.entrypoints.openai.protocol import FunctionCall, ToolCall from vllm.entrypoints.openai.tool_parsers import OpenAIToolParser from vllm.transformers_utils.tokenizer import get_tokenizer MODEL = "gpt2" @pytest.fixture(scope="module") def openai_tokenizer(): # The parser does not use the tokenizer, but the constructor requires it. return get_tokenizer(MODEL) @pytest.fixture def openai_tool_parser(openai_tokenizer): return OpenAIToolParser(openai_tokenizer) @pytest.fixture(scope="module") def harmony_encoding(): return load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS) def assert_tool_calls( actual_tool_calls: list[ToolCall], expected_tool_calls: list[ToolCall], ): assert len(actual_tool_calls) == len(expected_tool_calls) for actual_tool_call, expected_tool_call in zip(actual_tool_calls, expected_tool_calls): assert isinstance(actual_tool_call.id, str) assert len(actual_tool_call.id) > 16 # Default from protocol.py assert actual_tool_call.type == "function" assert actual_tool_call.function == expected_tool_call.function def test_extract_tool_calls_no_tools(openai_tool_parser, harmony_encoding): convo = Conversation.from_messages([ Message.from_role_and_content( Role.SYSTEM, SystemContent.new(), ), Message.from_role_and_content( Role.DEVELOPER, DeveloperContent.new().with_instructions("Talk like a pirate!")), Message.from_role_and_content(Role.USER, "Arrr, how be you?"), Message.from_role_and_content(Role.ASSISTANT, "This is a test").with_channel("final") ]) token_ids = harmony_encoding.render_conversation_for_completion( convo, Role.ASSISTANT) extracted_info = openai_tool_parser.extract_tool_calls( "", request=None, token_ids=token_ids, ) assert not extracted_info.tools_called assert extracted_info.tool_calls == [] assert extracted_info.content == "This is a test" @pytest.mark.parametrize("tool_args", [ '{"location": "Tokyo"}', '{\n"location": "Tokyo"\n}', ]) def test_extract_tool_calls_single_tool(openai_tool_parser, harmony_encoding, tool_args): convo = Conversation.from_messages([ Message.from_role_and_content(Role.USER, "What is the weather in Tokyo?"), Message.from_role_and_content( Role.ASSISTANT, 'User asks: "What is the weather in Tokyo?" We need to use get_current_weather tool.', # noqa: E501 ).with_channel("analysis"), Message.from_role_and_content( Role.ASSISTANT, tool_args).with_channel("commentary").with_recipient( "functions.get_current_weather").with_content_type("json"), ]) token_ids = harmony_encoding.render_conversation_for_completion( convo, Role.ASSISTANT) extracted_info = openai_tool_parser.extract_tool_calls( "", request=None, token_ids=token_ids, ) assert extracted_info.tools_called expected_tool_calls = [ ToolCall(function=FunctionCall( name="get_current_weather", arguments=json.dumps({"location": "Tokyo"}), )) ] assert_tool_calls(extracted_info.tool_calls, expected_tool_calls) assert extracted_info.content is None def test_extract_tool_calls_multiple_tools( openai_tool_parser, harmony_encoding, ): convo = Conversation.from_messages([ Message.from_role_and_content( Role.USER, "What is the weather in Tokyo based on where I'm at?"), Message.from_role_and_content( Role.ASSISTANT, 'User asks: "What is the weather in Tokyo?" based on their location. We need to use get_current_weather tool and get_user_location tool.', # noqa: E501 ).with_channel("analysis"), Message.from_role_and_content( Role.ASSISTANT, '{"location": "Tokyo"}').with_channel("commentary").with_recipient( "functions.get_current_weather").with_content_type("json"), Message.from_role_and_content( Role.ASSISTANT, '{"location": "Tokyo"}').with_channel("commentary").with_recipient( "functions.get_user_location").with_content_type("json"), Message.from_role_and_content( Role.ASSISTANT, '{"location": "Tokyo"}').with_channel( "commentary").with_recipient("functions.no_content_type"), Message.from_role_and_content(Role.ASSISTANT, "foo").with_channel( "commentary").with_recipient("functions.not_json_no_content_type"), Message.from_role_and_content( Role.ASSISTANT, '{}').with_channel("commentary").with_recipient( "functions.empty_args").with_content_type("json"), Message.from_role_and_content( Role.ASSISTANT, '').with_channel("commentary").with_recipient( "functions.no_args").with_content_type("json"), ]) token_ids = harmony_encoding.render_conversation_for_completion( convo, Role.ASSISTANT, ) extracted_info = openai_tool_parser.extract_tool_calls( "", request=None, token_ids=token_ids, ) assert extracted_info.tools_called expected_tool_calls = [ ToolCall(function=FunctionCall( name="get_current_weather", arguments=json.dumps({"location": "Tokyo"}), )), ToolCall(function=FunctionCall( name="get_user_location", arguments=json.dumps({"location": "Tokyo"}), )), ToolCall(function=FunctionCall( name="no_content_type", arguments=json.dumps({"location": "Tokyo"}), )), ToolCall(function=FunctionCall( name="not_json_no_content_type", arguments="foo", )), ToolCall(function=FunctionCall( name="empty_args", arguments=json.dumps({}), )), ToolCall(function=FunctionCall( name="no_args", arguments="", )) ] assert_tool_calls(extracted_info.tool_calls, expected_tool_calls) assert extracted_info.content is None def test_extract_tool_calls_with_content( openai_tool_parser, harmony_encoding, ): final_content = "This tool call will get the weather." convo = Conversation.from_messages([ Message.from_role_and_content( Role.USER, "What is the weather in Tokyo based on where I'm at?"), Message.from_role_and_content( Role.ASSISTANT, 'User asks: "What is the weather in Tokyo?" based on their location. We need to use get_current_weather tool and get_user_location tool.', # noqa: E501 ).with_channel("analysis"), Message.from_role_and_content( Role.ASSISTANT, '{"location": "Tokyo"}').with_channel("commentary").with_recipient( "functions.get_current_weather").with_content_type("json"), Message.from_role_and_content(Role.ASSISTANT, final_content).with_channel("final"), ]) token_ids = harmony_encoding.render_conversation_for_completion( convo, Role.ASSISTANT, ) extracted_info = openai_tool_parser.extract_tool_calls( "", request=None, token_ids=token_ids, ) assert extracted_info.tools_called expected_tool_calls = [ ToolCall(function=FunctionCall( name="get_current_weather", arguments=json.dumps({"location": "Tokyo"}), )), ] assert_tool_calls(extracted_info.tool_calls, expected_tool_calls) assert extracted_info.content == final_content