|  | 
| 8 | 8 | import uuid | 
| 9 | 9 | from dataclasses import dataclass | 
| 10 | 10 | from threading import Thread | 
| 11 |  | -from typing import Optional, Union | 
|  | 11 | +from typing import Any, Optional, Union | 
| 12 | 12 | from unittest.mock import MagicMock | 
| 13 | 13 | 
 | 
| 14 | 14 | import pytest | 
| @@ -331,6 +331,46 @@ def echo_dc( | 
| 331 | 331 |     return [val for _ in range(3)] if return_list else val | 
| 332 | 332 | 
 | 
| 333 | 333 | 
 | 
|  | 334 | +# Dummy utility function to test dict serialization with custom types. | 
|  | 335 | +def echo_dc_dict( | 
|  | 336 | +    self, | 
|  | 337 | +    msg: str, | 
|  | 338 | +    return_dict: bool = False, | 
|  | 339 | +) -> Union[MyDataclass, dict[str, MyDataclass]]: | 
|  | 340 | +    print(f"echo dc dict util function called: {msg}") | 
|  | 341 | +    val = None if msg is None else MyDataclass(msg) | 
|  | 342 | +    # Return dict of dataclasses to verify support for returning dicts | 
|  | 343 | +    # with custom value types. | 
|  | 344 | +    if return_dict: | 
|  | 345 | +        return {"key1": val, "key2": val, "key3": val} | 
|  | 346 | +    else: | 
|  | 347 | +        return val | 
|  | 348 | + | 
|  | 349 | + | 
|  | 350 | +# Dummy utility function to test nested structures with custom types. | 
|  | 351 | +def echo_dc_nested( | 
|  | 352 | +    self, | 
|  | 353 | +    msg: str, | 
|  | 354 | +    structure_type: str = "list_of_dicts", | 
|  | 355 | +) -> Any: | 
|  | 356 | +    print(f"echo dc nested util function called: {msg}, " | 
|  | 357 | +          f"structure: {structure_type}") | 
|  | 358 | +    val = None if msg is None else MyDataclass(msg) | 
|  | 359 | + | 
|  | 360 | +    if structure_type == "list_of_dicts":  # noqa | 
|  | 361 | +        # Return list of dicts: [{"a": val, "b": val}, {"c": val, "d": val}] | 
|  | 362 | +        return [{"a": val, "b": val}, {"c": val, "d": val}] | 
|  | 363 | +    elif structure_type == "dict_of_lists": | 
|  | 364 | +        # Return dict of lists: {"list1": [val, val], "list2": [val, val]} | 
|  | 365 | +        return {"list1": [val, val], "list2": [val, val]} | 
|  | 366 | +    elif structure_type == "deep_nested": | 
|  | 367 | +        # Return deeply nested: {"outer": [{"inner": [val, val]}, | 
|  | 368 | +        # {"inner": [val]}]} | 
|  | 369 | +        return {"outer": [{"inner": [val, val]}, {"inner": [val]}]} | 
|  | 370 | +    else: | 
|  | 371 | +        return val | 
|  | 372 | + | 
|  | 373 | + | 
| 334 | 374 | @pytest.mark.asyncio(loop_scope="function") | 
| 335 | 375 | async def test_engine_core_client_util_method_custom_return( | 
| 336 | 376 |         monkeypatch: pytest.MonkeyPatch): | 
| @@ -384,6 +424,167 @@ async def test_engine_core_client_util_method_custom_return( | 
| 384 | 424 |             client.shutdown() | 
| 385 | 425 | 
 | 
| 386 | 426 | 
 | 
|  | 427 | +@pytest.mark.asyncio(loop_scope="function") | 
|  | 428 | +async def test_engine_core_client_util_method_custom_dict_return( | 
|  | 429 | +        monkeypatch: pytest.MonkeyPatch): | 
|  | 430 | + | 
|  | 431 | +    with monkeypatch.context() as m: | 
|  | 432 | +        m.setenv("VLLM_USE_V1", "1") | 
|  | 433 | + | 
|  | 434 | +        # Must set insecure serialization to allow returning custom types. | 
|  | 435 | +        m.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1") | 
|  | 436 | + | 
|  | 437 | +        # Monkey-patch core engine utility function to test. | 
|  | 438 | +        m.setattr(EngineCore, "echo_dc_dict", echo_dc_dict, raising=False) | 
|  | 439 | + | 
|  | 440 | +        engine_args = EngineArgs(model=MODEL_NAME, enforce_eager=True) | 
|  | 441 | +        vllm_config = engine_args.create_engine_config( | 
|  | 442 | +            usage_context=UsageContext.UNKNOWN_CONTEXT) | 
|  | 443 | +        executor_class = Executor.get_class(vllm_config) | 
|  | 444 | + | 
|  | 445 | +        with set_default_torch_num_threads(1): | 
|  | 446 | +            client = EngineCoreClient.make_client( | 
|  | 447 | +                multiprocess_mode=True, | 
|  | 448 | +                asyncio_mode=True, | 
|  | 449 | +                vllm_config=vllm_config, | 
|  | 450 | +                executor_class=executor_class, | 
|  | 451 | +                log_stats=True, | 
|  | 452 | +            ) | 
|  | 453 | + | 
|  | 454 | +        try: | 
|  | 455 | +            # Test utility method returning custom / non-native data type. | 
|  | 456 | +            core_client: AsyncMPClient = client | 
|  | 457 | + | 
|  | 458 | +            # Test single object return | 
|  | 459 | +            result = await core_client.call_utility_async( | 
|  | 460 | +                "echo_dc_dict", "testarg3", False) | 
|  | 461 | +            assert isinstance(result, | 
|  | 462 | +                              MyDataclass) and result.message == "testarg3" | 
|  | 463 | + | 
|  | 464 | +            # Test dict return with custom value types | 
|  | 465 | +            result = await core_client.call_utility_async( | 
|  | 466 | +                "echo_dc_dict", "testarg3", True) | 
|  | 467 | +            assert isinstance(result, dict) and len(result) == 3 | 
|  | 468 | +            for key, val in result.items(): | 
|  | 469 | +                assert key in ["key1", "key2", "key3"] | 
|  | 470 | +                assert isinstance(val, | 
|  | 471 | +                                  MyDataclass) and val.message == "testarg3" | 
|  | 472 | + | 
|  | 473 | +            # Test returning dict with None values | 
|  | 474 | +            result = await core_client.call_utility_async( | 
|  | 475 | +                "echo_dc_dict", None, True) | 
|  | 476 | +            assert isinstance(result, dict) and len(result) == 3 | 
|  | 477 | +            for key, val in result.items(): | 
|  | 478 | +                assert key in ["key1", "key2", "key3"] | 
|  | 479 | +                assert val is None | 
|  | 480 | + | 
|  | 481 | +        finally: | 
|  | 482 | +            client.shutdown() | 
|  | 483 | + | 
|  | 484 | + | 
|  | 485 | +@pytest.mark.asyncio(loop_scope="function") | 
|  | 486 | +async def test_engine_core_client_util_method_nested_structures( | 
|  | 487 | +        monkeypatch: pytest.MonkeyPatch): | 
|  | 488 | + | 
|  | 489 | +    with monkeypatch.context() as m: | 
|  | 490 | +        m.setenv("VLLM_USE_V1", "1") | 
|  | 491 | + | 
|  | 492 | +        # Must set insecure serialization to allow returning custom types. | 
|  | 493 | +        m.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1") | 
|  | 494 | + | 
|  | 495 | +        # Monkey-patch core engine utility function to test. | 
|  | 496 | +        m.setattr(EngineCore, "echo_dc_nested", echo_dc_nested, raising=False) | 
|  | 497 | + | 
|  | 498 | +        engine_args = EngineArgs(model=MODEL_NAME, enforce_eager=True) | 
|  | 499 | +        vllm_config = engine_args.create_engine_config( | 
|  | 500 | +            usage_context=UsageContext.UNKNOWN_CONTEXT) | 
|  | 501 | +        executor_class = Executor.get_class(vllm_config) | 
|  | 502 | + | 
|  | 503 | +        with set_default_torch_num_threads(1): | 
|  | 504 | +            client = EngineCoreClient.make_client( | 
|  | 505 | +                multiprocess_mode=True, | 
|  | 506 | +                asyncio_mode=True, | 
|  | 507 | +                vllm_config=vllm_config, | 
|  | 508 | +                executor_class=executor_class, | 
|  | 509 | +                log_stats=True, | 
|  | 510 | +            ) | 
|  | 511 | + | 
|  | 512 | +        try: | 
|  | 513 | +            core_client: AsyncMPClient = client | 
|  | 514 | + | 
|  | 515 | +            # Test list of dicts: [{"a": val, "b": val}, {"c": val, "d": val}] | 
|  | 516 | +            result = await core_client.call_utility_async( | 
|  | 517 | +                "echo_dc_nested", "nested1", "list_of_dicts") | 
|  | 518 | +            assert isinstance(result, list) and len(result) == 2 | 
|  | 519 | +            for i, item in enumerate(result): | 
|  | 520 | +                assert isinstance(item, dict) | 
|  | 521 | +                if i == 0: | 
|  | 522 | +                    assert "a" in item and "b" in item | 
|  | 523 | +                    assert isinstance( | 
|  | 524 | +                        item["a"], | 
|  | 525 | +                        MyDataclass) and item["a"].message == "nested1" | 
|  | 526 | +                    assert isinstance( | 
|  | 527 | +                        item["b"], | 
|  | 528 | +                        MyDataclass) and item["b"].message == "nested1" | 
|  | 529 | +                else: | 
|  | 530 | +                    assert "c" in item and "d" in item | 
|  | 531 | +                    assert isinstance( | 
|  | 532 | +                        item["c"], | 
|  | 533 | +                        MyDataclass) and item["c"].message == "nested1" | 
|  | 534 | +                    assert isinstance( | 
|  | 535 | +                        item["d"], | 
|  | 536 | +                        MyDataclass) and item["d"].message == "nested1" | 
|  | 537 | + | 
|  | 538 | +            # Test dict of lists: {"list1": [val, val], "list2": [val, val]} | 
|  | 539 | +            result = await core_client.call_utility_async( | 
|  | 540 | +                "echo_dc_nested", "nested2", "dict_of_lists") | 
|  | 541 | +            assert isinstance(result, dict) and len(result) == 2 | 
|  | 542 | +            assert "list1" in result and "list2" in result | 
|  | 543 | +            for key, lst in result.items(): | 
|  | 544 | +                assert isinstance(lst, list) and len(lst) == 2 | 
|  | 545 | +                for item in lst: | 
|  | 546 | +                    assert isinstance( | 
|  | 547 | +                        item, MyDataclass) and item.message == "nested2" | 
|  | 548 | + | 
|  | 549 | +            # Test deeply nested: {"outer": [{"inner": [val, val]}, | 
|  | 550 | +            # {"inner": [val]}]} | 
|  | 551 | +            result = await core_client.call_utility_async( | 
|  | 552 | +                "echo_dc_nested", "nested3", "deep_nested") | 
|  | 553 | +            assert isinstance(result, dict) and "outer" in result | 
|  | 554 | +            outer_list = result["outer"] | 
|  | 555 | +            assert isinstance(outer_list, list) and len(outer_list) == 2 | 
|  | 556 | + | 
|  | 557 | +            # First dict in outer list should have "inner" with 2 items | 
|  | 558 | +            inner_dict1 = outer_list[0] | 
|  | 559 | +            assert isinstance(inner_dict1, dict) and "inner" in inner_dict1 | 
|  | 560 | +            inner_list1 = inner_dict1["inner"] | 
|  | 561 | +            assert isinstance(inner_list1, list) and len(inner_list1) == 2 | 
|  | 562 | +            for item in inner_list1: | 
|  | 563 | +                assert isinstance(item, | 
|  | 564 | +                                  MyDataclass) and item.message == "nested3" | 
|  | 565 | + | 
|  | 566 | +            # Second dict in outer list should have "inner" with 1 item | 
|  | 567 | +            inner_dict2 = outer_list[1] | 
|  | 568 | +            assert isinstance(inner_dict2, dict) and "inner" in inner_dict2 | 
|  | 569 | +            inner_list2 = inner_dict2["inner"] | 
|  | 570 | +            assert isinstance(inner_list2, list) and len(inner_list2) == 1 | 
|  | 571 | +            assert isinstance( | 
|  | 572 | +                inner_list2[0], | 
|  | 573 | +                MyDataclass) and inner_list2[0].message == "nested3" | 
|  | 574 | + | 
|  | 575 | +            # Test with None values in nested structures | 
|  | 576 | +            result = await core_client.call_utility_async( | 
|  | 577 | +                "echo_dc_nested", None, "list_of_dicts") | 
|  | 578 | +            assert isinstance(result, list) and len(result) == 2 | 
|  | 579 | +            for item in result: | 
|  | 580 | +                assert isinstance(item, dict) | 
|  | 581 | +                for val in item.values(): | 
|  | 582 | +                    assert val is None | 
|  | 583 | + | 
|  | 584 | +        finally: | 
|  | 585 | +            client.shutdown() | 
|  | 586 | + | 
|  | 587 | + | 
| 387 | 588 | @pytest.mark.parametrize( | 
| 388 | 589 |     "multiprocessing_mode,publisher_config", | 
| 389 | 590 |     [(True, "tcp"), (False, "inproc")], | 
|  | 
0 commit comments