Skip to content

Commit 53fa2cd

Browse files
committed
Merge branch 'main' into feat/multi-turn-tools-chat
Signed-off-by: Jared O'Connell <joconnel@redhat.com>
2 parents 45b97f5 + 8ce8419 commit 53fa2cd

13 files changed

Lines changed: 729 additions & 792 deletions

File tree

AGENTS.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,24 @@ tox -e tests -- -m regression
6565
- Use appropriate markers (`smoke`, `sanity`, `regression`)
6666
- Tests should be placed in files matching the name and path of the file under tests. E.g. `src/guidellm/benchmark/schemas/generative/entrypoints.py` -> `tests/unit/benchmark/schemas/generative/test_entrypoints.py`.
6767

68-
### Style Requirements
68+
### Quality Requirements
6969

7070
- All Python code must pass linting and formatting
7171
- All Python code must pass type checking
7272
- All tests must pass before committing
7373
- Markdown files must be properly formatted
74+
75+
### Style Requirements
76+
7477
- Public functions in `src/` code must use the reStructuredText docstring format
78+
- All imports **SHALL** be done at the top of the file
79+
- **DO NOT** use `getattr` or `setattr` as it hides incorrect usage of types
80+
81+
### Design Requirements
82+
83+
- Only touch sections of code that need to be changed for the given task
84+
- When handling variant-specific logic, encapsulate it in methods on registry class implementations rather than adding if/else branches to generic code paths
85+
- Class implementations must fully encapsulate their unique logic and that logic must not leak into caller code paths.
7586

7687
## Common Tasks
7788

src/guidellm/backends/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from guidellm.extras.vllm import HAS_VLLM
1313

