| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | import json |
| 5 | |
| 6 | import openai # use the official client for correctness check |
| 7 | import pytest |
| 8 | |
| 9 | MODEL_NAME = "Qwen/Qwen3-1.7B" |
| 10 | NAMESPACE = "mcp__computer_use" |
| 11 | TOOL_NAME = "get_app_state" |
| 12 | FLAT_TOOL_NAME = f"{NAMESPACE}__{TOOL_NAME}" |
| 13 | |
| 14 | tools = [ |
| 15 | { |
| 16 | "type": "namespace", |
| 17 | "name": NAMESPACE, |
| 18 | "description": "Computer control tools.", |
| 19 | "tools": [ |
| 20 | { |
| 21 | "type": "function", |
| 22 | "name": TOOL_NAME, |
| 23 | "description": "Get the current state of a desktop application.", |
| 24 | "parameters": { |
| 25 | "type": "object", |
| 26 | "properties": { |
| 27 | "app": { |
| 28 | "type": "string", |
| 29 | "description": "Application name, for example Chrome.", |
| 30 | } |
| 31 | }, |
| 32 | "required": ["app"], |
| 33 | "additionalProperties": False, |
| 34 | }, |
| 35 | } |
| 36 | ], |
| 37 | } |
| 38 | ] |
| 39 | |
| 40 | prompt = [ |
| 41 | { |
| 42 | "role": "user", |
| 43 | "content": "Use the computer app state tool to inspect Google Chrome.", |
| 44 | }, |
| 45 | ] |
| 46 | |
| 47 | |
| 48 | def _assert_namespace_tool_call(tool_call) -> None: |
| 49 | assert tool_call.type == "function_call" |
| 50 | assert tool_call.name == TOOL_NAME |
| 51 | assert tool_call.namespace == NAMESPACE |
| 52 | assert tool_call.name != FLAT_TOOL_NAME |
| 53 | |
| 54 | args = json.loads(tool_call.arguments) |
| 55 | assert args["app"] |
| 56 | |
| 57 | |
| 58 | @pytest.mark.asyncio |
| 59 | @pytest.mark.parametrize("model_name", [MODEL_NAME]) |
| 60 | async def test_namespace_tool_separator(client: openai.AsyncOpenAI, model_name: str): |
| 61 | response = await client.responses.create( |
| 62 | model=model_name, |
| 63 | input=prompt, |
| 64 | tools=tools, |
| 65 | temperature=0.0, |
| 66 | ) |
| 67 | |
| 68 | assert len(response.output) >= 1 |
| 69 | tool_call = next( |
| 70 | (out for out in response.output if out.type == "function_call"), None |
| 71 | ) |
| 72 | assert tool_call is not None |
| 73 | _assert_namespace_tool_call(tool_call) |
| 74 | |
| 75 | |
| 76 | @pytest.mark.asyncio |
| 77 | @pytest.mark.parametrize("model_name", [MODEL_NAME]) |
| 78 | async def test_namespace_tool_separator_streaming( |
| 79 | client: openai.AsyncOpenAI, model_name: str |
| 80 | ): |
| 81 | stream = await client.responses.create( |
| 82 | model=model_name, |
| 83 | input=prompt, |
| 84 | tools=tools, |
| 85 | temperature=0.0, |
| 86 | stream=True, |
| 87 | ) |
| 88 | events = [event async for event in stream] |
| 89 | |
| 90 | added_call = next( |
| 91 | ( |
| 92 | event.item |
| 93 | for event in events |
| 94 | if event.type == "response.output_item.added" |
| 95 | and getattr(event.item, "type", None) == "function_call" |
| 96 | ), |
| 97 | None, |
| 98 | ) |
| 99 | done_call = next( |
| 100 | ( |
| 101 | event.item |
| 102 | for event in events |
| 103 | if event.type == "response.output_item.done" |
| 104 | and getattr(event.item, "type", None) == "function_call" |
| 105 | ), |
| 106 | None, |
| 107 | ) |
| 108 | |
| 109 | assert added_call is not None |
| 110 | assert added_call.name == TOOL_NAME |
| 111 | assert added_call.namespace == NAMESPACE |
| 112 | |
| 113 | assert done_call is not None |
| 114 | _assert_namespace_tool_call(done_call) |