waldiez 0.5.8__py3-none-any.whl → 0.5.10__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 +112 -24
- waldiez/exporting/agent/exporter.py +3 -0
- waldiez/exporting/agent/extras/captain_agent_extras.py +44 -7
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -1
- waldiez/exporting/chats/utils/common.py +25 -23
- waldiez/exporting/core/__init__.py +0 -2
- waldiez/exporting/core/context.py +13 -13
- waldiez/exporting/core/protocols.py +0 -141
- waldiez/exporting/core/result.py +5 -5
- waldiez/exporting/flow/merger.py +2 -2
- waldiez/exporting/flow/orchestrator.py +1 -0
- waldiez/exporting/flow/utils/common.py +2 -2
- waldiez/exporting/flow/utils/importing.py +1 -0
- waldiez/exporting/flow/utils/logging.py +6 -7
- waldiez/exporting/tools/exporter.py +5 -0
- waldiez/exporting/tools/factory.py +4 -0
- waldiez/exporting/tools/processor.py +5 -1
- waldiez/io/_ws.py +13 -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 +17 -17
- waldiez/io/utils.py +1 -1
- waldiez/io/ws.py +9 -11
- waldiez/logger.py +180 -63
- waldiez/models/agents/agent/update_system_message.py +0 -2
- waldiez/models/agents/doc_agent/doc_agent.py +8 -1
- waldiez/models/common/dict_utils.py +169 -40
- waldiez/models/flow/flow.py +6 -6
- waldiez/models/flow/info.py +5 -1
- waldiez/models/model/_llm.py +28 -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 +474 -0
- waldiez/models/tool/predefined/_google.py +8 -6
- waldiez/models/tool/predefined/_perplexity.py +3 -0
- waldiez/models/tool/predefined/_searxng.py +3 -0
- waldiez/models/tool/predefined/_tavily.py +4 -1
- waldiez/models/tool/predefined/_wikipedia.py +4 -1
- 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 +310 -353
- waldiez/running/environment.py +1 -0
- waldiez/running/exceptions.py +9 -0
- waldiez/running/post_run.py +4 -4
- waldiez/running/pre_run.py +51 -40
- waldiez/running/protocol.py +21 -101
- waldiez/running/run_results.py +1 -1
- waldiez/running/standard_runner.py +84 -277
- waldiez/running/step_by_step/__init__.py +46 -0
- waldiez/running/step_by_step/breakpoints_mixin.py +188 -0
- waldiez/running/step_by_step/step_by_step_models.py +224 -0
- waldiez/running/step_by_step/step_by_step_runner.py +745 -0
- waldiez/running/subprocess_runner/__base__.py +282 -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 +455 -0
- waldiez/running/subprocess_runner/runner.py +561 -0
- waldiez/running/timeline_processor.py +1 -1
- waldiez/running/utils.py +376 -1
- waldiez/utils/version.py +2 -6
- waldiez/ws/__init__.py +70 -0
- waldiez/ws/__main__.py +15 -0
- waldiez/ws/_file_handler.py +201 -0
- waldiez/ws/cli.py +211 -0
- waldiez/ws/client_manager.py +835 -0
- waldiez/ws/errors.py +416 -0
- waldiez/ws/models.py +971 -0
- waldiez/ws/reloader.py +342 -0
- waldiez/ws/server.py +469 -0
- waldiez/ws/session_manager.py +393 -0
- waldiez/ws/session_stats.py +83 -0
- waldiez/ws/utils.py +385 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/METADATA +74 -74
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/RECORD +87 -65
- waldiez/running/patch_io_stream.py +0 -210
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/WHEEL +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
# pylint: disable=line-too-long
|
|
5
|
+
# pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false
|
|
6
|
+
# pyright: reportUnknownArgumentType=false, reportOptionalMemberAccess=false
|
|
7
|
+
# pylint: disable=duplicate-code
|
|
8
|
+
# flake8: noqa: E501
|
|
9
|
+
|
|
10
|
+
"""Step-by-step Waldiez runner with user interaction capabilities."""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import threading
|
|
14
|
+
import traceback
|
|
15
|
+
import uuid
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Iterable, Union
|
|
18
|
+
|
|
19
|
+
from pydantic import ValidationError
|
|
20
|
+
|
|
21
|
+
from waldiez.models.waldiez import Waldiez
|
|
22
|
+
|
|
23
|
+
from ..base_runner import WaldiezBaseRunner
|
|
24
|
+
from ..exceptions import StopRunningException
|
|
25
|
+
from ..run_results import WaldiezRunResults
|
|
26
|
+
from .breakpoints_mixin import BreakpointsMixin
|
|
27
|
+
from .step_by_step_models import (
|
|
28
|
+
HELP_MESSAGE,
|
|
29
|
+
VALID_CONTROL_COMMANDS,
|
|
30
|
+
WaldiezDebugError,
|
|
31
|
+
WaldiezDebugEventInfo,
|
|
32
|
+
WaldiezDebugInputRequest,
|
|
33
|
+
WaldiezDebugInputResponse,
|
|
34
|
+
WaldiezDebugMessage,
|
|
35
|
+
WaldiezDebugStats,
|
|
36
|
+
WaldiezDebugStepAction,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from autogen.events import BaseEvent # type: ignore
|
|
41
|
+
from autogen.messages import BaseMessage # type: ignore
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
DEBUG_INPUT_PROMPT = (
|
|
45
|
+
"[Step] (c)ontinue, (r)un, (q)uit, (i)nfo, (h)elp, (st)ats: "
|
|
46
|
+
)
|
|
47
|
+
MESSAGES = {
|
|
48
|
+
"workflow_starting": "<Waldiez step-by-step> - Starting workflow...",
|
|
49
|
+
"workflow_finished": "<Waldiez step-by-step> - Workflow finished",
|
|
50
|
+
"workflow_stopped": "<Waldiez step-by-step> - Workflow stopped by user",
|
|
51
|
+
"workflow_failed": (
|
|
52
|
+
"<Waldiez step-by-step> - Workflow execution failed: {error}"
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# pylint: disable=too-many-instance-attributes
|
|
58
|
+
# noinspection DuplicatedCode,StrFormat
|
|
59
|
+
class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
|
|
60
|
+
"""Step-by-step runner with user interaction and debugging capabilities."""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
waldiez: Waldiez,
|
|
65
|
+
output_path: str | Path | None = None,
|
|
66
|
+
uploads_root: str | Path | None = None,
|
|
67
|
+
structured_io: bool = False,
|
|
68
|
+
dot_env: str | Path | None = None,
|
|
69
|
+
auto_continue: bool = False,
|
|
70
|
+
breakpoints: Iterable[str] | None = None,
|
|
71
|
+
**kwargs: Any,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Initialize the step-by-step runner."""
|
|
74
|
+
super().__init__(
|
|
75
|
+
waldiez,
|
|
76
|
+
output_path=output_path,
|
|
77
|
+
uploads_root=uploads_root,
|
|
78
|
+
structured_io=structured_io,
|
|
79
|
+
dot_env=dot_env,
|
|
80
|
+
**kwargs,
|
|
81
|
+
)
|
|
82
|
+
BreakpointsMixin.__init__(self)
|
|
83
|
+
self._event_count = 0
|
|
84
|
+
self._processed_events = 0
|
|
85
|
+
self._step_mode = True
|
|
86
|
+
self._auto_continue = auto_continue
|
|
87
|
+
# Initialize breakpoints from the mixin and set initial breakpoints
|
|
88
|
+
if breakpoints:
|
|
89
|
+
self.set_breakpoints(breakpoints)
|
|
90
|
+
self._event_history: list[dict[str, Any]] = []
|
|
91
|
+
self._current_event: Union["BaseEvent", "BaseMessage", None] = None
|
|
92
|
+
self._known_participants = self.waldiez.info.participants
|
|
93
|
+
self._last_sender: str | None = None
|
|
94
|
+
self._last_recipient: str | None = None
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def auto_continue(self) -> bool:
|
|
98
|
+
"""Get whether auto-continue is enabled."""
|
|
99
|
+
return self._auto_continue
|
|
100
|
+
|
|
101
|
+
@auto_continue.setter
|
|
102
|
+
def auto_continue(self, value: bool) -> None:
|
|
103
|
+
"""Set whether auto-continue is enabled.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
value : bool
|
|
108
|
+
Whether to enable auto-continue.
|
|
109
|
+
"""
|
|
110
|
+
self._auto_continue = value
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def stop_requested(self) -> threading.Event:
|
|
114
|
+
"""Get the stop requested event."""
|
|
115
|
+
return self._stop_requested
|
|
116
|
+
|
|
117
|
+
def set_auto_continue(self, auto_continue: bool) -> None:
|
|
118
|
+
"""Set whether to automatically continue without user input.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
auto_continue : bool
|
|
123
|
+
Whether to automatically continue execution
|
|
124
|
+
without waiting for user input.
|
|
125
|
+
"""
|
|
126
|
+
self._auto_continue = auto_continue
|
|
127
|
+
self.log.info("Auto-continue mode set to: %s", auto_continue)
|
|
128
|
+
|
|
129
|
+
# pylint: disable=no-self-use
|
|
130
|
+
# noinspection PyMethodMayBeStatic
|
|
131
|
+
def print(self, *args: Any, **kwargs: Any) -> None:
|
|
132
|
+
"""Print.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
*args : Any
|
|
137
|
+
Positional arguments to print.
|
|
138
|
+
**kwargs : Any
|
|
139
|
+
Keyword arguments to print.
|
|
140
|
+
"""
|
|
141
|
+
WaldiezBaseRunner._print(*args, **kwargs)
|
|
142
|
+
|
|
143
|
+
def emit_event(self, event: Union["BaseEvent", "BaseMessage"]) -> None:
|
|
144
|
+
"""Emit an event.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
event : Union[BaseEvent, BaseMessage]
|
|
149
|
+
The event to emit.
|
|
150
|
+
"""
|
|
151
|
+
event_info = event.model_dump(
|
|
152
|
+
mode="json", exclude_none=True, fallback=str
|
|
153
|
+
)
|
|
154
|
+
event_info["count"] = self._event_count
|
|
155
|
+
self._last_sender = getattr(event, "sender", self._last_sender)
|
|
156
|
+
self._last_recipient = getattr(event, "recipient", self._last_recipient)
|
|
157
|
+
event_info["sender"] = self._last_sender
|
|
158
|
+
event_info["recipient"] = self._last_recipient
|
|
159
|
+
self.emit(WaldiezDebugEventInfo(event=event_info))
|
|
160
|
+
|
|
161
|
+
# noinspection PyTypeHints
|
|
162
|
+
def emit(self, message: WaldiezDebugMessage) -> None:
|
|
163
|
+
"""Emit a debug message.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
message : WaldiezDebugMessage
|
|
168
|
+
The debug message to emit.
|
|
169
|
+
"""
|
|
170
|
+
message_dump = message.model_dump(
|
|
171
|
+
mode="json", exclude_none=True, fallback=str
|
|
172
|
+
)
|
|
173
|
+
self.print(message_dump)
|
|
174
|
+
|
|
175
|
+
def _show_event_info(self) -> None:
|
|
176
|
+
if not self._current_event:
|
|
177
|
+
return
|
|
178
|
+
event_info = self._current_event.model_dump(
|
|
179
|
+
mode="json", exclude_none=True, fallback=str
|
|
180
|
+
)
|
|
181
|
+
self.emit(WaldiezDebugEventInfo(event=event_info))
|
|
182
|
+
|
|
183
|
+
def _show_stats(self) -> None:
|
|
184
|
+
stats_dict: dict[str, Any] = {
|
|
185
|
+
"events_processed": self._processed_events,
|
|
186
|
+
"total_events": self._event_count,
|
|
187
|
+
"step_mode": self._step_mode,
|
|
188
|
+
"auto_continue": self._auto_continue,
|
|
189
|
+
"breakpoints": sorted(self.get_breakpoints()),
|
|
190
|
+
"event_history_count": len(self._event_history),
|
|
191
|
+
}
|
|
192
|
+
self.emit(WaldiezDebugStats(stats=stats_dict))
|
|
193
|
+
|
|
194
|
+
def _get_user_response(
|
|
195
|
+
self, user_response: str, request_id: str
|
|
196
|
+
) -> tuple[str | None, bool]:
|
|
197
|
+
"""Get user response for step-by-step execution.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
user_response : str
|
|
202
|
+
The user's response.
|
|
203
|
+
request_id : str
|
|
204
|
+
The ID of the input request.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
tuple[str | None, bool]
|
|
209
|
+
The user's response or None if invalid,
|
|
210
|
+
and a boolean indicating validity.
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
response = WaldiezDebugInputResponse.model_validate_json(
|
|
214
|
+
user_response
|
|
215
|
+
)
|
|
216
|
+
except ValidationError as exc:
|
|
217
|
+
got = user_response.strip().lower()
|
|
218
|
+
# in cli mode, let's see if got raw response
|
|
219
|
+
# instead of a structured one
|
|
220
|
+
if got in VALID_CONTROL_COMMANDS:
|
|
221
|
+
return got, True
|
|
222
|
+
self.emit(WaldiezDebugError(error=f"Invalid input: {exc}"))
|
|
223
|
+
return None, False
|
|
224
|
+
|
|
225
|
+
if response.request_id != request_id:
|
|
226
|
+
self.emit(
|
|
227
|
+
WaldiezDebugError(
|
|
228
|
+
error=(
|
|
229
|
+
"Stale input received: "
|
|
230
|
+
f"{response.request_id} != {request_id}"
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
return None, False
|
|
235
|
+
return response.data, True
|
|
236
|
+
|
|
237
|
+
# pylint: disable=too-many-return-statements
|
|
238
|
+
def _parse_user_action( # noqa: C901
|
|
239
|
+
self, user_response: str, request_id: str
|
|
240
|
+
) -> WaldiezDebugStepAction:
|
|
241
|
+
"""Parse user action for step-by-step execution.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
user_response : str
|
|
246
|
+
The user's response.
|
|
247
|
+
request_id : str
|
|
248
|
+
The ID of the input request.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
WaldiezDebugStepAction
|
|
253
|
+
The action chosen by the user.
|
|
254
|
+
"""
|
|
255
|
+
self.log.debug("Parsing user action... '%s'", user_response)
|
|
256
|
+
user_input, is_valid = self._get_user_response(
|
|
257
|
+
user_response, request_id=request_id
|
|
258
|
+
)
|
|
259
|
+
if not is_valid:
|
|
260
|
+
return WaldiezDebugStepAction.UNKNOWN
|
|
261
|
+
if not user_input:
|
|
262
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
263
|
+
match user_input:
|
|
264
|
+
case "c":
|
|
265
|
+
self._step_mode = True
|
|
266
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
267
|
+
case "s":
|
|
268
|
+
self._step_mode = True
|
|
269
|
+
return WaldiezDebugStepAction.STEP
|
|
270
|
+
case "r":
|
|
271
|
+
self._step_mode = False
|
|
272
|
+
return WaldiezDebugStepAction.RUN
|
|
273
|
+
case "q":
|
|
274
|
+
self._stop_requested.set()
|
|
275
|
+
return WaldiezDebugStepAction.QUIT
|
|
276
|
+
case "i":
|
|
277
|
+
self._show_event_info()
|
|
278
|
+
return WaldiezDebugStepAction.INFO
|
|
279
|
+
case "h":
|
|
280
|
+
self.emit(HELP_MESSAGE)
|
|
281
|
+
return WaldiezDebugStepAction.HELP
|
|
282
|
+
case "st":
|
|
283
|
+
self._show_stats()
|
|
284
|
+
return WaldiezDebugStepAction.STATS
|
|
285
|
+
case "ab":
|
|
286
|
+
if self._current_event and hasattr(self._current_event, "type"):
|
|
287
|
+
self.add_breakpoint(self._current_event.type)
|
|
288
|
+
else:
|
|
289
|
+
self.emit(
|
|
290
|
+
WaldiezDebugError(
|
|
291
|
+
error="No current event to add breakpoint for"
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
return WaldiezDebugStepAction.ADD_BREAKPOINT
|
|
295
|
+
case "rb":
|
|
296
|
+
if self._current_event and hasattr(self._current_event, "type"):
|
|
297
|
+
self.remove_breakpoint(self._current_event.type)
|
|
298
|
+
else:
|
|
299
|
+
self.emit(
|
|
300
|
+
WaldiezDebugError(
|
|
301
|
+
error="No current event to remove breakpoint for"
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
return WaldiezDebugStepAction.REMOVE_BREAKPOINT
|
|
305
|
+
case "lb":
|
|
306
|
+
self.list_breakpoints()
|
|
307
|
+
return WaldiezDebugStepAction.LIST_BREAKPOINTS
|
|
308
|
+
case "cb":
|
|
309
|
+
self.clear_breakpoints()
|
|
310
|
+
return WaldiezDebugStepAction.CLEAR_BREAKPOINTS
|
|
311
|
+
case _:
|
|
312
|
+
self.emit(
|
|
313
|
+
WaldiezDebugError(
|
|
314
|
+
error=f"Unknown command: {user_input}, use 'h' for help"
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
return WaldiezDebugStepAction.UNKNOWN
|
|
318
|
+
|
|
319
|
+
def _get_user_action(self) -> WaldiezDebugStepAction:
|
|
320
|
+
"""Get user action for step-by-step execution.
|
|
321
|
+
|
|
322
|
+
Returns
|
|
323
|
+
-------
|
|
324
|
+
WaldiezDebugStepAction
|
|
325
|
+
The action chosen by the user.
|
|
326
|
+
"""
|
|
327
|
+
if self._auto_continue:
|
|
328
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
329
|
+
|
|
330
|
+
while True:
|
|
331
|
+
# pylint: disable=too-many-try-statements
|
|
332
|
+
request_id = str(uuid.uuid4())
|
|
333
|
+
try:
|
|
334
|
+
self.emit(
|
|
335
|
+
WaldiezDebugInputRequest(
|
|
336
|
+
prompt=DEBUG_INPUT_PROMPT, request_id=request_id
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
user_input = (
|
|
340
|
+
WaldiezBaseRunner.get_user_input(DEBUG_INPUT_PROMPT)
|
|
341
|
+
.strip()
|
|
342
|
+
.lower()
|
|
343
|
+
)
|
|
344
|
+
return self._parse_user_action(
|
|
345
|
+
user_input, request_id=request_id
|
|
346
|
+
)
|
|
347
|
+
except (KeyboardInterrupt, EOFError):
|
|
348
|
+
self._stop_requested.set()
|
|
349
|
+
return WaldiezDebugStepAction.QUIT
|
|
350
|
+
|
|
351
|
+
# pylint: disable=too-many-return-statements
|
|
352
|
+
async def _a_get_user_action(self) -> WaldiezDebugStepAction:
|
|
353
|
+
"""Get user action for step-by-step execution asynchronously.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
WaldiezDebugStepAction
|
|
358
|
+
The action chosen by the user.
|
|
359
|
+
"""
|
|
360
|
+
if self._auto_continue:
|
|
361
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
362
|
+
|
|
363
|
+
while True:
|
|
364
|
+
# pylint: disable=too-many-try-statements
|
|
365
|
+
request_id = str(uuid.uuid4())
|
|
366
|
+
try:
|
|
367
|
+
self.emit(
|
|
368
|
+
WaldiezDebugInputRequest(
|
|
369
|
+
prompt=DEBUG_INPUT_PROMPT, request_id=request_id
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
user_input = await WaldiezBaseRunner.a_get_user_input(
|
|
373
|
+
DEBUG_INPUT_PROMPT
|
|
374
|
+
)
|
|
375
|
+
user_input = user_input.strip().lower()
|
|
376
|
+
return self._parse_user_action(
|
|
377
|
+
user_input, request_id=request_id
|
|
378
|
+
)
|
|
379
|
+
except (KeyboardInterrupt, EOFError):
|
|
380
|
+
return WaldiezDebugStepAction.QUIT
|
|
381
|
+
|
|
382
|
+
def _handle_step_interaction(self) -> bool:
|
|
383
|
+
"""Handle step-by-step user interaction.
|
|
384
|
+
|
|
385
|
+
Returns
|
|
386
|
+
-------
|
|
387
|
+
bool
|
|
388
|
+
True to continue execution, False to stop.
|
|
389
|
+
"""
|
|
390
|
+
while True: # pragma: no branch
|
|
391
|
+
action = self._get_user_action()
|
|
392
|
+
|
|
393
|
+
if action in (
|
|
394
|
+
WaldiezDebugStepAction.CONTINUE,
|
|
395
|
+
WaldiezDebugStepAction.STEP,
|
|
396
|
+
):
|
|
397
|
+
return True
|
|
398
|
+
if action == WaldiezDebugStepAction.RUN:
|
|
399
|
+
return True
|
|
400
|
+
if action == WaldiezDebugStepAction.QUIT: # pragma: no branch
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
async def _a_handle_step_interaction(self) -> bool:
|
|
404
|
+
"""Handle step-by-step user interaction asynchronously.
|
|
405
|
+
|
|
406
|
+
Returns
|
|
407
|
+
-------
|
|
408
|
+
bool
|
|
409
|
+
True to continue execution, False to stop.
|
|
410
|
+
"""
|
|
411
|
+
while True: # pragma: no branch
|
|
412
|
+
action = await self._a_get_user_action()
|
|
413
|
+
|
|
414
|
+
if action in (
|
|
415
|
+
WaldiezDebugStepAction.CONTINUE,
|
|
416
|
+
WaldiezDebugStepAction.STEP,
|
|
417
|
+
):
|
|
418
|
+
return True
|
|
419
|
+
if action == WaldiezDebugStepAction.RUN:
|
|
420
|
+
return True
|
|
421
|
+
if action == WaldiezDebugStepAction.QUIT: # pragma: no branch
|
|
422
|
+
return False
|
|
423
|
+
|
|
424
|
+
# pylint: disable=unused-argument
|
|
425
|
+
def _run(
|
|
426
|
+
self,
|
|
427
|
+
temp_dir: Path,
|
|
428
|
+
output_file: Path,
|
|
429
|
+
uploads_root: Path | None,
|
|
430
|
+
skip_mmd: bool,
|
|
431
|
+
skip_timeline: bool,
|
|
432
|
+
**kwargs: Any,
|
|
433
|
+
) -> list[dict[str, Any]]:
|
|
434
|
+
"""Run the Waldiez workflow with step-by-step debugging."""
|
|
435
|
+
# pylint: disable=import-outside-toplevel
|
|
436
|
+
from autogen.io import IOStream # type: ignore
|
|
437
|
+
|
|
438
|
+
from waldiez.io import StructuredIOStream
|
|
439
|
+
|
|
440
|
+
results_container: WaldiezRunResults = {
|
|
441
|
+
"results": [],
|
|
442
|
+
"exception": None,
|
|
443
|
+
"completed": False,
|
|
444
|
+
}
|
|
445
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
446
|
+
try:
|
|
447
|
+
loaded_module = self._load_module(output_file, temp_dir)
|
|
448
|
+
if self._stop_requested.is_set(): # pragma: no cover
|
|
449
|
+
self.log.debug(
|
|
450
|
+
"Step-by-step execution stopped before workflow start"
|
|
451
|
+
)
|
|
452
|
+
return []
|
|
453
|
+
|
|
454
|
+
# noinspection DuplicatedCode
|
|
455
|
+
if self.structured_io:
|
|
456
|
+
stream = StructuredIOStream(
|
|
457
|
+
uploads_root=uploads_root, is_async=False
|
|
458
|
+
)
|
|
459
|
+
else:
|
|
460
|
+
stream = IOStream.get_default()
|
|
461
|
+
|
|
462
|
+
WaldiezBaseRunner._print = stream.print
|
|
463
|
+
WaldiezBaseRunner._input = stream.input
|
|
464
|
+
WaldiezBaseRunner._send = stream.send
|
|
465
|
+
|
|
466
|
+
self.print(MESSAGES["workflow_starting"])
|
|
467
|
+
self.print(self.waldiez.info.model_dump_json())
|
|
468
|
+
|
|
469
|
+
results = loaded_module.main(on_event=self._on_event)
|
|
470
|
+
results_container["results"] = results
|
|
471
|
+
self.print(MESSAGES["workflow_finished"])
|
|
472
|
+
|
|
473
|
+
except Exception as e:
|
|
474
|
+
if StopRunningException.reason in str(e):
|
|
475
|
+
raise StopRunningException(StopRunningException.reason) from e
|
|
476
|
+
results_container["exception"] = e
|
|
477
|
+
traceback.print_exc()
|
|
478
|
+
# noinspection StrFormat
|
|
479
|
+
self.print(MESSAGES["workflow_failed"].format(error=str(e)))
|
|
480
|
+
finally:
|
|
481
|
+
results_container["completed"] = True
|
|
482
|
+
|
|
483
|
+
return results_container["results"]
|
|
484
|
+
|
|
485
|
+
def _on_event(self, event: Union["BaseEvent", "BaseMessage"]) -> bool:
|
|
486
|
+
"""Process an event with step-by-step debugging.
|
|
487
|
+
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
event : Union[BaseEvent, BaseMessage]
|
|
491
|
+
The event to process.
|
|
492
|
+
|
|
493
|
+
Returns
|
|
494
|
+
-------
|
|
495
|
+
bool
|
|
496
|
+
True to continue, False to stop.
|
|
497
|
+
|
|
498
|
+
Raises
|
|
499
|
+
------
|
|
500
|
+
RuntimeError
|
|
501
|
+
If an error occurs while processing the event.
|
|
502
|
+
StopRunningException
|
|
503
|
+
If execution is stopped by user request.
|
|
504
|
+
"""
|
|
505
|
+
self._event_count += 1
|
|
506
|
+
self._current_event = event
|
|
507
|
+
|
|
508
|
+
if self._stop_requested.is_set():
|
|
509
|
+
self.log.debug(
|
|
510
|
+
"Step-by-step execution stopped before event processing"
|
|
511
|
+
)
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
# Store event in history
|
|
515
|
+
event_info = event.model_dump(
|
|
516
|
+
mode="json", exclude_none=True, fallback=str
|
|
517
|
+
)
|
|
518
|
+
event_info["count"] = self._event_count
|
|
519
|
+
self._event_history.append(event_info)
|
|
520
|
+
|
|
521
|
+
# pylint: disable=too-many-try-statements
|
|
522
|
+
try:
|
|
523
|
+
# Show event information if we should break
|
|
524
|
+
if self.should_break_on_event(
|
|
525
|
+
event, self._step_mode
|
|
526
|
+
): # pragma: no branch
|
|
527
|
+
self.emit_event(event)
|
|
528
|
+
if not self._handle_step_interaction():
|
|
529
|
+
self._stop_requested.set()
|
|
530
|
+
if hasattr(event, "type") and event.type == "input_request":
|
|
531
|
+
event.content.respond("exit")
|
|
532
|
+
return True
|
|
533
|
+
raise StopRunningException(StopRunningException.reason)
|
|
534
|
+
|
|
535
|
+
# Process the event
|
|
536
|
+
WaldiezBaseRunner.process_event(event)
|
|
537
|
+
self._processed_events += 1
|
|
538
|
+
|
|
539
|
+
except Exception as e:
|
|
540
|
+
if not isinstance(e, StopRunningException):
|
|
541
|
+
raise RuntimeError(
|
|
542
|
+
f"Error processing event {event}: "
|
|
543
|
+
f"{e}\n{traceback.format_exc()}"
|
|
544
|
+
) from e
|
|
545
|
+
raise StopRunningException(StopRunningException.reason) from e
|
|
546
|
+
return not self._stop_requested.is_set()
|
|
547
|
+
|
|
548
|
+
# pylint: disable=too-complex
|
|
549
|
+
async def _a_run(
|
|
550
|
+
self,
|
|
551
|
+
temp_dir: Path,
|
|
552
|
+
output_file: Path,
|
|
553
|
+
uploads_root: Path | None,
|
|
554
|
+
skip_mmd: bool = False,
|
|
555
|
+
skip_timeline: bool = False,
|
|
556
|
+
**kwargs: Any,
|
|
557
|
+
) -> list[dict[str, Any]]:
|
|
558
|
+
"""Run the Waldiez workflow with step-by-step debugging (async)."""
|
|
559
|
+
|
|
560
|
+
async def _execute_workflow() -> list[dict[str, Any]]:
|
|
561
|
+
"""Execute the workflow in an async context."""
|
|
562
|
+
# pylint: disable=import-outside-toplevel
|
|
563
|
+
from autogen.io import IOStream # pyright: ignore
|
|
564
|
+
|
|
565
|
+
from waldiez.io import StructuredIOStream
|
|
566
|
+
|
|
567
|
+
results: list[dict[str, Any]]
|
|
568
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
569
|
+
try:
|
|
570
|
+
loaded_module = self._load_module(output_file, temp_dir)
|
|
571
|
+
if self._stop_requested.is_set(): # pragma: no cover
|
|
572
|
+
self.log.debug(
|
|
573
|
+
"step-by-step execution stopped before workflow start"
|
|
574
|
+
)
|
|
575
|
+
return []
|
|
576
|
+
|
|
577
|
+
if self.structured_io:
|
|
578
|
+
stream = StructuredIOStream(
|
|
579
|
+
uploads_root=uploads_root, is_async=True
|
|
580
|
+
)
|
|
581
|
+
else:
|
|
582
|
+
stream = IOStream.get_default()
|
|
583
|
+
|
|
584
|
+
WaldiezBaseRunner._print = stream.print
|
|
585
|
+
WaldiezBaseRunner._input = stream.input
|
|
586
|
+
WaldiezBaseRunner._send = stream.send
|
|
587
|
+
|
|
588
|
+
self.print(MESSAGES["workflow_starting"])
|
|
589
|
+
self.print(self.waldiez.info.model_dump_json())
|
|
590
|
+
|
|
591
|
+
results = await loaded_module.main(on_event=self._a_on_event)
|
|
592
|
+
self.print(MESSAGES["workflow_finished"])
|
|
593
|
+
|
|
594
|
+
except Exception as e:
|
|
595
|
+
if StopRunningException.reason in str(e):
|
|
596
|
+
raise StopRunningException(
|
|
597
|
+
StopRunningException.reason
|
|
598
|
+
) from e
|
|
599
|
+
self.print(MESSAGES["workflow_failed"].format(error=str(e)))
|
|
600
|
+
traceback.print_exc()
|
|
601
|
+
return []
|
|
602
|
+
|
|
603
|
+
return results
|
|
604
|
+
|
|
605
|
+
# Create cancellable task
|
|
606
|
+
task = asyncio.create_task(_execute_workflow())
|
|
607
|
+
|
|
608
|
+
# Monitor for stop requests
|
|
609
|
+
# pylint: disable=too-many-try-statements
|
|
610
|
+
try:
|
|
611
|
+
while not task.done():
|
|
612
|
+
if self._stop_requested.is_set():
|
|
613
|
+
task.cancel()
|
|
614
|
+
self.log.debug("Step-by-step execution stopped by user")
|
|
615
|
+
break
|
|
616
|
+
await asyncio.sleep(0.1)
|
|
617
|
+
return await task
|
|
618
|
+
|
|
619
|
+
except asyncio.CancelledError:
|
|
620
|
+
self.log.debug("Step-by-step execution cancelled")
|
|
621
|
+
return []
|
|
622
|
+
|
|
623
|
+
async def _a_on_event(
|
|
624
|
+
self, event: Union["BaseEvent", "BaseMessage"]
|
|
625
|
+
) -> bool:
|
|
626
|
+
"""Process an event with step-by-step debugging asynchronously.
|
|
627
|
+
|
|
628
|
+
Parameters
|
|
629
|
+
----------
|
|
630
|
+
event : Union[BaseEvent, BaseMessage]
|
|
631
|
+
The event to process.
|
|
632
|
+
|
|
633
|
+
Returns
|
|
634
|
+
-------
|
|
635
|
+
bool
|
|
636
|
+
True to continue, False to stop.
|
|
637
|
+
|
|
638
|
+
Raises
|
|
639
|
+
------
|
|
640
|
+
RuntimeError
|
|
641
|
+
If an error occurs while processing the event.
|
|
642
|
+
StopRunningException
|
|
643
|
+
If execution is stopped by user request.
|
|
644
|
+
"""
|
|
645
|
+
self._event_count += 1
|
|
646
|
+
self._current_event = event
|
|
647
|
+
|
|
648
|
+
if self._stop_requested.is_set():
|
|
649
|
+
self.log.debug(
|
|
650
|
+
"Async step-by-step execution stopped before event processing"
|
|
651
|
+
)
|
|
652
|
+
return False
|
|
653
|
+
|
|
654
|
+
# Store event in history
|
|
655
|
+
event_info = event.model_dump(
|
|
656
|
+
mode="json", exclude_none=True, fallback=str
|
|
657
|
+
)
|
|
658
|
+
event_info["count"] = self._event_count
|
|
659
|
+
self._event_history.append(event_info)
|
|
660
|
+
|
|
661
|
+
# pylint: disable=too-many-try-statements
|
|
662
|
+
try:
|
|
663
|
+
# Show event information if we should break
|
|
664
|
+
if self.should_break_on_event(
|
|
665
|
+
event, self._step_mode
|
|
666
|
+
): # pragma: no branch
|
|
667
|
+
self.emit_event(event)
|
|
668
|
+
|
|
669
|
+
# Handle step interaction
|
|
670
|
+
if not await self._a_handle_step_interaction():
|
|
671
|
+
self._stop_requested.set()
|
|
672
|
+
if hasattr(event, "type") and event.type == "input_request":
|
|
673
|
+
event.content.respond("exit")
|
|
674
|
+
return True
|
|
675
|
+
raise StopRunningException(StopRunningException.reason)
|
|
676
|
+
|
|
677
|
+
# Process the event
|
|
678
|
+
await WaldiezBaseRunner.a_process_event(event)
|
|
679
|
+
self._processed_events += 1
|
|
680
|
+
|
|
681
|
+
except Exception as e:
|
|
682
|
+
if not isinstance(e, StopRunningException):
|
|
683
|
+
raise RuntimeError(
|
|
684
|
+
f"Error processing event {event}: "
|
|
685
|
+
f"{e}\n{traceback.format_exc()}"
|
|
686
|
+
) from e
|
|
687
|
+
raise StopRunningException(StopRunningException.reason) from e
|
|
688
|
+
return not self._stop_requested.is_set()
|
|
689
|
+
|
|
690
|
+
def get_execution_stats(self) -> dict[str, Any]:
|
|
691
|
+
"""Get execution statistics for step-by-step runner.
|
|
692
|
+
|
|
693
|
+
Returns
|
|
694
|
+
-------
|
|
695
|
+
dict[str, Any]
|
|
696
|
+
A dictionary containing execution statistics.
|
|
697
|
+
"""
|
|
698
|
+
return {
|
|
699
|
+
"total_events": self._event_count,
|
|
700
|
+
"processed_events": self._processed_events,
|
|
701
|
+
"event_processing_rate": (
|
|
702
|
+
self._processed_events / self._event_count
|
|
703
|
+
if self._event_count > 0
|
|
704
|
+
else 0
|
|
705
|
+
),
|
|
706
|
+
"step_mode": self._step_mode,
|
|
707
|
+
"auto_continue": self._auto_continue,
|
|
708
|
+
"breakpoints": sorted(self.get_breakpoints()),
|
|
709
|
+
"event_history_count": len(self._event_history),
|
|
710
|
+
"last_sender": self._last_sender,
|
|
711
|
+
"last_recipient": self._last_recipient,
|
|
712
|
+
"known_participants": [
|
|
713
|
+
p.model_dump() for p in self._known_participants
|
|
714
|
+
],
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
def get_event_history(self) -> list[dict[str, Any]]:
|
|
718
|
+
"""Get the history of processed events.
|
|
719
|
+
|
|
720
|
+
Returns
|
|
721
|
+
-------
|
|
722
|
+
list[dict[str, Any]]
|
|
723
|
+
List of event information dictionaries.
|
|
724
|
+
"""
|
|
725
|
+
return self._event_history.copy()
|
|
726
|
+
|
|
727
|
+
def enable_auto_continue(self, enabled: bool = True) -> None:
|
|
728
|
+
"""Enable or disable auto-continue mode.
|
|
729
|
+
|
|
730
|
+
Parameters
|
|
731
|
+
----------
|
|
732
|
+
enabled : bool, optional
|
|
733
|
+
Whether to enable auto-continue, by default True.
|
|
734
|
+
"""
|
|
735
|
+
self._auto_continue = enabled
|
|
736
|
+
|
|
737
|
+
def enable_step_mode(self, enabled: bool = True) -> None:
|
|
738
|
+
"""Enable or disable step mode.
|
|
739
|
+
|
|
740
|
+
Parameters
|
|
741
|
+
----------
|
|
742
|
+
enabled : bool, optional
|
|
743
|
+
Whether to enable step mode, by default True.
|
|
744
|
+
"""
|
|
745
|
+
self._step_mode = enabled
|