waldiez 0.5.10__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 +1 -0
- waldiez/exporting/agent/exporter.py +6 -6
- 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/handoff.py +1 -0
- waldiez/exporting/agent/extras/handoffs/target.py +1 -0
- waldiez/exporting/agent/termination.py +1 -0
- waldiez/exporting/core/constants.py +3 -1
- waldiez/exporting/core/extras/serializer.py +12 -10
- 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/utils/common.py +1 -1
- waldiez/exporting/flow/utils/importing.py +1 -1
- waldiez/exporting/flow/utils/logging.py +3 -75
- waldiez/io/__init__.py +3 -1
- waldiez/io/_ws.py +2 -0
- waldiez/io/structured.py +81 -28
- waldiez/io/utils.py +16 -10
- waldiez/io/ws.py +2 -2
- waldiez/models/agents/agent/agent.py +2 -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/handoff.py +2 -0
- waldiez/models/common/method_utils.py +2 -0
- waldiez/models/model/_llm.py +3 -0
- waldiez/models/tool/predefined/_email.py +3 -0
- waldiez/models/tool/predefined/_perplexity.py +1 -1
- waldiez/models/tool/predefined/_searxng.py +1 -1
- waldiez/models/tool/predefined/_wikipedia.py +1 -1
- waldiez/running/base_runner.py +81 -20
- waldiez/running/post_run.py +6 -0
- waldiez/running/pre_run.py +167 -45
- waldiez/running/standard_runner.py +5 -5
- waldiez/running/step_by_step/breakpoints_mixin.py +368 -44
- 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 +358 -41
- waldiez/running/step_by_step/step_by_step_runner.py +358 -353
- waldiez/running/subprocess_runner/__base__.py +4 -7
- waldiez/running/subprocess_runner/_async_runner.py +1 -1
- waldiez/running/subprocess_runner/_sync_runner.py +5 -4
- waldiez/running/subprocess_runner/runner.py +9 -0
- waldiez/running/utils.py +116 -2
- waldiez/ws/__init__.py +8 -7
- waldiez/ws/_file_handler.py +0 -2
- waldiez/ws/_mock.py +74 -0
- waldiez/ws/cli.py +27 -3
- waldiez/ws/client_manager.py +45 -29
- waldiez/ws/models.py +18 -1
- waldiez/ws/reloader.py +23 -2
- waldiez/ws/server.py +47 -8
- waldiez/ws/utils.py +29 -4
- {waldiez-0.5.10.dist-info → waldiez-0.6.0.dist-info}/METADATA +53 -44
- {waldiez-0.5.10.dist-info → waldiez-0.6.0.dist-info}/RECORD +62 -59
- {waldiez-0.5.10.dist-info → waldiez-0.6.0.dist-info}/WHEEL +0 -0
- {waldiez-0.5.10.dist-info → waldiez-0.6.0.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.10.dist-info → waldiez-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.10.dist-info → waldiez-0.6.0.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
# pylint: disable=unused-argument
|
|
4
4
|
"""Breakpoints management mixin for step-by-step debugging."""
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import logging
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterable, Union
|
|
7
9
|
|
|
8
10
|
from .step_by_step_models import (
|
|
11
|
+
WaldiezBreakpoint,
|
|
9
12
|
WaldiezDebugBreakpointAdded,
|
|
10
13
|
WaldiezDebugBreakpointCleared,
|
|
11
14
|
WaldiezDebugBreakpointRemoved,
|
|
@@ -19,13 +22,58 @@ if TYPE_CHECKING:
|
|
|
19
22
|
from autogen.messages import BaseMessage # type: ignore
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
def handle_breakpoint_errors(func: Callable[..., bool]) -> Callable[..., bool]:
|
|
26
|
+
"""Handle breakpoint-related errors.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
func : Callable
|
|
31
|
+
The function to decorate.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
Callable
|
|
36
|
+
The decorated function.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def _wrapper(self: "BreakpointsMixin", *args: Any, **kwargs: Any) -> bool:
|
|
40
|
+
try:
|
|
41
|
+
return func(self, *args, **kwargs)
|
|
42
|
+
except ValueError as e:
|
|
43
|
+
self.emit(WaldiezDebugError(error=f"Breakpoint error: {e}"))
|
|
44
|
+
return False
|
|
45
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
46
|
+
self.emit(
|
|
47
|
+
WaldiezDebugError(
|
|
48
|
+
error=f"Unexpected error in {func.__name__}: {e}"
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
logging.exception("Error in %s", func.__name__)
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
return _wrapper
|
|
55
|
+
|
|
56
|
+
|
|
22
57
|
class BreakpointsMixin:
|
|
23
58
|
"""Mixin class for managing breakpoints in step-by-step debugging."""
|
|
24
59
|
|
|
25
60
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
26
61
|
"""Initialize breakpoints storage."""
|
|
27
|
-
self._breakpoints: set[
|
|
62
|
+
self._breakpoints: set[WaldiezBreakpoint] = set()
|
|
28
63
|
|
|
64
|
+
# Statistics for monitoring
|
|
65
|
+
self._breakpoint_stats = {
|
|
66
|
+
"total_matches": 0,
|
|
67
|
+
"cache_hits": 0,
|
|
68
|
+
"cache_misses": 0,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Create the cached function with proper binding
|
|
72
|
+
self._check_breakpoint_match_cached = lru_cache(maxsize=1000)(
|
|
73
|
+
self._check_breakpoint_match_impl
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# noinspection PyTypeHints
|
|
29
77
|
def emit(self, message: WaldiezDebugMessage) -> None:
|
|
30
78
|
"""Emit a debug message. Implemented by the class using this mixin.
|
|
31
79
|
|
|
@@ -36,31 +84,109 @@ class BreakpointsMixin:
|
|
|
36
84
|
"""
|
|
37
85
|
raise NotImplementedError("emit method must be implemented")
|
|
38
86
|
|
|
39
|
-
def
|
|
40
|
-
"""
|
|
87
|
+
def _invalidate_cache(self) -> None:
|
|
88
|
+
"""Invalidate the event matching cache when breakpoints change."""
|
|
89
|
+
self._check_breakpoint_match_cached.cache_clear()
|
|
90
|
+
|
|
91
|
+
def _get_breakpoints_signature(self) -> frozenset[str]:
|
|
92
|
+
"""Get a hashable signature of current breakpoints."""
|
|
93
|
+
return frozenset(str(bp) for bp in self._breakpoints)
|
|
94
|
+
|
|
95
|
+
def _check_breakpoint_match_impl(
|
|
96
|
+
self,
|
|
97
|
+
event_type: str,
|
|
98
|
+
sender: str,
|
|
99
|
+
recipient: str,
|
|
100
|
+
breakpoints_sig: frozenset[str],
|
|
101
|
+
) -> bool:
|
|
102
|
+
"""Check if the event matches any breakpoints.
|
|
41
103
|
|
|
42
104
|
Parameters
|
|
43
105
|
----------
|
|
44
106
|
event_type : str
|
|
45
|
-
The event type to
|
|
107
|
+
The event type to check.
|
|
108
|
+
sender : str
|
|
109
|
+
The event sender.
|
|
110
|
+
recipient : str
|
|
111
|
+
The event recipient.
|
|
112
|
+
breakpoints_sig : frozenset[str]
|
|
113
|
+
Signature of current breakpoints for cache invalidation.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
bool
|
|
118
|
+
True if any breakpoint matches, False otherwise.
|
|
119
|
+
"""
|
|
120
|
+
event_dict = {
|
|
121
|
+
"type": event_type,
|
|
122
|
+
"sender": sender,
|
|
123
|
+
"recipient": recipient,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Reconstruct breakpoints from signature for cache safety
|
|
127
|
+
# noinspection PyBroadException
|
|
128
|
+
try:
|
|
129
|
+
breakpoints = {
|
|
130
|
+
WaldiezBreakpoint.from_string(bp_str)
|
|
131
|
+
for bp_str in breakpoints_sig
|
|
132
|
+
}
|
|
133
|
+
return any(bp.matches(event_dict) for bp in breakpoints)
|
|
134
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
135
|
+
# Fallback to current breakpoints if signature is malformed
|
|
136
|
+
return any(bp.matches(event_dict) for bp in self._breakpoints)
|
|
137
|
+
|
|
138
|
+
@handle_breakpoint_errors
|
|
139
|
+
def add_breakpoint(self, spec: str) -> bool:
|
|
140
|
+
"""Add a breakpoint for an event type.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
spec : str
|
|
145
|
+
The event type specification to add a breakpoint for.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
bool
|
|
150
|
+
True if the breakpoint was added successfully, False otherwise.
|
|
46
151
|
"""
|
|
47
|
-
if not
|
|
152
|
+
if not spec or not isinstance(spec, str): # pyright: ignore
|
|
48
153
|
self.emit(
|
|
49
154
|
WaldiezDebugError(
|
|
50
155
|
error="Invalid event type: must be a non-empty string"
|
|
51
156
|
)
|
|
52
157
|
)
|
|
53
|
-
return
|
|
158
|
+
return False
|
|
159
|
+
# pylint: disable=too-many-try-statements
|
|
160
|
+
try:
|
|
161
|
+
breakpoint_obj = WaldiezBreakpoint.from_string(spec)
|
|
162
|
+
|
|
163
|
+
# Check if breakpoint already exists
|
|
164
|
+
if breakpoint_obj in self._breakpoints:
|
|
165
|
+
self.emit(
|
|
166
|
+
WaldiezDebugError(
|
|
167
|
+
error=f"Breakpoint for '{spec}' already exists"
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
return False
|
|
54
171
|
|
|
55
|
-
|
|
56
|
-
|
|
172
|
+
self._breakpoints.add(breakpoint_obj)
|
|
173
|
+
self._invalidate_cache()
|
|
174
|
+
self.emit(WaldiezDebugBreakpointAdded(breakpoint=spec))
|
|
175
|
+
return True
|
|
57
176
|
|
|
58
|
-
|
|
59
|
-
|
|
177
|
+
except ValueError as e:
|
|
178
|
+
self.emit(
|
|
179
|
+
WaldiezDebugError(error=f"Invalid breakpoint format: {e}")
|
|
180
|
+
)
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
@handle_breakpoint_errors
|
|
184
|
+
def remove_breakpoint(self, spec: str | WaldiezBreakpoint) -> bool:
|
|
185
|
+
"""Remove a breakpoint based on its specification.
|
|
60
186
|
|
|
61
187
|
Parameters
|
|
62
188
|
----------
|
|
63
|
-
|
|
189
|
+
spec : str | WaldiezBreakpoint
|
|
64
190
|
The event type to remove the breakpoint for.
|
|
65
191
|
|
|
66
192
|
Returns
|
|
@@ -68,76 +194,131 @@ class BreakpointsMixin:
|
|
|
68
194
|
bool
|
|
69
195
|
True if the breakpoint was removed, False if it didn't exist.
|
|
70
196
|
"""
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
197
|
+
if isinstance(spec, WaldiezBreakpoint):
|
|
198
|
+
breakpoint_obj = spec
|
|
199
|
+
spec_str = str(spec)
|
|
200
|
+
elif isinstance(spec, str) and spec: # pyright: ignore
|
|
201
|
+
try:
|
|
202
|
+
breakpoint_obj = WaldiezBreakpoint.from_string(spec)
|
|
203
|
+
spec_str = spec
|
|
204
|
+
except ValueError as e:
|
|
205
|
+
self.emit(
|
|
206
|
+
WaldiezDebugError(error=f"Invalid breakpoint format: {e}")
|
|
75
207
|
)
|
|
208
|
+
return False
|
|
209
|
+
else:
|
|
210
|
+
self.emit(
|
|
211
|
+
WaldiezDebugError(error="Invalid breakpoint specification")
|
|
76
212
|
)
|
|
77
213
|
return False
|
|
78
214
|
|
|
79
|
-
if
|
|
80
|
-
self._breakpoints.remove(
|
|
81
|
-
self.
|
|
215
|
+
if breakpoint_obj in self._breakpoints:
|
|
216
|
+
self._breakpoints.remove(breakpoint_obj)
|
|
217
|
+
self._invalidate_cache()
|
|
218
|
+
self.emit(WaldiezDebugBreakpointRemoved(breakpoint=spec_str))
|
|
82
219
|
return True
|
|
220
|
+
|
|
83
221
|
self.emit(
|
|
84
222
|
WaldiezDebugError(
|
|
85
|
-
error=f"Breakpoint for '{
|
|
223
|
+
error=f"Breakpoint for '{spec_str}' does not exist"
|
|
86
224
|
)
|
|
87
225
|
)
|
|
88
226
|
return False
|
|
89
227
|
|
|
90
228
|
def list_breakpoints(self) -> None:
|
|
91
229
|
"""List all current breakpoints."""
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
)
|
|
230
|
+
breakpoints_list = sorted(self._breakpoints, key=str)
|
|
231
|
+
self.emit(WaldiezDebugBreakpointsList(breakpoints=breakpoints_list))
|
|
95
232
|
|
|
96
233
|
def clear_breakpoints(self) -> None:
|
|
97
234
|
"""Clear all breakpoints."""
|
|
98
235
|
count = len(self._breakpoints)
|
|
99
236
|
self._breakpoints.clear()
|
|
237
|
+
self._invalidate_cache()
|
|
238
|
+
|
|
100
239
|
if count > 0:
|
|
101
240
|
self.emit(
|
|
102
241
|
WaldiezDebugBreakpointCleared(
|
|
103
242
|
message=f"Cleared {count} breakpoint(s)"
|
|
104
243
|
)
|
|
105
244
|
)
|
|
245
|
+
else:
|
|
246
|
+
self.emit(
|
|
247
|
+
WaldiezDebugBreakpointCleared(message="No breakpoints to clear")
|
|
248
|
+
)
|
|
106
249
|
|
|
107
|
-
|
|
108
|
-
|
|
250
|
+
@handle_breakpoint_errors
|
|
251
|
+
def set_breakpoints(self, specs: Iterable[str | WaldiezBreakpoint]) -> bool:
|
|
252
|
+
"""Set which breakpoints to activate.
|
|
109
253
|
|
|
110
254
|
Parameters
|
|
111
255
|
----------
|
|
112
|
-
|
|
113
|
-
Iterable of event types to break on. Empty means
|
|
256
|
+
specs : Iterable[str | WaldiezBreakpoint]
|
|
257
|
+
Iterable of event types to break on. Empty means no breakpoints.
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
bool
|
|
262
|
+
True if all breakpoints were set successfully, False if any failed.
|
|
114
263
|
"""
|
|
115
|
-
|
|
264
|
+
new_breakpoints: set[WaldiezBreakpoint] = set()
|
|
265
|
+
errors: list[str] = []
|
|
266
|
+
|
|
267
|
+
for spec in specs:
|
|
268
|
+
try:
|
|
269
|
+
if isinstance(spec, WaldiezBreakpoint):
|
|
270
|
+
new_breakpoints.add(spec)
|
|
271
|
+
else:
|
|
272
|
+
new_breakpoints.add(WaldiezBreakpoint.from_string(spec))
|
|
273
|
+
except ValueError as e:
|
|
274
|
+
errors.append(f"Invalid breakpoint '{spec}': {e}")
|
|
275
|
+
|
|
276
|
+
if errors:
|
|
277
|
+
for error in errors:
|
|
278
|
+
self.emit(WaldiezDebugError(error=error))
|
|
279
|
+
return False
|
|
116
280
|
|
|
117
|
-
|
|
281
|
+
old_count = len(self._breakpoints)
|
|
282
|
+
self._breakpoints = new_breakpoints
|
|
283
|
+
new_count = len(self._breakpoints)
|
|
284
|
+
self._invalidate_cache()
|
|
285
|
+
|
|
286
|
+
self.emit(
|
|
287
|
+
WaldiezDebugBreakpointCleared(
|
|
288
|
+
message=f"Updated breakpoints: {old_count} -> {new_count}"
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
def get_breakpoints(self) -> set[WaldiezBreakpoint]:
|
|
118
294
|
"""Get current breakpoints.
|
|
119
295
|
|
|
120
296
|
Returns
|
|
121
297
|
-------
|
|
122
|
-
set[
|
|
298
|
+
set[WaldiezBreakpoint]
|
|
123
299
|
Set of current breakpoint event types.
|
|
124
300
|
"""
|
|
125
301
|
return self._breakpoints.copy()
|
|
126
302
|
|
|
127
|
-
def has_breakpoint(self,
|
|
128
|
-
"""Check if a breakpoint exists
|
|
303
|
+
def has_breakpoint(self, spec: str | WaldiezBreakpoint) -> bool:
|
|
304
|
+
"""Check if a breakpoint exists.
|
|
129
305
|
|
|
130
306
|
Parameters
|
|
131
307
|
----------
|
|
132
|
-
|
|
133
|
-
The
|
|
308
|
+
spec : str | WaldiezBreakpoint
|
|
309
|
+
The breakpoint specification to check.
|
|
134
310
|
|
|
135
311
|
Returns
|
|
136
312
|
-------
|
|
137
313
|
bool
|
|
138
314
|
True if a breakpoint exists for this event type.
|
|
139
315
|
"""
|
|
140
|
-
|
|
316
|
+
try:
|
|
317
|
+
if isinstance(spec, WaldiezBreakpoint):
|
|
318
|
+
return spec in self._breakpoints
|
|
319
|
+
return WaldiezBreakpoint.from_string(spec) in self._breakpoints
|
|
320
|
+
except ValueError:
|
|
321
|
+
return False
|
|
141
322
|
|
|
142
323
|
def should_break_on_event(
|
|
143
324
|
self, event: Union["BaseEvent", "BaseMessage"], step_mode: bool = True
|
|
@@ -156,9 +337,6 @@ class BreakpointsMixin:
|
|
|
156
337
|
bool
|
|
157
338
|
True if we should break, False otherwise.
|
|
158
339
|
"""
|
|
159
|
-
if not step_mode:
|
|
160
|
-
return False
|
|
161
|
-
|
|
162
340
|
# Get event type
|
|
163
341
|
event_type = getattr(event, "type", "unknown")
|
|
164
342
|
|
|
@@ -166,23 +344,169 @@ class BreakpointsMixin:
|
|
|
166
344
|
if event_type == "input_request":
|
|
167
345
|
return False
|
|
168
346
|
|
|
169
|
-
#
|
|
170
|
-
if not self._breakpoints:
|
|
347
|
+
# Quick path: if no breakpoints and not in step mode, don't break
|
|
348
|
+
if not self._breakpoints and not step_mode:
|
|
349
|
+
return False
|
|
350
|
+
|
|
351
|
+
# Quick path: if step mode and no specific breakpoints,
|
|
352
|
+
# break on everything
|
|
353
|
+
if step_mode and not self._breakpoints:
|
|
171
354
|
return True
|
|
172
355
|
|
|
173
|
-
# Check if this event
|
|
174
|
-
|
|
356
|
+
# Check if this event matches any breakpoint using caching
|
|
357
|
+
if hasattr(event, "model_dump"):
|
|
358
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
359
|
+
try:
|
|
360
|
+
event_dict = event.model_dump(
|
|
361
|
+
mode="python", exclude_none=True, fallback=str
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Extract event details for cache key
|
|
365
|
+
event_type_key = event_dict.get("type", "unknown")
|
|
366
|
+
sender = event_dict.get("sender", "")
|
|
367
|
+
recipient = event_dict.get("recipient", "")
|
|
368
|
+
|
|
369
|
+
# Get current breakpoints signature for cache invalidation
|
|
370
|
+
breakpoints_sig = self._get_breakpoints_signature()
|
|
371
|
+
|
|
372
|
+
# Check cached result
|
|
373
|
+
# noinspection PyBroadException
|
|
374
|
+
try:
|
|
375
|
+
matches_breakpoint = self._check_breakpoint_match_cached(
|
|
376
|
+
event_type_key, sender, recipient, breakpoints_sig
|
|
377
|
+
)
|
|
378
|
+
self._breakpoint_stats["cache_hits"] += 1
|
|
379
|
+
except Exception:
|
|
380
|
+
# Fallback to non-cached check
|
|
381
|
+
matches_breakpoint = any(
|
|
382
|
+
bp.matches(event_dict) for bp in self._breakpoints
|
|
383
|
+
)
|
|
384
|
+
self._breakpoint_stats["cache_misses"] += 1
|
|
385
|
+
|
|
386
|
+
self._breakpoint_stats["total_matches"] += 1
|
|
387
|
+
|
|
388
|
+
# If any breakpoint matches: break regardless of step_mode
|
|
389
|
+
if matches_breakpoint:
|
|
390
|
+
return True
|
|
391
|
+
|
|
392
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
393
|
+
logging.warning("Error processing event for breakpoints: %s", e)
|
|
394
|
+
self._breakpoint_stats["cache_misses"] += 1
|
|
395
|
+
|
|
396
|
+
# No specific breakpoints matched:
|
|
397
|
+
# - If step_mode, break on every event (single-step behavior)
|
|
398
|
+
# - If not step_mode, do not break
|
|
399
|
+
return bool(step_mode)
|
|
175
400
|
|
|
176
401
|
def get_breakpoint_stats(self) -> dict[str, Any]:
|
|
177
|
-
"""Get breakpoint statistics.
|
|
402
|
+
"""Get breakpoint statistics including performance metrics.
|
|
178
403
|
|
|
179
404
|
Returns
|
|
180
405
|
-------
|
|
181
406
|
dict[str, Any]
|
|
182
407
|
Dictionary containing breakpoint statistics.
|
|
183
408
|
"""
|
|
409
|
+
breakpoints: list[dict[str, Any]] = [
|
|
410
|
+
{
|
|
411
|
+
"type": bp.type.value,
|
|
412
|
+
"event_type": bp.event_type,
|
|
413
|
+
"agent_name": bp.agent_name,
|
|
414
|
+
"description": bp.description,
|
|
415
|
+
"string_repr": str(bp),
|
|
416
|
+
}
|
|
417
|
+
for bp in self._breakpoints
|
|
418
|
+
]
|
|
419
|
+
|
|
420
|
+
# Calculate cache efficiency
|
|
421
|
+
total_checks = (
|
|
422
|
+
self._breakpoint_stats["cache_hits"]
|
|
423
|
+
+ self._breakpoint_stats["cache_misses"]
|
|
424
|
+
)
|
|
425
|
+
cache_hit_rate = (
|
|
426
|
+
self._breakpoint_stats["cache_hits"] / total_checks
|
|
427
|
+
if total_checks > 0
|
|
428
|
+
else 0
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Get cache info from lru_cache
|
|
432
|
+
cache_info = self._check_breakpoint_match_cached.cache_info()
|
|
433
|
+
|
|
184
434
|
return {
|
|
185
435
|
"total_breakpoints": len(self._breakpoints),
|
|
186
|
-
"breakpoints":
|
|
436
|
+
"breakpoints": breakpoints,
|
|
187
437
|
"has_breakpoints": len(self._breakpoints) > 0,
|
|
438
|
+
"cache_stats": {
|
|
439
|
+
"cache_hit_rate": f"{cache_hit_rate:.2%}",
|
|
440
|
+
"cache_size": cache_info.currsize,
|
|
441
|
+
"cache_maxsize": cache_info.maxsize,
|
|
442
|
+
"total_matches": self._breakpoint_stats["total_matches"],
|
|
443
|
+
"cache_hits": cache_info.hits,
|
|
444
|
+
"cache_misses": cache_info.misses,
|
|
445
|
+
},
|
|
446
|
+
"performance": {
|
|
447
|
+
"lru_cache_info": {
|
|
448
|
+
"hits": cache_info.hits,
|
|
449
|
+
"misses": cache_info.misses,
|
|
450
|
+
"maxsize": cache_info.maxsize,
|
|
451
|
+
"currsize": cache_info.currsize,
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
def reset_stats(self) -> None:
|
|
457
|
+
"""Reset breakpoint statistics."""
|
|
458
|
+
self._breakpoint_stats = {
|
|
459
|
+
"total_matches": 0,
|
|
460
|
+
"cache_hits": 0,
|
|
461
|
+
"cache_misses": 0,
|
|
188
462
|
}
|
|
463
|
+
self._invalidate_cache()
|
|
464
|
+
|
|
465
|
+
def optimize_cache(self) -> None:
|
|
466
|
+
"""Manually optimize the cache by clearing it."""
|
|
467
|
+
self._invalidate_cache()
|
|
468
|
+
|
|
469
|
+
def export_breakpoints(self) -> list[str]:
|
|
470
|
+
"""Export breakpoints as a list of strings for persistence.
|
|
471
|
+
|
|
472
|
+
Returns
|
|
473
|
+
-------
|
|
474
|
+
list[str]
|
|
475
|
+
List of breakpoint specifications as strings.
|
|
476
|
+
"""
|
|
477
|
+
return [str(bp) for bp in sorted(self._breakpoints, key=str)]
|
|
478
|
+
|
|
479
|
+
def import_breakpoints(
|
|
480
|
+
self, breakpoint_specs: list[str]
|
|
481
|
+
) -> tuple[int, list[str]]:
|
|
482
|
+
"""Import breakpoints from a list of string specifications.
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
breakpoint_specs : list[str]
|
|
487
|
+
List of breakpoint specifications as strings.
|
|
488
|
+
|
|
489
|
+
Returns
|
|
490
|
+
-------
|
|
491
|
+
tuple[int, list[str]]
|
|
492
|
+
Tuple of (successful_imports, error_messages).
|
|
493
|
+
"""
|
|
494
|
+
successful = 0
|
|
495
|
+
errors: list[str] = []
|
|
496
|
+
|
|
497
|
+
for spec in breakpoint_specs:
|
|
498
|
+
# pylint: disable=too-many-try-statements
|
|
499
|
+
try:
|
|
500
|
+
breakpoint_obj = WaldiezBreakpoint.from_string(spec)
|
|
501
|
+
if breakpoint_obj not in self._breakpoints:
|
|
502
|
+
self._breakpoints.add(breakpoint_obj)
|
|
503
|
+
successful += 1
|
|
504
|
+
else:
|
|
505
|
+
errors.append(f"Breakpoint '{spec}' already exists")
|
|
506
|
+
except ValueError as e:
|
|
507
|
+
errors.append(f"Invalid breakpoint '{spec}': {e}")
|
|
508
|
+
|
|
509
|
+
if successful > 0:
|
|
510
|
+
self._invalidate_cache()
|
|
511
|
+
|
|
512
|
+
return successful, errors
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
# pylint: disable=unused-argument
|
|
4
|
+
"""Command handler for step-by-step execution."""
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING, Callable
|
|
7
|
+
|
|
8
|
+
from .step_by_step_models import (
|
|
9
|
+
HELP_MESSAGE,
|
|
10
|
+
WaldiezDebugError,
|
|
11
|
+
WaldiezDebugStepAction,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
# noinspection PyUnusedImports
|
|
16
|
+
from .step_by_step_runner import WaldiezStepByStepRunner
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# pylint: disable=too-few-public-methods
|
|
20
|
+
class CommandHandler:
|
|
21
|
+
"""Handler for debug commands to reduce complexity in main runner."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, runner: "WaldiezStepByStepRunner"):
|
|
24
|
+
self.runner = runner
|
|
25
|
+
self._command_map: dict[
|
|
26
|
+
str, Callable[[str | None], WaldiezDebugStepAction]
|
|
27
|
+
] = {
|
|
28
|
+
"c": self._handle_continue,
|
|
29
|
+
"s": self._handle_step,
|
|
30
|
+
"r": self._handle_run,
|
|
31
|
+
"q": self._handle_quit,
|
|
32
|
+
"i": self._handle_info,
|
|
33
|
+
"h": self._handle_help,
|
|
34
|
+
"st": self._handle_stats,
|
|
35
|
+
"ab": self._handle_add_breakpoint,
|
|
36
|
+
"rb": self._handle_remove_breakpoint,
|
|
37
|
+
"lb": self._handle_list_breakpoints,
|
|
38
|
+
"cb": self._handle_clear_breakpoints,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def handle_command(self, command_line: str) -> WaldiezDebugStepAction:
|
|
42
|
+
"""Handle a command line input.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
command_line : str
|
|
47
|
+
The command line input to handle.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
WaldiezDebugStepAction
|
|
52
|
+
The action to take for the command.
|
|
53
|
+
"""
|
|
54
|
+
if not command_line or not command_line.strip():
|
|
55
|
+
return self._handle_step(None) # Enter = step
|
|
56
|
+
parts = command_line.strip().split(maxsplit=1)
|
|
57
|
+
if not parts or not parts[0]:
|
|
58
|
+
return self._handle_unknown(None)
|
|
59
|
+
command = parts[0].lower()
|
|
60
|
+
args = parts[1] if len(parts) > 1 else None
|
|
61
|
+
|
|
62
|
+
handler = self._command_map.get(command, self._handle_unknown)
|
|
63
|
+
return handler(args)
|
|
64
|
+
|
|
65
|
+
def _handle_continue(self, args: str | None) -> WaldiezDebugStepAction:
|
|
66
|
+
self.runner.step_mode = True
|
|
67
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
68
|
+
|
|
69
|
+
def _handle_step(self, args: str | None) -> WaldiezDebugStepAction:
|
|
70
|
+
self.runner.step_mode = True
|
|
71
|
+
return WaldiezDebugStepAction.STEP
|
|
72
|
+
|
|
73
|
+
def _handle_run(self, args: str | None) -> WaldiezDebugStepAction:
|
|
74
|
+
self.runner.step_mode = False
|
|
75
|
+
return WaldiezDebugStepAction.RUN
|
|
76
|
+
|
|
77
|
+
def _handle_quit(self, args: str | None) -> WaldiezDebugStepAction:
|
|
78
|
+
self.runner.set_stop_requested()
|
|
79
|
+
return WaldiezDebugStepAction.QUIT
|
|
80
|
+
|
|
81
|
+
def _handle_info(self, args: str | None) -> WaldiezDebugStepAction:
|
|
82
|
+
self.runner.show_event_info()
|
|
83
|
+
return WaldiezDebugStepAction.INFO
|
|
84
|
+
|
|
85
|
+
def _handle_help(self, args: str | None) -> WaldiezDebugStepAction:
|
|
86
|
+
self.runner.emit(HELP_MESSAGE)
|
|
87
|
+
return WaldiezDebugStepAction.HELP
|
|
88
|
+
|
|
89
|
+
def _handle_stats(self, args: str | None) -> WaldiezDebugStepAction:
|
|
90
|
+
self.runner.show_stats()
|
|
91
|
+
return WaldiezDebugStepAction.STATS
|
|
92
|
+
|
|
93
|
+
def _handle_add_breakpoint(
|
|
94
|
+
self, args: str | None
|
|
95
|
+
) -> WaldiezDebugStepAction:
|
|
96
|
+
if args:
|
|
97
|
+
self.runner.add_breakpoint(args)
|
|
98
|
+
return WaldiezDebugStepAction.ADD_BREAKPOINT
|
|
99
|
+
current_event = self.runner.current_event
|
|
100
|
+
if current_event and hasattr(current_event, "type"):
|
|
101
|
+
self.runner.add_breakpoint(getattr(current_event, "type", ""))
|
|
102
|
+
return WaldiezDebugStepAction.ADD_BREAKPOINT
|
|
103
|
+
# else:
|
|
104
|
+
self.runner.emit(
|
|
105
|
+
WaldiezDebugError(
|
|
106
|
+
error=(
|
|
107
|
+
"No breakpoint specification provided "
|
|
108
|
+
"and no current event available"
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
return WaldiezDebugStepAction.ADD_BREAKPOINT
|
|
113
|
+
|
|
114
|
+
def _handle_remove_breakpoint(
|
|
115
|
+
self, args: str | None
|
|
116
|
+
) -> WaldiezDebugStepAction:
|
|
117
|
+
if args:
|
|
118
|
+
self.runner.remove_breakpoint(args)
|
|
119
|
+
return WaldiezDebugStepAction.REMOVE_BREAKPOINT
|
|
120
|
+
current_event = self.runner.current_event
|
|
121
|
+
if current_event and hasattr(current_event, "type"):
|
|
122
|
+
self.runner.remove_breakpoint(getattr(current_event, "type", ""))
|
|
123
|
+
return WaldiezDebugStepAction.REMOVE_BREAKPOINT
|
|
124
|
+
# else:
|
|
125
|
+
self.runner.emit(
|
|
126
|
+
WaldiezDebugError(
|
|
127
|
+
error=(
|
|
128
|
+
"No breakpoint specification provided and "
|
|
129
|
+
"no current event available"
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
return WaldiezDebugStepAction.REMOVE_BREAKPOINT
|
|
134
|
+
|
|
135
|
+
def _handle_list_breakpoints(
|
|
136
|
+
self, args: str | None
|
|
137
|
+
) -> WaldiezDebugStepAction:
|
|
138
|
+
self.runner.list_breakpoints()
|
|
139
|
+
return WaldiezDebugStepAction.LIST_BREAKPOINTS
|
|
140
|
+
|
|
141
|
+
def _handle_clear_breakpoints(
|
|
142
|
+
self, args: str | None
|
|
143
|
+
) -> WaldiezDebugStepAction:
|
|
144
|
+
self.runner.clear_breakpoints()
|
|
145
|
+
return WaldiezDebugStepAction.CLEAR_BREAKPOINTS
|
|
146
|
+
|
|
147
|
+
def _handle_unknown(self, args: str | None) -> WaldiezDebugStepAction:
|
|
148
|
+
self.runner.emit(
|
|
149
|
+
WaldiezDebugError(error="Unknown command. Use 'h' for help")
|
|
150
|
+
)
|
|
151
|
+
return WaldiezDebugStepAction.UNKNOWN
|