waldiez 0.4.8__py3-none-any.whl → 0.4.11__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.

Files changed (40) hide show
  1. waldiez/__init__.py +1 -2
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +90 -96
  4. waldiez/exporter.py +64 -9
  5. waldiez/exporting/agent/extras/rag/chroma_extras.py +21 -9
  6. waldiez/exporting/core/context.py +12 -0
  7. waldiez/exporting/core/extras/flow_extras.py +2 -14
  8. waldiez/exporting/core/types.py +21 -0
  9. waldiez/exporting/flow/exporter.py +4 -0
  10. waldiez/exporting/flow/factory.py +16 -0
  11. waldiez/exporting/flow/orchestrator.py +12 -0
  12. waldiez/exporting/flow/utils/__init__.py +2 -0
  13. waldiez/exporting/flow/utils/common.py +96 -2
  14. waldiez/exporting/flow/utils/logging.py +5 -6
  15. waldiez/io/mqtt.py +7 -3
  16. waldiez/io/structured.py +26 -2
  17. waldiez/models/common/method_utils.py +1 -1
  18. waldiez/models/tool/tool.py +2 -1
  19. waldiez/runner.py +402 -332
  20. waldiez/running/__init__.py +6 -28
  21. waldiez/running/base_runner.py +907 -0
  22. waldiez/running/environment.py +74 -0
  23. waldiez/running/import_runner.py +424 -0
  24. waldiez/running/patch_io_stream.py +208 -0
  25. waldiez/running/post_run.py +121 -0
  26. waldiez/running/pre_run.py +105 -0
  27. waldiez/running/protocol.py +281 -0
  28. waldiez/running/run_results.py +22 -0
  29. waldiez/running/subprocess_runner.py +100 -0
  30. waldiez/running/utils.py +134 -0
  31. waldiez/utils/__init__.py +2 -2
  32. waldiez/utils/version.py +49 -0
  33. {waldiez-0.4.8.dist-info → waldiez-0.4.11.dist-info}/METADATA +11 -11
  34. {waldiez-0.4.8.dist-info → waldiez-0.4.11.dist-info}/RECORD +38 -30
  35. waldiez/running/running.py +0 -388
  36. waldiez/utils/flaml_warnings.py +0 -17
  37. {waldiez-0.4.8.dist-info → waldiez-0.4.11.dist-info}/WHEEL +0 -0
  38. {waldiez-0.4.8.dist-info → waldiez-0.4.11.dist-info}/entry_points.txt +0 -0
  39. {waldiez-0.4.8.dist-info → waldiez-0.4.11.dist-info}/licenses/LICENSE +0 -0
  40. {waldiez-0.4.8.dist-info → waldiez-0.4.11.dist-info}/licenses/NOTICE.md +0 -0
@@ -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)