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
waldiez/running/environment.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"""Environment related utilities."""
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
|
+
import site
|
|
7
8
|
import sys
|
|
8
9
|
from typing import Generator
|
|
9
10
|
|
|
@@ -53,6 +54,79 @@ def refresh_environment() -> None:
|
|
|
53
54
|
# temp (until we handle/detect docker setup)
|
|
54
55
|
os.environ["AUTOGEN_USE_DOCKER"] = "0"
|
|
55
56
|
try_handle_the_np_thing()
|
|
57
|
+
reload_autogen()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# pylint: disable=too-complex,too-many-try-statements,unused-import
|
|
61
|
+
def reload_autogen() -> None: # noqa: C901
|
|
62
|
+
"""Reload the autogen package.
|
|
63
|
+
|
|
64
|
+
Try to avoid "please install package x" errors
|
|
65
|
+
when we already have the package installed
|
|
66
|
+
(but autogen is imported before it).
|
|
67
|
+
|
|
68
|
+
Raises
|
|
69
|
+
------
|
|
70
|
+
ImportError
|
|
71
|
+
If the autogen package cannot be reloaded.
|
|
72
|
+
AttributeError
|
|
73
|
+
If the autogen package cannot be reloaded due to missing attributes.
|
|
74
|
+
TypeError
|
|
75
|
+
If the autogen package cannot be reloaded due to type errors.
|
|
76
|
+
Exception
|
|
77
|
+
If any other error occurs during the reload process.
|
|
78
|
+
"""
|
|
79
|
+
site.main()
|
|
80
|
+
|
|
81
|
+
# Store IOStream state before deletion (if it exists)
|
|
82
|
+
default_io_stream = None
|
|
83
|
+
try:
|
|
84
|
+
from autogen.io import IOStream # type: ignore
|
|
85
|
+
|
|
86
|
+
default_io_stream = IOStream.get_default()
|
|
87
|
+
except (ImportError, AttributeError):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Remove autogen modules in reverse dependency order
|
|
92
|
+
autogen_modules = sorted(
|
|
93
|
+
[
|
|
94
|
+
name
|
|
95
|
+
for name in sys.modules
|
|
96
|
+
if name.startswith("autogen.")
|
|
97
|
+
and not name.startswith("autogen.io")
|
|
98
|
+
],
|
|
99
|
+
key=len,
|
|
100
|
+
reverse=True, # Longer names (deeper modules) first
|
|
101
|
+
)
|
|
102
|
+
for mod_name in autogen_modules:
|
|
103
|
+
if mod_name in sys.modules:
|
|
104
|
+
del sys.modules[mod_name]
|
|
105
|
+
|
|
106
|
+
if "autogen" in sys.modules:
|
|
107
|
+
del sys.modules["autogen"]
|
|
108
|
+
|
|
109
|
+
# Re-import autogen
|
|
110
|
+
# pylint: disable=unused-import
|
|
111
|
+
import autogen # pyright: ignore
|
|
112
|
+
|
|
113
|
+
# Restore IOStream state if we had it
|
|
114
|
+
if default_io_stream is not None:
|
|
115
|
+
try:
|
|
116
|
+
from autogen.io import IOStream # pyright: ignore
|
|
117
|
+
|
|
118
|
+
IOStream.set_default(default_io_stream)
|
|
119
|
+
except (ImportError, AttributeError, TypeError):
|
|
120
|
+
# If the old IOStream instance is incompatible, ignore
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
# If reload fails, at least try to re-import autogen
|
|
125
|
+
try:
|
|
126
|
+
import autogen # type: ignore # noqa: F401
|
|
127
|
+
except ImportError:
|
|
128
|
+
pass
|
|
129
|
+
raise e
|
|
56
130
|
|
|
57
131
|
|
|
58
132
|
def try_handle_the_np_thing() -> None:
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
# flake8: noqa: C901
|
|
5
|
+
# pylint: disable=too-many-try-statements,import-outside-toplevel,
|
|
6
|
+
# pylint: disable=too-complex,unused-argument
|
|
7
|
+
"""Run a waldiez flow.
|
|
8
|
+
|
|
9
|
+
The flow is first converted to an autogen flow with agents, chats, and tools.
|
|
10
|
+
We then chown to temporary directory, call the flow's `main()` and
|
|
11
|
+
return the results. Before running the flow, any additional environment
|
|
12
|
+
variables specified in the waldiez file are set.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import importlib.util
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from types import ModuleType
|
|
21
|
+
from typing import TYPE_CHECKING, Callable, Union
|
|
22
|
+
|
|
23
|
+
from waldiez.models.waldiez import Waldiez
|
|
24
|
+
from waldiez.running.patch_io_stream import patch_io_stream
|
|
25
|
+
|
|
26
|
+
from .base_runner import WaldiezBaseRunner
|
|
27
|
+
from .run_results import WaldiezRunResults
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from autogen import ChatResult # type: ignore
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WaldiezImportRunner(WaldiezBaseRunner):
|
|
34
|
+
"""Waldiez runner class."""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
waldiez: Waldiez,
|
|
39
|
+
output_path: str | Path | None = None,
|
|
40
|
+
uploads_root: str | Path | None = None,
|
|
41
|
+
structured_io: bool = False,
|
|
42
|
+
isolated: bool = False,
|
|
43
|
+
threaded: bool = True,
|
|
44
|
+
skip_patch_io: bool = False,
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Initialize the Waldiez manager."""
|
|
47
|
+
super().__init__(
|
|
48
|
+
waldiez,
|
|
49
|
+
output_path=output_path,
|
|
50
|
+
uploads_root=uploads_root,
|
|
51
|
+
structured_io=structured_io,
|
|
52
|
+
isolated=isolated,
|
|
53
|
+
threaded=threaded,
|
|
54
|
+
skip_patch_io=skip_patch_io,
|
|
55
|
+
)
|
|
56
|
+
self._execution_thread: threading.Thread | None = None
|
|
57
|
+
self._execution_loop: asyncio.AbstractEventLoop | None = None
|
|
58
|
+
self._loaded_module: ModuleType | None = None
|
|
59
|
+
|
|
60
|
+
def _run(
|
|
61
|
+
self,
|
|
62
|
+
temp_dir: Path,
|
|
63
|
+
output_file: Path,
|
|
64
|
+
uploads_root: Path | None,
|
|
65
|
+
skip_mmd: bool,
|
|
66
|
+
) -> Union["ChatResult", list["ChatResult"], dict[int, "ChatResult"]]:
|
|
67
|
+
"""Run the Waldiez workflow."""
|
|
68
|
+
if self.threaded:
|
|
69
|
+
return self._run_threaded(
|
|
70
|
+
temp_dir=temp_dir,
|
|
71
|
+
output_file=output_file,
|
|
72
|
+
uploads_root=uploads_root,
|
|
73
|
+
skip_mmd=skip_mmd,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return self._run_not_threaded(
|
|
77
|
+
temp_dir=temp_dir,
|
|
78
|
+
output_file=output_file,
|
|
79
|
+
uploads_root=uploads_root,
|
|
80
|
+
skip_mmd=skip_mmd,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _run_not_threaded(
|
|
84
|
+
self,
|
|
85
|
+
temp_dir: Path,
|
|
86
|
+
output_file: Path,
|
|
87
|
+
uploads_root: Path | None,
|
|
88
|
+
skip_mmd: bool,
|
|
89
|
+
) -> Union[
|
|
90
|
+
"ChatResult",
|
|
91
|
+
list["ChatResult"],
|
|
92
|
+
dict[int, "ChatResult"],
|
|
93
|
+
]:
|
|
94
|
+
"""Run the Waldiez workflow in a blocking manner."""
|
|
95
|
+
from autogen.io import IOStream # type: ignore
|
|
96
|
+
|
|
97
|
+
from waldiez.io import StructuredIOStream
|
|
98
|
+
|
|
99
|
+
results_container: WaldiezRunResults = {
|
|
100
|
+
"results": None,
|
|
101
|
+
"exception": None,
|
|
102
|
+
"completed": False,
|
|
103
|
+
}
|
|
104
|
+
if not self.structured_io and not self.skip_patch_io:
|
|
105
|
+
patch_io_stream(self.waldiez.is_async)
|
|
106
|
+
printer: Callable[..., None] = print
|
|
107
|
+
try:
|
|
108
|
+
file_name = output_file.name
|
|
109
|
+
module_name = file_name.replace(".py", "")
|
|
110
|
+
spec = importlib.util.spec_from_file_location(
|
|
111
|
+
module_name, temp_dir / file_name
|
|
112
|
+
)
|
|
113
|
+
if not spec or not spec.loader:
|
|
114
|
+
raise ImportError("Could not import the flow")
|
|
115
|
+
if self.structured_io:
|
|
116
|
+
stream = StructuredIOStream(
|
|
117
|
+
uploads_root=uploads_root, is_async=False
|
|
118
|
+
)
|
|
119
|
+
printer = stream.print
|
|
120
|
+
with IOStream.set_default(stream):
|
|
121
|
+
self._loaded_module = importlib.util.module_from_spec(spec)
|
|
122
|
+
spec.loader.exec_module(self._loaded_module)
|
|
123
|
+
printer("<Waldiez> - Starting workflow...")
|
|
124
|
+
printer(self.waldiez.info.model_dump_json())
|
|
125
|
+
results = self._loaded_module.main()
|
|
126
|
+
else:
|
|
127
|
+
printer = IOStream.get_default().print
|
|
128
|
+
self._loaded_module = importlib.util.module_from_spec(spec)
|
|
129
|
+
spec.loader.exec_module(self._loaded_module)
|
|
130
|
+
printer("<Waldiez> - Starting workflow...")
|
|
131
|
+
printer(self.waldiez.info.model_dump_json())
|
|
132
|
+
results = self._loaded_module.main()
|
|
133
|
+
results_container["results"] = results
|
|
134
|
+
printer("<Waldiez> - Workflow finished")
|
|
135
|
+
except SystemExit:
|
|
136
|
+
printer("<Waldiez> - Workflow stopped by user")
|
|
137
|
+
results_container["results"] = []
|
|
138
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
139
|
+
results_container["exception"] = e
|
|
140
|
+
printer("<Waldiez> - Workflow execution failed: %s", e)
|
|
141
|
+
finally:
|
|
142
|
+
results_container["completed"] = True
|
|
143
|
+
return results_container["results"] or []
|
|
144
|
+
|
|
145
|
+
# pylint: disable=too-many-statements,duplicate-code
|
|
146
|
+
def _run_threaded(
|
|
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
|
+
]:
|
|
157
|
+
"""Run the Waldiez workflow."""
|
|
158
|
+
results_container: WaldiezRunResults = {
|
|
159
|
+
"results": None,
|
|
160
|
+
"exception": None,
|
|
161
|
+
"completed": False,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
def _execute_workflow() -> None:
|
|
165
|
+
"""Execute the workflow in a separate thread."""
|
|
166
|
+
from autogen.io import IOStream # pyright: ignore
|
|
167
|
+
|
|
168
|
+
from waldiez.io import StructuredIOStream
|
|
169
|
+
|
|
170
|
+
if not self.structured_io and not self.skip_patch_io:
|
|
171
|
+
patch_io_stream(self.waldiez.is_async)
|
|
172
|
+
printer: Callable[..., None] = print
|
|
173
|
+
try:
|
|
174
|
+
file_name = output_file.name
|
|
175
|
+
module_name = file_name.replace(".py", "")
|
|
176
|
+
spec = importlib.util.spec_from_file_location(
|
|
177
|
+
module_name, temp_dir / file_name
|
|
178
|
+
)
|
|
179
|
+
if not spec or not spec.loader:
|
|
180
|
+
raise ImportError("Could not import the flow")
|
|
181
|
+
if self.structured_io:
|
|
182
|
+
stream = StructuredIOStream(
|
|
183
|
+
uploads_root=uploads_root, is_async=False
|
|
184
|
+
)
|
|
185
|
+
printer = stream.print
|
|
186
|
+
with IOStream.set_default(stream):
|
|
187
|
+
self._loaded_module = importlib.util.module_from_spec(
|
|
188
|
+
spec
|
|
189
|
+
)
|
|
190
|
+
spec.loader.exec_module(self._loaded_module)
|
|
191
|
+
printer("<Waldiez> - Starting workflow...")
|
|
192
|
+
printer(self.waldiez.info.model_dump_json())
|
|
193
|
+
results = self._loaded_module.main()
|
|
194
|
+
else:
|
|
195
|
+
printer = IOStream.get_default().print
|
|
196
|
+
self._loaded_module = importlib.util.module_from_spec(spec)
|
|
197
|
+
spec.loader.exec_module(self._loaded_module)
|
|
198
|
+
printer("<Waldiez> - Starting workflow...")
|
|
199
|
+
printer(self.waldiez.info.model_dump_json())
|
|
200
|
+
results = self._loaded_module.main()
|
|
201
|
+
results_container["results"] = results
|
|
202
|
+
printer("<Waldiez> - Workflow finished")
|
|
203
|
+
except SystemExit:
|
|
204
|
+
printer("<Waldiez> - Workflow stopped by user")
|
|
205
|
+
results_container["results"] = []
|
|
206
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
207
|
+
results_container["exception"] = e
|
|
208
|
+
printer("<Waldiez> - Workflow execution failed: %s", e)
|
|
209
|
+
finally:
|
|
210
|
+
results_container["completed"] = True
|
|
211
|
+
self._execution_loop = None
|
|
212
|
+
self._execution_thread = None
|
|
213
|
+
|
|
214
|
+
# Execute in a separate thread for responsive stopping
|
|
215
|
+
self._execution_thread = threading.Thread(
|
|
216
|
+
target=_execute_workflow, daemon=True
|
|
217
|
+
)
|
|
218
|
+
self._execution_thread.start()
|
|
219
|
+
|
|
220
|
+
# Wait for completion while checking for stop requests
|
|
221
|
+
while self._execution_thread and self._execution_thread.is_alive():
|
|
222
|
+
if self._stop_requested.is_set():
|
|
223
|
+
self.log.info(
|
|
224
|
+
"Stop requested, waiting for graceful shutdown..."
|
|
225
|
+
)
|
|
226
|
+
self._execution_thread.join(timeout=5.0)
|
|
227
|
+
if self._execution_thread.is_alive():
|
|
228
|
+
self.log.warning("Workflow did not stop gracefully")
|
|
229
|
+
break
|
|
230
|
+
if results_container["completed"] is True:
|
|
231
|
+
break
|
|
232
|
+
time.sleep(0.1)
|
|
233
|
+
|
|
234
|
+
# Handle results
|
|
235
|
+
exception = results_container["exception"]
|
|
236
|
+
if exception is not None:
|
|
237
|
+
self._last_exception = exception
|
|
238
|
+
raise exception
|
|
239
|
+
|
|
240
|
+
self._last_results = results_container["results"] or []
|
|
241
|
+
return self._last_results
|
|
242
|
+
|
|
243
|
+
async def _a_run(
|
|
244
|
+
self,
|
|
245
|
+
temp_dir: Path,
|
|
246
|
+
output_file: Path,
|
|
247
|
+
uploads_root: Path | None,
|
|
248
|
+
skip_mmd: bool,
|
|
249
|
+
) -> Union[
|
|
250
|
+
"ChatResult",
|
|
251
|
+
list["ChatResult"],
|
|
252
|
+
dict[int, "ChatResult"],
|
|
253
|
+
]:
|
|
254
|
+
"""Async execution using asyncio tasks."""
|
|
255
|
+
|
|
256
|
+
async def _execute_workflow() -> Union[
|
|
257
|
+
"ChatResult",
|
|
258
|
+
list["ChatResult"],
|
|
259
|
+
dict[int, "ChatResult"],
|
|
260
|
+
]:
|
|
261
|
+
"""Execute the workflow in an async context."""
|
|
262
|
+
from autogen.io import IOStream # pyright: ignore
|
|
263
|
+
|
|
264
|
+
from waldiez.io import StructuredIOStream
|
|
265
|
+
|
|
266
|
+
printer: Callable[..., None] = print
|
|
267
|
+
if not self.structured_io and not self.skip_patch_io:
|
|
268
|
+
patch_io_stream(self.waldiez.is_async)
|
|
269
|
+
try:
|
|
270
|
+
file_name = output_file.name
|
|
271
|
+
module_name = file_name.replace(".py", "")
|
|
272
|
+
spec = importlib.util.spec_from_file_location(
|
|
273
|
+
module_name, temp_dir / file_name
|
|
274
|
+
)
|
|
275
|
+
if not spec or not spec.loader:
|
|
276
|
+
raise ImportError("Could not import the flow")
|
|
277
|
+
if self.structured_io:
|
|
278
|
+
stream = StructuredIOStream(
|
|
279
|
+
uploads_root=uploads_root, is_async=True
|
|
280
|
+
)
|
|
281
|
+
printer = stream.print
|
|
282
|
+
with IOStream.set_default(stream):
|
|
283
|
+
printer("<Waldiez> - Starting workflow...")
|
|
284
|
+
printer(self.waldiez.info.model_dump_json())
|
|
285
|
+
self._loaded_module = importlib.util.module_from_spec(
|
|
286
|
+
spec
|
|
287
|
+
)
|
|
288
|
+
spec.loader.exec_module(self._loaded_module)
|
|
289
|
+
results = await self._loaded_module.main()
|
|
290
|
+
self._last_results = results
|
|
291
|
+
else:
|
|
292
|
+
printer = IOStream.get_default().print
|
|
293
|
+
printer("<Waldiez> - Starting workflow...")
|
|
294
|
+
printer(self.waldiez.info.model_dump_json())
|
|
295
|
+
self._loaded_module = importlib.util.module_from_spec(spec)
|
|
296
|
+
spec.loader.exec_module(self._loaded_module)
|
|
297
|
+
results = await self._loaded_module.main()
|
|
298
|
+
self._last_results = results
|
|
299
|
+
printer("<Waldiez> - Workflow finished")
|
|
300
|
+
return results
|
|
301
|
+
|
|
302
|
+
except SystemExit:
|
|
303
|
+
printer("Workflow stopped by user")
|
|
304
|
+
return []
|
|
305
|
+
except Exception as e:
|
|
306
|
+
self._last_exception = e
|
|
307
|
+
printer("Workflow execution failed: %s", e)
|
|
308
|
+
raise
|
|
309
|
+
|
|
310
|
+
# Create cancellable task
|
|
311
|
+
task = asyncio.create_task(_execute_workflow())
|
|
312
|
+
|
|
313
|
+
# Monitor for stop requests
|
|
314
|
+
try:
|
|
315
|
+
while not task.done():
|
|
316
|
+
if self._stop_requested.is_set():
|
|
317
|
+
self.log.info("Stop requested, cancelling task...")
|
|
318
|
+
task.cancel()
|
|
319
|
+
break
|
|
320
|
+
await asyncio.sleep(0.1)
|
|
321
|
+
|
|
322
|
+
return await task
|
|
323
|
+
|
|
324
|
+
except asyncio.CancelledError:
|
|
325
|
+
self.log.info("Workflow cancelled")
|
|
326
|
+
return []
|
|
327
|
+
|
|
328
|
+
def _after_run(
|
|
329
|
+
self,
|
|
330
|
+
results: Union[
|
|
331
|
+
"ChatResult",
|
|
332
|
+
list["ChatResult"],
|
|
333
|
+
dict[int, "ChatResult"],
|
|
334
|
+
],
|
|
335
|
+
output_file: Path,
|
|
336
|
+
uploads_root: Path | None,
|
|
337
|
+
temp_dir: Path,
|
|
338
|
+
skip_mmd: bool,
|
|
339
|
+
) -> None:
|
|
340
|
+
super()._after_run(
|
|
341
|
+
results=results,
|
|
342
|
+
output_file=output_file,
|
|
343
|
+
uploads_root=uploads_root,
|
|
344
|
+
temp_dir=temp_dir,
|
|
345
|
+
skip_mmd=skip_mmd,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Clean up module reference
|
|
349
|
+
self._loaded_module = None
|
|
350
|
+
|
|
351
|
+
def _start(
|
|
352
|
+
self,
|
|
353
|
+
temp_dir: Path,
|
|
354
|
+
output_file: Path,
|
|
355
|
+
uploads_root: Path | None,
|
|
356
|
+
skip_mmd: bool = False,
|
|
357
|
+
) -> None:
|
|
358
|
+
"""Start the Waldiez workflow."""
|
|
359
|
+
|
|
360
|
+
def run_in_background() -> None:
|
|
361
|
+
"""Run the workflow in a background thread."""
|
|
362
|
+
try:
|
|
363
|
+
# Reuse the blocking run logic but in a background thread
|
|
364
|
+
self._run_threaded(
|
|
365
|
+
temp_dir=temp_dir,
|
|
366
|
+
output_file=output_file,
|
|
367
|
+
uploads_root=uploads_root,
|
|
368
|
+
skip_mmd=skip_mmd,
|
|
369
|
+
)
|
|
370
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
371
|
+
self._last_exception = e
|
|
372
|
+
self.log.error("Background workflow failed: %s", e)
|
|
373
|
+
|
|
374
|
+
# Start background execution
|
|
375
|
+
background_thread = threading.Thread(
|
|
376
|
+
target=run_in_background, daemon=True
|
|
377
|
+
)
|
|
378
|
+
background_thread.start()
|
|
379
|
+
|
|
380
|
+
async def _a_start(
|
|
381
|
+
self,
|
|
382
|
+
temp_dir: Path,
|
|
383
|
+
output_file: Path,
|
|
384
|
+
uploads_root: Path | None,
|
|
385
|
+
skip_mmd: bool = False,
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Start the Waldiez workflow asynchronously."""
|
|
388
|
+
|
|
389
|
+
async def run_in_background() -> None:
|
|
390
|
+
"""Run the workflow in an async context."""
|
|
391
|
+
try:
|
|
392
|
+
await self._a_run(
|
|
393
|
+
temp_dir=temp_dir,
|
|
394
|
+
output_file=output_file,
|
|
395
|
+
uploads_root=uploads_root,
|
|
396
|
+
skip_mmd=skip_mmd,
|
|
397
|
+
)
|
|
398
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
399
|
+
self._last_exception = e
|
|
400
|
+
self.log.error("Background workflow failed: %s", e)
|
|
401
|
+
|
|
402
|
+
# Start background task
|
|
403
|
+
asyncio.create_task(run_in_background())
|
|
404
|
+
|
|
405
|
+
def _stop(self) -> None:
|
|
406
|
+
"""Stop the Waldiez workflow."""
|
|
407
|
+
self.log.info("Stopping workflow execution...")
|
|
408
|
+
self._stop_requested.set()
|
|
409
|
+
|
|
410
|
+
# Wait for graceful shutdown
|
|
411
|
+
if self._execution_thread and self._execution_thread.is_alive():
|
|
412
|
+
self._execution_thread.join(timeout=5.0)
|
|
413
|
+
|
|
414
|
+
if self._execution_thread and self._execution_thread.is_alive():
|
|
415
|
+
self.log.warning("Workflow thread did not stop gracefully")
|
|
416
|
+
|
|
417
|
+
async def _a_stop(self) -> None:
|
|
418
|
+
"""Stop the Waldiez workflow asynchronously."""
|
|
419
|
+
self.log.info("Stopping workflow execution (async)...")
|
|
420
|
+
self._stop_requested.set()
|
|
421
|
+
|
|
422
|
+
# For async, we rely on the task cancellation in _a_run
|
|
423
|
+
# Let's give it a moment to respond
|
|
424
|
+
await asyncio.sleep(0.5)
|