waldiez 0.5.9__py3-none-any.whl → 0.6.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/_version.py +1 -1
- waldiez/cli.py +113 -24
- waldiez/exporting/agent/exporter.py +9 -6
- waldiez/exporting/agent/extras/captain_agent_extras.py +44 -7
- waldiez/exporting/agent/extras/group_manager_agent_extas.py +6 -1
- waldiez/exporting/agent/extras/handoffs/after_work.py +1 -0
- waldiez/exporting/agent/extras/handoffs/available.py +1 -0
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -1
- waldiez/exporting/agent/extras/handoffs/handoff.py +1 -0
- waldiez/exporting/agent/extras/handoffs/target.py +1 -0
- waldiez/exporting/agent/termination.py +1 -0
- waldiez/exporting/chats/utils/common.py +25 -23
- waldiez/exporting/core/__init__.py +0 -2
- waldiez/exporting/core/constants.py +3 -1
- waldiez/exporting/core/context.py +13 -13
- waldiez/exporting/core/extras/serializer.py +12 -10
- waldiez/exporting/core/protocols.py +0 -141
- waldiez/exporting/core/result.py +5 -5
- waldiez/exporting/core/types.py +1 -0
- waldiez/exporting/core/utils/llm_config.py +2 -2
- waldiez/exporting/flow/execution_generator.py +1 -0
- waldiez/exporting/flow/merger.py +2 -2
- waldiez/exporting/flow/orchestrator.py +1 -0
- waldiez/exporting/flow/utils/common.py +3 -3
- waldiez/exporting/flow/utils/importing.py +1 -0
- waldiez/exporting/flow/utils/logging.py +7 -80
- waldiez/exporting/tools/exporter.py +5 -0
- waldiez/exporting/tools/factory.py +4 -0
- waldiez/exporting/tools/processor.py +5 -1
- waldiez/io/__init__.py +3 -1
- waldiez/io/_ws.py +15 -5
- waldiez/io/models/content/image.py +1 -0
- waldiez/io/models/user_input.py +4 -4
- waldiez/io/models/user_response.py +1 -0
- waldiez/io/mqtt.py +1 -1
- waldiez/io/structured.py +98 -45
- waldiez/io/utils.py +17 -11
- waldiez/io/ws.py +10 -12
- waldiez/logger.py +180 -63
- waldiez/models/agents/agent/agent.py +2 -1
- waldiez/models/agents/agent/update_system_message.py +0 -2
- waldiez/models/agents/doc_agent/doc_agent.py +8 -1
- waldiez/models/chat/chat.py +1 -0
- waldiez/models/chat/chat_data.py +0 -2
- waldiez/models/common/base.py +2 -0
- waldiez/models/common/dict_utils.py +169 -40
- waldiez/models/common/handoff.py +2 -0
- waldiez/models/common/method_utils.py +2 -0
- waldiez/models/flow/flow.py +6 -6
- waldiez/models/flow/info.py +5 -1
- waldiez/models/model/_llm.py +31 -14
- waldiez/models/model/model.py +4 -1
- waldiez/models/model/model_data.py +18 -5
- waldiez/models/tool/predefined/_config.py +5 -1
- waldiez/models/tool/predefined/_duckduckgo.py +4 -0
- waldiez/models/tool/predefined/_email.py +477 -0
- waldiez/models/tool/predefined/_google.py +4 -1
- waldiez/models/tool/predefined/_perplexity.py +4 -1
- waldiez/models/tool/predefined/_searxng.py +4 -1
- waldiez/models/tool/predefined/_tavily.py +4 -1
- waldiez/models/tool/predefined/_wikipedia.py +5 -2
- waldiez/models/tool/predefined/_youtube.py +4 -1
- waldiez/models/tool/predefined/protocol.py +3 -0
- waldiez/models/tool/tool.py +22 -4
- waldiez/models/waldiez.py +12 -0
- waldiez/runner.py +37 -54
- waldiez/running/__init__.py +6 -0
- waldiez/running/base_runner.py +381 -363
- waldiez/running/environment.py +1 -0
- waldiez/running/exceptions.py +9 -0
- waldiez/running/post_run.py +10 -4
- waldiez/running/pre_run.py +199 -66
- waldiez/running/protocol.py +21 -101
- waldiez/running/run_results.py +1 -1
- waldiez/running/standard_runner.py +83 -276
- waldiez/running/step_by_step/__init__.py +46 -0
- waldiez/running/step_by_step/breakpoints_mixin.py +512 -0
- waldiez/running/step_by_step/command_handler.py +151 -0
- waldiez/running/step_by_step/events_processor.py +199 -0
- waldiez/running/step_by_step/step_by_step_models.py +541 -0
- waldiez/running/step_by_step/step_by_step_runner.py +750 -0
- waldiez/running/subprocess_runner/__base__.py +279 -0
- waldiez/running/subprocess_runner/__init__.py +16 -0
- waldiez/running/subprocess_runner/_async_runner.py +362 -0
- waldiez/running/subprocess_runner/_sync_runner.py +456 -0
- waldiez/running/subprocess_runner/runner.py +570 -0
- waldiez/running/timeline_processor.py +1 -1
- waldiez/running/utils.py +492 -3
- waldiez/utils/version.py +2 -6
- waldiez/ws/__init__.py +71 -0
- waldiez/ws/__main__.py +15 -0
- waldiez/ws/_file_handler.py +199 -0
- waldiez/ws/_mock.py +74 -0
- waldiez/ws/cli.py +235 -0
- waldiez/ws/client_manager.py +851 -0
- waldiez/ws/errors.py +416 -0
- waldiez/ws/models.py +988 -0
- waldiez/ws/reloader.py +363 -0
- waldiez/ws/server.py +508 -0
- waldiez/ws/session_manager.py +393 -0
- waldiez/ws/session_stats.py +83 -0
- waldiez/ws/utils.py +410 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/METADATA +105 -96
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/RECORD +108 -83
- waldiez/running/patch_io_stream.py +0 -210
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/WHEEL +0 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# pyright: reportAttributeAccessIssue=false,reportUnknownArgumentType=false
|
|
7
|
-
# pylint: disable=too-many-try-statements,import-outside-toplevel,line-too-long,
|
|
8
|
-
# pylint: disable=too-complex,unused-argument,duplicate-code,broad-exception-caught
|
|
4
|
+
# pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false
|
|
5
|
+
# pylint: disable=duplicate-code,too-few-public-methods
|
|
9
6
|
"""Run a waldiez flow.
|
|
10
7
|
|
|
11
8
|
The flow is first converted to an autogen flow with agents, chats, and tools.
|
|
@@ -15,26 +12,29 @@ variables specified in the waldiez file are set.
|
|
|
15
12
|
"""
|
|
16
13
|
|
|
17
14
|
import asyncio
|
|
18
|
-
import getpass
|
|
19
|
-
import importlib.util
|
|
20
|
-
import sys
|
|
21
|
-
import threading
|
|
22
15
|
import traceback
|
|
23
16
|
from pathlib import Path
|
|
24
|
-
from
|
|
25
|
-
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Union
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Union
|
|
26
18
|
|
|
27
19
|
from waldiez.models.waldiez import Waldiez
|
|
28
20
|
from waldiez.running.run_results import WaldiezRunResults
|
|
29
21
|
|
|
30
22
|
from .base_runner import WaldiezBaseRunner
|
|
31
|
-
from .utils import chdir
|
|
32
23
|
|
|
33
24
|
if TYPE_CHECKING:
|
|
34
25
|
from autogen.events import BaseEvent # type: ignore
|
|
35
26
|
from autogen.messages import BaseMessage # type: ignore
|
|
36
27
|
|
|
37
28
|
|
|
29
|
+
MESSAGES = {
|
|
30
|
+
"workflow_starting": "<Waldiez> - Starting workflow...",
|
|
31
|
+
"workflow_finished": "<Waldiez> - Workflow finished",
|
|
32
|
+
"workflow_stopped": "<Waldiez> - Workflow stopped by user",
|
|
33
|
+
"workflow_failed": "<Waldiez> - Workflow execution failed: {error}",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# noinspection StrFormat
|
|
38
38
|
class WaldiezStandardRunner(WaldiezBaseRunner):
|
|
39
39
|
"""Run a waldiez flow in a standard way."""
|
|
40
40
|
|
|
@@ -56,49 +56,23 @@ class WaldiezStandardRunner(WaldiezBaseRunner):
|
|
|
56
56
|
dot_env=dot_env,
|
|
57
57
|
**kwargs,
|
|
58
58
|
)
|
|
59
|
-
self._execution_thread: threading.Thread | None = None
|
|
60
|
-
self._loaded_module: ModuleType | None = None
|
|
61
59
|
self._event_count = 0
|
|
62
60
|
self._processed_events = 0
|
|
63
61
|
|
|
64
|
-
def _load_module(self, output_file: Path, temp_dir: Path) -> ModuleType:
|
|
65
|
-
"""Load the module from the waldiez file."""
|
|
66
|
-
file_name = output_file.name
|
|
67
|
-
module_name = file_name.replace(".py", "")
|
|
68
|
-
spec = importlib.util.spec_from_file_location(
|
|
69
|
-
module_name, temp_dir / file_name
|
|
70
|
-
)
|
|
71
|
-
if not spec or not spec.loader:
|
|
72
|
-
raise ImportError("Could not import the flow")
|
|
73
|
-
module = importlib.util.module_from_spec(spec)
|
|
74
|
-
spec.loader.exec_module(module)
|
|
75
|
-
if not hasattr(module, "main"):
|
|
76
|
-
raise ImportError(
|
|
77
|
-
"The waldiez file does not contain a main() function"
|
|
78
|
-
)
|
|
79
|
-
self._loaded_module = module
|
|
80
|
-
return module
|
|
81
|
-
|
|
82
62
|
@staticmethod
|
|
83
|
-
def
|
|
84
|
-
"""
|
|
63
|
+
def print(*args: Any, **kwargs: Any) -> None:
|
|
64
|
+
"""Print.
|
|
85
65
|
|
|
86
66
|
Parameters
|
|
87
67
|
----------
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Returns
|
|
94
|
-
-------
|
|
95
|
-
str
|
|
96
|
-
The user input as a string.
|
|
68
|
+
*args : Any
|
|
69
|
+
Positional arguments to print.
|
|
70
|
+
**kwargs : Any
|
|
71
|
+
Keyword arguments to print.
|
|
97
72
|
"""
|
|
98
|
-
|
|
99
|
-
return getpass.getpass(prompt)
|
|
100
|
-
return input(prompt)
|
|
73
|
+
WaldiezBaseRunner.print(*args, **kwargs)
|
|
101
74
|
|
|
75
|
+
# pylint: disable=unused-argument
|
|
102
76
|
def _run(
|
|
103
77
|
self,
|
|
104
78
|
temp_dir: Path,
|
|
@@ -109,48 +83,49 @@ class WaldiezStandardRunner(WaldiezBaseRunner):
|
|
|
109
83
|
**kwargs: Any,
|
|
110
84
|
) -> list[dict[str, Any]]:
|
|
111
85
|
"""Run the Waldiez workflow."""
|
|
86
|
+
# pylint: disable=import-outside-toplevel
|
|
112
87
|
from autogen.io import IOStream # type: ignore
|
|
113
88
|
|
|
114
89
|
from waldiez.io import StructuredIOStream
|
|
115
90
|
|
|
116
|
-
self._print: Callable[..., None] = print
|
|
117
|
-
self._input: (
|
|
118
|
-
Callable[..., str] | Callable[..., Coroutine[Any, Any, str]]
|
|
119
|
-
) = WaldiezStandardRunner.standard_input
|
|
120
91
|
results_container: WaldiezRunResults = {
|
|
121
92
|
"results": [],
|
|
122
93
|
"exception": None,
|
|
123
94
|
"completed": False,
|
|
124
95
|
}
|
|
96
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
125
97
|
try:
|
|
126
|
-
|
|
98
|
+
loaded_module = self._load_module(output_file, temp_dir)
|
|
127
99
|
if self._stop_requested.is_set():
|
|
128
|
-
self.log.
|
|
129
|
-
"
|
|
100
|
+
self.log.debug(
|
|
101
|
+
"Execution stopped before AG2 workflow start (sync)"
|
|
130
102
|
)
|
|
131
103
|
return []
|
|
104
|
+
# noinspection DuplicatedCode
|
|
132
105
|
if self.structured_io:
|
|
133
106
|
stream = StructuredIOStream(
|
|
134
107
|
uploads_root=uploads_root, is_async=False
|
|
135
108
|
)
|
|
136
109
|
else:
|
|
137
110
|
stream = IOStream.get_default()
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
self.
|
|
142
|
-
self.
|
|
143
|
-
results =
|
|
111
|
+
WaldiezBaseRunner._print = stream.print
|
|
112
|
+
WaldiezBaseRunner._input = stream.input
|
|
113
|
+
WaldiezBaseRunner._send = stream.send
|
|
114
|
+
self.print(MESSAGES["workflow_starting"])
|
|
115
|
+
self.print(self.waldiez.info.model_dump_json())
|
|
116
|
+
results = loaded_module.main(
|
|
144
117
|
on_event=self._on_event,
|
|
145
118
|
)
|
|
146
119
|
results_container["results"] = results
|
|
147
|
-
self.
|
|
148
|
-
except SystemExit:
|
|
149
|
-
self.
|
|
150
|
-
|
|
120
|
+
self.print(MESSAGES["workflow_finished"])
|
|
121
|
+
except SystemExit: # pragma: no cover
|
|
122
|
+
self.log.debug("Execution stopped by user (sync)")
|
|
123
|
+
self.print(MESSAGES["workflow_stopped"])
|
|
124
|
+
except BaseException as e: # pragma: no cover
|
|
151
125
|
results_container["exception"] = e
|
|
152
126
|
traceback.print_exc()
|
|
153
|
-
self.
|
|
127
|
+
self.print(MESSAGES["workflow_failed"].format(error=e))
|
|
128
|
+
|
|
154
129
|
finally:
|
|
155
130
|
results_container["completed"] = True
|
|
156
131
|
return results_container["results"]
|
|
@@ -162,153 +137,52 @@ class WaldiezStandardRunner(WaldiezBaseRunner):
|
|
|
162
137
|
"""Process an event from the workflow."""
|
|
163
138
|
self._event_count += 1
|
|
164
139
|
if self._stop_requested.is_set():
|
|
165
|
-
self.log.
|
|
166
|
-
"
|
|
140
|
+
self.log.debug(
|
|
141
|
+
"Execution stopped before AG2 workflow event processing (sync)"
|
|
167
142
|
)
|
|
168
143
|
return False
|
|
169
144
|
try:
|
|
170
|
-
|
|
171
|
-
if event.type == "input_request":
|
|
172
|
-
prompt = getattr(
|
|
173
|
-
event,
|
|
174
|
-
"prompt",
|
|
175
|
-
getattr(event.content, "prompt", "> "),
|
|
176
|
-
)
|
|
177
|
-
password = getattr(
|
|
178
|
-
event,
|
|
179
|
-
"password",
|
|
180
|
-
getattr(event.content, "password", False),
|
|
181
|
-
)
|
|
182
|
-
user_input = self._input(
|
|
183
|
-
prompt,
|
|
184
|
-
password=password,
|
|
185
|
-
)
|
|
186
|
-
event.content.respond(user_input)
|
|
187
|
-
else:
|
|
188
|
-
self._send(event) # pyright: ignore
|
|
145
|
+
WaldiezBaseRunner.process_event(event)
|
|
189
146
|
self._processed_events += 1
|
|
147
|
+
except SystemExit: # pragma: no cover
|
|
148
|
+
self.log.debug("Execution stopped by user (sync)")
|
|
149
|
+
return False
|
|
190
150
|
except Exception as e:
|
|
151
|
+
if self._stop_requested.is_set():
|
|
152
|
+
self.log.debug("Exception during stop, returning False")
|
|
153
|
+
return False
|
|
191
154
|
raise RuntimeError(
|
|
192
155
|
f"Error processing event {event}: {e}\n{traceback.format_exc()}"
|
|
193
156
|
) from e
|
|
194
|
-
if event.type == "run_completion":
|
|
195
|
-
self._signal_completion()
|
|
196
|
-
WaldiezBaseRunner._running = False
|
|
197
157
|
return not self._stop_requested.is_set()
|
|
198
158
|
|
|
199
|
-
def _start(
|
|
200
|
-
self,
|
|
201
|
-
temp_dir: Path,
|
|
202
|
-
output_file: Path,
|
|
203
|
-
uploads_root: Path | None,
|
|
204
|
-
skip_mmd: bool,
|
|
205
|
-
skip_timeline: bool,
|
|
206
|
-
) -> None:
|
|
207
|
-
"""Start the workflow in a non-blocking way."""
|
|
208
|
-
if self._execution_thread and self._execution_thread.is_alive():
|
|
209
|
-
raise RuntimeError("Non-blocking execution already in progress")
|
|
210
|
-
|
|
211
|
-
# Reset completion state
|
|
212
|
-
self._reset_completion_state()
|
|
213
|
-
|
|
214
|
-
# Create thread with proper integration
|
|
215
|
-
self._execution_thread = threading.Thread(
|
|
216
|
-
target=self._threaded_run,
|
|
217
|
-
args=(temp_dir, output_file, uploads_root, skip_mmd, skip_timeline),
|
|
218
|
-
name=f"WaldiezStandardRunner-{self.waldiez.name}",
|
|
219
|
-
daemon=False, # Not daemon so we can properly join
|
|
220
|
-
)
|
|
221
|
-
self._execution_thread.start()
|
|
222
|
-
|
|
223
|
-
def _threaded_run(
|
|
224
|
-
self,
|
|
225
|
-
temp_dir: Path,
|
|
226
|
-
output_file: Path,
|
|
227
|
-
uploads_root: Path | None,
|
|
228
|
-
skip_mmd: bool = False,
|
|
229
|
-
skip_timeline: bool = False,
|
|
230
|
-
) -> None:
|
|
231
|
-
"""Run in a separate thread with proper lifecycle."""
|
|
232
|
-
try:
|
|
233
|
-
# Change to temp directory and manage sys.path
|
|
234
|
-
with chdir(to=temp_dir):
|
|
235
|
-
sys.path.insert(0, str(temp_dir))
|
|
236
|
-
try:
|
|
237
|
-
results = self._run(
|
|
238
|
-
temp_dir=temp_dir,
|
|
239
|
-
output_file=output_file,
|
|
240
|
-
uploads_root=uploads_root,
|
|
241
|
-
skip_mmd=skip_mmd,
|
|
242
|
-
skip_timeline=skip_timeline,
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
# Store results
|
|
246
|
-
self._last_results = results
|
|
247
|
-
|
|
248
|
-
# Call after_run hooks
|
|
249
|
-
self.after_run(
|
|
250
|
-
results=results,
|
|
251
|
-
output_file=output_file,
|
|
252
|
-
uploads_root=uploads_root,
|
|
253
|
-
temp_dir=temp_dir,
|
|
254
|
-
skip_mmd=skip_mmd,
|
|
255
|
-
skip_timeline=skip_timeline,
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
finally:
|
|
259
|
-
# Clean up sys.path
|
|
260
|
-
if sys.path and sys.path[0] == str(temp_dir):
|
|
261
|
-
sys.path.pop(0)
|
|
262
|
-
|
|
263
|
-
except Exception as e:
|
|
264
|
-
self._last_exception = e
|
|
265
|
-
self.log.error("Threaded execution failed: %s", e)
|
|
266
|
-
|
|
267
|
-
finally:
|
|
268
|
-
# Signal completion and mark as not running
|
|
269
|
-
self._signal_completion()
|
|
270
|
-
WaldiezBaseRunner._running = False
|
|
271
|
-
|
|
272
159
|
async def _a_on_event(
|
|
273
160
|
self,
|
|
274
161
|
event: Union["BaseEvent", "BaseMessage"],
|
|
275
162
|
) -> bool:
|
|
276
163
|
"""Process an event from the workflow asynchronously."""
|
|
277
164
|
self._event_count += 1
|
|
278
|
-
if self._stop_requested.is_set():
|
|
279
|
-
self.log.
|
|
280
|
-
"
|
|
165
|
+
if self._stop_requested.is_set(): # pragma: no cover
|
|
166
|
+
self.log.debug(
|
|
167
|
+
"Execution stopped before AG2 workflow event processing (async)"
|
|
281
168
|
)
|
|
282
169
|
return False
|
|
283
170
|
try:
|
|
284
|
-
|
|
285
|
-
if event.type == "input_request":
|
|
286
|
-
prompt = getattr(
|
|
287
|
-
event,
|
|
288
|
-
"prompt",
|
|
289
|
-
getattr(event.content, "prompt", "> "),
|
|
290
|
-
)
|
|
291
|
-
password = getattr(
|
|
292
|
-
event,
|
|
293
|
-
"password",
|
|
294
|
-
getattr(event.content, "password", False),
|
|
295
|
-
)
|
|
296
|
-
user_input = self._input(
|
|
297
|
-
prompt,
|
|
298
|
-
password=password,
|
|
299
|
-
)
|
|
300
|
-
if asyncio.iscoroutine(user_input):
|
|
301
|
-
user_input = await user_input
|
|
302
|
-
await event.content.respond(user_input)
|
|
303
|
-
else:
|
|
304
|
-
self._send(event) # pyright: ignore
|
|
171
|
+
await WaldiezBaseRunner.a_process_event(event)
|
|
305
172
|
self._processed_events += 1
|
|
173
|
+
except SystemExit: # pragma: no cover
|
|
174
|
+
self.log.debug("Execution stopped by user (async)")
|
|
175
|
+
return False
|
|
306
176
|
except Exception as e:
|
|
177
|
+
if self._stop_requested.is_set():
|
|
178
|
+
self.log.debug("Exception during stop, returning False")
|
|
179
|
+
return False
|
|
307
180
|
raise RuntimeError(
|
|
308
181
|
f"Error processing event {event}: {e}\n{traceback.format_exc()}"
|
|
309
182
|
) from e
|
|
310
183
|
return not self._stop_requested.is_set()
|
|
311
184
|
|
|
185
|
+
# pylint: disable=too-complex
|
|
312
186
|
async def _a_run(
|
|
313
187
|
self,
|
|
314
188
|
temp_dir: Path,
|
|
@@ -324,39 +198,43 @@ class WaldiezStandardRunner(WaldiezBaseRunner):
|
|
|
324
198
|
async def _execute_workflow() -> list[dict[str, Any]]:
|
|
325
199
|
# fmt: on
|
|
326
200
|
"""Execute the workflow in an async context."""
|
|
201
|
+
# pylint: disable=import-outside-toplevel
|
|
327
202
|
from autogen.io import IOStream # pyright: ignore
|
|
328
203
|
|
|
329
204
|
from waldiez.io import StructuredIOStream
|
|
330
205
|
|
|
331
|
-
results: list[dict[str, Any]]
|
|
206
|
+
results: list[dict[str, Any]]
|
|
207
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
332
208
|
try:
|
|
333
|
-
|
|
334
|
-
if self._stop_requested.is_set():
|
|
335
|
-
self.log.
|
|
336
|
-
"
|
|
209
|
+
loaded_module = self._load_module(output_file, temp_dir)
|
|
210
|
+
if self._stop_requested.is_set(): # pragma: no cover
|
|
211
|
+
self.log.debug(
|
|
212
|
+
"Execution stopped before AG2 "
|
|
213
|
+
"workflow event processing (async)"
|
|
337
214
|
)
|
|
338
215
|
return []
|
|
216
|
+
# noinspection DuplicatedCode
|
|
339
217
|
if self.structured_io:
|
|
340
218
|
stream = StructuredIOStream(
|
|
341
219
|
uploads_root=uploads_root, is_async=True
|
|
342
220
|
)
|
|
343
221
|
else:
|
|
344
222
|
stream = IOStream.get_default()
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
self.
|
|
349
|
-
self.
|
|
350
|
-
results = await
|
|
223
|
+
WaldiezBaseRunner._print = stream.print
|
|
224
|
+
WaldiezBaseRunner._input = stream.input
|
|
225
|
+
WaldiezBaseRunner._send = stream.send
|
|
226
|
+
self.print(MESSAGES["workflow_starting"])
|
|
227
|
+
self.print(self.waldiez.info.model_dump_json())
|
|
228
|
+
results = await loaded_module.main( # pyright: ignore
|
|
351
229
|
on_event=self._a_on_event
|
|
352
230
|
)
|
|
353
|
-
|
|
354
|
-
|
|
231
|
+
self.print(MESSAGES["workflow_finished"])
|
|
232
|
+
except SystemExit: # pragma: no cover
|
|
233
|
+
self.log.debug("Execution stopped by user (async)")
|
|
234
|
+
self.print(MESSAGES["workflow_stopped"])
|
|
355
235
|
return []
|
|
356
|
-
except Exception as e:
|
|
357
|
-
self.
|
|
358
|
-
f"<Waldiez> - Error loading workflow: {e}\n{traceback.format_exc()}"
|
|
359
|
-
)
|
|
236
|
+
except Exception as e: # pragma: no cover
|
|
237
|
+
self.print(MESSAGES["workflow_failed"].format(error=str(e)))
|
|
360
238
|
raise RuntimeError(
|
|
361
239
|
f"Error loading workflow: {e}\n{traceback.format_exc()}"
|
|
362
240
|
) from e
|
|
@@ -366,88 +244,17 @@ class WaldiezStandardRunner(WaldiezBaseRunner):
|
|
|
366
244
|
task = asyncio.create_task(_execute_workflow())
|
|
367
245
|
|
|
368
246
|
# Monitor for stop requests
|
|
247
|
+
# pylint: disable=too-many-try-statements
|
|
369
248
|
try:
|
|
370
249
|
while not task.done():
|
|
371
250
|
if self._stop_requested.is_set():
|
|
372
251
|
task.cancel()
|
|
373
|
-
self.log.
|
|
252
|
+
self.log.debug("Execution stopped by user (async)")
|
|
374
253
|
break
|
|
375
254
|
await asyncio.sleep(0.1)
|
|
376
255
|
# Return the task result when completed
|
|
377
256
|
return await task
|
|
378
257
|
|
|
379
258
|
except asyncio.CancelledError:
|
|
380
|
-
self.log.
|
|
259
|
+
self.log.debug("Execution cancelled (async)")
|
|
381
260
|
return []
|
|
382
|
-
|
|
383
|
-
async def _a_start(
|
|
384
|
-
self,
|
|
385
|
-
temp_dir: Path,
|
|
386
|
-
output_file: Path,
|
|
387
|
-
uploads_root: Path | None,
|
|
388
|
-
skip_mmd: bool = False,
|
|
389
|
-
skip_timeline: bool = False,
|
|
390
|
-
) -> None:
|
|
391
|
-
"""Start the Waldiez workflow asynchronously."""
|
|
392
|
-
|
|
393
|
-
async def run_in_background() -> None:
|
|
394
|
-
"""Run the Waldiez workflow in a background thread."""
|
|
395
|
-
try:
|
|
396
|
-
results = await self._a_run(
|
|
397
|
-
temp_dir,
|
|
398
|
-
output_file,
|
|
399
|
-
uploads_root,
|
|
400
|
-
skip_mmd=skip_mmd,
|
|
401
|
-
skip_timeline=skip_timeline,
|
|
402
|
-
)
|
|
403
|
-
if results:
|
|
404
|
-
self._print(f"<Waldiez> - Workflow completed: {results}")
|
|
405
|
-
except Exception as e:
|
|
406
|
-
self._print(
|
|
407
|
-
f"<Waldiez> - Error during workflow: {e}\n{traceback.format_exc()}"
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
asyncio.create_task(run_in_background())
|
|
411
|
-
|
|
412
|
-
def _stop(self) -> None:
|
|
413
|
-
"""Stop the Waldiez workflow."""
|
|
414
|
-
self.log.info("Stopping workflow execution...")
|
|
415
|
-
self._stop_requested.set()
|
|
416
|
-
|
|
417
|
-
# Wait for graceful shutdown
|
|
418
|
-
if self._execution_thread and self._execution_thread.is_alive():
|
|
419
|
-
self._execution_thread.join(timeout=5.0)
|
|
420
|
-
|
|
421
|
-
if self._execution_thread and self._execution_thread.is_alive():
|
|
422
|
-
self.log.warning("Workflow thread did not stop gracefully")
|
|
423
|
-
|
|
424
|
-
async def _a_stop(self) -> None:
|
|
425
|
-
"""Stop the Waldiez workflow asynchronously."""
|
|
426
|
-
self.log.info("Stopping workflow execution (async)...")
|
|
427
|
-
self._stop_requested.set()
|
|
428
|
-
|
|
429
|
-
# For async, we rely on the task cancellation in _a_run
|
|
430
|
-
# Let's give it a moment to respond
|
|
431
|
-
await asyncio.sleep(0.5)
|
|
432
|
-
|
|
433
|
-
def get_execution_stats(self) -> dict[str, Any]:
|
|
434
|
-
"""Get execution statistics for standard runner.
|
|
435
|
-
|
|
436
|
-
Returns
|
|
437
|
-
-------
|
|
438
|
-
dict[str, Any]
|
|
439
|
-
A dictionary containing execution statistics such as total events,
|
|
440
|
-
processed events, whether a module was loaded, and event processing rate.
|
|
441
|
-
"""
|
|
442
|
-
base_stats = super().get_execution_stats()
|
|
443
|
-
return {
|
|
444
|
-
**base_stats,
|
|
445
|
-
"total_events": self._event_count,
|
|
446
|
-
"processed_events": self._processed_events,
|
|
447
|
-
"has_loaded_module": self._loaded_module is not None,
|
|
448
|
-
"event_processing_rate": (
|
|
449
|
-
self._processed_events / self._event_count
|
|
450
|
-
if self._event_count > 0
|
|
451
|
-
else 0
|
|
452
|
-
),
|
|
453
|
-
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
"""Step-by-step runner for Waldiez workflows."""
|
|
5
|
+
|
|
6
|
+
from .breakpoints_mixin import BreakpointsMixin
|
|
7
|
+
from .step_by_step_models import (
|
|
8
|
+
WaldiezDebugBreakpointAdded,
|
|
9
|
+
WaldiezDebugBreakpointCleared,
|
|
10
|
+
WaldiezDebugBreakpointRemoved,
|
|
11
|
+
WaldiezDebugBreakpointsList,
|
|
12
|
+
WaldiezDebugError,
|
|
13
|
+
WaldiezDebugEventInfo,
|
|
14
|
+
WaldiezDebugHelp,
|
|
15
|
+
WaldiezDebugHelpCommand,
|
|
16
|
+
WaldiezDebugHelpCommandGroup,
|
|
17
|
+
WaldiezDebugInputRequest,
|
|
18
|
+
WaldiezDebugInputResponse,
|
|
19
|
+
WaldiezDebugMessage,
|
|
20
|
+
WaldiezDebugMessageWrapper,
|
|
21
|
+
WaldiezDebugPrint,
|
|
22
|
+
WaldiezDebugStats,
|
|
23
|
+
WaldiezDebugStepAction,
|
|
24
|
+
)
|
|
25
|
+
from .step_by_step_runner import WaldiezStepByStepRunner
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"BreakpointsMixin",
|
|
29
|
+
"WaldiezDebugError",
|
|
30
|
+
"WaldiezDebugEventInfo",
|
|
31
|
+
"WaldiezDebugHelp",
|
|
32
|
+
"WaldiezDebugHelpCommand",
|
|
33
|
+
"WaldiezDebugHelpCommandGroup",
|
|
34
|
+
"WaldiezDebugInputRequest",
|
|
35
|
+
"WaldiezDebugInputResponse",
|
|
36
|
+
"WaldiezDebugMessage",
|
|
37
|
+
"WaldiezDebugMessageWrapper",
|
|
38
|
+
"WaldiezDebugPrint",
|
|
39
|
+
"WaldiezDebugStats",
|
|
40
|
+
"WaldiezDebugStepAction",
|
|
41
|
+
"WaldiezStepByStepRunner",
|
|
42
|
+
"WaldiezDebugBreakpointAdded",
|
|
43
|
+
"WaldiezDebugBreakpointCleared",
|
|
44
|
+
"WaldiezDebugBreakpointRemoved",
|
|
45
|
+
"WaldiezDebugBreakpointsList",
|
|
46
|
+
]
|