14-
from .backend import Backend, BackendArgs, BackendType
14+
from .backend import Backend, BackendArgs
1515
from .openai import (
1616
AudioRequestHandler,
1717
ChatCompletionsRequestHandler,
@@ -32,7 +32,6 @@
3232
"AudioRequestHandler",
3333
"Backend",
3434
"BackendArgs",
35-
"BackendType",
3635
"ChatCompletionsRequestHandler",
3736
"OpenAIHTTPBackend",
3837
"OpenAIRequestHandler",

src/guidellm/backends/backend.py

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,62 @@
88

99
from __future__ import annotations
1010

11-
from abc import abstractmethod
12-
from typing import Literal
11+
from abc import ABC, abstractmethod
12+
from typing import ClassVar
1313

14-
from pydantic import BaseModel, ConfigDict
14+
from pydantic import ConfigDict, Field
1515

1616
from guidellm.scheduler import BackendInterface
17-
from guidellm.schemas import GenerationRequest, GenerationResponse
17+
from guidellm.schemas import (
18+
GenerationRequest,
19+
GenerationResponse,
20+
PydanticClassRegistryMixin,
21+
)
1822
from guidellm.utils.registry import RegistryMixin
1923

2024
__all__ = [
2125
"Backend",
2226
"BackendArgs",
23-
"BackendType",
2427
]
2528

2629

27-
BackendType = Literal["openai_http", "vllm_python"]
30+
class BackendArgs(PydanticClassRegistryMixin["BackendArgs"], ABC):
31+
"""
32+
Base class for backend creation arguments.
33+
34+
This class serves as a base for defining argument models used in the creation
35+
of backend instances. It inherits from PydanticClassRegistryMixin to enable
36+
automatic registration of subclasses, allowing for flexible and extensible
37+
backend configurations.
38+
39+
:cvar schema_discriminator: Field name for polymorphic deserialization
40+
"""
41+
42+
model_config = ConfigDict(
43+
extra="forbid",
44+
serialize_by_alias=True,
45+
ser_json_bytes="base64",
46+
val_json_bytes="base64",
47+
)
48+
49+
schema_discriminator: ClassVar[str] = "type"
50+
51+
@classmethod
52+
def __pydantic_schema_base_type__(cls) -> type[BackendArgs]:
53+
"""
54+
Return base type for polymorphic validation hierarchy.
2855
56+
:return: Base BackendArgs class for schema validation
57+
"""
58+
if cls.__name__ == "BackendArgs":
59+
return cls
2960

30-
class BackendArgs(BaseModel):
31-
"""Base class for backend creation argument models."""
61+
return BackendArgs
3262

33-
# Allow for extra fields until we make BackendArgs the sole source of truth
34-
model_config = ConfigDict(extra="allow")
63+
type_: str = Field(
64+
alias="type",
65+
description="Type identifier for the backend configuration.",
66+
)
3567

3668

3769
class Backend(
@@ -57,18 +89,19 @@ class Backend(
5789
::
5890
@Backend.register("my_backend")
5991
class MyBackend(Backend):
60-
def __init__(self, api_key: str):
61-
super().__init__("my_backend")
62-
self.api_key = api_key
92+
def __init__(self, args: MyBackendArgs):
93+
super().__init__(args)
94+
self.api_key = args.api_key
6395
6496
async def process_startup(self):
6597
self.client = MyAPIClient(self.api_key)
6698
67-
backend = Backend.create("my_backend", api_key="secret")
99+
args = MyBackendArgs(api_key="secret")
100+
backend = Backend.create(args)
68101
"""
69102

70103
@classmethod
71-
def create(cls, type_: str, **kwargs) -> Backend:
104+
def create(cls, args: BackendArgs) -> Backend:
72105
"""
73106
Create a backend instance based on the backend type.
74107
@@ -77,6 +110,7 @@ def create(cls, type_: str, **kwargs) -> Backend:
77110
:return: An instance of a subclass of Backend
78111
:raises ValueError: If the backend type is not registered
79112
"""
113+
type_ = args.type_
80114

81115
backend = cls.get_registered_object(type_)
82116

@@ -86,34 +120,15 @@ def create(cls, type_: str, **kwargs) -> Backend:
86120
f"Available types: {list(cls.registry.keys()) if cls.registry else []}"
87121
)
88122

89-
return backend(**kwargs)
90-
91-
@classmethod
92-
def get_backend_args(cls, type_: str) -> type[BackendArgs]:
93-
"""
94-
Return the Pydantic model class for the backend's creation arguments.
123+
return backend(args)
95124

96-
:param type_: The backend type identifier
97-
:return: The backend's BackendArgs subclass
98-
:raises ValueError: If the backend type is not registered
99-
"""
100-
backend_class = cls.get_registered_object(type_)
101-
102-
if backend_class is None:
103-
raise ValueError(
104-
f"Backend type '{type_}' is not registered. "
105-
f"Available types: {list(cls.registry.keys()) if cls.registry else []}"
106-
)
107-
108-
return backend_class.backend_args()
109-
110-
def __init__(self, type_: str):
125+
def __init__(self, args: BackendArgs):
111126
"""
112127
Initialize a backend instance.
113128
114129
:param type_: The backend type identifier
115130
"""
116-
self.type_ = type_
131+
self.type_ = args.type_
117132

118133
@property
119134
def processes_limit(self) -> int | None:
@@ -130,19 +145,6 @@ def requests_limit(self) -> int | None:
130145
"""
131146
return None
132147

133-
@classmethod
134-
@abstractmethod
135-
def backend_args(cls) -> type[BackendArgs]:
136-
"""
137-
Return the Pydantic model class for this backend's creation arguments.
138-
139-
The model defines the parameters (e.g. target, model) that the CLI/benchmark
140-
supply when creating the backend. Used for validation and error messages.
141-
142-
:return: A BackendArgs subclass whose fields are the creation params
143-
"""
144-
...
145-
146148
@abstractmethod
147149
async def default_model(self) -> str:
148150
"""

0 commit comments

Comments
 (0)