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
|
@@ -0,0 +1,750 @@
|
|
|
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
|
+
from collections import deque
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Iterable, Union
|
|
18
|
+
|
|
19
|
+
from pydantic import ValidationError
|
|
20
|
+
|
|
21
|
+
from waldiez.io.utils import DEBUG_INPUT_PROMPT, gen_id
|
|
22
|
+
from waldiez.models.waldiez import Waldiez
|
|
23
|
+
from waldiez.running.step_by_step.command_handler import CommandHandler
|
|
24
|
+
from waldiez.running.step_by_step.events_processor import EventProcessor
|
|
25
|
+
|
|
26
|
+
from ..base_runner import WaldiezBaseRunner
|
|
27
|
+
from ..exceptions import StopRunningException
|
|
28
|
+
from ..run_results import WaldiezRunResults
|
|
29
|
+
from .breakpoints_mixin import BreakpointsMixin
|
|
30
|
+
from .step_by_step_models import (
|
|
31
|
+
VALID_CONTROL_COMMANDS,
|
|
32
|
+
WaldiezDebugConfig,
|
|
33
|
+
WaldiezDebugError,
|
|
34
|
+
WaldiezDebugEventInfo,
|
|
35
|
+
WaldiezDebugInputRequest,
|
|
36
|
+
WaldiezDebugInputResponse,
|
|
37
|
+
WaldiezDebugMessage,
|
|
38
|
+
WaldiezDebugStats,
|
|
39
|
+
WaldiezDebugStepAction,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from autogen.events import BaseEvent # type: ignore
|
|
44
|
+
from autogen.messages import BaseMessage # type: ignore
|
|
45
|
+
|
|
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
|
+
"""Refactored step-by-step runner with improved architecture."""
|
|
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
|
+
config: WaldiezDebugConfig | None = None,
|
|
72
|
+
**kwargs: Any,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Initialize the step-by-step runner."""
|
|
75
|
+
super().__init__(
|
|
76
|
+
waldiez,
|
|
77
|
+
output_path=output_path,
|
|
78
|
+
uploads_root=uploads_root,
|
|
79
|
+
structured_io=structured_io,
|
|
80
|
+
dot_env=dot_env,
|
|
81
|
+
**kwargs,
|
|
82
|
+
)
|
|
83
|
+
BreakpointsMixin.__init__(self)
|
|
84
|
+
|
|
85
|
+
# Configuration
|
|
86
|
+
self._config = config or WaldiezDebugConfig()
|
|
87
|
+
self._config.auto_continue = auto_continue
|
|
88
|
+
|
|
89
|
+
# Core state
|
|
90
|
+
self._event_count = 0
|
|
91
|
+
self._processed_events = 0
|
|
92
|
+
self._step_mode = self._config.step_mode
|
|
93
|
+
|
|
94
|
+
# Use deque for efficient FIFO operations on event history
|
|
95
|
+
self._event_history: deque[dict[str, Any]] = deque(
|
|
96
|
+
maxlen=self._config.max_event_history
|
|
97
|
+
)
|
|
98
|
+
self._current_event: Union["BaseEvent", "BaseMessage", None] = None
|
|
99
|
+
|
|
100
|
+
# Participant tracking
|
|
101
|
+
self._known_participants = self.waldiez.info.participants
|
|
102
|
+
self._last_sender: str | None = None
|
|
103
|
+
self._last_recipient: str | None = None
|
|
104
|
+
|
|
105
|
+
# Initialize breakpoints
|
|
106
|
+
if breakpoints:
|
|
107
|
+
_, errors = self.import_breakpoints(list(breakpoints))
|
|
108
|
+
if errors:
|
|
109
|
+
for error in errors:
|
|
110
|
+
self.log.warning("Breakpoint import error: %s", error)
|
|
111
|
+
|
|
112
|
+
# Command handling
|
|
113
|
+
self._command_handler = CommandHandler(self)
|
|
114
|
+
self._event_processor = EventProcessor(self)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def auto_continue(self) -> bool:
|
|
118
|
+
"""Get whether auto-continue is enabled."""
|
|
119
|
+
return self._config.auto_continue
|
|
120
|
+
|
|
121
|
+
@auto_continue.setter
|
|
122
|
+
def auto_continue(self, value: bool) -> None:
|
|
123
|
+
"""Set whether auto-continue is enabled.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
value : bool
|
|
128
|
+
Whether to enable auto-continue.
|
|
129
|
+
"""
|
|
130
|
+
self._config.auto_continue = value
|
|
131
|
+
self.log.debug("Auto-continue mode set to: %s", value)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def step_mode(self) -> bool:
|
|
135
|
+
"""Get the step mode.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
bool
|
|
140
|
+
Whether the step mode is enabled.
|
|
141
|
+
"""
|
|
142
|
+
return self._step_mode
|
|
143
|
+
|
|
144
|
+
@step_mode.setter
|
|
145
|
+
def step_mode(self, value: bool) -> None:
|
|
146
|
+
"""Set the step mode.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
value : bool
|
|
151
|
+
Whether to enable step mode.
|
|
152
|
+
"""
|
|
153
|
+
self._step_mode = value
|
|
154
|
+
self.log.debug("Step mode set to: %s", value)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def last_sender(self) -> str | None:
|
|
158
|
+
"""Get the last sender.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
str | None
|
|
163
|
+
The last sender, if available.
|
|
164
|
+
"""
|
|
165
|
+
return self._last_sender
|
|
166
|
+
|
|
167
|
+
@last_sender.setter
|
|
168
|
+
def last_sender(self, value: str | None) -> None:
|
|
169
|
+
"""Set the last sender.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
value : str | None
|
|
174
|
+
The last sender to set.
|
|
175
|
+
"""
|
|
176
|
+
self._last_sender = value
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def last_recipient(self) -> str | None:
|
|
180
|
+
"""Get the last recipient.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
str | None
|
|
185
|
+
The last recipient, if available.
|
|
186
|
+
"""
|
|
187
|
+
return self._last_recipient
|
|
188
|
+
|
|
189
|
+
@last_recipient.setter
|
|
190
|
+
def last_recipient(self, value: str | None) -> None:
|
|
191
|
+
"""Set the last recipient.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
value : str | None
|
|
196
|
+
The last recipient to set.
|
|
197
|
+
"""
|
|
198
|
+
self._last_recipient = value
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def stop_requested(self) -> threading.Event:
|
|
202
|
+
"""Get the stop requested event."""
|
|
203
|
+
return self._stop_requested
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def max_event_history(self) -> int:
|
|
207
|
+
"""Get the maximum event history size."""
|
|
208
|
+
return self._config.max_event_history
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def print(*args: Any, **kwargs: Any) -> None:
|
|
212
|
+
"""Print method.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
*args : Any
|
|
217
|
+
Positional arguments to print.
|
|
218
|
+
**kwargs : Any
|
|
219
|
+
Keyword arguments to print.
|
|
220
|
+
"""
|
|
221
|
+
WaldiezBaseRunner.print(*args, **kwargs)
|
|
222
|
+
|
|
223
|
+
def add_to_history(self, event_info: dict[str, Any]) -> None:
|
|
224
|
+
"""Add an event to the history.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
event_info : dict[str, Any]
|
|
229
|
+
The event information to add to the history.
|
|
230
|
+
"""
|
|
231
|
+
self._event_history.append(event_info)
|
|
232
|
+
|
|
233
|
+
def pop_event(self) -> None:
|
|
234
|
+
"""Pop event from the history."""
|
|
235
|
+
if self._event_history:
|
|
236
|
+
self._event_history.popleft()
|
|
237
|
+
|
|
238
|
+
def emit_event(
|
|
239
|
+
self, event: Union["BaseEvent", "BaseMessage", dict[str, Any]]
|
|
240
|
+
) -> None:
|
|
241
|
+
"""Emit an event.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
event : BaseEvent | BaseMessage | dict[str, Any]
|
|
246
|
+
The event to emit.
|
|
247
|
+
"""
|
|
248
|
+
if not isinstance(event, dict):
|
|
249
|
+
event_info = event.model_dump(
|
|
250
|
+
mode="json", exclude_none=True, fallback=str
|
|
251
|
+
)
|
|
252
|
+
event_info["count"] = self._event_count
|
|
253
|
+
event_info["sender"] = getattr(event, "sender", self._last_sender)
|
|
254
|
+
event_info["recipient"] = getattr(
|
|
255
|
+
event, "recipient", self._last_recipient
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
event_info = event
|
|
259
|
+
self.emit(WaldiezDebugEventInfo(event=event_info))
|
|
260
|
+
|
|
261
|
+
# noinspection PyTypeHints
|
|
262
|
+
def emit(self, message: WaldiezDebugMessage) -> None:
|
|
263
|
+
"""Emit a debug message.
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
message : WaldiezDebugMessage
|
|
268
|
+
The message to emit.
|
|
269
|
+
"""
|
|
270
|
+
message_dump = message.model_dump(
|
|
271
|
+
mode="json", exclude_none=True, fallback=str
|
|
272
|
+
)
|
|
273
|
+
self.print(message_dump)
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def current_event(self) -> Union["BaseEvent", "BaseMessage", None]:
|
|
277
|
+
"""Get the current event.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
Union["BaseEvent", "BaseMessage", None]
|
|
282
|
+
The current event, if available.
|
|
283
|
+
"""
|
|
284
|
+
return self._current_event
|
|
285
|
+
|
|
286
|
+
@current_event.setter
|
|
287
|
+
def current_event(
|
|
288
|
+
self, value: Union["BaseEvent", "BaseMessage", None]
|
|
289
|
+
) -> None:
|
|
290
|
+
"""Set the current event.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
value : Union["BaseEvent", "BaseMessage", None]
|
|
295
|
+
The event to set as the current event.
|
|
296
|
+
"""
|
|
297
|
+
self._current_event = value
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def event_count(self) -> int:
|
|
301
|
+
"""Get the current event count.
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
int
|
|
306
|
+
The current event count.
|
|
307
|
+
"""
|
|
308
|
+
return self._event_count
|
|
309
|
+
|
|
310
|
+
def event_plus_one(self) -> None:
|
|
311
|
+
"""Increment the current event count."""
|
|
312
|
+
self._event_count += 1
|
|
313
|
+
|
|
314
|
+
def show_event_info(self) -> None:
|
|
315
|
+
"""Show detailed information about the current event."""
|
|
316
|
+
if not self._current_event:
|
|
317
|
+
self.emit(WaldiezDebugError(error="No current event to display"))
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
event_info = self._current_event.model_dump(
|
|
321
|
+
mode="json", exclude_none=True, fallback=str
|
|
322
|
+
)
|
|
323
|
+
# Add additional context
|
|
324
|
+
event_info["_meta"] = {
|
|
325
|
+
"event_number": self._event_count,
|
|
326
|
+
"processed_events": self._processed_events,
|
|
327
|
+
"step_mode": self._step_mode,
|
|
328
|
+
"has_breakpoints": len(self._breakpoints) > 0,
|
|
329
|
+
}
|
|
330
|
+
self.emit(WaldiezDebugEventInfo(event=event_info))
|
|
331
|
+
|
|
332
|
+
def show_stats(self) -> None:
|
|
333
|
+
"""Show comprehensive execution statistics."""
|
|
334
|
+
base_stats: dict[str, Any] = {
|
|
335
|
+
"execution": {
|
|
336
|
+
"events_processed": self._processed_events,
|
|
337
|
+
"total_events": self._event_count,
|
|
338
|
+
"processing_rate": (
|
|
339
|
+
f"{(self._processed_events / self._event_count * 100):.1f}%"
|
|
340
|
+
if self._event_count > 0
|
|
341
|
+
else "0%"
|
|
342
|
+
),
|
|
343
|
+
},
|
|
344
|
+
"mode": {
|
|
345
|
+
"step_mode": self._step_mode,
|
|
346
|
+
"auto_continue": self._config.auto_continue,
|
|
347
|
+
},
|
|
348
|
+
"history": {
|
|
349
|
+
"event_history_count": len(self._event_history),
|
|
350
|
+
"max_history_size": self._config.max_event_history,
|
|
351
|
+
"memory_usage": f"{len(self._event_history) * 200}B (est.)",
|
|
352
|
+
},
|
|
353
|
+
"participants": {
|
|
354
|
+
"last_sender": self._last_sender,
|
|
355
|
+
"last_recipient": self._last_recipient,
|
|
356
|
+
"known_participants": len(self._known_participants),
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
# Merge with breakpoint stats
|
|
361
|
+
breakpoint_stats = self.get_breakpoint_stats()
|
|
362
|
+
stats_dict: dict[str, Any] = {
|
|
363
|
+
**base_stats,
|
|
364
|
+
"breakpoints": breakpoint_stats,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
self.emit(WaldiezDebugStats(stats=stats_dict))
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def execution_stats(self) -> dict[str, Any]:
|
|
371
|
+
"""Get comprehensive execution statistics.
|
|
372
|
+
|
|
373
|
+
Returns
|
|
374
|
+
-------
|
|
375
|
+
dict[str, Any]
|
|
376
|
+
A dictionary containing execution statistics.
|
|
377
|
+
"""
|
|
378
|
+
base_stats: dict[str, Any] = {
|
|
379
|
+
"total_events": self._event_count,
|
|
380
|
+
"processed_events": self._processed_events,
|
|
381
|
+
"event_processing_rate": (
|
|
382
|
+
self._processed_events / self._event_count
|
|
383
|
+
if self._event_count > 0
|
|
384
|
+
else 0
|
|
385
|
+
),
|
|
386
|
+
"step_mode": self._step_mode,
|
|
387
|
+
"auto_continue": self._config.auto_continue,
|
|
388
|
+
"event_history_count": len(self._event_history),
|
|
389
|
+
"last_sender": self._last_sender,
|
|
390
|
+
"last_recipient": self._last_recipient,
|
|
391
|
+
"known_participants": [
|
|
392
|
+
p.model_dump() for p in self._known_participants
|
|
393
|
+
],
|
|
394
|
+
"config": self._config.model_dump(),
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {**base_stats, "breakpoints": self.get_breakpoint_stats()}
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def event_history(self) -> list[dict[str, Any]]:
|
|
401
|
+
"""Get the history of processed events.
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
list[dict[str, Any]]
|
|
406
|
+
A list of dictionaries containing event history.
|
|
407
|
+
"""
|
|
408
|
+
return list(self._event_history)
|
|
409
|
+
|
|
410
|
+
def reset_session(self) -> None:
|
|
411
|
+
"""Reset the debugging session state."""
|
|
412
|
+
self._event_count = 0
|
|
413
|
+
self._processed_events = 0
|
|
414
|
+
self._event_history.clear()
|
|
415
|
+
self._current_event = None
|
|
416
|
+
self._last_sender = None
|
|
417
|
+
self._last_recipient = None
|
|
418
|
+
self.reset_stats()
|
|
419
|
+
self.log.info("Debug session reset")
|
|
420
|
+
|
|
421
|
+
def _get_user_response(
|
|
422
|
+
self,
|
|
423
|
+
user_response: str,
|
|
424
|
+
request_id: str,
|
|
425
|
+
skip_id_check: bool = False,
|
|
426
|
+
) -> tuple[str | None, bool]:
|
|
427
|
+
"""Get and validate user response."""
|
|
428
|
+
try:
|
|
429
|
+
response = WaldiezDebugInputResponse.model_validate_json(
|
|
430
|
+
user_response
|
|
431
|
+
)
|
|
432
|
+
except ValidationError as exc:
|
|
433
|
+
# Handle raw CLI input
|
|
434
|
+
got = user_response.strip().lower()
|
|
435
|
+
if got in VALID_CONTROL_COMMANDS:
|
|
436
|
+
return got, True
|
|
437
|
+
self.emit(WaldiezDebugError(error=f"Invalid input: {exc}"))
|
|
438
|
+
return None, False
|
|
439
|
+
|
|
440
|
+
if not skip_id_check and response.request_id != request_id:
|
|
441
|
+
self.emit(
|
|
442
|
+
WaldiezDebugError(
|
|
443
|
+
error=f"Stale input received: {response.request_id} != {request_id}"
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
return None, False
|
|
447
|
+
|
|
448
|
+
return response.data, True
|
|
449
|
+
|
|
450
|
+
def _parse_user_action(
|
|
451
|
+
self, user_response: str, request_id: str
|
|
452
|
+
) -> WaldiezDebugStepAction:
|
|
453
|
+
"""Parse user action using the command handler."""
|
|
454
|
+
self.log.debug("Parsing user action... '%s'", user_response)
|
|
455
|
+
|
|
456
|
+
user_input, is_valid = self._get_user_response(
|
|
457
|
+
user_response,
|
|
458
|
+
request_id=request_id,
|
|
459
|
+
skip_id_check=True,
|
|
460
|
+
)
|
|
461
|
+
if not is_valid:
|
|
462
|
+
return WaldiezDebugStepAction.UNKNOWN
|
|
463
|
+
|
|
464
|
+
return self._command_handler.handle_command(user_input or "")
|
|
465
|
+
|
|
466
|
+
def _get_user_action(self) -> WaldiezDebugStepAction:
|
|
467
|
+
"""Get user action with timeout support."""
|
|
468
|
+
if self._config.auto_continue:
|
|
469
|
+
self.step_mode = True
|
|
470
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
471
|
+
|
|
472
|
+
while True:
|
|
473
|
+
request_id = gen_id()
|
|
474
|
+
try:
|
|
475
|
+
if not self.structured_io:
|
|
476
|
+
self.emit(
|
|
477
|
+
WaldiezDebugInputRequest(
|
|
478
|
+
prompt=DEBUG_INPUT_PROMPT, request_id=request_id
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
482
|
+
self.log.warning("Failed to emit input request: %s", e)
|
|
483
|
+
try:
|
|
484
|
+
user_input = WaldiezBaseRunner.get_user_input(
|
|
485
|
+
DEBUG_INPUT_PROMPT
|
|
486
|
+
).strip()
|
|
487
|
+
return self._parse_user_action(
|
|
488
|
+
user_input, request_id=request_id
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
except (KeyboardInterrupt, EOFError):
|
|
492
|
+
self._stop_requested.set()
|
|
493
|
+
return WaldiezDebugStepAction.QUIT
|
|
494
|
+
|
|
495
|
+
async def _a_get_user_action(self) -> WaldiezDebugStepAction:
|
|
496
|
+
"""Get user action asynchronously."""
|
|
497
|
+
if self._config.auto_continue:
|
|
498
|
+
self.step_mode = True
|
|
499
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
500
|
+
|
|
501
|
+
while True:
|
|
502
|
+
request_id = gen_id()
|
|
503
|
+
# pylint: disable=too-many-try-statements
|
|
504
|
+
try:
|
|
505
|
+
self.emit(
|
|
506
|
+
WaldiezDebugInputRequest(
|
|
507
|
+
prompt=DEBUG_INPUT_PROMPT, request_id=request_id
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
user_input = await WaldiezBaseRunner.a_get_user_input(
|
|
512
|
+
DEBUG_INPUT_PROMPT
|
|
513
|
+
)
|
|
514
|
+
user_input = user_input.strip()
|
|
515
|
+
return self._parse_user_action(
|
|
516
|
+
user_input, request_id=request_id
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
except (KeyboardInterrupt, EOFError):
|
|
520
|
+
return WaldiezDebugStepAction.QUIT
|
|
521
|
+
|
|
522
|
+
def _handle_step_interaction(self) -> bool:
|
|
523
|
+
"""Handle step-by-step user interaction."""
|
|
524
|
+
while True:
|
|
525
|
+
action = self._get_user_action()
|
|
526
|
+
if action in (
|
|
527
|
+
WaldiezDebugStepAction.CONTINUE,
|
|
528
|
+
WaldiezDebugStepAction.STEP,
|
|
529
|
+
):
|
|
530
|
+
return True
|
|
531
|
+
if action == WaldiezDebugStepAction.RUN:
|
|
532
|
+
return True
|
|
533
|
+
if action == WaldiezDebugStepAction.QUIT:
|
|
534
|
+
return False
|
|
535
|
+
# For other actions (info, help, etc.), continue the loop
|
|
536
|
+
|
|
537
|
+
async def _a_handle_step_interaction(self) -> bool:
|
|
538
|
+
"""Handle step-by-step user interaction asynchronously."""
|
|
539
|
+
while True:
|
|
540
|
+
action = await self._a_get_user_action()
|
|
541
|
+
if action in (
|
|
542
|
+
WaldiezDebugStepAction.CONTINUE,
|
|
543
|
+
WaldiezDebugStepAction.STEP,
|
|
544
|
+
):
|
|
545
|
+
return True
|
|
546
|
+
if action == WaldiezDebugStepAction.RUN:
|
|
547
|
+
return True
|
|
548
|
+
if action == WaldiezDebugStepAction.QUIT:
|
|
549
|
+
return False
|
|
550
|
+
# For other actions (info, help, etc.), continue the loop
|
|
551
|
+
|
|
552
|
+
def _run(
|
|
553
|
+
self,
|
|
554
|
+
temp_dir: Path,
|
|
555
|
+
output_file: Path,
|
|
556
|
+
uploads_root: Path | None,
|
|
557
|
+
skip_mmd: bool,
|
|
558
|
+
skip_timeline: bool,
|
|
559
|
+
**kwargs: Any,
|
|
560
|
+
) -> list[dict[str, Any]]:
|
|
561
|
+
"""Run the Waldiez workflow with step-by-step debugging."""
|
|
562
|
+
# pylint: disable=import-outside-toplevel
|
|
563
|
+
from autogen.io import IOStream # type: ignore
|
|
564
|
+
|
|
565
|
+
from waldiez.io import StructuredIOStream
|
|
566
|
+
|
|
567
|
+
results_container: WaldiezRunResults = {
|
|
568
|
+
"results": [],
|
|
569
|
+
"exception": None,
|
|
570
|
+
"completed": False,
|
|
571
|
+
}
|
|
572
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
573
|
+
try:
|
|
574
|
+
loaded_module = self._load_module(output_file, temp_dir)
|
|
575
|
+
if self._stop_requested.is_set():
|
|
576
|
+
self.log.debug(
|
|
577
|
+
"Step-by-step execution stopped before workflow start"
|
|
578
|
+
)
|
|
579
|
+
return []
|
|
580
|
+
|
|
581
|
+
# Setup I/O
|
|
582
|
+
if self.structured_io:
|
|
583
|
+
stream = StructuredIOStream(
|
|
584
|
+
uploads_root=uploads_root, is_async=False
|
|
585
|
+
)
|
|
586
|
+
else:
|
|
587
|
+
stream = IOStream.get_default()
|
|
588
|
+
|
|
589
|
+
WaldiezBaseRunner._print = stream.print
|
|
590
|
+
WaldiezBaseRunner._input = stream.input
|
|
591
|
+
WaldiezBaseRunner._send = stream.send
|
|
592
|
+
|
|
593
|
+
self.print(MESSAGES["workflow_starting"])
|
|
594
|
+
self.print(self.waldiez.info.model_dump_json())
|
|
595
|
+
|
|
596
|
+
results = loaded_module.main(on_event=self._on_event)
|
|
597
|
+
results_container["results"] = results
|
|
598
|
+
self.print(MESSAGES["workflow_finished"])
|
|
599
|
+
|
|
600
|
+
except Exception as e:
|
|
601
|
+
if StopRunningException.reason in str(e):
|
|
602
|
+
raise StopRunningException(StopRunningException.reason) from e
|
|
603
|
+
results_container["exception"] = e
|
|
604
|
+
traceback.print_exc()
|
|
605
|
+
self.print(MESSAGES["workflow_failed"].format(error=str(e)))
|
|
606
|
+
finally:
|
|
607
|
+
results_container["completed"] = True
|
|
608
|
+
|
|
609
|
+
return results_container["results"]
|
|
610
|
+
|
|
611
|
+
def _on_event(self, event: Union["BaseEvent", "BaseMessage"]) -> bool:
|
|
612
|
+
"""Process an event with step-by-step debugging."""
|
|
613
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
614
|
+
try:
|
|
615
|
+
# Use the event processor for core logic
|
|
616
|
+
result = self._event_processor.process_event(event)
|
|
617
|
+
|
|
618
|
+
if result["action"] == "stop":
|
|
619
|
+
self.log.debug(
|
|
620
|
+
"Step-by-step execution stopped before event processing"
|
|
621
|
+
)
|
|
622
|
+
return False
|
|
623
|
+
self.emit_event(result["event_info"])
|
|
624
|
+
# Handle breakpoint logic
|
|
625
|
+
if result["should_break"]:
|
|
626
|
+
if not self._handle_step_interaction():
|
|
627
|
+
self._stop_requested.set()
|
|
628
|
+
if hasattr(event, "type") and event.type == "input_request":
|
|
629
|
+
event.content.respond("exit")
|
|
630
|
+
return True
|
|
631
|
+
raise StopRunningException(StopRunningException.reason)
|
|
632
|
+
|
|
633
|
+
# Process the actual event
|
|
634
|
+
WaldiezBaseRunner.process_event(event, skip_send=True)
|
|
635
|
+
self._processed_events += 1
|
|
636
|
+
|
|
637
|
+
except Exception as e:
|
|
638
|
+
if not isinstance(e, StopRunningException):
|
|
639
|
+
raise RuntimeError(
|
|
640
|
+
f"Error processing event {event}: {e}\n{traceback.format_exc()}"
|
|
641
|
+
) from e
|
|
642
|
+
raise StopRunningException(StopRunningException.reason) from e
|
|
643
|
+
|
|
644
|
+
return not self._stop_requested.is_set()
|
|
645
|
+
|
|
646
|
+
# pylint: disable=too-complex
|
|
647
|
+
async def _a_run(
|
|
648
|
+
self,
|
|
649
|
+
temp_dir: Path,
|
|
650
|
+
output_file: Path,
|
|
651
|
+
uploads_root: Path | None,
|
|
652
|
+
skip_mmd: bool = False,
|
|
653
|
+
skip_timeline: bool = False,
|
|
654
|
+
**kwargs: Any,
|
|
655
|
+
) -> list[dict[str, Any]]:
|
|
656
|
+
"""Run the Waldiez workflow with step-by-step debugging (async)."""
|
|
657
|
+
|
|
658
|
+
async def _execute_workflow() -> list[dict[str, Any]]:
|
|
659
|
+
# pylint: disable=import-outside-toplevel
|
|
660
|
+
from autogen.io import IOStream # pyright: ignore
|
|
661
|
+
|
|
662
|
+
from waldiez.io import StructuredIOStream
|
|
663
|
+
|
|
664
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
665
|
+
try:
|
|
666
|
+
loaded_module = self._load_module(output_file, temp_dir)
|
|
667
|
+
if self._stop_requested.is_set():
|
|
668
|
+
self.log.debug(
|
|
669
|
+
"Step-by-step execution stopped before workflow start"
|
|
670
|
+
)
|
|
671
|
+
return []
|
|
672
|
+
|
|
673
|
+
if self.structured_io:
|
|
674
|
+
stream = StructuredIOStream(
|
|
675
|
+
uploads_root=uploads_root, is_async=True
|
|
676
|
+
)
|
|
677
|
+
else:
|
|
678
|
+
stream = IOStream.get_default()
|
|
679
|
+
|
|
680
|
+
WaldiezBaseRunner._print = stream.print
|
|
681
|
+
WaldiezBaseRunner._input = stream.input
|
|
682
|
+
WaldiezBaseRunner._send = stream.send
|
|
683
|
+
|
|
684
|
+
self.print(MESSAGES["workflow_starting"])
|
|
685
|
+
self.print(self.waldiez.info.model_dump_json())
|
|
686
|
+
|
|
687
|
+
results = await loaded_module.main(on_event=self._a_on_event)
|
|
688
|
+
self.print(MESSAGES["workflow_finished"])
|
|
689
|
+
return results
|
|
690
|
+
|
|
691
|
+
except Exception as e:
|
|
692
|
+
if StopRunningException.reason in str(e):
|
|
693
|
+
raise StopRunningException(
|
|
694
|
+
StopRunningException.reason
|
|
695
|
+
) from e
|
|
696
|
+
self.print(MESSAGES["workflow_failed"].format(error=str(e)))
|
|
697
|
+
traceback.print_exc()
|
|
698
|
+
return []
|
|
699
|
+
|
|
700
|
+
# Create and monitor cancellable task
|
|
701
|
+
task = asyncio.create_task(_execute_workflow())
|
|
702
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
703
|
+
try:
|
|
704
|
+
while not task.done():
|
|
705
|
+
if self._stop_requested.is_set():
|
|
706
|
+
task.cancel()
|
|
707
|
+
self.log.debug("Step-by-step execution stopped by user")
|
|
708
|
+
break
|
|
709
|
+
await asyncio.sleep(0.1)
|
|
710
|
+
return await task
|
|
711
|
+
except asyncio.CancelledError:
|
|
712
|
+
self.log.debug("Step-by-step execution cancelled")
|
|
713
|
+
return []
|
|
714
|
+
|
|
715
|
+
async def _a_on_event(
|
|
716
|
+
self, event: Union["BaseEvent", "BaseMessage"]
|
|
717
|
+
) -> bool:
|
|
718
|
+
"""Process an event with step-by-step debugging asynchronously."""
|
|
719
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
720
|
+
try:
|
|
721
|
+
# Use the event processor for core logic
|
|
722
|
+
result = self._event_processor.process_event(event)
|
|
723
|
+
|
|
724
|
+
if result["action"] == "stop":
|
|
725
|
+
self.log.debug(
|
|
726
|
+
"Async step-by-step execution stopped before event processing"
|
|
727
|
+
)
|
|
728
|
+
return False
|
|
729
|
+
self.emit_event(result["event_info"])
|
|
730
|
+
# Handle breakpoint logic
|
|
731
|
+
if result["should_break"]:
|
|
732
|
+
if not await self._a_handle_step_interaction():
|
|
733
|
+
self._stop_requested.set()
|
|
734
|
+
if hasattr(event, "type") and event.type == "input_request":
|
|
735
|
+
event.content.respond("exit")
|
|
736
|
+
return True
|
|
737
|
+
raise StopRunningException(StopRunningException.reason)
|
|
738
|
+
|
|
739
|
+
# Process the actual event
|
|
740
|
+
await WaldiezBaseRunner.a_process_event(event, skip_send=True)
|
|
741
|
+
self._processed_events += 1
|
|
742
|
+
|
|
743
|
+
except Exception as e:
|
|
744
|
+
if not isinstance(e, StopRunningException):
|
|
745
|
+
raise RuntimeError(
|
|
746
|
+
f"Error processing event {event}: {e}\n{traceback.format_exc()}"
|
|
747
|
+
) from e
|
|
748
|
+
raise StopRunningException(StopRunningException.reason) from e
|
|
749
|
+
|
|
750
|
+
return not self._stop_requested.is_set()
|