guidellm 0.3.1__py3-none-any.whl → 0.6.0a5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. guidellm/__init__.py +5 -2
  2. guidellm/__main__.py +524 -255
  3. guidellm/backends/__init__.py +33 -0
  4. guidellm/backends/backend.py +109 -0
  5. guidellm/backends/openai.py +340 -0
  6. guidellm/backends/response_handlers.py +428 -0
  7. guidellm/benchmark/__init__.py +69 -39
  8. guidellm/benchmark/benchmarker.py +160 -316
  9. guidellm/benchmark/entrypoints.py +560 -127
  10. guidellm/benchmark/outputs/__init__.py +24 -0
  11. guidellm/benchmark/outputs/console.py +633 -0
  12. guidellm/benchmark/outputs/csv.py +721 -0
  13. guidellm/benchmark/outputs/html.py +473 -0
  14. guidellm/benchmark/outputs/output.py +169 -0
  15. guidellm/benchmark/outputs/serialized.py +69 -0
  16. guidellm/benchmark/profiles.py +718 -0
  17. guidellm/benchmark/progress.py +553 -556
  18. guidellm/benchmark/scenarios/__init__.py +40 -0
  19. guidellm/benchmark/scenarios/chat.json +6 -0
  20. guidellm/benchmark/scenarios/rag.json +6 -0
  21. guidellm/benchmark/schemas/__init__.py +66 -0
  22. guidellm/benchmark/schemas/base.py +402 -0
  23. guidellm/benchmark/schemas/generative/__init__.py +55 -0
  24. guidellm/benchmark/schemas/generative/accumulator.py +841 -0
  25. guidellm/benchmark/schemas/generative/benchmark.py +163 -0
  26. guidellm/benchmark/schemas/generative/entrypoints.py +381 -0
  27. guidellm/benchmark/schemas/generative/metrics.py +927 -0
  28. guidellm/benchmark/schemas/generative/report.py +158 -0
  29. guidellm/data/__init__.py +34 -4
  30. guidellm/data/builders.py +541 -0
  31. guidellm/data/collators.py +16 -0
  32. guidellm/data/config.py +120 -0
  33. guidellm/data/deserializers/__init__.py +49 -0
  34. guidellm/data/deserializers/deserializer.py +141 -0
  35. guidellm/data/deserializers/file.py +223 -0
  36. guidellm/data/deserializers/huggingface.py +94 -0
  37. guidellm/data/deserializers/memory.py +194 -0
  38. guidellm/data/deserializers/synthetic.py +246 -0
  39. guidellm/data/entrypoints.py +52 -0
  40. guidellm/data/loaders.py +190 -0
  41. guidellm/data/preprocessors/__init__.py +27 -0
  42. guidellm/data/preprocessors/formatters.py +410 -0
  43. guidellm/data/preprocessors/mappers.py +196 -0
  44. guidellm/data/preprocessors/preprocessor.py +30 -0
  45. guidellm/data/processor.py +29 -0
  46. guidellm/data/schemas.py +175 -0
  47. guidellm/data/utils/__init__.py +6 -0
  48. guidellm/data/utils/dataset.py +94 -0
  49. guidellm/extras/__init__.py +4 -0
  50. guidellm/extras/audio.py +220 -0
  51. guidellm/extras/vision.py +242 -0
  52. guidellm/logger.py +2 -2
  53. guidellm/mock_server/__init__.py +8 -0
  54. guidellm/mock_server/config.py +84 -0
  55. guidellm/mock_server/handlers/__init__.py +17 -0
  56. guidellm/mock_server/handlers/chat_completions.py +280 -0
  57. guidellm/mock_server/handlers/completions.py +280 -0
  58. guidellm/mock_server/handlers/tokenizer.py +142 -0
  59. guidellm/mock_server/models.py +510 -0
  60. guidellm/mock_server/server.py +238 -0
  61. guidellm/mock_server/utils.py +302 -0
  62. guidellm/scheduler/__init__.py +69 -26
  63. guidellm/scheduler/constraints/__init__.py +49 -0
  64. guidellm/scheduler/constraints/constraint.py +325 -0
  65. guidellm/scheduler/constraints/error.py +411 -0
  66. guidellm/scheduler/constraints/factory.py +182 -0
  67. guidellm/scheduler/constraints/request.py +312 -0
  68. guidellm/scheduler/constraints/saturation.py +722 -0
  69. guidellm/scheduler/environments.py +252 -0
  70. guidellm/scheduler/scheduler.py +137 -368
  71. guidellm/scheduler/schemas.py +358 -0
  72. guidellm/scheduler/strategies.py +617 -0
  73. guidellm/scheduler/worker.py +413 -419
  74. guidellm/scheduler/worker_group.py +712 -0
  75. guidellm/schemas/__init__.py +65 -0
  76. guidellm/schemas/base.py +417 -0
  77. guidellm/schemas/info.py +188 -0
  78. guidellm/schemas/request.py +235 -0
  79. guidellm/schemas/request_stats.py +349 -0
  80. guidellm/schemas/response.py +124 -0
  81. guidellm/schemas/statistics.py +1018 -0
  82. guidellm/{config.py → settings.py} +31 -24
  83. guidellm/utils/__init__.py +71 -8
  84. guidellm/utils/auto_importer.py +98 -0
  85. guidellm/utils/cli.py +132 -5
  86. guidellm/utils/console.py +566 -0
  87. guidellm/utils/encoding.py +778 -0
  88. guidellm/utils/functions.py +159 -0
  89. guidellm/utils/hf_datasets.py +1 -2
  90. guidellm/utils/hf_transformers.py +4 -4
  91. guidellm/utils/imports.py +9 -0
  92. guidellm/utils/messaging.py +1118 -0
  93. guidellm/utils/mixins.py +115 -0
  94. guidellm/utils/random.py +3 -4
  95. guidellm/utils/registry.py +220 -0
  96. guidellm/utils/singleton.py +133 -0
  97. guidellm/utils/synchronous.py +159 -0
  98. guidellm/utils/text.py +163 -50
  99. guidellm/utils/typing.py +41 -0
  100. guidellm/version.py +2 -2
  101. guidellm-0.6.0a5.dist-info/METADATA +364 -0
  102. guidellm-0.6.0a5.dist-info/RECORD +109 -0
  103. guidellm/backend/__init__.py +0 -23
  104. guidellm/backend/backend.py +0 -259
  105. guidellm/backend/openai.py +0 -708
  106. guidellm/backend/response.py +0 -136
  107. guidellm/benchmark/aggregator.py +0 -760
  108. guidellm/benchmark/benchmark.py +0 -837
  109. guidellm/benchmark/output.py +0 -997
  110. guidellm/benchmark/profile.py +0 -409
  111. guidellm/benchmark/scenario.py +0 -104
  112. guidellm/data/prideandprejudice.txt.gz +0 -0
  113. guidellm/dataset/__init__.py +0 -22
  114. guidellm/dataset/creator.py +0 -213
  115. guidellm/dataset/entrypoints.py +0 -42
  116. guidellm/dataset/file.py +0 -92
  117. guidellm/dataset/hf_datasets.py +0 -62
  118. guidellm/dataset/in_memory.py +0 -132
  119. guidellm/dataset/synthetic.py +0 -287
  120. guidellm/objects/__init__.py +0 -18
  121. guidellm/objects/pydantic.py +0 -89
  122. guidellm/objects/statistics.py +0 -953
  123. guidellm/preprocess/__init__.py +0 -3
  124. guidellm/preprocess/dataset.py +0 -374
  125. guidellm/presentation/__init__.py +0 -28
  126. guidellm/presentation/builder.py +0 -27
  127. guidellm/presentation/data_models.py +0 -232
  128. guidellm/presentation/injector.py +0 -66
  129. guidellm/request/__init__.py +0 -18
  130. guidellm/request/loader.py +0 -284
  131. guidellm/request/request.py +0 -79
  132. guidellm/request/types.py +0 -10
  133. guidellm/scheduler/queues.py +0 -25
  134. guidellm/scheduler/result.py +0 -155
  135. guidellm/scheduler/strategy.py +0 -495
  136. guidellm-0.3.1.dist-info/METADATA +0 -329
  137. guidellm-0.3.1.dist-info/RECORD +0 -62
  138. {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/WHEEL +0 -0
  139. {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/entry_points.txt +0 -0
  140. {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/licenses/LICENSE +0 -0
  141. {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,33 @@
1
+ """
2
+ Backend infrastructure for GuideLLM language model interactions.
3
+
4
+ Provides abstract base classes, concrete backend implementations, and response
5
+ handlers for standardized communication with generative AI model providers.
6
+ The backend system supports distributed execution across worker processes with
7
+ pluggable response handlers for different API formats. Key components include
8
+ the abstract Backend base class, OpenAI-compatible HTTP backend, and response
9
+ handlers for processing streaming and non-streaming API responses.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from .backend import Backend, BackendType
15
+ from .openai import OpenAIHTTPBackend
16
+ from .response_handlers import (
17
+ AudioResponseHandler,
18
+ ChatCompletionsResponseHandler,
19
+ GenerationResponseHandler,
20
+ GenerationResponseHandlerFactory,
21
+ TextCompletionsResponseHandler,
22
+ )
23
+
24
+ __all__ = [
25
+ "AudioResponseHandler",
26
+ "Backend",
27
+ "BackendType",
28
+ "ChatCompletionsResponseHandler",
29
+ "GenerationResponseHandler",
30
+ "GenerationResponseHandlerFactory",
31
+ "OpenAIHTTPBackend",
32
+ "TextCompletionsResponseHandler",
33
+ ]
@@ -0,0 +1,109 @@
1
+ """
2
+ Backend interface and registry for generative AI model interactions.
3
+
4
+ Provides the abstract base class for implementing backends that communicate with
5
+ generative AI models. Backends handle the lifecycle of generation requests and
6
+ provide a standard interface for distributed execution across worker processes.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import abstractmethod
12
+ from typing import Literal
13
+
14
+ from guidellm.scheduler import BackendInterface
15
+ from guidellm.schemas import GenerationRequest, GenerationResponse
16
+ from guidellm.utils import RegistryMixin
17
+
18
+ __all__ = [
19
+ "Backend",
20
+ "BackendType",
21
+ ]
22
+
23
+
24
+ BackendType = Literal["openai_http"]
25
+
26
+
27
+ class Backend(
28
+ RegistryMixin["type[Backend]"],
29
+ BackendInterface[GenerationRequest, GenerationResponse],
30
+ ):
31
+ """
32
+ Base class for generative AI backends with registry and lifecycle management.
33
+
34
+ Provides a standard interface for backends that communicate with generative AI
35
+ models. Combines the registry pattern for automatic discovery with a defined
36
+ lifecycle for process-based distributed execution. Backend state must be
37
+ pickleable for distributed execution across process boundaries.
38
+
39
+ Backend lifecycle phases:
40
+ 1. Creation and configuration
41
+ 2. Process startup - Initialize resources in worker process
42
+ 3. Validation - Verify backend readiness
43
+ 4. Request resolution - Process generation requests
44
+ 5. Process shutdown - Clean up resources
45
+
46
+ Example:
47
+ ::
48
+ @Backend.register("my_backend")
49
+ class MyBackend(Backend):
50
+ def __init__(self, api_key: str):
51
+ super().__init__("my_backend")
52
+ self.api_key = api_key
53
+
54
+ async def process_startup(self):
55
+ self.client = MyAPIClient(self.api_key)
56
+
57
+ backend = Backend.create("my_backend", api_key="secret")
58
+ """
59
+
60
+ @classmethod
61
+ def create(cls, type_: BackendType, **kwargs) -> Backend:
62
+ """
63
+ Create a backend instance based on the backend type.
64
+
65
+ :param type_: The type of backend to create
66
+ :param kwargs: Additional arguments for backend initialization
67
+ :return: An instance of a subclass of Backend
68
+ :raises ValueError: If the backend type is not registered
69
+ """
70
+
71
+ backend = cls.get_registered_object(type_)
72
+
73
+ if backend is None:
74
+ raise ValueError(
75
+ f"Backend type '{type_}' is not registered. "
76
+ f"Available types: {list(cls.registry.keys()) if cls.registry else []}"
77
+ )
78
+
79
+ return backend(**kwargs)
80
+
81
+ def __init__(self, type_: BackendType):
82
+ """
83
+ Initialize a backend instance.
84
+
85
+ :param type_: The backend type identifier
86
+ """
87
+ self.type_ = type_
88
+
89
+ @property
90
+ def processes_limit(self) -> int | None:
91
+ """
92
+ :return: Maximum number of worker processes supported, None if unlimited
93
+ """
94
+ return None
95
+
96
+ @property
97
+ def requests_limit(self) -> int | None:
98
+ """
99
+ :return: Maximum number of concurrent requests supported globally,
100
+ None if unlimited
101
+ """
102
+ return None
103
+
104
+ @abstractmethod
105
+ async def default_model(self) -> str:
106
+ """
107
+ :return: The default model name or identifier for generation requests,
108
+ """
109
+ ...
@@ -0,0 +1,340 @@
1
+ """
2
+ OpenAI HTTP backend implementation for GuideLLM.
3
+
4
+ Provides HTTP-based backend for OpenAI-compatible servers including OpenAI API,
5
+ vLLM servers, and other compatible inference engines. Supports text and chat
6
+ completions with streaming, authentication, and multimodal capabilities.
7
+ Handles request formatting, response parsing, error handling, and token usage
8
+ tracking with flexible parameter customization.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import time
15
+ from collections.abc import AsyncIterator
16
+ from typing import Any
17
+
18
+ import httpx
19
+
20
+ from guidellm.backends.backend import Backend
21
+ from guidellm.backends.response_handlers import GenerationResponseHandlerFactory
22
+ from guidellm.schemas import GenerationRequest, GenerationResponse, RequestInfo
23
+
24
+ __all__ = ["OpenAIHTTPBackend"]
25
+
26
+
27
+ @Backend.register("openai_http")
28
+ class OpenAIHTTPBackend(Backend):
29
+ """
30
+ HTTP backend for OpenAI-compatible servers.
31
+
32
+ Supports OpenAI API, vLLM servers, and other compatible endpoints with
33
+ text/chat completions, streaming, authentication, and multimodal inputs.
34
+ Handles request formatting, response parsing, error handling, and token
35
+ usage tracking with flexible parameter customization.
36
+
37
+ Example:
38
+ ::
39
+ backend = OpenAIHTTPBackend(
40
+ target="http://localhost:8000",
41
+ model="gpt-3.5-turbo",
42
+ api_key="your-api-key"
43
+ )
44
+
45
+ await backend.process_startup()
46
+ async for response, request_info in backend.resolve(request, info):
47
+ process_response(response)
48
+ await backend.process_shutdown()
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ target: str,
54
+ model: str = "",
55
+ api_routes: dict[str, str] | None = None,
56
+ response_handlers: dict[str, Any] | None = None,
57
+ timeout: float = 60.0,
58
+ http2: bool = True,
59
+ follow_redirects: bool = True,
60
+ verify: bool = False,
61
+ validate_backend: bool | str | dict[str, Any] = True,
62
+ ):
63
+ """
64
+ Initialize OpenAI HTTP backend with server configuration.
65
+
66
+ :param target: Base URL of the OpenAI-compatible server
67
+ :param model: Model identifier for generation requests
68
+ :param api_routes: Custom API endpoint routes mapping
69
+ :param response_handlers: Custom response handlers for different request types
70
+ :param timeout: Request timeout in seconds
71
+ :param http2: Enable HTTP/2 protocol support
72
+ :param follow_redirects: Follow HTTP redirects automatically
73
+ :param verify: Enable SSL certificate verification
74
+ :param validate_backend: Backend validation configuration
75
+ """
76
+ super().__init__(type_="openai_http")
77
+
78
+ # Request Values
79
+ self.target = target.rstrip("/").removesuffix("/v1")
80
+ self.model = model
81
+
82
+ # Store configuration
83
+ self.api_routes = api_routes or {
84
+ "health": "health",
85
+ "models": "v1/models",
86
+ "text_completions": "v1/completions",
87
+ "chat_completions": "v1/chat/completions",
88
+ "audio_transcriptions": "v1/audio/transcriptions",
89
+ "audio_translations": "v1/audio/translations",
90
+ }
91
+ self.response_handlers = response_handlers
92
+ self.timeout = timeout
93
+ self.http2 = http2
94
+ self.follow_redirects = follow_redirects
95
+ self.verify = verify
96
+ self.validate_backend: dict[str, Any] | None = self._resolve_validate_kwargs(
97
+ validate_backend
98
+ )
99
+
100
+ # Runtime state
101
+ self._in_process = False
102
+ self._async_client: httpx.AsyncClient | None = None
103
+
104
+ @property
105
+ def info(self) -> dict[str, Any]:
106
+ """
107
+ Get backend configuration details.
108
+
109
+ :return: Dictionary containing backend configuration details
110
+ """
111
+ return {
112
+ "target": self.target,
113
+ "model": self.model,
114
+ "timeout": self.timeout,
115
+ "http2": self.http2,
116
+ "follow_redirects": self.follow_redirects,
117
+ "verify": self.verify,
118
+ "openai_paths": self.api_routes,
119
+ "validate_backend": self.validate_backend,
120
+ }
121
+
122
+ async def process_startup(self):
123
+ """
124
+ Initialize HTTP client and backend resources.
125
+
126
+ :raises RuntimeError: If backend is already initialized
127
+ :raises httpx.RequestError: If HTTP client cannot be created
128
+ """
129
+ if self._in_process:
130
+ raise RuntimeError("Backend already started up for process.")
131
+
132
+ self._async_client = httpx.AsyncClient(
133
+ http2=self.http2,
134
+ timeout=self.timeout,
135
+ follow_redirects=self.follow_redirects,
136
+ verify=self.verify,
137
+ # Allow unlimited connections
138
+ limits=httpx.Limits(
139
+ max_connections=None,
140
+ max_keepalive_connections=None,
141
+ keepalive_expiry=5.0, # default
142
+ ),
143
+ )
144
+ self._in_process = True
145
+
146
+ async def process_shutdown(self):
147
+ """
148
+ Clean up HTTP client and backend resources.
149
+
150
+ :raises RuntimeError: If backend was not properly initialized
151
+ :raises httpx.RequestError: If HTTP client cannot be closed
152
+ """
153
+ if not self._in_process:
154
+ raise RuntimeError("Backend not started up for process.")
155
+
156
+ await self._async_client.aclose() # type: ignore [union-attr]
157
+ self._async_client = None
158
+ self._in_process = False
159
+
160
+ async def validate(self):
161
+ """
162
+ Validate backend connectivity and configuration.
163
+
164
+ :raises RuntimeError: If backend cannot connect or validate configuration
165
+ """
166
+ if self._async_client is None:
167
+ raise RuntimeError("Backend not started up for process.")
168
+
169
+ if not self.validate_backend:
170
+ return
171
+
172
+ try:
173
+ response = await self._async_client.request(**self.validate_backend)
174
+ response.raise_for_status()
175
+ except Exception as exc:
176
+ raise RuntimeError(
177
+ "Backend validation request failed. Could not connect to the server "
178
+ "or validate the backend configuration."
179
+ ) from exc
180
+
181
+ async def available_models(self) -> list[str]:
182
+ """
183
+ Get available models from the target server.
184
+
185
+ :return: List of model identifiers
186
+ :raises httpx.HTTPError: If models endpoint returns an error
187
+ :raises RuntimeError: If backend is not initialized
188
+ """
189
+ if self._async_client is None:
190
+ raise RuntimeError("Backend not started up for process.")
191
+
192
+ target = f"{self.target}/{self.api_routes['models']}"
193
+ response = await self._async_client.get(target)
194
+ response.raise_for_status()
195
+
196
+ return [item["id"] for item in response.json()["data"]]
197
+
198
+ async def default_model(self) -> str:
199
+ """
200
+ Get the default model for this backend.
201
+
202
+ :return: Model name or None if no model is available
203
+ """
204
+ if self.model or not self._in_process:
205
+ return self.model
206
+
207
+ models = await self.available_models()
208
+ return models[0] if models else ""
209
+
210
+ async def resolve( # type: ignore[override]
211
+ self,
212
+ request: GenerationRequest,
213
+ request_info: RequestInfo,
214
+ history: list[tuple[GenerationRequest, GenerationResponse]] | None = None,
215
+ ) -> AsyncIterator[tuple[GenerationResponse, RequestInfo]]:
216
+ """
217
+ Process generation request and yield progressive responses.
218
+
219
+ Handles request formatting, timing tracking, API communication, and
220
+ response parsing with streaming support.
221
+
222
+ :param request: Generation request with content and parameters
223
+ :param request_info: Request tracking info updated with timing metadata
224
+ :param history: Conversation history (currently not supported)
225
+ :raises NotImplementedError: If history is provided
226
+ :raises RuntimeError: If backend is not initialized
227
+ :raises ValueError: If request type is unsupported
228
+ :yields: Tuples of (response, updated_request_info) as generation progresses
229
+ """
230
+ if self._async_client is None:
231
+ raise RuntimeError("Backend not started up for process.")
232
+
233
+ if history is not None:
234
+ raise NotImplementedError("Multi-turn requests not yet supported")
235
+
236
+ if (request_path := self.api_routes.get(request.request_type)) is None:
237
+ raise ValueError(f"Unsupported request type '{request.request_type}'")
238
+
239
+ request_url = f"{self.target}/{request_path}"
240
+ request_files = (
241
+ {
242
+ key: tuple(value) if isinstance(value, list) else value
243
+ for key, value in request.arguments.files.items()
244
+ }
245
+ if request.arguments.files
246
+ else None
247
+ )
248
+ request_json = request.arguments.body if not request_files else None
249
+ request_data = request.arguments.body if request_files else None
250
+ response_handler = GenerationResponseHandlerFactory.create(
251
+ request.request_type, handler_overrides=self.response_handlers
252
+ )
253
+
254
+ if not request.arguments.stream:
255
+ request_info.timings.request_start = time.time()
256
+ response = await self._async_client.request(
257
+ request.arguments.method or "POST",
258
+ request_url,
259
+ params=request.arguments.params,
260
+ headers=request.arguments.headers,
261
+ json=request_json,
262
+ data=request_data,
263
+ files=request_files,
264
+ )
265
+ request_info.timings.request_end = time.time()
266
+ response.raise_for_status()
267
+ data = response.json()
268
+ yield response_handler.compile_non_streaming(request, data), request_info
269
+ return
270
+
271
+ try:
272
+ request_info.timings.request_start = time.time()
273
+
274
+ async with self._async_client.stream(
275
+ request.arguments.method or "POST",
276
+ request_url,
277
+ params=request.arguments.params,
278
+ headers=request.arguments.headers,
279
+ json=request_json,
280
+ data=request_data,
281
+ files=request_files,
282
+ ) as stream:
283
+ stream.raise_for_status()
284
+ end_reached = False
285
+
286
+ async for chunk in stream.aiter_lines():
287
+ iter_time = time.time()
288
+
289
+ if request_info.timings.first_request_iteration is None:
290
+ request_info.timings.first_request_iteration = iter_time
291
+ request_info.timings.last_request_iteration = iter_time
292
+ request_info.timings.request_iterations += 1
293
+
294
+ iterations = response_handler.add_streaming_line(chunk)
295
+ if iterations is None or iterations <= 0 or end_reached:
296
+ end_reached = end_reached or iterations is None
297
+ continue
298
+
299
+ if request_info.timings.first_token_iteration is None:
300
+ request_info.timings.first_token_iteration = iter_time
301
+ request_info.timings.token_iterations = 0
302
+
303
+ request_info.timings.last_token_iteration = iter_time
304
+ request_info.timings.token_iterations += iterations
305
+
306
+ request_info.timings.request_end = time.time()
307
+ yield response_handler.compile_streaming(request), request_info
308
+ except asyncio.CancelledError as err:
309
+ # Yield current result to store iterative results before propagating
310
+ yield response_handler.compile_streaming(request), request_info
311
+ raise err
312
+
313
+ def _resolve_validate_kwargs(
314
+ self, validate_backend: bool | str | dict[str, Any]
315
+ ) -> dict[str, Any] | None:
316
+ if not (validate_kwargs := validate_backend):
317
+ return None
318
+
319
+ if validate_kwargs is True:
320
+ validate_kwargs = "health"
321
+
322
+ if isinstance(validate_kwargs, str) and validate_kwargs in self.api_routes:
323
+ validate_kwargs = f"{self.target}/{self.api_routes[validate_kwargs]}"
324
+
325
+ if isinstance(validate_kwargs, str):
326
+ validate_kwargs = {
327
+ "method": "GET",
328
+ "url": validate_kwargs,
329
+ }
330
+
331
+ if not isinstance(validate_kwargs, dict) or "url" not in validate_kwargs:
332
+ raise ValueError(
333
+ "validate_backend must be a boolean, string, or dictionary and contain "
334
+ f"a target URL. Got: {validate_kwargs}"
335
+ )
336
+
337
+ if "method" not in validate_kwargs:
338
+ validate_kwargs["method"] = "GET"
339
+
340
+ return validate_kwargs