# 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" def test_extract_tool_calls_single_tool(openai_tool_parser, harmony_encoding): 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, '{"location": "Tokyo"}').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"), ]) 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"}), )) ] assert_tool_calls(extracted_info.tool_calls, expected_tool_calls) assert extracted_info.content is None