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
@@ -0,0 +1,208 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ # pylint: disable=import-outside-toplevel, broad-exception-caught
5
+ # pylint: disable=too-many-try-statements, too-complex
6
+ # noqa: C901
7
+ """Patch ag2's IOStream if a flow is async.
8
+
9
+ # let's keep an eye here:
10
+ # https://github.com/ag2ai/ag2/blob/main/autogen/agentchat/conversable_agent.py#L2973
11
+ # reply = await iostream.input(prompt) ???? (await???)
12
+ """
13
+
14
+ import inspect
15
+ import sys
16
+ from typing import Any, Callable
17
+
18
+ from asyncer import syncify
19
+
20
+
21
+ def patch_io_stream(is_async: bool) -> None:
22
+ """Patch the IOStream to handle async flows.
23
+
24
+ Parameters
25
+ ----------
26
+ is_async : bool
27
+ Whether the flow is async or not.
28
+ """
29
+ if is_async:
30
+ patch_async_io_stream()
31
+ else:
32
+ patch_sync_io_stream()
33
+
34
+
35
+ def patch_sync_io_stream() -> None:
36
+ """Patch the IOStream to handle async flows."""
37
+ from autogen.io import IOStream # type: ignore
38
+
39
+ iostream = IOStream.get_default()
40
+ original_input = iostream.input
41
+
42
+ def _safe_input(prompt: str = "", *, password: bool = False) -> str:
43
+ """Async input method."""
44
+ try:
45
+ input_or_coro = original_input(prompt, password=password)
46
+ if inspect.iscoroutine(input_or_coro):
47
+
48
+ async def _async_input() -> str:
49
+ reply = await input_or_coro
50
+ return reply
51
+
52
+ return syncify(_async_input)()
53
+ return input_or_coro
54
+
55
+ except EOFError:
56
+ # Handle EOFError gracefully
57
+ return ""
58
+
59
+ iostream.input = _safe_input # pyright: ignore
60
+ iostream.print = get_printer() # pyright: ignore
61
+ IOStream.set_default(iostream)
62
+
63
+
64
+ def patch_async_io_stream() -> None:
65
+ """Patch the IOStream to handle async flows."""
66
+ from autogen.io import IOStream # pyright: ignore
67
+
68
+ iostream = IOStream.get_default()
69
+ original_input = iostream.input
70
+
71
+ async def _async_input(prompt: str = "", *, password: bool = False) -> str:
72
+ """Async input method."""
73
+ try:
74
+ input_or_coro = original_input(prompt, password=password)
75
+ if inspect.iscoroutine(input_or_coro):
76
+ reply = await input_or_coro
77
+ else:
78
+ reply = input_or_coro
79
+ return reply
80
+ except EOFError:
81
+ # Handle EOFError gracefully
82
+ return ""
83
+
84
+ iostream.input = _async_input # pyright: ignore
85
+ iostream.print = get_printer() # pyright: ignore
86
+ IOStream.set_default(iostream)
87
+
88
+
89
+ def get_printer() -> Callable[..., None]: # noqa: C901
90
+ """Get the printer function.
91
+
92
+ Returns
93
+ -------
94
+ Callable[..., None]
95
+ The printer function that handles Unicode encoding errors gracefully.
96
+ """
97
+ try:
98
+ from autogen.io import IOStream # pyright: ignore
99
+
100
+ printer = IOStream.get_default().print
101
+ except ImportError:
102
+ # Fallback to standard print if autogen is not available
103
+ printer = print
104
+
105
+ def safe_printer(*args: Any, **kwargs: Any) -> None: # noqa: C901
106
+ """Safe printer that handles Unicode encoding errors.
107
+
108
+ Parameters
109
+ ----------
110
+ *args : Any
111
+ Arguments to pass to the printer
112
+ **kwargs : Any
113
+ Keyword arguments to pass to the printer
114
+ """
115
+ try:
116
+ printer(*args, **kwargs)
117
+ except UnicodeEncodeError:
118
+ # First fallback: try to get a safe string representation
119
+ try:
120
+ msg, flush = get_what_to_print(*args, **kwargs)
121
+ # Convert problematic characters to safe representations
122
+ safe_msg = msg.encode("utf-8", errors="replace").decode("utf-8")
123
+ printer(safe_msg, end="", flush=flush)
124
+ except (UnicodeEncodeError, UnicodeDecodeError):
125
+ # Second fallback: use built-in print with safe encoding
126
+ try:
127
+ # Convert args to safe string representations
128
+ safe_args: list[str] = []
129
+ for arg in args:
130
+ try:
131
+ safe_args.append(
132
+ str(arg)
133
+ .encode("utf-8", errors="replace")
134
+ .decode("utf-8")
135
+ )
136
+ except (UnicodeEncodeError, UnicodeDecodeError):
137
+ safe_args.append(repr(arg))
138
+
139
+ # Use built-in print instead of the custom printer
140
+ print(*safe_args, **kwargs)
141
+
142
+ except Exception:
143
+ # Final fallback: write directly to stderr buffer
144
+ try:
145
+ error_msg = (
146
+ "Could not print the message "
147
+ "due to encoding issues.\n"
148
+ )
149
+ if hasattr(sys.stderr, "buffer"):
150
+ sys.stderr.buffer.write(
151
+ error_msg.encode("utf-8", errors="replace")
152
+ )
153
+ sys.stderr.buffer.flush()
154
+ else:
155
+ sys.stderr.write(error_msg)
156
+ sys.stderr.flush()
157
+ except Exception:
158
+ # If even this fails, we're in a very bad state
159
+ pass
160
+ except Exception as e:
161
+ # Handle any other unexpected errors
162
+ try:
163
+ error_msg = f"Unexpected error in printer: {str(e)}\n"
164
+ if hasattr(sys.stderr, "buffer"):
165
+ sys.stderr.buffer.write(
166
+ error_msg.encode("utf-8", errors="replace")
167
+ )
168
+ sys.stderr.buffer.flush()
169
+ else:
170
+ sys.stderr.write(error_msg)
171
+ sys.stderr.flush()
172
+ except Exception:
173
+ pass
174
+
175
+ return safe_printer
176
+
177
+
178
+ def get_what_to_print(*args: Any, **kwargs: Any) -> tuple[str, bool]:
179
+ """Extract message and flush flag from print arguments.
180
+
181
+ Parameters
182
+ ----------
183
+ *args : Any
184
+ Arguments to print
185
+ **kwargs : Any
186
+ Keyword arguments for print function
187
+
188
+ Returns
189
+ -------
190
+ tuple[str, bool]
191
+ Message to print and flush flag
192
+ """
193
+ # Convert all args to strings and join with spaces (like print does)
194
+ msg = " ".join(str(arg) for arg in args)
195
+
196
+ # Handle sep parameter
197
+ sep = kwargs.get("sep", " ")
198
+ if len(args) > 1:
199
+ msg = sep.join(str(arg) for arg in args)
200
+
201
+ # Handle end parameter
202
+ end = kwargs.get("end", "\n")
203
+ msg += end
204
+
205
+ # Handle flush parameter
206
+ flush = kwargs.get("flush", False)
207
+
208
+ return msg, flush
@@ -0,0 +1,121 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ # pylint: disable=unused-argument
5
+ """Utilities for running code."""
6
+
7
+ import datetime
8
+ import shutil
9
+ from pathlib import Path
10
+ from typing import Optional, Union
11
+
12
+ from .gen_seq_diagram import generate_sequence_diagram
13
+
14
+
15
+ def after_run(
16
+ temp_dir: Path,
17
+ output_file: Optional[Union[str, Path]],
18
+ flow_name: str,
19
+ uploads_root: Optional[Path] = None,
20
+ skip_mmd: bool = False,
21
+ ) -> None:
22
+ """Actions to perform after running the flow.
23
+
24
+ Parameters
25
+ ----------
26
+ temp_dir : Path
27
+ The temporary directory.
28
+ output_file : Optional[Union[str, Path]]
29
+ The output file.
30
+ flow_name : str
31
+ The flow name.
32
+ uploads_root : Optional[Path], optional
33
+ The runtime uploads root, by default None
34
+ skip_mmd : bool, optional
35
+ Whether to skip the mermaid sequence diagram generation,
36
+ by default, False
37
+ """
38
+ if isinstance(output_file, str):
39
+ output_file = Path(output_file)
40
+ mmd_dir = output_file.parent if output_file else Path.cwd()
41
+ if skip_mmd is False:
42
+ events_csv_path = temp_dir / "logs" / "events.csv"
43
+ if events_csv_path.exists():
44
+ print("Generating mermaid sequence diagram...")
45
+ mmd_path = temp_dir / f"{flow_name}.mmd"
46
+ generate_sequence_diagram(events_csv_path, mmd_path)
47
+ if (
48
+ not output_file
49
+ and mmd_path.exists()
50
+ and mmd_path != mmd_dir / f"{flow_name}.mmd"
51
+ ):
52
+ try:
53
+ shutil.copyfile(mmd_path, mmd_dir / f"{flow_name}.mmd")
54
+ except BaseException: # pylint: disable=broad-exception-caught
55
+ pass
56
+ if output_file:
57
+ destination_dir = output_file.parent
58
+ destination_dir = (
59
+ destination_dir
60
+ / "waldiez_out"
61
+ / datetime.datetime.now().strftime("%Y%m%d%H%M%S")
62
+ )
63
+ destination_dir.mkdir(parents=True, exist_ok=True)
64
+ # copy the contents of the temp dir to the destination dir
65
+ print(f"Copying the results to {destination_dir}")
66
+ copy_results(
67
+ temp_dir=temp_dir,
68
+ output_file=output_file,
69
+ destination_dir=destination_dir,
70
+ )
71
+ shutil.rmtree(temp_dir)
72
+
73
+
74
+ def copy_results(
75
+ temp_dir: Path,
76
+ output_file: Path,
77
+ destination_dir: Path,
78
+ ) -> None:
79
+ """Copy the results to the output directory.
80
+
81
+ Parameters
82
+ ----------
83
+ temp_dir : Path
84
+ The temporary directory.
85
+ output_file : Path
86
+ The output file.
87
+ destination_dir : Path
88
+ The destination directory.
89
+ """
90
+ temp_dir.mkdir(parents=True, exist_ok=True)
91
+ output_dir = output_file.parent
92
+ for item in temp_dir.iterdir():
93
+ # skip cache files
94
+ if (
95
+ item.name.startswith("__pycache__")
96
+ or item.name.endswith(".pyc")
97
+ or item.name.endswith(".pyo")
98
+ or item.name.endswith(".pyd")
99
+ or item.name == ".cache"
100
+ ):
101
+ continue
102
+ if item.is_file():
103
+ # let's also copy the "tree of thoughts" image
104
+ # to the output directory
105
+ if item.name.endswith("tree_of_thoughts.png") or item.name.endswith(
106
+ "reasoning_tree.json"
107
+ ):
108
+ shutil.copy(item, output_dir / item.name)
109
+ shutil.copy(item, destination_dir)
110
+ else:
111
+ shutil.copytree(item, destination_dir / item.name)
112
+ if output_file.is_file():
113
+ if output_file.suffix == ".waldiez":
114
+ output_file = output_file.with_suffix(".py")
115
+ if output_file.suffix == ".py":
116
+ src = temp_dir / output_file.name
117
+ if src.exists():
118
+ dst = destination_dir / output_file.name
119
+ if dst.exists():
120
+ dst.unlink()
121
+ shutil.copyfile(src, output_dir / output_file.name)
@@ -0,0 +1,105 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ """Actions to perform before running the flow."""
4
+
5
+ import asyncio
6
+ import io
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ from typing import Callable
11
+
12
+ from .environment import in_virtualenv, is_root
13
+ from .utils import strip_ansi
14
+
15
+
16
+ def install_requirements(
17
+ extra_requirements: set[str],
18
+ printer: Callable[..., None] = print,
19
+ ) -> None:
20
+ """Install the requirements.
21
+
22
+ Parameters
23
+ ----------
24
+ extra_requirements : set[str]
25
+ The extra requirements.
26
+ printer : Callable[..., None]
27
+ The printer function to use, defaults to print.
28
+ """
29
+ requirements_string = ", ".join(extra_requirements)
30
+ printer(f"Installing requirements: {requirements_string}")
31
+ pip_install = [sys.executable, "-m", "pip", "install"]
32
+ break_system_packages = ""
33
+ if not in_virtualenv(): # it should
34
+ # if not, let's try to install as user
35
+ # not sure if --break-system-packages is safe,
36
+ # but it might fail if we don't
37
+ break_system_packages = os.environ.get("PIP_BREAK_SYSTEM_PACKAGES", "")
38
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = "1"
39
+ if not is_root():
40
+ pip_install.append("--user")
41
+ pip_install.extend(extra_requirements)
42
+ # pylint: disable=too-many-try-statements
43
+ try:
44
+ with subprocess.Popen(
45
+ pip_install,
46
+ stdout=subprocess.PIPE,
47
+ stderr=subprocess.PIPE,
48
+ ) as proc:
49
+ if proc.stdout:
50
+ for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):
51
+ printer(strip_ansi(line.strip()))
52
+ if proc.stderr:
53
+ for line in io.TextIOWrapper(proc.stderr, encoding="utf-8"):
54
+ printer(strip_ansi(line.strip()))
55
+ finally:
56
+ if not in_virtualenv():
57
+ # restore the old env var
58
+ if break_system_packages:
59
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = break_system_packages
60
+ else:
61
+ del os.environ["PIP_BREAK_SYSTEM_PACKAGES"]
62
+
63
+
64
+ async def a_install_requirements(
65
+ extra_requirements: set[str],
66
+ printer: Callable[..., None] = print,
67
+ ) -> None:
68
+ """Install the requirements asynchronously.
69
+
70
+ Parameters
71
+ ----------
72
+ extra_requirements : set[str]
73
+ The extra requirements.
74
+ printer : Callable[..., None]
75
+ The printer function to use, defaults to print.
76
+ """
77
+ requirements_string = ", ".join(extra_requirements)
78
+ printer(f"Installing requirements: {requirements_string}")
79
+ pip_install = [sys.executable, "-m", "pip", "install"]
80
+ break_system_packages = ""
81
+ if not in_virtualenv():
82
+ break_system_packages = os.environ.get("PIP_BREAK_SYSTEM_PACKAGES", "")
83
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = "1"
84
+ if not is_root():
85
+ pip_install.extend(["--user"])
86
+ pip_install.extend(extra_requirements)
87
+ # pylint: disable=too-many-try-statements
88
+ try:
89
+ proc = await asyncio.create_subprocess_exec(
90
+ *pip_install,
91
+ stdout=asyncio.subprocess.PIPE,
92
+ stderr=asyncio.subprocess.PIPE,
93
+ )
94
+ if proc.stdout:
95
+ async for line in proc.stdout:
96
+ printer(strip_ansi(line.decode().strip()))
97
+ if proc.stderr:
98
+ async for line in proc.stderr:
99
+ printer(strip_ansi(line.decode().strip()))
100
+ finally:
101
+ if not in_virtualenv():
102
+ if break_system_packages:
103
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = break_system_packages
104
+ else:
105
+ del os.environ["PIP_BREAK_SYSTEM_PACKAGES"]