waldiez 0.4.9__py3-none-any.whl → 0.5.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 waldiez might be problematic. Click here for more details.
- waldiez/__init__.py +1 -2
- waldiez/_version.py +1 -1
- waldiez/cli.py +65 -58
- waldiez/exporter.py +64 -9
- waldiez/exporting/agent/extras/group_manager_agent_extas.py +1 -1
- waldiez/exporting/core/context.py +12 -0
- waldiez/exporting/core/extras/flow_extras.py +2 -14
- waldiez/exporting/core/types.py +21 -0
- waldiez/exporting/flow/exporter.py +4 -0
- waldiez/exporting/flow/factory.py +16 -0
- waldiez/exporting/flow/orchestrator.py +12 -0
- waldiez/exporting/flow/utils/__init__.py +2 -0
- waldiez/exporting/flow/utils/common.py +96 -2
- waldiez/exporting/flow/utils/logging.py +5 -6
- waldiez/io/mqtt.py +7 -3
- waldiez/io/structured.py +5 -1
- waldiez/models/common/method_utils.py +1 -1
- waldiez/models/tool/tool.py +2 -1
- waldiez/runner.py +402 -321
- waldiez/running/__init__.py +6 -34
- waldiez/running/base_runner.py +907 -0
- waldiez/running/environment.py +74 -0
- waldiez/running/import_runner.py +424 -0
- waldiez/running/patch_io_stream.py +208 -0
- waldiez/running/post_run.py +26 -24
- waldiez/running/pre_run.py +2 -46
- waldiez/running/protocol.py +281 -0
- waldiez/running/run_results.py +22 -0
- waldiez/running/subprocess_runner.py +100 -0
- waldiez/utils/__init__.py +0 -4
- waldiez/utils/version.py +4 -2
- {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/METADATA +39 -113
- {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/RECORD +42 -37
- waldiez/utils/flaml_warnings.py +0 -17
- /waldiez/{utils/cli_extras → cli_extras}/__init__.py +0 -0
- /waldiez/{utils/cli_extras → cli_extras}/jupyter.py +0 -0
- /waldiez/{utils/cli_extras → cli_extras}/runner.py +0 -0
- /waldiez/{utils/cli_extras → cli_extras}/studio.py +0 -0
- /waldiez/running/{util.py → utils.py} +0 -0
- {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/WHEEL +0 -0
- {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/entry_points.txt +0 -0
- {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
# pylint: disable=too-many-instance-attributes,unused-argument
|
|
5
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
6
|
+
# pylint: disable=too-many-public-methods,too-many-locals
|
|
7
|
+
|
|
8
|
+
"""Base runner for Waldiez workflows."""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
import threading
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import TracebackType
|
|
15
|
+
from typing import TYPE_CHECKING, Type, Union
|
|
16
|
+
|
|
17
|
+
from anyio.from_thread import start_blocking_portal
|
|
18
|
+
from typing_extensions import Self
|
|
19
|
+
|
|
20
|
+
from waldiez.exporter import WaldiezExporter
|
|
21
|
+
from waldiez.logger import WaldiezLogger, get_logger
|
|
22
|
+
from waldiez.models import Waldiez
|
|
23
|
+
from waldiez.utils import get_waldiez_version
|
|
24
|
+
|
|
25
|
+
from .environment import refresh_environment, reset_env_vars, set_env_vars
|
|
26
|
+
from .post_run import after_run
|
|
27
|
+
from .pre_run import (
|
|
28
|
+
a_install_requirements,
|
|
29
|
+
install_requirements,
|
|
30
|
+
)
|
|
31
|
+
from .protocol import WaldiezRunnerProtocol
|
|
32
|
+
from .utils import (
|
|
33
|
+
a_chdir,
|
|
34
|
+
chdir,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from autogen import ChatResult # type: ignore[import-untyped]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WaldiezBaseRunner(WaldiezRunnerProtocol):
|
|
42
|
+
"""Base runner for Waldiez.
|
|
43
|
+
|
|
44
|
+
Methods to override:
|
|
45
|
+
- _before_run: Actions to perform before running the flow.
|
|
46
|
+
- _a_before_run: Async actions to perform before running the flow.
|
|
47
|
+
- _run: Actual implementation of the run logic.
|
|
48
|
+
- _a_run: Async implementation of the run logic.
|
|
49
|
+
- _after_run: Actions to perform after running the flow.
|
|
50
|
+
- _a_after_run: Async actions to perform after running the flow.
|
|
51
|
+
- _start: Implementation of non-blocking start logic.
|
|
52
|
+
- _a_start: Async implementation of non-blocking start logic.
|
|
53
|
+
- _stop: Actions to perform when stopping the flow.
|
|
54
|
+
- _a_stop: Async actions to perform when stopping the flow.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
_threaded: bool
|
|
58
|
+
_structured_io: bool
|
|
59
|
+
_isolated: bool
|
|
60
|
+
_output_path: str | Path | None
|
|
61
|
+
_uploads_root: str | Path | None
|
|
62
|
+
_skip_patch_io: bool
|
|
63
|
+
_running: bool
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
waldiez: Waldiez,
|
|
68
|
+
output_path: str | Path | None,
|
|
69
|
+
uploads_root: str | Path | None,
|
|
70
|
+
structured_io: bool,
|
|
71
|
+
isolated: bool,
|
|
72
|
+
threaded: bool,
|
|
73
|
+
skip_patch_io: bool = False,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Initialize the Waldiez manager."""
|
|
76
|
+
self._waldiez = waldiez
|
|
77
|
+
WaldiezBaseRunner._running = False
|
|
78
|
+
WaldiezBaseRunner._structured_io = structured_io
|
|
79
|
+
WaldiezBaseRunner._isolated = isolated
|
|
80
|
+
WaldiezBaseRunner._output_path = output_path
|
|
81
|
+
WaldiezBaseRunner._uploads_root = uploads_root
|
|
82
|
+
WaldiezBaseRunner._threaded = threaded
|
|
83
|
+
WaldiezBaseRunner._skip_patch_io = skip_patch_io
|
|
84
|
+
self._called_install_requirements = False
|
|
85
|
+
self._exporter = WaldiezExporter(waldiez)
|
|
86
|
+
self._stop_requested = threading.Event()
|
|
87
|
+
self._logger = get_logger()
|
|
88
|
+
self._last_results: Union[
|
|
89
|
+
"ChatResult",
|
|
90
|
+
list["ChatResult"],
|
|
91
|
+
dict[int, "ChatResult"],
|
|
92
|
+
] = []
|
|
93
|
+
self._last_exception: Exception | None = None
|
|
94
|
+
|
|
95
|
+
def is_running(self) -> bool:
|
|
96
|
+
"""Check if the workflow is currently running.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
bool
|
|
101
|
+
True if the workflow is running, False otherwise.
|
|
102
|
+
"""
|
|
103
|
+
return WaldiezBaseRunner._running
|
|
104
|
+
|
|
105
|
+
# ===================================================================
|
|
106
|
+
# PRIVATE METHODS TO OVERRIDE IN SUBCLASSES
|
|
107
|
+
# ===================================================================
|
|
108
|
+
def _before_run(
|
|
109
|
+
self,
|
|
110
|
+
output_file: Path,
|
|
111
|
+
uploads_root: Path | None,
|
|
112
|
+
) -> Path:
|
|
113
|
+
"""Run before the flow execution."""
|
|
114
|
+
self.log.info("Preparing workflow file: %s", output_file)
|
|
115
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
116
|
+
file_name = output_file.name
|
|
117
|
+
with chdir(to=temp_dir):
|
|
118
|
+
self._exporter.export(
|
|
119
|
+
path=file_name,
|
|
120
|
+
force=True,
|
|
121
|
+
uploads_root=uploads_root,
|
|
122
|
+
# if not isolated, we use structured IO in a context manager
|
|
123
|
+
structured_io=WaldiezBaseRunner._structured_io
|
|
124
|
+
and WaldiezBaseRunner._isolated,
|
|
125
|
+
skip_patch_io=WaldiezBaseRunner._skip_patch_io,
|
|
126
|
+
)
|
|
127
|
+
return temp_dir
|
|
128
|
+
|
|
129
|
+
async def _a_before_run(
|
|
130
|
+
self,
|
|
131
|
+
output_file: Path,
|
|
132
|
+
uploads_root: Path | None,
|
|
133
|
+
) -> Path:
|
|
134
|
+
"""Run before the flow execution asynchronously."""
|
|
135
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
136
|
+
file_name = output_file.name
|
|
137
|
+
async with a_chdir(to=temp_dir):
|
|
138
|
+
self._exporter.export(
|
|
139
|
+
path=file_name,
|
|
140
|
+
uploads_root=uploads_root,
|
|
141
|
+
structured_io=self.structured_io,
|
|
142
|
+
force=True,
|
|
143
|
+
)
|
|
144
|
+
return temp_dir
|
|
145
|
+
|
|
146
|
+
def _run(
|
|
147
|
+
self,
|
|
148
|
+
temp_dir: Path,
|
|
149
|
+
output_file: Path,
|
|
150
|
+
uploads_root: Path | None,
|
|
151
|
+
skip_mmd: bool,
|
|
152
|
+
) -> Union[
|
|
153
|
+
"ChatResult",
|
|
154
|
+
list["ChatResult"],
|
|
155
|
+
dict[int, "ChatResult"],
|
|
156
|
+
]: # pyright: ignore
|
|
157
|
+
"""Run the Waldiez flow."""
|
|
158
|
+
raise NotImplementedError(
|
|
159
|
+
"The _run method must be implemented in the subclass."
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def _a_run(
|
|
163
|
+
self,
|
|
164
|
+
temp_dir: Path,
|
|
165
|
+
output_file: Path,
|
|
166
|
+
uploads_root: Path | None,
|
|
167
|
+
skip_mmd: bool,
|
|
168
|
+
) -> Union[
|
|
169
|
+
"ChatResult",
|
|
170
|
+
list["ChatResult"],
|
|
171
|
+
dict[int, "ChatResult"],
|
|
172
|
+
]: # pyright: ignore
|
|
173
|
+
"""Run the Waldiez flow asynchronously."""
|
|
174
|
+
raise NotImplementedError(
|
|
175
|
+
"The _a_run method must be implemented in the subclass."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def _start(
|
|
179
|
+
self,
|
|
180
|
+
temp_dir: Path,
|
|
181
|
+
output_file: Path,
|
|
182
|
+
uploads_root: Path | None,
|
|
183
|
+
skip_mmd: bool,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Start running the Waldiez flow in a non-blocking way."""
|
|
186
|
+
raise NotImplementedError(
|
|
187
|
+
"The _start method must be implemented in the subclass."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def _a_start(
|
|
191
|
+
self,
|
|
192
|
+
temp_dir: Path,
|
|
193
|
+
output_file: Path,
|
|
194
|
+
uploads_root: Path | None,
|
|
195
|
+
skip_mmd: bool,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Start running the Waldiez flow in a non-blocking way asynchronously.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
temp_dir : Path
|
|
202
|
+
The path to the temporary directory created for the run.
|
|
203
|
+
output_file : Path
|
|
204
|
+
The path to the output file.
|
|
205
|
+
uploads_root : Path | None
|
|
206
|
+
The root path for uploads, if any.
|
|
207
|
+
structured_io : bool
|
|
208
|
+
Whether to use structured IO instead of the default 'input/print'.
|
|
209
|
+
skip_mmd : bool
|
|
210
|
+
Whether to skip generating the mermaid diagram.
|
|
211
|
+
"""
|
|
212
|
+
raise NotImplementedError(
|
|
213
|
+
"The _a_start method must be implemented in the subclass."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def _after_run(
|
|
217
|
+
self,
|
|
218
|
+
results: Union[
|
|
219
|
+
"ChatResult",
|
|
220
|
+
list["ChatResult"],
|
|
221
|
+
dict[int, "ChatResult"],
|
|
222
|
+
],
|
|
223
|
+
output_file: Path,
|
|
224
|
+
uploads_root: Path | None,
|
|
225
|
+
temp_dir: Path,
|
|
226
|
+
skip_mmd: bool,
|
|
227
|
+
) -> None:
|
|
228
|
+
"""Run after the flow execution."""
|
|
229
|
+
# Save results
|
|
230
|
+
self._last_results = results
|
|
231
|
+
|
|
232
|
+
# Reset stop flag for next run
|
|
233
|
+
self._stop_requested.clear()
|
|
234
|
+
after_run(
|
|
235
|
+
temp_dir=temp_dir,
|
|
236
|
+
output_file=output_file,
|
|
237
|
+
flow_name=self.waldiez.name,
|
|
238
|
+
uploads_root=uploads_root,
|
|
239
|
+
skip_mmd=skip_mmd,
|
|
240
|
+
)
|
|
241
|
+
self.log.info("Cleanup completed")
|
|
242
|
+
|
|
243
|
+
async def _a_after_run(
|
|
244
|
+
self,
|
|
245
|
+
results: Union[
|
|
246
|
+
"ChatResult",
|
|
247
|
+
list["ChatResult"],
|
|
248
|
+
dict[int, "ChatResult"],
|
|
249
|
+
],
|
|
250
|
+
output_file: Path,
|
|
251
|
+
uploads_root: Path | None,
|
|
252
|
+
temp_dir: Path,
|
|
253
|
+
skip_mmd: bool,
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Run after the flow execution asynchronously."""
|
|
256
|
+
self._after_run(
|
|
257
|
+
results=results,
|
|
258
|
+
output_file=output_file,
|
|
259
|
+
uploads_root=uploads_root,
|
|
260
|
+
temp_dir=temp_dir,
|
|
261
|
+
skip_mmd=skip_mmd,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def _stop(self) -> None:
|
|
265
|
+
"""Actions to perform when stopping the flow."""
|
|
266
|
+
raise NotImplementedError(
|
|
267
|
+
"The _stop method must be implemented in the subclass."
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
async def _a_stop(self) -> None:
|
|
271
|
+
"""Asynchronously perform actions when stopping the flow."""
|
|
272
|
+
raise NotImplementedError(
|
|
273
|
+
"The _a_stop method must be implemented in the subclass."
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# ===================================================================
|
|
277
|
+
# HELPER METHODS
|
|
278
|
+
# ===================================================================
|
|
279
|
+
@staticmethod
|
|
280
|
+
def _prepare_paths(
|
|
281
|
+
output_path: str | Path | None = None,
|
|
282
|
+
uploads_root: str | Path | None = None,
|
|
283
|
+
) -> tuple[Path, Path | None]:
|
|
284
|
+
"""Prepare the output and uploads paths."""
|
|
285
|
+
uploads_root_path: Path | None = None
|
|
286
|
+
if uploads_root is not None:
|
|
287
|
+
uploads_root_path = Path(uploads_root)
|
|
288
|
+
WaldiezBaseRunner._uploads_root = uploads_root_path
|
|
289
|
+
|
|
290
|
+
if output_path is not None:
|
|
291
|
+
output_path = Path(output_path)
|
|
292
|
+
WaldiezBaseRunner._output_path = output_path
|
|
293
|
+
if not WaldiezBaseRunner._output_path:
|
|
294
|
+
WaldiezBaseRunner._output_path = Path.cwd() / "waldiez_flow.py"
|
|
295
|
+
output_file: Path = Path(WaldiezBaseRunner._output_path)
|
|
296
|
+
return output_file, uploads_root_path
|
|
297
|
+
|
|
298
|
+
def gather_requirements(self) -> set[str]:
|
|
299
|
+
"""Gather extra requirements to install before running the flow.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
set[str]
|
|
304
|
+
A set of requirements that are not already installed and do not
|
|
305
|
+
include 'waldiez' in their name.
|
|
306
|
+
"""
|
|
307
|
+
extra_requirements = {
|
|
308
|
+
req
|
|
309
|
+
for req in self.waldiez.requirements
|
|
310
|
+
if req not in sys.modules and "waldiez" not in req
|
|
311
|
+
}
|
|
312
|
+
waldiez_version = get_waldiez_version()
|
|
313
|
+
if "waldiez" not in sys.modules:
|
|
314
|
+
extra_requirements.add(f"waldiez=={waldiez_version}")
|
|
315
|
+
return extra_requirements
|
|
316
|
+
|
|
317
|
+
def install_requirements(self) -> None:
|
|
318
|
+
"""Install the requirements for the flow."""
|
|
319
|
+
if not self._called_install_requirements:
|
|
320
|
+
self._called_install_requirements = True
|
|
321
|
+
extra_requirements = self.gather_requirements()
|
|
322
|
+
if extra_requirements:
|
|
323
|
+
install_requirements(extra_requirements)
|
|
324
|
+
|
|
325
|
+
async def a_install_requirements(self) -> None:
|
|
326
|
+
"""Install the requirements for the flow asynchronously."""
|
|
327
|
+
if not self._called_install_requirements:
|
|
328
|
+
self._called_install_requirements = True
|
|
329
|
+
extra_requirements = self.gather_requirements()
|
|
330
|
+
if extra_requirements:
|
|
331
|
+
await a_install_requirements(extra_requirements)
|
|
332
|
+
|
|
333
|
+
# ===================================================================
|
|
334
|
+
# PUBLIC PROTOCOL IMPLEMENTATION
|
|
335
|
+
# ===================================================================
|
|
336
|
+
|
|
337
|
+
def before_run(
|
|
338
|
+
self,
|
|
339
|
+
output_file: Path,
|
|
340
|
+
uploads_root: Path | None,
|
|
341
|
+
) -> Path:
|
|
342
|
+
"""Run the before_run method synchronously.
|
|
343
|
+
|
|
344
|
+
Parameters
|
|
345
|
+
----------
|
|
346
|
+
output_file : Path
|
|
347
|
+
The path to the output file.
|
|
348
|
+
uploads_root : Path | None
|
|
349
|
+
The root path for uploads, if any.
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
Path
|
|
354
|
+
The path to the temporary directory created before running the flow.
|
|
355
|
+
"""
|
|
356
|
+
return self._before_run(
|
|
357
|
+
output_file=output_file,
|
|
358
|
+
uploads_root=uploads_root,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
async def a_before_run(
|
|
362
|
+
self,
|
|
363
|
+
output_file: Path,
|
|
364
|
+
uploads_root: Path | None,
|
|
365
|
+
) -> Path:
|
|
366
|
+
"""Run the _a_before_run method asynchronously.
|
|
367
|
+
|
|
368
|
+
Parameters
|
|
369
|
+
----------
|
|
370
|
+
output_file : Path
|
|
371
|
+
The path to the output file.
|
|
372
|
+
uploads_root : Path | None
|
|
373
|
+
The root path for uploads, if any.
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
Path
|
|
378
|
+
The path to the temporary directory created before running the flow.
|
|
379
|
+
"""
|
|
380
|
+
return await self._a_before_run(
|
|
381
|
+
output_file=output_file,
|
|
382
|
+
uploads_root=uploads_root,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def run(
|
|
386
|
+
self,
|
|
387
|
+
output_path: str | Path | None = None,
|
|
388
|
+
uploads_root: str | Path | None = None,
|
|
389
|
+
structured_io: bool | None = None,
|
|
390
|
+
threaded: bool | None = None,
|
|
391
|
+
skip_patch_io: bool | None = None,
|
|
392
|
+
skip_mmd: bool = False,
|
|
393
|
+
) -> Union[
|
|
394
|
+
"ChatResult",
|
|
395
|
+
list["ChatResult"],
|
|
396
|
+
dict[int, "ChatResult"],
|
|
397
|
+
]: # pyright: ignore
|
|
398
|
+
"""Run the Waldiez flow in blocking mode.
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
output_path : str | Path | None
|
|
403
|
+
The output path, by default None.
|
|
404
|
+
uploads_root : str | Path | None
|
|
405
|
+
The runtime uploads root, by default None.
|
|
406
|
+
structured_io : bool
|
|
407
|
+
Whether to use structured IO instead of the default 'input/print',
|
|
408
|
+
by default False.
|
|
409
|
+
threaded : bool | None
|
|
410
|
+
Whether to run the flow in a threaded environment, by default None.
|
|
411
|
+
skip_mmd : bool
|
|
412
|
+
Whether to skip generating the mermaid diagram, by default False.
|
|
413
|
+
skip_patch_io : bool | None
|
|
414
|
+
Whether to skip patching the IO streams, by default None.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
|
|
419
|
+
The result of the run, which can be a single ChatResult,
|
|
420
|
+
a list of ChatResults,
|
|
421
|
+
or a dictionary mapping indices to ChatResults.
|
|
422
|
+
|
|
423
|
+
Raises
|
|
424
|
+
------
|
|
425
|
+
RuntimeError
|
|
426
|
+
If the runner is already running.
|
|
427
|
+
"""
|
|
428
|
+
if skip_patch_io is not None:
|
|
429
|
+
WaldiezBaseRunner._skip_patch_io = skip_patch_io
|
|
430
|
+
if structured_io is not None:
|
|
431
|
+
WaldiezBaseRunner._structured_io = structured_io
|
|
432
|
+
if threaded is not None:
|
|
433
|
+
WaldiezBaseRunner._threaded = threaded
|
|
434
|
+
if self.is_running():
|
|
435
|
+
raise RuntimeError("Workflow already running")
|
|
436
|
+
if self.waldiez.is_async:
|
|
437
|
+
with start_blocking_portal(backend="asyncio") as portal:
|
|
438
|
+
return portal.call(
|
|
439
|
+
self.a_run,
|
|
440
|
+
output_path,
|
|
441
|
+
uploads_root,
|
|
442
|
+
structured_io,
|
|
443
|
+
skip_patch_io,
|
|
444
|
+
skip_mmd,
|
|
445
|
+
)
|
|
446
|
+
output_file, uploads_root_path = self._prepare_paths(
|
|
447
|
+
output_path=output_path,
|
|
448
|
+
uploads_root=uploads_root,
|
|
449
|
+
)
|
|
450
|
+
temp_dir = self.before_run(
|
|
451
|
+
output_file=output_file,
|
|
452
|
+
uploads_root=uploads_root_path,
|
|
453
|
+
)
|
|
454
|
+
self.install_requirements()
|
|
455
|
+
refresh_environment()
|
|
456
|
+
WaldiezBaseRunner._running = True
|
|
457
|
+
results: Union[
|
|
458
|
+
"ChatResult",
|
|
459
|
+
list["ChatResult"],
|
|
460
|
+
dict[int, "ChatResult"],
|
|
461
|
+
] = []
|
|
462
|
+
old_env_vars = set_env_vars(self.waldiez.get_flow_env_vars())
|
|
463
|
+
try:
|
|
464
|
+
with chdir(to=temp_dir):
|
|
465
|
+
sys.path.insert(0, str(temp_dir))
|
|
466
|
+
results = self._run(
|
|
467
|
+
temp_dir=temp_dir,
|
|
468
|
+
output_file=output_file,
|
|
469
|
+
uploads_root=uploads_root_path,
|
|
470
|
+
skip_mmd=skip_mmd,
|
|
471
|
+
)
|
|
472
|
+
finally:
|
|
473
|
+
WaldiezBaseRunner._running = False
|
|
474
|
+
reset_env_vars(old_env_vars)
|
|
475
|
+
self.after_run(
|
|
476
|
+
results=results,
|
|
477
|
+
output_file=output_file,
|
|
478
|
+
uploads_root=uploads_root_path,
|
|
479
|
+
temp_dir=temp_dir,
|
|
480
|
+
skip_mmd=skip_mmd,
|
|
481
|
+
)
|
|
482
|
+
if sys.path[0] == str(temp_dir):
|
|
483
|
+
sys.path.pop(0)
|
|
484
|
+
return results
|
|
485
|
+
|
|
486
|
+
async def a_run(
|
|
487
|
+
self,
|
|
488
|
+
output_path: str | Path | None = None,
|
|
489
|
+
uploads_root: str | Path | None = None,
|
|
490
|
+
structured_io: bool | None = None,
|
|
491
|
+
skip_patch_io: bool | None = None,
|
|
492
|
+
skip_mmd: bool = False,
|
|
493
|
+
) -> Union[
|
|
494
|
+
"ChatResult",
|
|
495
|
+
list["ChatResult"],
|
|
496
|
+
dict[int, "ChatResult"],
|
|
497
|
+
]: # pyright: ignore
|
|
498
|
+
"""Run the Waldiez flow asynchronously.
|
|
499
|
+
|
|
500
|
+
Parameters
|
|
501
|
+
----------
|
|
502
|
+
output_path : str | Path | None
|
|
503
|
+
The output path, by default None.
|
|
504
|
+
uploads_root : str | Path | None
|
|
505
|
+
The runtime uploads root, by default None.
|
|
506
|
+
structured_io : bool
|
|
507
|
+
Whether to use structured IO instead of the default 'input/print',
|
|
508
|
+
by default False.
|
|
509
|
+
skip_patch_io : bool | None
|
|
510
|
+
Whether to skip patching I/O, by default None.
|
|
511
|
+
skip_mmd : bool
|
|
512
|
+
Whether to skip generating the mermaid diagram, by default False.
|
|
513
|
+
|
|
514
|
+
Returns
|
|
515
|
+
-------
|
|
516
|
+
Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
|
|
517
|
+
The result of the run, which can be a single ChatResult,
|
|
518
|
+
a list of ChatResults,
|
|
519
|
+
or a dictionary mapping indices to ChatResults.
|
|
520
|
+
|
|
521
|
+
Raises
|
|
522
|
+
------
|
|
523
|
+
RuntimeError
|
|
524
|
+
If the runner is already running.
|
|
525
|
+
"""
|
|
526
|
+
if skip_patch_io is not None:
|
|
527
|
+
WaldiezBaseRunner._skip_patch_io = skip_patch_io
|
|
528
|
+
if structured_io is not None:
|
|
529
|
+
WaldiezBaseRunner._structured_io = structured_io
|
|
530
|
+
if self.is_running():
|
|
531
|
+
raise RuntimeError("Workflow already running")
|
|
532
|
+
output_file, uploads_root_path = self._prepare_paths(
|
|
533
|
+
output_path=output_path,
|
|
534
|
+
uploads_root=uploads_root,
|
|
535
|
+
)
|
|
536
|
+
temp_dir = await self._a_before_run(
|
|
537
|
+
output_file=output_file,
|
|
538
|
+
uploads_root=uploads_root_path,
|
|
539
|
+
)
|
|
540
|
+
await self.a_install_requirements()
|
|
541
|
+
refresh_environment()
|
|
542
|
+
WaldiezBaseRunner._running = True
|
|
543
|
+
results: Union[
|
|
544
|
+
"ChatResult",
|
|
545
|
+
list["ChatResult"],
|
|
546
|
+
dict[int, "ChatResult"],
|
|
547
|
+
] = []
|
|
548
|
+
try:
|
|
549
|
+
async with a_chdir(to=temp_dir):
|
|
550
|
+
sys.path.insert(0, str(temp_dir))
|
|
551
|
+
results = await self._a_run(
|
|
552
|
+
temp_dir=temp_dir,
|
|
553
|
+
output_file=output_file,
|
|
554
|
+
uploads_root=uploads_root_path,
|
|
555
|
+
skip_mmd=skip_mmd,
|
|
556
|
+
)
|
|
557
|
+
finally:
|
|
558
|
+
WaldiezBaseRunner._running = False
|
|
559
|
+
await self._a_after_run(
|
|
560
|
+
results=results,
|
|
561
|
+
output_file=output_file,
|
|
562
|
+
uploads_root=uploads_root_path,
|
|
563
|
+
temp_dir=temp_dir,
|
|
564
|
+
skip_mmd=skip_mmd,
|
|
565
|
+
)
|
|
566
|
+
if sys.path[0] == str(temp_dir):
|
|
567
|
+
sys.path.pop(0)
|
|
568
|
+
return results
|
|
569
|
+
|
|
570
|
+
def start(
|
|
571
|
+
self,
|
|
572
|
+
output_path: str | Path | None,
|
|
573
|
+
uploads_root: str | Path | None,
|
|
574
|
+
structured_io: bool | None = None,
|
|
575
|
+
skip_patch_io: bool | None = None,
|
|
576
|
+
skip_mmd: bool = False,
|
|
577
|
+
) -> None:
|
|
578
|
+
"""Start running the Waldiez flow in a non-blocking way.
|
|
579
|
+
|
|
580
|
+
Parameters
|
|
581
|
+
----------
|
|
582
|
+
output_path : str | Path | None
|
|
583
|
+
The output path.
|
|
584
|
+
uploads_root : str | Path | None
|
|
585
|
+
The runtime uploads root.
|
|
586
|
+
structured_io : bool | None
|
|
587
|
+
Whether to use structured IO instead of the default 'input/print'.
|
|
588
|
+
skip_patch_io : bool | None
|
|
589
|
+
Whether to skip patching I/O, by default None.
|
|
590
|
+
skip_mmd : bool
|
|
591
|
+
Whether to skip generating the mermaid diagram, by default False.
|
|
592
|
+
|
|
593
|
+
Raises
|
|
594
|
+
------
|
|
595
|
+
RuntimeError
|
|
596
|
+
If the runner is already running.
|
|
597
|
+
"""
|
|
598
|
+
if skip_patch_io is not None:
|
|
599
|
+
WaldiezBaseRunner._skip_patch_io = skip_patch_io
|
|
600
|
+
if structured_io is not None:
|
|
601
|
+
WaldiezBaseRunner._structured_io = structured_io
|
|
602
|
+
if self.is_running():
|
|
603
|
+
raise RuntimeError("Workflow already running")
|
|
604
|
+
output_file, uploads_root_path = self._prepare_paths(
|
|
605
|
+
output_path=output_path,
|
|
606
|
+
uploads_root=uploads_root,
|
|
607
|
+
)
|
|
608
|
+
temp_dir = self.before_run(
|
|
609
|
+
output_file=output_file,
|
|
610
|
+
uploads_root=uploads_root_path,
|
|
611
|
+
)
|
|
612
|
+
self.install_requirements()
|
|
613
|
+
refresh_environment()
|
|
614
|
+
WaldiezBaseRunner._running = True
|
|
615
|
+
self._start(
|
|
616
|
+
temp_dir=temp_dir,
|
|
617
|
+
output_file=output_file,
|
|
618
|
+
uploads_root=uploads_root_path,
|
|
619
|
+
skip_mmd=skip_mmd,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
async def a_start(
|
|
623
|
+
self,
|
|
624
|
+
output_path: str | Path | None,
|
|
625
|
+
uploads_root: str | Path | None,
|
|
626
|
+
structured_io: bool | None = None,
|
|
627
|
+
skip_patch_io: bool | None = None,
|
|
628
|
+
skip_mmd: bool = False,
|
|
629
|
+
) -> None:
|
|
630
|
+
"""Asynchronously start running the Waldiez flow in a non-blocking way.
|
|
631
|
+
|
|
632
|
+
Parameters
|
|
633
|
+
----------
|
|
634
|
+
output_path : str | Path | None
|
|
635
|
+
The output path.
|
|
636
|
+
uploads_root : str | Path | None
|
|
637
|
+
The runtime uploads root.
|
|
638
|
+
structured_io : bool | None = None
|
|
639
|
+
Whether to use structured IO instead of the default 'input/print'.
|
|
640
|
+
skip_patch_io : bool | None = None
|
|
641
|
+
Whether to skip patching I/O, by default None.
|
|
642
|
+
skip_mmd : bool | None = None
|
|
643
|
+
Whether to skip generating the mermaid diagram, by default None.
|
|
644
|
+
|
|
645
|
+
Raises
|
|
646
|
+
------
|
|
647
|
+
RuntimeError
|
|
648
|
+
If the runner is already running.
|
|
649
|
+
"""
|
|
650
|
+
if skip_patch_io is not None:
|
|
651
|
+
WaldiezBaseRunner._skip_patch_io = skip_patch_io
|
|
652
|
+
if structured_io is not None:
|
|
653
|
+
WaldiezBaseRunner._structured_io = structured_io
|
|
654
|
+
if self.is_running():
|
|
655
|
+
raise RuntimeError("Workflow already running")
|
|
656
|
+
output_file, uploads_root_path = self._prepare_paths(
|
|
657
|
+
output_path=output_path,
|
|
658
|
+
uploads_root=uploads_root,
|
|
659
|
+
)
|
|
660
|
+
temp_dir = await self._a_before_run(
|
|
661
|
+
output_file=output_file,
|
|
662
|
+
uploads_root=uploads_root_path,
|
|
663
|
+
)
|
|
664
|
+
await self.a_install_requirements()
|
|
665
|
+
refresh_environment()
|
|
666
|
+
WaldiezBaseRunner._running = True
|
|
667
|
+
await self._a_start(
|
|
668
|
+
temp_dir=temp_dir,
|
|
669
|
+
output_file=output_file,
|
|
670
|
+
uploads_root=uploads_root_path,
|
|
671
|
+
skip_mmd=skip_mmd,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
def after_run(
|
|
675
|
+
self,
|
|
676
|
+
results: Union[
|
|
677
|
+
"ChatResult",
|
|
678
|
+
list["ChatResult"],
|
|
679
|
+
dict[int, "ChatResult"],
|
|
680
|
+
],
|
|
681
|
+
output_file: Path,
|
|
682
|
+
uploads_root: Path | None,
|
|
683
|
+
temp_dir: Path,
|
|
684
|
+
skip_mmd: bool,
|
|
685
|
+
) -> None:
|
|
686
|
+
"""Actions to perform after running the flow.
|
|
687
|
+
|
|
688
|
+
Parameters
|
|
689
|
+
----------
|
|
690
|
+
results : Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
|
|
691
|
+
The results of the flow run.
|
|
692
|
+
output_file : Path
|
|
693
|
+
The path to the output file.
|
|
694
|
+
uploads_root : Path | None
|
|
695
|
+
The root path for uploads, if any.
|
|
696
|
+
temp_dir : Path
|
|
697
|
+
The path to the temporary directory used during the run.
|
|
698
|
+
skip_mmd : bool
|
|
699
|
+
Whether to skip generating the mermaid diagram.
|
|
700
|
+
"""
|
|
701
|
+
self._after_run(
|
|
702
|
+
results=results,
|
|
703
|
+
output_file=output_file,
|
|
704
|
+
uploads_root=uploads_root,
|
|
705
|
+
temp_dir=temp_dir,
|
|
706
|
+
skip_mmd=skip_mmd,
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
async def a_after_run(
|
|
710
|
+
self,
|
|
711
|
+
results: Union[
|
|
712
|
+
"ChatResult",
|
|
713
|
+
list["ChatResult"],
|
|
714
|
+
dict[int, "ChatResult"],
|
|
715
|
+
],
|
|
716
|
+
output_file: Path,
|
|
717
|
+
uploads_root: Path | None,
|
|
718
|
+
temp_dir: Path,
|
|
719
|
+
skip_mmd: bool,
|
|
720
|
+
) -> None:
|
|
721
|
+
"""Asynchronously perform actions after running the flow.
|
|
722
|
+
|
|
723
|
+
Parameters
|
|
724
|
+
----------
|
|
725
|
+
results : Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
|
|
726
|
+
The results of the flow run.
|
|
727
|
+
output_file : Path
|
|
728
|
+
The path to the output file.
|
|
729
|
+
uploads_root : Path | None
|
|
730
|
+
The root path for uploads, if any.
|
|
731
|
+
temp_dir : Path
|
|
732
|
+
The path to the temporary directory used during the run.
|
|
733
|
+
skip_mmd : bool
|
|
734
|
+
Whether to skip generating the mermaid diagram.
|
|
735
|
+
"""
|
|
736
|
+
await self._a_after_run(
|
|
737
|
+
results=results,
|
|
738
|
+
output_file=output_file,
|
|
739
|
+
uploads_root=uploads_root,
|
|
740
|
+
temp_dir=temp_dir,
|
|
741
|
+
skip_mmd=skip_mmd,
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
def stop(self) -> None:
|
|
745
|
+
"""Stop the runner if it is running."""
|
|
746
|
+
if not self.is_running():
|
|
747
|
+
return
|
|
748
|
+
try:
|
|
749
|
+
self._stop()
|
|
750
|
+
finally:
|
|
751
|
+
WaldiezBaseRunner._running = False
|
|
752
|
+
|
|
753
|
+
async def a_stop(self) -> None:
|
|
754
|
+
"""Asynchronously stop the runner if it is running."""
|
|
755
|
+
if not self.is_running():
|
|
756
|
+
return
|
|
757
|
+
try:
|
|
758
|
+
await self._a_stop()
|
|
759
|
+
finally:
|
|
760
|
+
WaldiezBaseRunner._running = False
|
|
761
|
+
|
|
762
|
+
# ===================================================================
|
|
763
|
+
# PROPERTIES AND CONTEXT MANAGERS
|
|
764
|
+
# ===================================================================
|
|
765
|
+
|
|
766
|
+
@property
|
|
767
|
+
def waldiez(self) -> Waldiez:
|
|
768
|
+
"""Get the Waldiez instance."""
|
|
769
|
+
return self._waldiez
|
|
770
|
+
|
|
771
|
+
@property
|
|
772
|
+
def is_async(self) -> bool:
|
|
773
|
+
"""Check if the workflow is async."""
|
|
774
|
+
return self.waldiez.is_async
|
|
775
|
+
|
|
776
|
+
@property
|
|
777
|
+
def running(self) -> bool:
|
|
778
|
+
"""Get the running status."""
|
|
779
|
+
return self.is_running()
|
|
780
|
+
|
|
781
|
+
@property
|
|
782
|
+
def log(self) -> WaldiezLogger:
|
|
783
|
+
"""Get the logger for the runner."""
|
|
784
|
+
return self._logger
|
|
785
|
+
|
|
786
|
+
@property
|
|
787
|
+
def threaded(self) -> bool:
|
|
788
|
+
"""Check if the runner is running in a threaded environment."""
|
|
789
|
+
return WaldiezBaseRunner._threaded
|
|
790
|
+
|
|
791
|
+
@property
|
|
792
|
+
def structured_io(self) -> bool:
|
|
793
|
+
"""Check if the runner is using structured IO."""
|
|
794
|
+
return WaldiezBaseRunner._structured_io
|
|
795
|
+
|
|
796
|
+
@property
|
|
797
|
+
def isolated(self) -> bool:
|
|
798
|
+
"""Check if the runner is running in an isolated environment."""
|
|
799
|
+
return WaldiezBaseRunner._isolated
|
|
800
|
+
|
|
801
|
+
@property
|
|
802
|
+
def output_path(self) -> str | Path | None:
|
|
803
|
+
"""Get the output path for the runner."""
|
|
804
|
+
return WaldiezBaseRunner._output_path
|
|
805
|
+
|
|
806
|
+
@property
|
|
807
|
+
def uploads_root(self) -> str | Path | None:
|
|
808
|
+
"""Get the uploads root path for the runner."""
|
|
809
|
+
return WaldiezBaseRunner._uploads_root
|
|
810
|
+
|
|
811
|
+
@property
|
|
812
|
+
def skip_patch_io(self) -> bool:
|
|
813
|
+
"""Check if the runner is skipping patching IO."""
|
|
814
|
+
return WaldiezBaseRunner._skip_patch_io
|
|
815
|
+
|
|
816
|
+
@classmethod
|
|
817
|
+
def load(
|
|
818
|
+
cls,
|
|
819
|
+
waldiez_file: str | Path,
|
|
820
|
+
name: str | None = None,
|
|
821
|
+
description: str | None = None,
|
|
822
|
+
tags: list[str] | None = None,
|
|
823
|
+
requirements: list[str] | None = None,
|
|
824
|
+
output_path: str | Path | None = None,
|
|
825
|
+
uploads_root: str | Path | None = None,
|
|
826
|
+
structured_io: bool = False,
|
|
827
|
+
isolated: bool = False,
|
|
828
|
+
threaded: bool = False,
|
|
829
|
+
skip_patch_io: bool = True,
|
|
830
|
+
) -> "WaldiezBaseRunner":
|
|
831
|
+
"""Load a waldiez flow from a file and create a runner.
|
|
832
|
+
|
|
833
|
+
Parameters
|
|
834
|
+
----------
|
|
835
|
+
waldiez_file : str | Path
|
|
836
|
+
The path to the waldiez file.
|
|
837
|
+
name : str | None, optional
|
|
838
|
+
The name of the flow, by default None.
|
|
839
|
+
description : str | None, optional
|
|
840
|
+
The description of the flow, by default None.
|
|
841
|
+
tags : list[str] | None, optional
|
|
842
|
+
The tags for the flow, by default None.
|
|
843
|
+
requirements : list[str] | None, optional
|
|
844
|
+
The requirements for the flow, by default None.
|
|
845
|
+
output_path : str | Path | None, optional
|
|
846
|
+
The path to save the output file, by default None.
|
|
847
|
+
uploads_root : str | Path | None, optional
|
|
848
|
+
The root path for uploads, by default None.
|
|
849
|
+
structured_io : bool, optional
|
|
850
|
+
Whether to use structured IO instead of the default 'input/print',
|
|
851
|
+
by default False.
|
|
852
|
+
isolated : bool, optional
|
|
853
|
+
Whether to run the flow in an isolated environment, default False.
|
|
854
|
+
threaded : bool, optional
|
|
855
|
+
Whether to run the flow in a threaded environment, default False.
|
|
856
|
+
skip_patch_io : bool, optional
|
|
857
|
+
Whether to skip patching IO, by default True.
|
|
858
|
+
|
|
859
|
+
Returns
|
|
860
|
+
-------
|
|
861
|
+
WaldiezBaseRunner
|
|
862
|
+
An instance of WaldiezBaseRunner initialized with the loaded flow.
|
|
863
|
+
"""
|
|
864
|
+
waldiez = Waldiez.load(
|
|
865
|
+
waldiez_file,
|
|
866
|
+
name=name,
|
|
867
|
+
description=description,
|
|
868
|
+
tags=tags,
|
|
869
|
+
requirements=requirements,
|
|
870
|
+
)
|
|
871
|
+
return cls(
|
|
872
|
+
waldiez=waldiez,
|
|
873
|
+
output_path=output_path,
|
|
874
|
+
uploads_root=uploads_root,
|
|
875
|
+
structured_io=structured_io,
|
|
876
|
+
isolated=isolated,
|
|
877
|
+
threaded=threaded,
|
|
878
|
+
skip_patch_io=skip_patch_io,
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
def __enter__(self) -> Self:
|
|
882
|
+
"""Enter the context manager."""
|
|
883
|
+
return self
|
|
884
|
+
|
|
885
|
+
async def __aenter__(self) -> Self:
|
|
886
|
+
"""Enter the context manager asynchronously."""
|
|
887
|
+
return self
|
|
888
|
+
|
|
889
|
+
def __exit__(
|
|
890
|
+
self,
|
|
891
|
+
exc_type: Type[BaseException],
|
|
892
|
+
exc_value: BaseException,
|
|
893
|
+
traceback: TracebackType,
|
|
894
|
+
) -> None:
|
|
895
|
+
"""Exit the context manager."""
|
|
896
|
+
if self.is_running():
|
|
897
|
+
self.stop()
|
|
898
|
+
|
|
899
|
+
async def __aexit__(
|
|
900
|
+
self,
|
|
901
|
+
exc_type: Type[BaseException],
|
|
902
|
+
exc_value: BaseException,
|
|
903
|
+
traceback: TracebackType,
|
|
904
|
+
) -> None:
|
|
905
|
+
"""Exit the context manager asynchronously."""
|
|
906
|
+
if self.is_running():
|
|
907
|
+
await self.a_stop()
|