diff --git a/tests/entrypoints/test_responses_utils.py b/tests/entrypoints/test_responses_utils.py new file mode 100644 index 000000000000..48bf06088bc0 --- /dev/null +++ b/tests/entrypoints/test_responses_utils.py @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +from vllm.entrypoints.responses_utils import ( + convert_tool_responses_to_completions_format, +) + + +class TestResponsesUtils: + """Tests for convert_tool_responses_to_completions_format function.""" + + def test_convert_tool_responses_to_completions_format(self): + """Test basic conversion of a flat tool schema to nested format.""" + input_tool = { + "type": "function", + "name": "get_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"}, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, + }, + "required": ["location", "unit"], + }, + } + + result = convert_tool_responses_to_completions_format(input_tool) + + assert result == {"type": "function", "function": input_tool} diff --git a/tests/v1/entrypoints/openai/serving_responses/conftest.py b/tests/v1/entrypoints/openai/serving_responses/conftest.py index 8081e5fa1d83..b948b6d058a5 100644 --- a/tests/v1/entrypoints/openai/serving_responses/conftest.py +++ b/tests/v1/entrypoints/openai/serving_responses/conftest.py @@ -30,7 +30,10 @@ def server_with_store(default_server_args): with RemoteOpenAIServer( MODEL_NAME, default_server_args, - env_dict={"VLLM_ENABLE_RESPONSES_API_STORE": "1"}, + env_dict={ + "VLLM_ENABLE_RESPONSES_API_STORE": "1", + "VLLM_SERVER_DEV_MODE": "1", + }, ) as remote_server: yield remote_server diff --git a/tests/v1/entrypoints/openai/serving_responses/test_function_call.py b/tests/v1/entrypoints/openai/serving_responses/test_function_call.py index cf57956a9dea..90161e7c221b 100644 --- a/tests/v1/entrypoints/openai/serving_responses/test_function_call.py +++ b/tests/v1/entrypoints/openai/serving_responses/test_function_call.py @@ -116,6 +116,7 @@ async def test_function_tool_use( input=prompt, tools=tools, tool_choice=tool_choice, + temperature=0.0, ) assert len(response.output) >= 1 diff --git a/vllm/entrypoints/openai/serving_responses.py b/vllm/entrypoints/openai/serving_responses.py index 9b79e50c3208..06efb43ecb7b 100644 --- a/vllm/entrypoints/openai/serving_responses.py +++ b/vllm/entrypoints/openai/serving_responses.py @@ -48,7 +48,6 @@ from openai.types.responses.response_output_text import Logprob, LogprobTopLogpr from openai.types.responses.response_reasoning_item import ( Content as ResponseReasoningTextContent, ) -from openai.types.responses.tool import Tool from openai_harmony import Message as OpenAIHarmonyMessage from vllm import envs @@ -94,7 +93,11 @@ from vllm.entrypoints.openai.protocol import ( ) from vllm.entrypoints.openai.serving_engine import OpenAIServing from vllm.entrypoints.openai.serving_models import OpenAIServingModels -from vllm.entrypoints.responses_utils import construct_chat_message_with_tool_call +from vllm.entrypoints.responses_utils import ( + construct_chat_message_with_tool_call, + convert_tool_responses_to_completions_format, + extract_tool_types, +) from vllm.entrypoints.tool_server import ToolServer from vllm.inputs.data import TokensPrompt as EngineTokensPrompt from vllm.logger import init_logger @@ -108,23 +111,6 @@ from vllm.utils import random_uuid logger = init_logger(__name__) -def extract_tool_types(tools: list[Tool]) -> set[str]: - """ - Extracts the tool types from the given tools. - """ - tool_types: set[str] = set() - for tool in tools: - if tool.type == "mcp": - # Allow the MCP Tool type to enable built in tools if the - # server_label is allowlisted in - # envs.VLLM_GPT_OSS_SYSTEM_TOOL_MCP_LABELS - if tool.server_label in envs.VLLM_GPT_OSS_SYSTEM_TOOL_MCP_LABELS: - tool_types.add(tool.server_label) - else: - tool_types.add(tool.type) - return tool_types - - class OpenAIServingResponses(OpenAIServing): def __init__( self, @@ -513,7 +499,10 @@ class OpenAIServingResponses(OpenAIServing): ): tool_dicts = None else: - tool_dicts = [tool.model_dump() for tool in request.tools] + tool_dicts = [ + convert_tool_responses_to_completions_format(tool.model_dump()) + for tool in request.tools + ] # Construct the input messages. messages = self._construct_input_messages(request, prev_response) _, request_prompts, engine_prompts = await self._preprocess_chat( diff --git a/vllm/entrypoints/responses_utils.py b/vllm/entrypoints/responses_utils.py index 6eb7c0b70a67..d966f58804b6 100644 --- a/vllm/entrypoints/responses_utils.py +++ b/vllm/entrypoints/responses_utils.py @@ -10,7 +10,9 @@ from openai.types.chat.chat_completion_message_tool_call_param import ( Function as FunctionCallTool, ) from openai.types.responses import ResponseFunctionToolCall +from openai.types.responses.tool import Tool +from vllm import envs from vllm.entrypoints.openai.protocol import ( ChatCompletionMessageParam, ResponseInputOutputItem, @@ -43,3 +45,33 @@ def construct_chat_message_with_tool_call( tool_call_id=item.get("call_id"), ) return item # type: ignore + + +def extract_tool_types(tools: list[Tool]) -> set[str]: + """ + Extracts the tool types from the given tools. + """ + tool_types: set[str] = set() + for tool in tools: + if tool.type == "mcp": + # Allow the MCP Tool type to enable built in tools if the + # server_label is allowlisted in + # envs.VLLM_GPT_OSS_SYSTEM_TOOL_MCP_LABELS + if tool.server_label in envs.VLLM_GPT_OSS_SYSTEM_TOOL_MCP_LABELS: + tool_types.add(tool.server_label) + else: + tool_types.add(tool.type) + return tool_types + + +def convert_tool_responses_to_completions_format(tool: dict) -> dict: + """ + Convert a flat tool schema: + {"type": "function", "name": "...", "description": "...", "parameters": {...}} + into: + {"type": "function", "function": {...}} + """ + return { + "type": "function", + "function": tool, + }