waldiez 0.5.2__py3-none-any.whl → 0.5.4__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 +5 -27
- waldiez/exporter.py +0 -13
- waldiez/exporting/agent/exporter.py +38 -0
- waldiez/exporting/agent/extras/__init__.py +2 -0
- waldiez/exporting/agent/extras/doc_agent_extras.py +366 -0
- waldiez/exporting/agent/extras/group_member_extras.py +3 -2
- waldiez/exporting/agent/processor.py +113 -15
- waldiez/exporting/chats/processor.py +2 -21
- waldiez/exporting/chats/utils/common.py +66 -1
- waldiez/exporting/chats/utils/group.py +6 -3
- waldiez/exporting/chats/utils/nested.py +1 -1
- waldiez/exporting/chats/utils/sequential.py +25 -9
- waldiez/exporting/chats/utils/single.py +8 -6
- waldiez/exporting/core/context.py +0 -12
- waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -1
- waldiez/exporting/core/extras/base.py +20 -17
- waldiez/exporting/core/extras/path_resolver.py +39 -41
- waldiez/exporting/core/extras/serializer.py +16 -1
- waldiez/exporting/core/protocols.py +17 -0
- waldiez/exporting/core/types.py +6 -9
- waldiez/exporting/flow/execution_generator.py +56 -21
- waldiez/exporting/flow/exporter.py +1 -4
- waldiez/exporting/flow/factory.py +0 -9
- waldiez/exporting/flow/file_generator.py +6 -0
- waldiez/exporting/flow/orchestrator.py +27 -21
- waldiez/exporting/flow/utils/__init__.py +0 -2
- waldiez/exporting/flow/utils/common.py +15 -96
- waldiez/exporting/flow/utils/importing.py +4 -0
- waldiez/io/mqtt.py +33 -14
- waldiez/io/redis.py +18 -13
- waldiez/io/structured.py +9 -4
- waldiez/io/utils.py +32 -0
- waldiez/io/ws.py +8 -2
- waldiez/models/__init__.py +6 -0
- waldiez/models/agents/__init__.py +8 -0
- waldiez/models/agents/agent/agent.py +136 -38
- waldiez/models/agents/agent/agent_type.py +3 -2
- waldiez/models/agents/agents.py +10 -0
- waldiez/models/agents/doc_agent/__init__.py +13 -0
- waldiez/models/agents/doc_agent/doc_agent.py +126 -0
- waldiez/models/agents/doc_agent/doc_agent_data.py +149 -0
- waldiez/models/agents/doc_agent/rag_query_engine.py +127 -0
- waldiez/models/chat/chat_message.py +1 -1
- waldiez/models/flow/flow.py +13 -2
- waldiez/models/model/__init__.py +2 -2
- waldiez/models/model/_aws.py +75 -0
- waldiez/models/model/_llm.py +516 -0
- waldiez/models/model/_price.py +30 -0
- waldiez/models/model/model.py +45 -2
- waldiez/models/model/model_data.py +2 -83
- waldiez/models/tool/predefined/_duckduckgo.py +123 -0
- waldiez/models/tool/predefined/_google.py +31 -9
- waldiez/models/tool/predefined/_perplexity.py +161 -0
- waldiez/models/tool/predefined/_searxng.py +152 -0
- waldiez/models/tool/predefined/_tavily.py +46 -9
- waldiez/models/tool/predefined/_wikipedia.py +26 -6
- waldiez/models/tool/predefined/_youtube.py +36 -8
- waldiez/models/tool/predefined/registry.py +6 -0
- waldiez/models/waldiez.py +12 -0
- waldiez/runner.py +184 -382
- waldiez/running/__init__.py +2 -4
- waldiez/running/base_runner.py +136 -118
- waldiez/running/environment.py +61 -17
- waldiez/running/post_run.py +70 -14
- waldiez/running/pre_run.py +42 -0
- waldiez/running/protocol.py +42 -48
- waldiez/running/run_results.py +5 -5
- waldiez/running/standard_runner.py +429 -0
- waldiez/running/timeline_processor.py +1166 -0
- waldiez/utils/version.py +12 -1
- {waldiez-0.5.2.dist-info → waldiez-0.5.4.dist-info}/METADATA +61 -63
- {waldiez-0.5.2.dist-info → waldiez-0.5.4.dist-info}/RECORD +77 -66
- waldiez/running/import_runner.py +0 -424
- waldiez/running/subprocess_runner.py +0 -100
- {waldiez-0.5.2.dist-info → waldiez-0.5.4.dist-info}/WHEEL +0 -0
- {waldiez-0.5.2.dist-info → waldiez-0.5.4.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.2.dist-info → waldiez-0.5.4.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.2.dist-info → waldiez-0.5.4.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
# flake8: noqa: C901, E501
|
|
5
|
+
# pyright: reportUnknownMemberType=false, reportUnknownVariableType=false
|
|
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
|
|
9
|
+
"""Run a waldiez flow.
|
|
10
|
+
|
|
11
|
+
The flow is first converted to an autogen flow with agents, chats, and tools.
|
|
12
|
+
We then chown to temporary directory, call the flow's `main()` and
|
|
13
|
+
return the results. Before running the flow, any additional environment
|
|
14
|
+
variables specified in the waldiez file are set.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import importlib.util
|
|
19
|
+
import sys
|
|
20
|
+
import threading
|
|
21
|
+
import traceback
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from types import ModuleType
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Union
|
|
25
|
+
|
|
26
|
+
from waldiez.models.waldiez import Waldiez
|
|
27
|
+
from waldiez.running.run_results import WaldiezRunResults
|
|
28
|
+
|
|
29
|
+
from .base_runner import WaldiezBaseRunner
|
|
30
|
+
from .utils import chdir
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from autogen.events import BaseEvent # type: ignore
|
|
34
|
+
from autogen.io.run_response import ( # type: ignore
|
|
35
|
+
AsyncRunResponseProtocol,
|
|
36
|
+
RunResponseProtocol,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class WaldiezStandardRunner(WaldiezBaseRunner):
|
|
41
|
+
"""Run a waldiez flow in a standard way."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
waldiez: Waldiez,
|
|
46
|
+
output_path: str | Path | None = None,
|
|
47
|
+
uploads_root: str | Path | None = None,
|
|
48
|
+
structured_io: bool = False,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Initialize the Waldiez manager."""
|
|
51
|
+
super().__init__(
|
|
52
|
+
waldiez,
|
|
53
|
+
output_path=output_path,
|
|
54
|
+
uploads_root=uploads_root,
|
|
55
|
+
structured_io=structured_io,
|
|
56
|
+
)
|
|
57
|
+
self._execution_thread: threading.Thread | None = None
|
|
58
|
+
self._loaded_module: ModuleType | None = None
|
|
59
|
+
self._event_count = 0
|
|
60
|
+
self._processed_events = 0
|
|
61
|
+
|
|
62
|
+
def _load_module(self, output_file: Path, temp_dir: Path) -> ModuleType:
|
|
63
|
+
"""Load the module from the waldiez file."""
|
|
64
|
+
file_name = output_file.name
|
|
65
|
+
module_name = file_name.replace(".py", "")
|
|
66
|
+
spec = importlib.util.spec_from_file_location(
|
|
67
|
+
module_name, temp_dir / file_name
|
|
68
|
+
)
|
|
69
|
+
if not spec or not spec.loader:
|
|
70
|
+
raise ImportError("Could not import the flow")
|
|
71
|
+
module = importlib.util.module_from_spec(spec)
|
|
72
|
+
spec.loader.exec_module(module)
|
|
73
|
+
if not hasattr(module, "main"):
|
|
74
|
+
raise ImportError(
|
|
75
|
+
"The waldiez file does not contain a main() function"
|
|
76
|
+
)
|
|
77
|
+
self._loaded_module = module
|
|
78
|
+
return module
|
|
79
|
+
|
|
80
|
+
def _run(
|
|
81
|
+
self,
|
|
82
|
+
temp_dir: Path,
|
|
83
|
+
output_file: Path,
|
|
84
|
+
uploads_root: Path | None,
|
|
85
|
+
skip_mmd: bool,
|
|
86
|
+
skip_timeline: bool,
|
|
87
|
+
) -> Union[list["RunResponseProtocol"], list["AsyncRunResponseProtocol"]]:
|
|
88
|
+
"""Run the Waldiez workflow."""
|
|
89
|
+
from autogen.io import IOStream # type: ignore
|
|
90
|
+
|
|
91
|
+
from waldiez.io import StructuredIOStream
|
|
92
|
+
|
|
93
|
+
self._print: Callable[..., None] = print
|
|
94
|
+
self._input: (
|
|
95
|
+
Callable[..., str] | Callable[..., Coroutine[Any, Any, str]]
|
|
96
|
+
) = input
|
|
97
|
+
results_container: WaldiezRunResults = {
|
|
98
|
+
"results": [],
|
|
99
|
+
"exception": None,
|
|
100
|
+
"completed": False,
|
|
101
|
+
}
|
|
102
|
+
try:
|
|
103
|
+
self._loaded_module = self._load_module(output_file, temp_dir)
|
|
104
|
+
if self._stop_requested.is_set():
|
|
105
|
+
self.log.info(
|
|
106
|
+
"Async execution stopped before AG2 workflow start"
|
|
107
|
+
)
|
|
108
|
+
return []
|
|
109
|
+
if self.structured_io:
|
|
110
|
+
stream = StructuredIOStream(
|
|
111
|
+
uploads_root=uploads_root, is_async=False
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
stream = IOStream.get_default()
|
|
115
|
+
self._print = stream.print
|
|
116
|
+
self._input = stream.input
|
|
117
|
+
self._send = stream.send
|
|
118
|
+
self._print("<Waldiez> - Starting workflow...")
|
|
119
|
+
self._print(self.waldiez.info.model_dump_json())
|
|
120
|
+
results = self._loaded_module.main(
|
|
121
|
+
on_event=self._on_event,
|
|
122
|
+
)
|
|
123
|
+
results_container["results"] = results
|
|
124
|
+
self._print("<Waldiez> - Workflow finished")
|
|
125
|
+
except SystemExit:
|
|
126
|
+
self._print("<Waldiez> - Workflow stopped by user")
|
|
127
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
128
|
+
results_container["exception"] = e
|
|
129
|
+
traceback.print_exc()
|
|
130
|
+
self._print(f"<Waldiez> - Workflow execution failed: {e}")
|
|
131
|
+
finally:
|
|
132
|
+
results_container["completed"] = True
|
|
133
|
+
return results_container["results"]
|
|
134
|
+
|
|
135
|
+
def _on_event(
|
|
136
|
+
self,
|
|
137
|
+
event: "BaseEvent",
|
|
138
|
+
) -> bool:
|
|
139
|
+
"""Process an event from the workflow."""
|
|
140
|
+
self._event_count += 1
|
|
141
|
+
if self._stop_requested.is_set():
|
|
142
|
+
self.log.info(
|
|
143
|
+
"Async execution stopped before AG2 workflow event processing"
|
|
144
|
+
)
|
|
145
|
+
return False
|
|
146
|
+
try:
|
|
147
|
+
if hasattr(event, "type"):
|
|
148
|
+
if event.type == "input_request":
|
|
149
|
+
prompt = getattr(
|
|
150
|
+
event,
|
|
151
|
+
"prompt",
|
|
152
|
+
getattr(event.content, "prompt", "> "),
|
|
153
|
+
)
|
|
154
|
+
password = getattr(
|
|
155
|
+
event,
|
|
156
|
+
"password",
|
|
157
|
+
getattr(event.content, "password", False),
|
|
158
|
+
)
|
|
159
|
+
user_input = self._input(
|
|
160
|
+
prompt,
|
|
161
|
+
password=password,
|
|
162
|
+
)
|
|
163
|
+
event.content.respond(user_input)
|
|
164
|
+
else:
|
|
165
|
+
self._send(event)
|
|
166
|
+
self._processed_events += 1
|
|
167
|
+
except Exception as e:
|
|
168
|
+
raise RuntimeError(
|
|
169
|
+
f"Error processing event {event}: {e}\n{traceback.format_exc()}"
|
|
170
|
+
) from e
|
|
171
|
+
if event.type == "run_completion":
|
|
172
|
+
self._signal_completion()
|
|
173
|
+
WaldiezBaseRunner._running = False
|
|
174
|
+
return not self._stop_requested.is_set()
|
|
175
|
+
|
|
176
|
+
def _start(
|
|
177
|
+
self,
|
|
178
|
+
temp_dir: Path,
|
|
179
|
+
output_file: Path,
|
|
180
|
+
uploads_root: Path | None,
|
|
181
|
+
skip_mmd: bool,
|
|
182
|
+
skip_timeline: bool,
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Start the workflow in a non-blocking way."""
|
|
185
|
+
if self._execution_thread and self._execution_thread.is_alive():
|
|
186
|
+
raise RuntimeError("Non-blocking execution already in progress")
|
|
187
|
+
|
|
188
|
+
# Reset completion state
|
|
189
|
+
self._reset_completion_state()
|
|
190
|
+
|
|
191
|
+
# Create thread with proper integration
|
|
192
|
+
self._execution_thread = threading.Thread(
|
|
193
|
+
target=self._threaded_run,
|
|
194
|
+
args=(temp_dir, output_file, uploads_root, skip_mmd, skip_timeline),
|
|
195
|
+
name=f"WaldiezStandardRunner-{self.waldiez.name}",
|
|
196
|
+
daemon=False, # Not daemon so we can properly join
|
|
197
|
+
)
|
|
198
|
+
self._execution_thread.start()
|
|
199
|
+
|
|
200
|
+
def _threaded_run(
|
|
201
|
+
self,
|
|
202
|
+
temp_dir: Path,
|
|
203
|
+
output_file: Path,
|
|
204
|
+
uploads_root: Path | None,
|
|
205
|
+
skip_mmd: bool = False,
|
|
206
|
+
skip_timeline: bool = False,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""Run in a separate thread with proper lifecycle."""
|
|
209
|
+
try:
|
|
210
|
+
# Change to temp directory and manage sys.path
|
|
211
|
+
with chdir(to=temp_dir):
|
|
212
|
+
sys.path.insert(0, str(temp_dir))
|
|
213
|
+
try:
|
|
214
|
+
results = self._run(
|
|
215
|
+
temp_dir=temp_dir,
|
|
216
|
+
output_file=output_file,
|
|
217
|
+
uploads_root=uploads_root,
|
|
218
|
+
skip_mmd=skip_mmd,
|
|
219
|
+
skip_timeline=skip_timeline,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Store results
|
|
223
|
+
self._last_results = results
|
|
224
|
+
|
|
225
|
+
# Call after_run hooks
|
|
226
|
+
self.after_run(
|
|
227
|
+
results=results,
|
|
228
|
+
output_file=output_file,
|
|
229
|
+
uploads_root=uploads_root,
|
|
230
|
+
temp_dir=temp_dir,
|
|
231
|
+
skip_mmd=skip_mmd,
|
|
232
|
+
skip_timeline=skip_timeline,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
finally:
|
|
236
|
+
# Clean up sys.path
|
|
237
|
+
if sys.path and sys.path[0] == str(temp_dir):
|
|
238
|
+
sys.path.pop(0)
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
self._last_exception = e
|
|
242
|
+
self.log.error("Threaded execution failed: %s", e)
|
|
243
|
+
|
|
244
|
+
finally:
|
|
245
|
+
# Signal completion and mark as not running
|
|
246
|
+
self._signal_completion()
|
|
247
|
+
WaldiezBaseRunner._running = False
|
|
248
|
+
|
|
249
|
+
async def _a_on_event(
|
|
250
|
+
self,
|
|
251
|
+
event: "BaseEvent",
|
|
252
|
+
) -> bool:
|
|
253
|
+
"""Process an event from the workflow asynchronously."""
|
|
254
|
+
self._event_count += 1
|
|
255
|
+
if self._stop_requested.is_set():
|
|
256
|
+
self.log.info(
|
|
257
|
+
"Async execution stopped before AG2 workflow event processing"
|
|
258
|
+
)
|
|
259
|
+
return False
|
|
260
|
+
try:
|
|
261
|
+
if hasattr(event, "type"):
|
|
262
|
+
if event.type == "input_request":
|
|
263
|
+
prompt = getattr(
|
|
264
|
+
event,
|
|
265
|
+
"prompt",
|
|
266
|
+
getattr(event.content, "prompt", "> "),
|
|
267
|
+
)
|
|
268
|
+
password = getattr(
|
|
269
|
+
event,
|
|
270
|
+
"password",
|
|
271
|
+
getattr(event.content, "password", False),
|
|
272
|
+
)
|
|
273
|
+
user_input = self._input(
|
|
274
|
+
prompt,
|
|
275
|
+
password=password,
|
|
276
|
+
)
|
|
277
|
+
if asyncio.iscoroutine(user_input):
|
|
278
|
+
user_input = await user_input
|
|
279
|
+
await event.content.respond(user_input)
|
|
280
|
+
else:
|
|
281
|
+
self._send(event)
|
|
282
|
+
self._processed_events += 1
|
|
283
|
+
except Exception as e:
|
|
284
|
+
raise RuntimeError(
|
|
285
|
+
f"Error processing event {event}: {e}\n{traceback.format_exc()}"
|
|
286
|
+
) from e
|
|
287
|
+
return not self._stop_requested.is_set()
|
|
288
|
+
|
|
289
|
+
async def _a_run(
|
|
290
|
+
self,
|
|
291
|
+
temp_dir: Path,
|
|
292
|
+
output_file: Path,
|
|
293
|
+
uploads_root: Path | None,
|
|
294
|
+
skip_mmd: bool = False,
|
|
295
|
+
skip_timeline: bool = False,
|
|
296
|
+
) -> Union[list["RunResponseProtocol"], list["AsyncRunResponseProtocol"]]:
|
|
297
|
+
"""Run the Waldiez workflow asynchronously."""
|
|
298
|
+
|
|
299
|
+
# fmt: off
|
|
300
|
+
async def _execute_workflow() -> Union[list["RunResponseProtocol"], list["AsyncRunResponseProtocol"]]:
|
|
301
|
+
# fmt: on
|
|
302
|
+
"""Execute the workflow in an async context."""
|
|
303
|
+
from autogen.io import IOStream # pyright: ignore
|
|
304
|
+
|
|
305
|
+
from waldiez.io import StructuredIOStream
|
|
306
|
+
|
|
307
|
+
results: Union[list["AsyncRunResponseProtocol"], list["RunResponseProtocol"]] = []
|
|
308
|
+
try:
|
|
309
|
+
self._loaded_module = self._load_module(output_file, temp_dir)
|
|
310
|
+
if self._stop_requested.is_set():
|
|
311
|
+
self.log.info(
|
|
312
|
+
"Async execution stopped before AG2 workflow start"
|
|
313
|
+
)
|
|
314
|
+
return []
|
|
315
|
+
if self.structured_io:
|
|
316
|
+
stream = StructuredIOStream(
|
|
317
|
+
uploads_root=uploads_root, is_async=True
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
stream = IOStream.get_default()
|
|
321
|
+
self._print = stream.print
|
|
322
|
+
self._input = stream.input
|
|
323
|
+
self._send = stream.send
|
|
324
|
+
self._print("<Waldiez> - Starting workflow...")
|
|
325
|
+
self._print(self.waldiez.info.model_dump_json())
|
|
326
|
+
results = await self._loaded_module.main(
|
|
327
|
+
on_event=self._a_on_event
|
|
328
|
+
)
|
|
329
|
+
except SystemExit:
|
|
330
|
+
self._print("<Waldiez> - Workflow stopped by user")
|
|
331
|
+
return []
|
|
332
|
+
except Exception as e:
|
|
333
|
+
self._print(
|
|
334
|
+
f"<Waldiez> - Error loading workflow: {e}\n{traceback.format_exc()}"
|
|
335
|
+
)
|
|
336
|
+
raise RuntimeError(
|
|
337
|
+
f"Error loading workflow: {e}\n{traceback.format_exc()}"
|
|
338
|
+
) from e
|
|
339
|
+
return results
|
|
340
|
+
|
|
341
|
+
# Create cancellable task
|
|
342
|
+
task = asyncio.create_task(_execute_workflow())
|
|
343
|
+
|
|
344
|
+
# Monitor for stop requests
|
|
345
|
+
try:
|
|
346
|
+
while not task.done():
|
|
347
|
+
if self._stop_requested.is_set():
|
|
348
|
+
task.cancel()
|
|
349
|
+
self.log.info("Async execution stopped by user")
|
|
350
|
+
break
|
|
351
|
+
await asyncio.sleep(0.1)
|
|
352
|
+
# Return the task result when completed
|
|
353
|
+
return await task
|
|
354
|
+
|
|
355
|
+
except asyncio.CancelledError:
|
|
356
|
+
self.log.info("Async execution cancelled")
|
|
357
|
+
return []
|
|
358
|
+
|
|
359
|
+
async def _a_start(
|
|
360
|
+
self,
|
|
361
|
+
temp_dir: Path,
|
|
362
|
+
output_file: Path,
|
|
363
|
+
uploads_root: Path | None,
|
|
364
|
+
skip_mmd: bool = False,
|
|
365
|
+
skip_timeline: bool = False,
|
|
366
|
+
) -> None:
|
|
367
|
+
"""Start the Waldiez workflow asynchronously."""
|
|
368
|
+
|
|
369
|
+
async def run_in_background() -> None:
|
|
370
|
+
"""Run the Waldiez workflow in a background thread."""
|
|
371
|
+
try:
|
|
372
|
+
results = await self._a_run(
|
|
373
|
+
temp_dir,
|
|
374
|
+
output_file,
|
|
375
|
+
uploads_root,
|
|
376
|
+
skip_mmd=skip_mmd,
|
|
377
|
+
skip_timeline=skip_timeline,
|
|
378
|
+
)
|
|
379
|
+
if results:
|
|
380
|
+
self._print(f"<Waldiez> - Workflow completed: {results}")
|
|
381
|
+
except Exception as e:
|
|
382
|
+
self._print(
|
|
383
|
+
f"<Waldiez> - Error during workflow: {e}\n{traceback.format_exc()}"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
asyncio.create_task(run_in_background())
|
|
387
|
+
|
|
388
|
+
def _stop(self) -> None:
|
|
389
|
+
"""Stop the Waldiez workflow."""
|
|
390
|
+
self.log.info("Stopping workflow execution...")
|
|
391
|
+
self._stop_requested.set()
|
|
392
|
+
|
|
393
|
+
# Wait for graceful shutdown
|
|
394
|
+
if self._execution_thread and self._execution_thread.is_alive():
|
|
395
|
+
self._execution_thread.join(timeout=5.0)
|
|
396
|
+
|
|
397
|
+
if self._execution_thread and self._execution_thread.is_alive():
|
|
398
|
+
self.log.warning("Workflow thread did not stop gracefully")
|
|
399
|
+
|
|
400
|
+
async def _a_stop(self) -> None:
|
|
401
|
+
"""Stop the Waldiez workflow asynchronously."""
|
|
402
|
+
self.log.info("Stopping workflow execution (async)...")
|
|
403
|
+
self._stop_requested.set()
|
|
404
|
+
|
|
405
|
+
# For async, we rely on the task cancellation in _a_run
|
|
406
|
+
# Let's give it a moment to respond
|
|
407
|
+
await asyncio.sleep(0.5)
|
|
408
|
+
|
|
409
|
+
def get_execution_stats(self) -> dict[str, Any]:
|
|
410
|
+
"""Get execution statistics for standard runner.
|
|
411
|
+
|
|
412
|
+
Returns
|
|
413
|
+
-------
|
|
414
|
+
dict[str, Any]
|
|
415
|
+
A dictionary containing execution statistics such as total events,
|
|
416
|
+
processed events, whether a module was loaded, and event processing rate.
|
|
417
|
+
"""
|
|
418
|
+
base_stats = super().get_execution_stats()
|
|
419
|
+
return {
|
|
420
|
+
**base_stats,
|
|
421
|
+
"total_events": self._event_count,
|
|
422
|
+
"processed_events": self._processed_events,
|
|
423
|
+
"has_loaded_module": self._loaded_module is not None,
|
|
424
|
+
"event_processing_rate": (
|
|
425
|
+
self._processed_events / self._event_count
|
|
426
|
+
if self._event_count > 0
|
|
427
|
+
else 0
|
|
428
|
+
),
|
|
429
|
+
}
|