guidellm 0.1.0__py3-none-any.whl → 0.2.0__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.

Potentially problematic release.


This version of guidellm might be problematic. Click here for more details.

Files changed (69) hide show
  1. guidellm/__init__.py +38 -6
  2. guidellm/__main__.py +294 -0
  3. guidellm/backend/__init__.py +19 -6
  4. guidellm/backend/backend.py +238 -0
  5. guidellm/backend/openai.py +532 -122
  6. guidellm/backend/response.py +132 -0
  7. guidellm/benchmark/__init__.py +73 -0
  8. guidellm/benchmark/aggregator.py +760 -0
  9. guidellm/benchmark/benchmark.py +838 -0
  10. guidellm/benchmark/benchmarker.py +334 -0
  11. guidellm/benchmark/entrypoints.py +141 -0
  12. guidellm/benchmark/output.py +946 -0
  13. guidellm/benchmark/profile.py +409 -0
  14. guidellm/benchmark/progress.py +720 -0
  15. guidellm/config.py +34 -56
  16. guidellm/data/__init__.py +4 -0
  17. guidellm/data/prideandprejudice.txt.gz +0 -0
  18. guidellm/dataset/__init__.py +22 -0
  19. guidellm/dataset/creator.py +213 -0
  20. guidellm/dataset/entrypoints.py +42 -0
  21. guidellm/dataset/file.py +90 -0
  22. guidellm/dataset/hf_datasets.py +62 -0
  23. guidellm/dataset/in_memory.py +132 -0
  24. guidellm/dataset/synthetic.py +262 -0
  25. guidellm/objects/__init__.py +18 -0
  26. guidellm/objects/pydantic.py +60 -0
  27. guidellm/objects/statistics.py +947 -0
  28. guidellm/request/__init__.py +12 -10
  29. guidellm/request/loader.py +281 -0
  30. guidellm/request/request.py +79 -0
  31. guidellm/scheduler/__init__.py +51 -3
  32. guidellm/scheduler/result.py +137 -0
  33. guidellm/scheduler/scheduler.py +382 -0
  34. guidellm/scheduler/strategy.py +493 -0
  35. guidellm/scheduler/types.py +7 -0
  36. guidellm/scheduler/worker.py +511 -0
  37. guidellm/utils/__init__.py +16 -29
  38. guidellm/utils/colors.py +8 -0
  39. guidellm/utils/hf_transformers.py +35 -0
  40. guidellm/utils/random.py +43 -0
  41. guidellm/utils/text.py +118 -357
  42. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info}/METADATA +96 -79
  43. guidellm-0.2.0.dist-info/RECORD +48 -0
  44. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info}/WHEEL +1 -1
  45. guidellm-0.2.0.dist-info/entry_points.txt +2 -0
  46. guidellm/backend/base.py +0 -320
  47. guidellm/core/__init__.py +0 -24
  48. guidellm/core/distribution.py +0 -190
  49. guidellm/core/report.py +0 -321
  50. guidellm/core/request.py +0 -44
  51. guidellm/core/result.py +0 -545
  52. guidellm/core/serializable.py +0 -169
  53. guidellm/executor/__init__.py +0 -10
  54. guidellm/executor/base.py +0 -213
  55. guidellm/executor/profile_generator.py +0 -343
  56. guidellm/main.py +0 -336
  57. guidellm/request/base.py +0 -194
  58. guidellm/request/emulated.py +0 -391
  59. guidellm/request/file.py +0 -76
  60. guidellm/request/transformers.py +0 -100
  61. guidellm/scheduler/base.py +0 -374
  62. guidellm/scheduler/load_generator.py +0 -196
  63. guidellm/utils/injector.py +0 -70
  64. guidellm/utils/progress.py +0 -196
  65. guidellm/utils/transformers.py +0 -151
  66. guidellm-0.1.0.dist-info/RECORD +0 -35
  67. guidellm-0.1.0.dist-info/entry_points.txt +0 -3
  68. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info/licenses}/LICENSE +0 -0
  69. {guidellm-0.1.0.dist-info → guidellm-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,15 @@
1
- from .base import GenerationMode, RequestGenerator
2
- from .emulated import EmulatedConfig, EmulatedRequestGenerator
3
- from .file import FileRequestGenerator
4
- from .transformers import TransformersDatasetRequestGenerator
1
+ from .loader import (
2
+ GenerativeRequestLoader,
3
+ GenerativeRequestLoaderDescription,
4
+ RequestLoader,
5
+ RequestLoaderDescription,
6
+ )
7
+ from .request import GenerationRequest
5
8
 
6
9
  __all__ = [
7
- "EmulatedConfig",
8
- "EmulatedRequestGenerator",
9
- "FileRequestGenerator",
10
- "GenerationMode",
11
- "RequestGenerator",
12
- "TransformersDatasetRequestGenerator",
10
+ "RequestLoader",
11
+ "RequestLoaderDescription",
12
+ "GenerativeRequestLoaderDescription",
13
+ "GenerativeRequestLoader",
14
+ "GenerationRequest",
13
15
  ]
@@ -0,0 +1,281 @@
1
+ from abc import abstractmethod
2
+ from collections.abc import Iterable, Iterator
3
+ from pathlib import Path
4
+ from typing import (
5
+ Any,
6
+ Literal,
7
+ Optional,
8
+ Union,
9
+ )
10
+
11
+ from datasets import Dataset, DatasetDict, IterableDataset, IterableDatasetDict
12
+ from transformers import PreTrainedTokenizerBase # type: ignore[import]
13
+
14
+ from guidellm.dataset import ColumnInputTypes, load_dataset
15
+ from guidellm.objects import StandardBaseModel
16
+ from guidellm.request.request import GenerationRequest
17
+
18
+ __all__ = [
19
+ "RequestLoaderDescription",
20
+ "RequestLoader",
21
+ "GenerativeRequestLoaderDescription",
22
+ "GenerativeRequestLoader",
23
+ ]
24
+
25
+
26
+ class RequestLoaderDescription(StandardBaseModel):
27
+ type_: Literal["request_loader"] = "request_loader"
28
+
29
+
30
+ class RequestLoader(Iterable):
31
+ @abstractmethod
32
+ def __iter__(self): ...
33
+
34
+ @abstractmethod
35
+ def __len__(self): ...
36
+
37
+ @property
38
+ @abstractmethod
39
+ def description(self) -> RequestLoaderDescription: ...
40
+
41
+
42
+ class GenerativeRequestLoaderDescription(RequestLoaderDescription):
43
+ type_: Literal["generative_request_loader"] = "generative_request_loader" # type: ignore[assignment]
44
+ data: str
45
+ data_args: Optional[dict[str, Any]]
46
+ processor: str
47
+ processor_args: Optional[dict[str, Any]]
48
+
49
+
50
+ class GenerativeRequestLoader(RequestLoader):
51
+ DEFAULT_PROMPT_COLUMNS = [
52
+ "prompt",
53
+ "prompts",
54
+ "instruction",
55
+ "instructions",
56
+ "question",
57
+ "questions",
58
+ "input",
59
+ "inputs",
60
+ "context",
61
+ "content",
62
+ "conversation",
63
+ "conversations",
64
+ "text",
65
+ ]
66
+
67
+ def __init__(
68
+ self,
69
+ data: Union[
70
+ str,
71
+ Path,
72
+ Iterable[Union[str, dict[str, Any]]],
73
+ Dataset,
74
+ DatasetDict,
75
+ IterableDataset,
76
+ IterableDatasetDict,
77
+ ],
78
+ data_args: Optional[dict[str, Any]],
79
+ processor: Optional[Union[str, Path, PreTrainedTokenizerBase]],
80
+ processor_args: Optional[dict[str, Any]],
81
+ shuffle: bool = True,
82
+ iter_type: Literal["finite", "infinite"] = "finite",
83
+ random_seed: int = 42,
84
+ ):
85
+ self.data = data
86
+ self.data_args = data_args
87
+ dataset, args_column_mappings = load_dataset(
88
+ data,
89
+ data_args,
90
+ processor,
91
+ processor_args,
92
+ random_seed,
93
+ )
94
+ self.dataset = dataset
95
+ self.processor = processor
96
+ self.processor_args = processor_args
97
+ self.shuffle = shuffle
98
+ self.iter_type = iter_type
99
+ self.random_seed = random_seed
100
+
101
+ self.column_mappings = self._create_column_mappings(args_column_mappings)
102
+ self.preserve_iter_state = iter_type == "infinite" # ensure no caching requests
103
+ self._preserved_iter = None
104
+
105
+ def __iter__(self) -> Iterator[GenerationRequest]:
106
+ scope_create_count = 0
107
+
108
+ while (dataset_iter := self._get_dataset_iter(scope_create_count)) is not None:
109
+ scope_create_count += 1
110
+
111
+ for item in dataset_iter:
112
+ yield self._create_request(item)
113
+
114
+ self._preserved_iter = None
115
+
116
+ def __len__(self) -> int:
117
+ if self.iter_type == "finite":
118
+ return self.num_unique_items()
119
+
120
+ raise ValueError(f"Unable to determine length of dataset: {self.data}")
121
+
122
+ @property
123
+ def description(self) -> GenerativeRequestLoaderDescription:
124
+ return GenerativeRequestLoaderDescription(
125
+ data=str(self.data),
126
+ data_args=self.data_args,
127
+ processor=str(self.processor),
128
+ processor_args=self.processor_args,
129
+ )
130
+
131
+ def num_unique_items(self, raise_err: bool = True) -> int:
132
+ try:
133
+ return len(self.dataset)
134
+ except Exception: # noqa: BLE001, S110
135
+ pass
136
+
137
+ dataset_size = self.dataset.info.dataset_size
138
+ if dataset_size is not None:
139
+ return dataset_size
140
+
141
+ if raise_err:
142
+ raise ValueError("Unable to determine number of items in the dataset")
143
+
144
+ return -1
145
+
146
+ def _create_column_mappings(
147
+ self,
148
+ args_column_mappings: dict[ColumnInputTypes, str],
149
+ ) -> dict[ColumnInputTypes, str]:
150
+ column_mappings: dict[ColumnInputTypes, str] = {}
151
+
152
+ if "text_column" in args_column_mappings:
153
+ column_mappings["prompt_column"] = args_column_mappings["text_column"]
154
+ else:
155
+ column_mappings["prompt_column"] = self._extract_text_column()
156
+
157
+ if "prompt_tokens_count_column" in args_column_mappings:
158
+ column_mappings["prompt_tokens_count_column"] = args_column_mappings[
159
+ "prompt_tokens_count_column"
160
+ ]
161
+ elif prompt_tokens_count_column := self._extract_prompt_tokens_count_column():
162
+ column_mappings["prompt_tokens_count_column"] = prompt_tokens_count_column
163
+
164
+ if "output_tokens_count_column" in args_column_mappings:
165
+ column_mappings["output_tokens_count_column"] = args_column_mappings[
166
+ "output_tokens_count_column"
167
+ ]
168
+ elif output_tokens_count_column := self._extract_output_tokens_count_column():
169
+ column_mappings["output_tokens_count_column"] = output_tokens_count_column
170
+
171
+ return column_mappings
172
+
173
+ def _extract_text_column(self) -> str:
174
+ column_names = self._dataset_columns(
175
+ err_msg=(
176
+ "Unable to determine text column from dataset and it is required. "
177
+ "To specify the text column, set the 'text_column' key in the "
178
+ "'data_args' dictionary."
179
+ )
180
+ )
181
+
182
+ if not column_names:
183
+ raise ValueError(
184
+ "Unable to determine text column from dataset and it is required. "
185
+ "To specify the text column, set the 'text_column' key in the "
186
+ "'data_args' dictionary."
187
+ )
188
+
189
+ if len(column_names) == 1:
190
+ return column_names[0]
191
+
192
+ for def_column in self.DEFAULT_PROMPT_COLUMNS:
193
+ if def_column in column_names:
194
+ return def_column
195
+
196
+ raise ValueError(
197
+ f"Unable to determine text column from dataset columns: {column_names}. "
198
+ "To specify the text column, set the 'text_column' key in the "
199
+ "'data_args' dictionary."
200
+ )
201
+
202
+ def _extract_prompt_tokens_count_column(self) -> Optional[str]:
203
+ column_names = self._dataset_columns()
204
+
205
+ if column_names and "prompt_tokens_count" in column_names:
206
+ return "prompt_tokens_count"
207
+
208
+ if column_names and "prompt_tokens" in column_names:
209
+ return "prompt_tokens"
210
+
211
+ return None
212
+
213
+ def _extract_output_tokens_count_column(self) -> Optional[str]:
214
+ column_names = self._dataset_columns()
215
+
216
+ if column_names and "output_tokens_count" in column_names:
217
+ return "output_tokens_count"
218
+
219
+ if column_names and "output_tokens" in column_names:
220
+ return "output_tokens"
221
+
222
+ return None
223
+
224
+ def _dataset_columns(self, err_msg: Optional[str] = None) -> Optional[list[str]]:
225
+ try:
226
+ column_names = self.dataset.column_names
227
+
228
+ if not column_names and err_msg:
229
+ raise ValueError(f"No column names found in dataset: {self.data}")
230
+ except Exception as err:
231
+ if err_msg:
232
+ raise ValueError(err_msg) from err
233
+
234
+ column_names = None
235
+
236
+ return column_names
237
+
238
+ def _get_dataset_iter(
239
+ self, scope_create_count: int
240
+ ) -> Optional[Iterator[dict[str, Any]]]:
241
+ if scope_create_count > 0 and self.iter_type != "infinite":
242
+ return None
243
+
244
+ if self.preserve_iter_state and self._preserved_iter is not None:
245
+ return self._preserved_iter
246
+
247
+ dataset = (
248
+ self.dataset
249
+ if not self.shuffle
250
+ else self.dataset.shuffle(seed=self.random_seed)
251
+ )
252
+
253
+ dataset_iter = iter(dataset)
254
+
255
+ if self.preserve_iter_state:
256
+ self._preserved_iter = dataset_iter
257
+
258
+ return dataset_iter
259
+
260
+ def _create_request(self, item: dict[str, Any]) -> GenerationRequest:
261
+ prompt_tokens = (
262
+ item[self.column_mappings["prompt_tokens_count_column"]]
263
+ if "prompt_tokens_count_column" in self.column_mappings
264
+ else None
265
+ )
266
+ output_tokens = (
267
+ item[self.column_mappings["output_tokens_count_column"]]
268
+ if "output_tokens_count_column" in self.column_mappings
269
+ else None
270
+ )
271
+
272
+ return GenerationRequest(
273
+ request_type="text_completions",
274
+ content=item[self.column_mappings["prompt_column"]],
275
+ stats=(
276
+ {"prompt_tokens": prompt_tokens} if prompt_tokens is not None else {}
277
+ ),
278
+ constraints=(
279
+ {"output_tokens": output_tokens} if output_tokens is not None else {}
280
+ ),
281
+ )
@@ -0,0 +1,79 @@
1
+ import uuid
2
+ from typing import Any, Literal, Optional
3
+
4
+ from pydantic import Field
5
+
6
+ from guidellm.objects.pydantic import StandardBaseModel
7
+
8
+ __all__ = ["GenerationRequest"]
9
+
10
+
11
+ class GenerationRequest(StandardBaseModel):
12
+ """
13
+ A class representing a request for generation.
14
+ This class is used to encapsulate the details of a generation request,
15
+ including the request ID, type, content, parameters, statistics, and constraints.
16
+ It is designed to be used with the BackendRequestsWorker class to handle
17
+ the generation process.
18
+
19
+ :param request_id: The unique identifier for the request.
20
+ :param request_type: The type of request (e.g., text, chat).
21
+ :param content: The content for the request to send to the backend.
22
+ If request_type is 'text', this should be a string or list of strings
23
+ which will be resolved by backend.text_completions.
24
+ If request_type is 'chat', this should be a string,
25
+ a list of (str, Dict[str, Union[str, Dict[str, str]], Path, Image]),
26
+ or Any raw content which will be resolved by backend.chat_completions.
27
+ If raw content, raw_content=True must be passed in the params.
28
+ :param params: Additional parameters for the request passed in as kwargs.
29
+ For an http backend, these are passed into the body of the request.
30
+ :param stats: Statistics for the request, such as the number of prompt tokens.
31
+ Used for tracking and reporting purposes.
32
+ :param constraints: Constraints for the request, such as the maximum number
33
+ of output tokens. Used for controlling the behavior of the backend.
34
+ """
35
+
36
+ request_id: Optional[str] = Field(
37
+ default_factory=lambda: str(uuid.uuid4()),
38
+ description="The unique identifier for the request.",
39
+ )
40
+ request_type: Literal["text_completions", "chat_completions"] = Field(
41
+ default="text_completions",
42
+ description=(
43
+ "The type of request (e.g., text, chat). "
44
+ "If request_type='text_completions', resolved by backend.text_completions. "
45
+ "If request_typ='chat_completions', resolved by backend.chat_completions."
46
+ ),
47
+ )
48
+ content: Any = Field(
49
+ description=(
50
+ "The content for the request to send to the backend. "
51
+ "If request_type is 'text', this should be a string or list of strings "
52
+ "which will be resolved by backend.text_completions. "
53
+ "If request_type is 'chat', this should be a string, "
54
+ "a list of (str, Dict[str, Union[str, Dict[str, str]], Path, Image]), "
55
+ "or Any raw content which will be resolved by backend.chat_completions. "
56
+ "If raw content, raw_content=True must be passed in the params."
57
+ )
58
+ )
59
+ params: dict[str, Any] = Field(
60
+ default_factory=dict,
61
+ description=(
62
+ "Additional parameters for the request that will be passed in as kwargs. "
63
+ "For an http backend, these are passed into the body of the request. "
64
+ ),
65
+ )
66
+ stats: dict[Literal["prompt_tokens"], int] = Field(
67
+ default_factory=dict,
68
+ description=(
69
+ "Statistics for the request, such as the number of prompt tokens. "
70
+ "Used for tracking and reporting purposes."
71
+ ),
72
+ )
73
+ constraints: dict[Literal["output_tokens"], int] = Field(
74
+ default_factory=dict,
75
+ description=(
76
+ "Constraints for the request, such as the maximum number of output tokens. "
77
+ "Used for controlling the behavior of the backend."
78
+ ),
79
+ )
@@ -1,4 +1,52 @@
1
- from .base import Scheduler, SchedulerResult
2
- from .load_generator import LoadGenerationMode, LoadGenerator
1
+ from .result import (
2
+ SchedulerRequestInfo,
3
+ SchedulerRequestResult,
4
+ SchedulerResult,
5
+ SchedulerRunInfo,
6
+ )
7
+ from .scheduler import Scheduler
8
+ from .strategy import (
9
+ AsyncConstantStrategy,
10
+ AsyncPoissonStrategy,
11
+ ConcurrentStrategy,
12
+ SchedulingStrategy,
13
+ StrategyType,
14
+ SynchronousStrategy,
15
+ ThroughputStrategy,
16
+ strategy_display_str,
17
+ )
18
+ from .types import RequestT, ResponseT
19
+ from .worker import (
20
+ GenerativeRequestsWorker,
21
+ GenerativeRequestsWorkerDescription,
22
+ RequestsWorker,
23
+ ResolveStatus,
24
+ WorkerDescription,
25
+ WorkerProcessRequest,
26
+ WorkerProcessResult,
27
+ )
3
28
 
4
- __all__ = ["LoadGenerationMode", "LoadGenerator", "Scheduler", "SchedulerResult"]
29
+ __all__ = [
30
+ "SchedulerRequestInfo",
31
+ "SchedulerRequestResult",
32
+ "SchedulerResult",
33
+ "SchedulerRunInfo",
34
+ "Scheduler",
35
+ "AsyncConstantStrategy",
36
+ "AsyncPoissonStrategy",
37
+ "ConcurrentStrategy",
38
+ "SchedulingStrategy",
39
+ "StrategyType",
40
+ "SynchronousStrategy",
41
+ "ThroughputStrategy",
42
+ "strategy_display_str",
43
+ "RequestT",
44
+ "ResponseT",
45
+ "WorkerProcessRequest",
46
+ "WorkerProcessResult",
47
+ "ResolveStatus",
48
+ "WorkerDescription",
49
+ "RequestsWorker",
50
+ "GenerativeRequestsWorkerDescription",
51
+ "GenerativeRequestsWorker",
52
+ ]
@@ -0,0 +1,137 @@
1
+ from typing import (
2
+ Generic,
3
+ Literal,
4
+ Optional,
5
+ )
6
+
7
+ from guidellm.objects import StandardBaseModel
8
+ from guidellm.scheduler.strategy import SchedulingStrategy
9
+ from guidellm.scheduler.types import RequestT, ResponseT
10
+
11
+ __all__ = [
12
+ "SchedulerResult",
13
+ "SchedulerRequestResult",
14
+ "SchedulerRunInfo",
15
+ "SchedulerRequestInfo",
16
+ ]
17
+
18
+
19
+ class SchedulerRunInfo(StandardBaseModel):
20
+ """
21
+ Information about the current run of the scheduler.
22
+ This class holds metadata about the scheduling run,
23
+ including the start and end times, the number of processes,
24
+ and the scheduling strategy used.
25
+ It also tracks the number of requests created, queued, pending,
26
+ and completed during the run.
27
+
28
+ :param start_time: The start time of the scheduling run.
29
+ :param end_time: The end time of the scheduling run;
30
+ if None, then this will be math.inf.
31
+ :param end_number: The maximum number of requests to be processed;
32
+ if None, then this will be math.inf.
33
+ :param processes: The number of processes used in the scheduling run.
34
+ :param strategy: The scheduling strategy used in the run.
35
+ This should be an instance of SchedulingStrategy.
36
+ :param created_requests: The number of requests created during the run.
37
+ :param queued_requests: The number of requests queued during the run.
38
+ :param scheduled_requests: The number of requests scheduled during the run.
39
+ (requests pending being sent to the worker but recieved by a process)
40
+ :param processing_requests: The number of requests actively being run.
41
+ :param completed_requests: The number of requests completed during the run.
42
+ """
43
+
44
+ start_time: float
45
+ end_time: float
46
+ end_number: float
47
+ processes: int
48
+ strategy: SchedulingStrategy
49
+
50
+ created_requests: int = 0
51
+ queued_requests: int = 0
52
+ scheduled_requests: int = 0
53
+ processing_requests: int = 0
54
+ completed_requests: int = 0
55
+
56
+
57
+ class SchedulerRequestInfo(StandardBaseModel):
58
+ """
59
+ Information about a specific request run through the scheduler.
60
+ This class holds metadata about the request, including
61
+ the targeted start time, queued time, start time, end time,
62
+ and the process ID that handled the request.
63
+
64
+ :param targeted_start_time: The targeted start time for the request (time.time()).
65
+ :param queued_time: The time the request was queued (time.time()).
66
+ :param scheduled_time: The time the request was scheduled (time.time())
67
+ (any sleep time before the request was sent to the worker).
68
+ :param worker_start: The time the worker started processing request (time.time()).
69
+ :param worker_end: The time the worker finished processing request. (time.time()).
70
+ :param process_id: The ID of the underlying process that handled the request.
71
+ """
72
+
73
+ requested: bool = False
74
+ completed: bool = False
75
+ errored: bool = False
76
+ canceled: bool = False
77
+
78
+ targeted_start_time: float = -1
79
+ queued_time: float = -1
80
+ dequeued_time: float = -1
81
+ scheduled_time: float = -1
82
+ worker_start: float = -1
83
+ request_start: float = -1
84
+ request_end: float = -1
85
+ worker_end: float = -1
86
+ process_id: int = -1
87
+
88
+
89
+ class SchedulerResult(StandardBaseModel):
90
+ """
91
+ The yielded, iterative result for a scheduler run.
92
+ These are triggered on the start and end of the run,
93
+ as well as on the start and end of each request.
94
+ Depending on the type, it will hold the request and response
95
+ along with information and statistics about the request and general run.
96
+
97
+ :param type_: The type of the result, which can be one of:
98
+ - "run_start": Indicates the start of the run.
99
+ - "run_complete": Indicates the completion of the run (teardown happens after).
100
+ - "request_start": Indicates the start of a request.
101
+ - "request_complete": Indicates the completion of a request.
102
+ :param request: The request that was processed.
103
+ :param response: The response from the worker for the request.
104
+ :param request_info: Information about the request, including
105
+ the targeted start time, queued time, start time, end time,
106
+ and the process ID that handled the request.
107
+ :param run_info: Information about the current run of the scheduler,
108
+ including the start and end times, the number of processes,
109
+ and the scheduling strategy used.
110
+ It also tracks the number of requests created, queued, pending,
111
+ and completed during the run.
112
+ """
113
+
114
+ pydantic_type: Literal["scheduler_result"] = "scheduler_result"
115
+ type_: Literal[
116
+ "run_start",
117
+ "run_complete",
118
+ "request_scheduled",
119
+ "request_start",
120
+ "request_complete",
121
+ ]
122
+ run_info: SchedulerRunInfo
123
+
124
+
125
+ class SchedulerRequestResult(
126
+ SchedulerResult,
127
+ Generic[RequestT, ResponseT],
128
+ ):
129
+ pydantic_type: Literal["scheduler_request_result"] = "scheduler_request_result" # type: ignore[assignment]
130
+ type_: Literal[
131
+ "request_scheduled",
132
+ "request_start",
133
+ "request_complete",
134
+ ]
135
+ request: RequestT
136
+ request_info: SchedulerRequestInfo
137
+ response: Optional[ResponseT] = None