waldiez 0.5.10__py3-none-any.whl → 0.6.1__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/__init__.py +1 -1
- waldiez/_version.py +1 -1
- waldiez/cli.py +19 -7
- waldiez/cli_extras/jupyter.py +3 -0
- waldiez/cli_extras/runner.py +3 -1
- waldiez/cli_extras/studio.py +3 -1
- waldiez/exporter.py +9 -3
- waldiez/exporting/agent/exporter.py +15 -16
- waldiez/exporting/agent/extras/captain_agent_extras.py +6 -6
- waldiez/exporting/agent/extras/doc_agent_extras.py +6 -6
- waldiez/exporting/agent/extras/group_manager_agent_extas.py +40 -24
- waldiez/exporting/agent/extras/group_member_extras.py +6 -5
- waldiez/exporting/agent/extras/handoffs/after_work.py +2 -1
- waldiez/exporting/agent/extras/handoffs/available.py +2 -1
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
- waldiez/exporting/agent/extras/handoffs/handoff.py +2 -1
- waldiez/exporting/agent/extras/handoffs/target.py +7 -4
- waldiez/exporting/agent/extras/rag/chroma_extras.py +27 -19
- waldiez/exporting/agent/extras/rag/mongo_extras.py +8 -8
- waldiez/exporting/agent/extras/rag/pgvector_extras.py +5 -5
- waldiez/exporting/agent/extras/rag/qdrant_extras.py +5 -4
- waldiez/exporting/agent/extras/rag/vector_db_extras.py +1 -1
- waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +5 -7
- waldiez/exporting/agent/extras/reasoning_agent_extras.py +3 -5
- waldiez/exporting/agent/termination.py +1 -0
- waldiez/exporting/chats/exporter.py +4 -4
- waldiez/exporting/chats/processor.py +1 -2
- waldiez/exporting/chats/utils/common.py +89 -48
- waldiez/exporting/chats/utils/group.py +9 -9
- waldiez/exporting/chats/utils/nested.py +7 -7
- waldiez/exporting/chats/utils/sequential.py +1 -1
- waldiez/exporting/chats/utils/single.py +2 -2
- waldiez/exporting/core/constants.py +3 -1
- waldiez/exporting/core/content.py +7 -7
- waldiez/exporting/core/context.py +5 -3
- waldiez/exporting/core/exporter.py +5 -3
- waldiez/exporting/core/exporters.py +2 -2
- waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
- waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
- waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
- waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
- waldiez/exporting/core/extras/base.py +7 -5
- waldiez/exporting/core/extras/flow_extras.py +4 -5
- waldiez/exporting/core/extras/model_extras.py +2 -2
- waldiez/exporting/core/extras/path_resolver.py +1 -2
- waldiez/exporting/core/extras/serializer.py +13 -11
- waldiez/exporting/core/protocols.py +6 -5
- waldiez/exporting/core/result.py +25 -28
- waldiez/exporting/core/types.py +11 -10
- waldiez/exporting/core/utils/llm_config.py +4 -4
- waldiez/exporting/core/validation.py +10 -11
- waldiez/exporting/flow/execution_generator.py +99 -10
- waldiez/exporting/flow/exporter.py +2 -2
- waldiez/exporting/flow/factory.py +2 -2
- waldiez/exporting/flow/file_generator.py +4 -2
- waldiez/exporting/flow/merger.py +5 -3
- waldiez/exporting/flow/orchestrator.py +72 -2
- waldiez/exporting/flow/utils/common.py +6 -6
- waldiez/exporting/flow/utils/importing.py +7 -8
- waldiez/exporting/flow/utils/linting.py +25 -9
- waldiez/exporting/flow/utils/logging.py +5 -77
- waldiez/exporting/models/exporter.py +8 -8
- waldiez/exporting/models/processor.py +5 -5
- waldiez/exporting/tools/exporter.py +2 -2
- waldiez/exporting/tools/processor.py +7 -4
- waldiez/io/__init__.py +11 -5
- waldiez/io/_ws.py +12 -6
- waldiez/io/models/constants.py +10 -10
- waldiez/io/models/content/audio.py +1 -0
- waldiez/io/models/content/base.py +20 -18
- waldiez/io/models/content/file.py +1 -0
- waldiez/io/models/content/image.py +1 -0
- waldiez/io/models/content/text.py +1 -0
- waldiez/io/models/content/video.py +1 -0
- waldiez/io/models/user_input.py +10 -5
- waldiez/io/models/user_response.py +17 -16
- waldiez/io/mqtt.py +18 -31
- waldiez/io/redis.py +18 -22
- waldiez/io/structured.py +122 -70
- waldiez/io/utils.py +19 -10
- waldiez/io/ws.py +7 -3
- waldiez/logger.py +16 -3
- waldiez/models/agents/__init__.py +3 -0
- waldiez/models/agents/agent/agent.py +25 -17
- waldiez/models/agents/agent/agent_data.py +25 -22
- waldiez/models/agents/agent/code_execution.py +9 -11
- waldiez/models/agents/agent/termination_message.py +10 -12
- waldiez/models/agents/agent/update_system_message.py +2 -4
- waldiez/models/agents/agents.py +8 -8
- waldiez/models/agents/assistant/assistant.py +6 -3
- waldiez/models/agents/assistant/assistant_data.py +2 -2
- waldiez/models/agents/captain/captain_agent.py +7 -4
- waldiez/models/agents/captain/captain_agent_data.py +5 -7
- waldiez/models/agents/doc_agent/doc_agent.py +7 -4
- waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
- waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
- waldiez/models/agents/extra_requirements.py +3 -3
- waldiez/models/agents/group_manager/group_manager.py +12 -7
- waldiez/models/agents/group_manager/group_manager_data.py +13 -12
- waldiez/models/agents/group_manager/speakers.py +17 -19
- waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
- waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
- waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
- waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
- waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
- waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
- waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
- waldiez/models/agents/user_proxy/user_proxy.py +6 -3
- waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
- waldiez/models/chat/chat.py +28 -20
- waldiez/models/chat/chat_data.py +22 -21
- waldiez/models/chat/chat_message.py +9 -9
- waldiez/models/chat/chat_nested.py +9 -9
- waldiez/models/chat/chat_summary.py +6 -6
- waldiez/models/common/__init__.py +2 -0
- waldiez/models/common/ag2_version.py +2 -0
- waldiez/models/common/base.py +2 -0
- waldiez/models/common/dict_utils.py +8 -6
- waldiez/models/common/handoff.py +20 -17
- waldiez/models/common/method_utils.py +9 -7
- waldiez/models/common/naming.py +49 -0
- waldiez/models/flow/flow.py +11 -6
- waldiez/models/flow/flow_data.py +23 -17
- waldiez/models/flow/info.py +3 -3
- waldiez/models/flow/naming.py +2 -1
- waldiez/models/model/_aws.py +11 -13
- waldiez/models/model/_llm.py +8 -0
- waldiez/models/model/_price.py +2 -4
- waldiez/models/model/extra_requirements.py +1 -3
- waldiez/models/model/model.py +2 -2
- waldiez/models/model/model_data.py +21 -21
- waldiez/models/tool/extra_requirements.py +2 -4
- waldiez/models/tool/predefined/_duckduckgo.py +1 -0
- waldiez/models/tool/predefined/_email.py +4 -0
- waldiez/models/tool/predefined/_google.py +1 -0
- waldiez/models/tool/predefined/_perplexity.py +2 -1
- waldiez/models/tool/predefined/_searxng.py +2 -1
- waldiez/models/tool/predefined/_tavily.py +1 -0
- waldiez/models/tool/predefined/_wikipedia.py +2 -1
- waldiez/models/tool/predefined/_youtube.py +1 -0
- waldiez/models/tool/tool.py +8 -5
- waldiez/models/tool/tool_data.py +2 -2
- waldiez/models/waldiez.py +152 -4
- waldiez/runner.py +11 -5
- waldiez/running/async_utils.py +192 -0
- waldiez/running/base_runner.py +155 -241
- waldiez/running/dir_utils.py +52 -0
- waldiez/running/environment.py +10 -44
- waldiez/running/events_mixin.py +252 -0
- waldiez/running/exceptions.py +20 -0
- waldiez/running/gen_seq_diagram.py +18 -15
- waldiez/running/io_utils.py +216 -0
- waldiez/running/protocol.py +11 -5
- waldiez/running/requirements_mixin.py +65 -0
- waldiez/running/results_mixin.py +926 -0
- waldiez/running/standard_runner.py +24 -27
- waldiez/running/step_by_step/breakpoints_mixin.py +503 -47
- waldiez/running/step_by_step/command_handler.py +154 -0
- waldiez/running/step_by_step/events_processor.py +379 -0
- waldiez/running/step_by_step/step_by_step_models.py +425 -41
- waldiez/running/step_by_step/step_by_step_runner.py +437 -382
- waldiez/running/subprocess_runner/__base__.py +13 -8
- waldiez/running/subprocess_runner/_async_runner.py +6 -4
- waldiez/running/subprocess_runner/_sync_runner.py +11 -6
- waldiez/running/subprocess_runner/runner.py +48 -23
- waldiez/running/timeline_processor.py +1 -1
- waldiez/utils/__init__.py +2 -0
- waldiez/utils/conflict_checker.py +4 -4
- waldiez/utils/python_manager.py +415 -0
- waldiez/ws/__init__.py +8 -7
- waldiez/ws/_file_handler.py +18 -20
- waldiez/ws/_mock.py +75 -0
- waldiez/ws/cli.py +58 -10
- waldiez/ws/client_manager.py +77 -53
- waldiez/ws/errors.py +3 -0
- waldiez/ws/models.py +61 -53
- waldiez/ws/reloader.py +33 -4
- waldiez/ws/server.py +121 -52
- waldiez/ws/session_manager.py +8 -9
- waldiez/ws/session_stats.py +1 -1
- waldiez/ws/utils.py +33 -5
- {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/METADATA +107 -109
- waldiez-0.6.1.dist-info/RECORD +254 -0
- waldiez/running/post_run.py +0 -180
- waldiez/running/pre_run.py +0 -159
- waldiez/running/run_results.py +0 -14
- waldiez/running/utils.py +0 -511
- waldiez-0.5.10.dist-info/RECORD +0 -248
- {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
- {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
3
4
|
# pylint: disable=unused-argument
|
|
5
|
+
# pyright: reportDeprecated=false, reportMissingTypeStubs=false
|
|
6
|
+
# pyright: reportUnusedParameter=false, reportUnnecessaryIsInstance=false
|
|
7
|
+
# pyright: reportUnknownMemberType=false, reportUnknownVariableType=false
|
|
8
|
+
# pyright: reportUnknownArgumentType=false
|
|
9
|
+
|
|
4
10
|
"""Breakpoints management mixin for step-by-step debugging."""
|
|
5
11
|
|
|
6
|
-
|
|
12
|
+
import logging
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Callable, Union
|
|
7
16
|
|
|
8
17
|
from .step_by_step_models import (
|
|
18
|
+
WaldiezBreakpoint,
|
|
9
19
|
WaldiezDebugBreakpointAdded,
|
|
10
20
|
WaldiezDebugBreakpointCleared,
|
|
11
21
|
WaldiezDebugBreakpointRemoved,
|
|
12
22
|
WaldiezDebugBreakpointsList,
|
|
23
|
+
WaldiezDebugConfig,
|
|
13
24
|
WaldiezDebugError,
|
|
14
25
|
WaldiezDebugMessage,
|
|
15
26
|
)
|
|
@@ -19,13 +30,102 @@ if TYPE_CHECKING:
|
|
|
19
30
|
from autogen.messages import BaseMessage # type: ignore
|
|
20
31
|
|
|
21
32
|
|
|
33
|
+
def handle_breakpoint_errors(func: Callable[..., bool]) -> Callable[..., bool]:
|
|
34
|
+
"""Handle breakpoint-related errors.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
func : Callable
|
|
39
|
+
The function to decorate.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
Callable
|
|
44
|
+
The decorated function.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def _wrapper(self: "BreakpointsMixin", *args: Any, **kwargs: Any) -> bool:
|
|
48
|
+
try:
|
|
49
|
+
return func(self, *args, **kwargs)
|
|
50
|
+
except ValueError as e:
|
|
51
|
+
self.emit(WaldiezDebugError(error=f"Breakpoint error: {e}"))
|
|
52
|
+
return False
|
|
53
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
54
|
+
self.emit(
|
|
55
|
+
WaldiezDebugError(
|
|
56
|
+
error=f"Unexpected error in {func.__name__}: {e}"
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
logging.exception("Error in %s", func.__name__)
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
return _wrapper
|
|
63
|
+
|
|
64
|
+
|
|
22
65
|
class BreakpointsMixin:
|
|
23
66
|
"""Mixin class for managing breakpoints in step-by-step debugging."""
|
|
24
67
|
|
|
68
|
+
_breakpoints: set[WaldiezBreakpoint]
|
|
69
|
+
_agent_id_to_name: dict[str, str]
|
|
70
|
+
_config: WaldiezDebugConfig
|
|
71
|
+
|
|
25
72
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
26
73
|
"""Initialize breakpoints storage."""
|
|
27
|
-
self._breakpoints
|
|
74
|
+
self._breakpoints = set()
|
|
75
|
+
self._agent_id_to_name = {}
|
|
76
|
+
|
|
77
|
+
# Statistics for monitoring
|
|
78
|
+
self._breakpoint_stats = {
|
|
79
|
+
"total_matches": 0,
|
|
80
|
+
"cache_hits": 0,
|
|
81
|
+
"cache_misses": 0,
|
|
82
|
+
}
|
|
28
83
|
|
|
84
|
+
# Create the cached function with proper binding
|
|
85
|
+
self._check_breakpoint_match_cached = lru_cache(maxsize=1000)(
|
|
86
|
+
self._check_breakpoint_match_impl
|
|
87
|
+
)
|
|
88
|
+
self._config = kwargs.get("config", WaldiezDebugConfig())
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def get_initial_breakpoints(
|
|
92
|
+
items: Iterable[Any],
|
|
93
|
+
) -> set[WaldiezBreakpoint]:
|
|
94
|
+
"""Get initial breakpoints.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
items : Iterable[Any]
|
|
99
|
+
The items to parse for getting the endpoints.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
set[WaldiezBreakpoint]
|
|
104
|
+
The parsed breakpoints.
|
|
105
|
+
"""
|
|
106
|
+
breakpoints: set[WaldiezBreakpoint] = set()
|
|
107
|
+
for item in items:
|
|
108
|
+
if isinstance(item, str):
|
|
109
|
+
try:
|
|
110
|
+
entry = WaldiezBreakpoint.from_string(item)
|
|
111
|
+
breakpoints.add(entry)
|
|
112
|
+
except BaseException: # pylint: disable=broad-exception-caught
|
|
113
|
+
pass
|
|
114
|
+
elif isinstance(item, WaldiezBreakpoint):
|
|
115
|
+
breakpoints.add(item)
|
|
116
|
+
return breakpoints
|
|
117
|
+
|
|
118
|
+
def set_agent_id_to_name(self, mapping: dict[str, str]) -> None:
|
|
119
|
+
"""Set the agent id to agent name mapping.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
mapping : dict[str, str]
|
|
124
|
+
The agent id to agent name mapping.
|
|
125
|
+
"""
|
|
126
|
+
self._agent_id_to_name = mapping
|
|
127
|
+
|
|
128
|
+
# noinspection PyTypeHints
|
|
29
129
|
def emit(self, message: WaldiezDebugMessage) -> None:
|
|
30
130
|
"""Emit a debug message. Implemented by the class using this mixin.
|
|
31
131
|
|
|
@@ -36,31 +136,126 @@ class BreakpointsMixin:
|
|
|
36
136
|
"""
|
|
37
137
|
raise NotImplementedError("emit method must be implemented")
|
|
38
138
|
|
|
39
|
-
def
|
|
40
|
-
"""
|
|
139
|
+
def _invalidate_cache(self) -> None:
|
|
140
|
+
"""Invalidate the event matching cache when breakpoints change."""
|
|
141
|
+
self._check_breakpoint_match_cached.cache_clear()
|
|
142
|
+
|
|
143
|
+
def _get_breakpoints_signature(self) -> frozenset[str]:
|
|
144
|
+
"""Get a hashable signature of current breakpoints."""
|
|
145
|
+
return frozenset(str(bp) for bp in self._breakpoints)
|
|
146
|
+
|
|
147
|
+
def _check_breakpoint_match_impl(
|
|
148
|
+
self,
|
|
149
|
+
event_type: str,
|
|
150
|
+
sender: str,
|
|
151
|
+
recipient: str,
|
|
152
|
+
sender_only: bool,
|
|
153
|
+
breakpoints_sig: frozenset[str],
|
|
154
|
+
) -> bool:
|
|
155
|
+
"""Check if the event matches any breakpoints.
|
|
41
156
|
|
|
42
157
|
Parameters
|
|
43
158
|
----------
|
|
44
159
|
event_type : str
|
|
45
|
-
The event type to
|
|
160
|
+
The event type to check.
|
|
161
|
+
sender : str
|
|
162
|
+
The event sender.
|
|
163
|
+
recipient : str
|
|
164
|
+
The event recipient.
|
|
165
|
+
sender_only : bool
|
|
166
|
+
Only check for event's sender agent.
|
|
167
|
+
breakpoints_sig : frozenset[str]
|
|
168
|
+
Signature of current breakpoints for cache invalidation.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
bool
|
|
173
|
+
True if any breakpoint matches, False otherwise.
|
|
46
174
|
"""
|
|
47
|
-
|
|
175
|
+
event_dict = {
|
|
176
|
+
"type": event_type,
|
|
177
|
+
"sender": self._agent_id_to_name.get(sender, sender),
|
|
178
|
+
"recipient": self._agent_id_to_name.get(recipient, recipient),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Reconstruct breakpoints from signature for cache safety
|
|
182
|
+
# noinspection PyBroadException
|
|
183
|
+
try:
|
|
184
|
+
breakpoints = {
|
|
185
|
+
WaldiezBreakpoint.from_string(bp_str)
|
|
186
|
+
for bp_str in breakpoints_sig
|
|
187
|
+
}
|
|
188
|
+
return any(
|
|
189
|
+
bp.matches(
|
|
190
|
+
event_dict,
|
|
191
|
+
self._agent_id_to_name,
|
|
192
|
+
sender_only=sender_only,
|
|
193
|
+
)
|
|
194
|
+
for bp in breakpoints
|
|
195
|
+
)
|
|
196
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
197
|
+
# Fallback to current breakpoints if signature is malformed
|
|
198
|
+
return any(
|
|
199
|
+
bp.matches(
|
|
200
|
+
event_dict,
|
|
201
|
+
self._agent_id_to_name,
|
|
202
|
+
sender_only=sender_only,
|
|
203
|
+
)
|
|
204
|
+
for bp in self._breakpoints
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
@handle_breakpoint_errors
|
|
208
|
+
def add_breakpoint(self, spec: str) -> bool:
|
|
209
|
+
"""Add a breakpoint for an event type.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
spec : str
|
|
214
|
+
The event type specification to add a breakpoint for.
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
bool
|
|
219
|
+
True if the breakpoint was added successfully, False otherwise.
|
|
220
|
+
"""
|
|
221
|
+
if not spec or not isinstance(spec, str):
|
|
48
222
|
self.emit(
|
|
49
223
|
WaldiezDebugError(
|
|
50
224
|
error="Invalid event type: must be a non-empty string"
|
|
51
225
|
)
|
|
52
226
|
)
|
|
53
|
-
return
|
|
227
|
+
return False
|
|
228
|
+
# pylint: disable=too-many-try-statements
|
|
229
|
+
try:
|
|
230
|
+
breakpoint_obj = WaldiezBreakpoint.from_string(spec)
|
|
231
|
+
|
|
232
|
+
# Check if breakpoint already exists
|
|
233
|
+
if breakpoint_obj in self._breakpoints:
|
|
234
|
+
self.emit(
|
|
235
|
+
WaldiezDebugError(
|
|
236
|
+
error=f"Breakpoint for '{spec}' already exists"
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
self._breakpoints.add(breakpoint_obj)
|
|
242
|
+
self._invalidate_cache()
|
|
243
|
+
self.emit(WaldiezDebugBreakpointAdded(breakpoint=spec))
|
|
244
|
+
return True
|
|
54
245
|
|
|
55
|
-
|
|
56
|
-
|
|
246
|
+
except ValueError as e:
|
|
247
|
+
self.emit(
|
|
248
|
+
WaldiezDebugError(error=f"Invalid breakpoint format: {e}")
|
|
249
|
+
)
|
|
250
|
+
return False
|
|
57
251
|
|
|
58
|
-
|
|
59
|
-
|
|
252
|
+
@handle_breakpoint_errors
|
|
253
|
+
def remove_breakpoint(self, spec: str | WaldiezBreakpoint) -> bool:
|
|
254
|
+
"""Remove a breakpoint based on its specification.
|
|
60
255
|
|
|
61
256
|
Parameters
|
|
62
257
|
----------
|
|
63
|
-
|
|
258
|
+
spec : str | WaldiezBreakpoint
|
|
64
259
|
The event type to remove the breakpoint for.
|
|
65
260
|
|
|
66
261
|
Returns
|
|
@@ -68,79 +263,209 @@ class BreakpointsMixin:
|
|
|
68
263
|
bool
|
|
69
264
|
True if the breakpoint was removed, False if it didn't exist.
|
|
70
265
|
"""
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
266
|
+
if isinstance(spec, WaldiezBreakpoint):
|
|
267
|
+
breakpoint_obj = spec
|
|
268
|
+
spec_str = str(spec)
|
|
269
|
+
elif isinstance(spec, str) and spec:
|
|
270
|
+
try:
|
|
271
|
+
breakpoint_obj = WaldiezBreakpoint.from_string(spec)
|
|
272
|
+
spec_str = spec
|
|
273
|
+
except ValueError as e:
|
|
274
|
+
self.emit(
|
|
275
|
+
WaldiezDebugError(error=f"Invalid breakpoint format: {e}")
|
|
75
276
|
)
|
|
277
|
+
return False
|
|
278
|
+
else:
|
|
279
|
+
self.emit(
|
|
280
|
+
WaldiezDebugError(error="Invalid breakpoint specification")
|
|
76
281
|
)
|
|
77
282
|
return False
|
|
78
283
|
|
|
79
|
-
if
|
|
80
|
-
self._breakpoints.remove(
|
|
81
|
-
self.
|
|
284
|
+
if breakpoint_obj in self._breakpoints:
|
|
285
|
+
self._breakpoints.remove(breakpoint_obj)
|
|
286
|
+
self._invalidate_cache()
|
|
287
|
+
self.emit(WaldiezDebugBreakpointRemoved(breakpoint=spec_str))
|
|
82
288
|
return True
|
|
289
|
+
|
|
83
290
|
self.emit(
|
|
84
291
|
WaldiezDebugError(
|
|
85
|
-
error=f"Breakpoint for '{
|
|
292
|
+
error=f"Breakpoint for '{spec_str}' does not exist"
|
|
86
293
|
)
|
|
87
294
|
)
|
|
88
295
|
return False
|
|
89
296
|
|
|
90
297
|
def list_breakpoints(self) -> None:
|
|
91
298
|
"""List all current breakpoints."""
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
)
|
|
299
|
+
breakpoints_list = sorted(self._breakpoints, key=str)
|
|
300
|
+
self.emit(WaldiezDebugBreakpointsList(breakpoints=breakpoints_list))
|
|
95
301
|
|
|
96
302
|
def clear_breakpoints(self) -> None:
|
|
97
303
|
"""Clear all breakpoints."""
|
|
98
304
|
count = len(self._breakpoints)
|
|
99
305
|
self._breakpoints.clear()
|
|
306
|
+
self._invalidate_cache()
|
|
307
|
+
|
|
100
308
|
if count > 0:
|
|
101
309
|
self.emit(
|
|
102
310
|
WaldiezDebugBreakpointCleared(
|
|
103
311
|
message=f"Cleared {count} breakpoint(s)"
|
|
104
312
|
)
|
|
105
313
|
)
|
|
314
|
+
else:
|
|
315
|
+
self.emit(
|
|
316
|
+
WaldiezDebugBreakpointCleared(message="No breakpoints to clear")
|
|
317
|
+
)
|
|
106
318
|
|
|
107
|
-
|
|
108
|
-
|
|
319
|
+
@handle_breakpoint_errors
|
|
320
|
+
def set_breakpoints(self, specs: Iterable[str | WaldiezBreakpoint]) -> bool:
|
|
321
|
+
"""Set which breakpoints to activate.
|
|
109
322
|
|
|
110
323
|
Parameters
|
|
111
324
|
----------
|
|
112
|
-
|
|
113
|
-
Iterable of event types to break on. Empty means
|
|
325
|
+
specs : Iterable[str | WaldiezBreakpoint]
|
|
326
|
+
Iterable of event types to break on. Empty means no breakpoints.
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
bool
|
|
331
|
+
True if all breakpoints were set successfully, False if any failed.
|
|
114
332
|
"""
|
|
115
|
-
|
|
333
|
+
new_breakpoints: set[WaldiezBreakpoint] = set()
|
|
334
|
+
errors: list[str] = []
|
|
335
|
+
|
|
336
|
+
for spec in specs:
|
|
337
|
+
try:
|
|
338
|
+
if isinstance(spec, WaldiezBreakpoint):
|
|
339
|
+
new_breakpoints.add(spec)
|
|
340
|
+
else:
|
|
341
|
+
new_breakpoints.add(WaldiezBreakpoint.from_string(spec))
|
|
342
|
+
except ValueError as e:
|
|
343
|
+
errors.append(f"Invalid breakpoint '{spec}': {e}")
|
|
344
|
+
|
|
345
|
+
if errors:
|
|
346
|
+
for error in errors:
|
|
347
|
+
self.emit(WaldiezDebugError(error=error))
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
old_count = len(self._breakpoints)
|
|
351
|
+
self._breakpoints = new_breakpoints
|
|
352
|
+
new_count = len(self._breakpoints)
|
|
353
|
+
self._invalidate_cache()
|
|
354
|
+
|
|
355
|
+
self.emit(
|
|
356
|
+
WaldiezDebugBreakpointCleared(
|
|
357
|
+
message=f"Updated breakpoints: {old_count} -> {new_count}"
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
return True
|
|
116
361
|
|
|
117
|
-
def get_breakpoints(self) -> set[
|
|
362
|
+
def get_breakpoints(self) -> set[WaldiezBreakpoint]:
|
|
118
363
|
"""Get current breakpoints.
|
|
119
364
|
|
|
120
365
|
Returns
|
|
121
366
|
-------
|
|
122
|
-
set[
|
|
367
|
+
set[WaldiezBreakpoint]
|
|
123
368
|
Set of current breakpoint event types.
|
|
124
369
|
"""
|
|
125
370
|
return self._breakpoints.copy()
|
|
126
371
|
|
|
127
|
-
def has_breakpoint(self,
|
|
128
|
-
"""Check if a breakpoint exists
|
|
372
|
+
def has_breakpoint(self, spec: str | WaldiezBreakpoint) -> bool:
|
|
373
|
+
"""Check if a breakpoint exists.
|
|
129
374
|
|
|
130
375
|
Parameters
|
|
131
376
|
----------
|
|
132
|
-
|
|
133
|
-
The
|
|
377
|
+
spec : str | WaldiezBreakpoint
|
|
378
|
+
The breakpoint specification to check.
|
|
134
379
|
|
|
135
380
|
Returns
|
|
136
381
|
-------
|
|
137
382
|
bool
|
|
138
383
|
True if a breakpoint exists for this event type.
|
|
139
384
|
"""
|
|
140
|
-
|
|
385
|
+
try:
|
|
386
|
+
if isinstance(spec, WaldiezBreakpoint):
|
|
387
|
+
return spec in self._breakpoints
|
|
388
|
+
return WaldiezBreakpoint.from_string(spec) in self._breakpoints
|
|
389
|
+
except ValueError:
|
|
390
|
+
return False
|
|
391
|
+
|
|
392
|
+
@staticmethod
|
|
393
|
+
def _get_event_core(event_dict: dict[str, Any]) -> tuple[str, str, str]:
|
|
394
|
+
event_type = event_dict.get("type", "unknown")
|
|
395
|
+
sender = event_dict.get("sender", "")
|
|
396
|
+
if not sender:
|
|
397
|
+
event_content = event_dict.get("content", {})
|
|
398
|
+
if isinstance(event_content, dict):
|
|
399
|
+
sender = event_content.get(
|
|
400
|
+
"sender",
|
|
401
|
+
event_content.get("speaker", ""),
|
|
402
|
+
)
|
|
403
|
+
if not isinstance(sender, str):
|
|
404
|
+
sender = ""
|
|
405
|
+
recipient = event_dict.get("recipient", "")
|
|
406
|
+
if not recipient:
|
|
407
|
+
event_content = event_dict.get("content", {})
|
|
408
|
+
if isinstance(event_content, dict):
|
|
409
|
+
recipient = event_content.get("recipient")
|
|
410
|
+
if not isinstance(recipient, str):
|
|
411
|
+
recipient = ""
|
|
412
|
+
return event_type, sender, recipient
|
|
413
|
+
|
|
414
|
+
def _got_breakpoint_match(
|
|
415
|
+
self, event_dump: dict[str, Any], sender_only: bool
|
|
416
|
+
) -> bool:
|
|
417
|
+
event_type, sender, recipient = BreakpointsMixin._get_event_core(
|
|
418
|
+
event_dump
|
|
419
|
+
)
|
|
420
|
+
event_dict = {
|
|
421
|
+
"type": event_type,
|
|
422
|
+
"sender": sender,
|
|
423
|
+
"recipient": recipient,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
has_agent_breakpoints = any(
|
|
427
|
+
bp.agent is not None for bp in self._breakpoints
|
|
428
|
+
)
|
|
429
|
+
if has_agent_breakpoints:
|
|
430
|
+
# Don't use cache for agent-based breakpoints
|
|
431
|
+
# (we might have a mix of agent ids and names)
|
|
432
|
+
matches_breakpoint = any(
|
|
433
|
+
bp.matches(
|
|
434
|
+
event_dict,
|
|
435
|
+
self._agent_id_to_name,
|
|
436
|
+
sender_only=sender_only,
|
|
437
|
+
)
|
|
438
|
+
for bp in self._breakpoints
|
|
439
|
+
)
|
|
440
|
+
else:
|
|
441
|
+
# Get current breakpoints signature for cache invalidation
|
|
442
|
+
breakpoints_sig = self._get_breakpoints_signature()
|
|
443
|
+
|
|
444
|
+
# Check cached result
|
|
445
|
+
# noinspection PyBroadException
|
|
446
|
+
try:
|
|
447
|
+
matches_breakpoint = self._check_breakpoint_match_cached(
|
|
448
|
+
event_type, sender, recipient, sender_only, breakpoints_sig
|
|
449
|
+
)
|
|
450
|
+
self._breakpoint_stats["cache_hits"] += 1
|
|
451
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
452
|
+
# Fallback to non-cached check
|
|
453
|
+
matches_breakpoint = any(
|
|
454
|
+
bp.matches(
|
|
455
|
+
event_dict,
|
|
456
|
+
self._agent_id_to_name,
|
|
457
|
+
sender_only=sender_only,
|
|
458
|
+
)
|
|
459
|
+
for bp in self._breakpoints
|
|
460
|
+
)
|
|
461
|
+
self._breakpoint_stats["cache_misses"] += 1
|
|
462
|
+
|
|
463
|
+
return matches_breakpoint
|
|
141
464
|
|
|
142
465
|
def should_break_on_event(
|
|
143
|
-
self,
|
|
466
|
+
self,
|
|
467
|
+
event: Union["BaseEvent", "BaseMessage"],
|
|
468
|
+
sender_only: bool,
|
|
144
469
|
) -> bool:
|
|
145
470
|
"""Determine if we should break on this event.
|
|
146
471
|
|
|
@@ -148,17 +473,14 @@ class BreakpointsMixin:
|
|
|
148
473
|
----------
|
|
149
474
|
event : Union[BaseEvent, BaseMessage]
|
|
150
475
|
The event to check.
|
|
151
|
-
|
|
152
|
-
|
|
476
|
+
sender_only : bool
|
|
477
|
+
Only check for event's sender agent.
|
|
153
478
|
|
|
154
479
|
Returns
|
|
155
480
|
-------
|
|
156
481
|
bool
|
|
157
482
|
True if we should break, False otherwise.
|
|
158
483
|
"""
|
|
159
|
-
if not step_mode:
|
|
160
|
-
return False
|
|
161
|
-
|
|
162
484
|
# Get event type
|
|
163
485
|
event_type = getattr(event, "type", "unknown")
|
|
164
486
|
|
|
@@ -166,23 +488,157 @@ class BreakpointsMixin:
|
|
|
166
488
|
if event_type == "input_request":
|
|
167
489
|
return False
|
|
168
490
|
|
|
169
|
-
# If no specific breakpoints set, break on all events
|
|
170
491
|
if not self._breakpoints:
|
|
171
|
-
return
|
|
492
|
+
return not bool(self._config.auto_continue)
|
|
493
|
+
|
|
494
|
+
# Check if this event matches any breakpoint using caching
|
|
495
|
+
if not hasattr(event, "model_dump"):
|
|
496
|
+
return not bool(self._config.auto_continue)
|
|
497
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
498
|
+
try:
|
|
499
|
+
event_dump = event.model_dump(
|
|
500
|
+
mode="python", exclude_none=True, fallback=str
|
|
501
|
+
)
|
|
502
|
+
matches_breakpoint = self._got_breakpoint_match(
|
|
503
|
+
event_dump, sender_only=sender_only
|
|
504
|
+
)
|
|
505
|
+
# If any breakpoint matches: break regardless of step_mode
|
|
506
|
+
if matches_breakpoint:
|
|
507
|
+
self._breakpoint_stats["total_matches"] += 1
|
|
508
|
+
return True
|
|
172
509
|
|
|
173
|
-
|
|
174
|
-
|
|
510
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
511
|
+
logging.warning("Error processing event for breakpoints: %s", e)
|
|
512
|
+
self._breakpoint_stats["cache_misses"] += 1
|
|
513
|
+
|
|
514
|
+
return not bool(self._config.auto_continue)
|
|
175
515
|
|
|
176
516
|
def get_breakpoint_stats(self) -> dict[str, Any]:
|
|
177
|
-
"""Get breakpoint statistics.
|
|
517
|
+
"""Get breakpoint statistics including performance metrics.
|
|
178
518
|
|
|
179
519
|
Returns
|
|
180
520
|
-------
|
|
181
521
|
dict[str, Any]
|
|
182
522
|
Dictionary containing breakpoint statistics.
|
|
183
523
|
"""
|
|
524
|
+
breakpoints: list[dict[str, Any]] = [
|
|
525
|
+
{
|
|
526
|
+
"type": bp.type.value,
|
|
527
|
+
"event_type": bp.event_type,
|
|
528
|
+
"agent": bp.agent,
|
|
529
|
+
"description": bp.description,
|
|
530
|
+
"string_repr": str(bp),
|
|
531
|
+
}
|
|
532
|
+
for bp in self._breakpoints
|
|
533
|
+
]
|
|
534
|
+
|
|
535
|
+
# Calculate cache efficiency
|
|
536
|
+
total_checks = (
|
|
537
|
+
self._breakpoint_stats["cache_hits"]
|
|
538
|
+
+ self._breakpoint_stats["cache_misses"]
|
|
539
|
+
)
|
|
540
|
+
cache_hit_rate = (
|
|
541
|
+
self._breakpoint_stats["cache_hits"] / total_checks
|
|
542
|
+
if total_checks > 0
|
|
543
|
+
else 0
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
# Get cache info from lru_cache
|
|
547
|
+
cache_info = self._check_breakpoint_match_cached.cache_info()
|
|
548
|
+
|
|
184
549
|
return {
|
|
185
550
|
"total_breakpoints": len(self._breakpoints),
|
|
186
|
-
"breakpoints":
|
|
551
|
+
"breakpoints": breakpoints,
|
|
187
552
|
"has_breakpoints": len(self._breakpoints) > 0,
|
|
553
|
+
"cache_stats": {
|
|
554
|
+
"cache_hit_rate": f"{cache_hit_rate:.2%}",
|
|
555
|
+
"cache_size": cache_info.currsize,
|
|
556
|
+
"cache_maxsize": cache_info.maxsize,
|
|
557
|
+
"total_matches": self._breakpoint_stats["total_matches"],
|
|
558
|
+
"cache_hits": cache_info.hits,
|
|
559
|
+
"cache_misses": cache_info.misses,
|
|
560
|
+
},
|
|
561
|
+
"performance": {
|
|
562
|
+
"lru_cache_info": {
|
|
563
|
+
"hits": cache_info.hits,
|
|
564
|
+
"misses": cache_info.misses,
|
|
565
|
+
"maxsize": cache_info.maxsize,
|
|
566
|
+
"currsize": cache_info.currsize,
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
def reset_stats(self) -> None:
|
|
572
|
+
"""Reset breakpoint statistics."""
|
|
573
|
+
self._breakpoint_stats = {
|
|
574
|
+
"total_matches": 0,
|
|
575
|
+
"cache_hits": 0,
|
|
576
|
+
"cache_misses": 0,
|
|
188
577
|
}
|
|
578
|
+
self._invalidate_cache()
|
|
579
|
+
|
|
580
|
+
def optimize_cache(self) -> None:
|
|
581
|
+
"""Manually optimize the cache by clearing it."""
|
|
582
|
+
self._invalidate_cache()
|
|
583
|
+
|
|
584
|
+
def export_breakpoints(self) -> list[str]:
|
|
585
|
+
"""Export breakpoints as a list of strings for persistence.
|
|
586
|
+
|
|
587
|
+
Returns
|
|
588
|
+
-------
|
|
589
|
+
list[str]
|
|
590
|
+
List of breakpoint specifications as strings.
|
|
591
|
+
"""
|
|
592
|
+
return [str(bp) for bp in sorted(self._breakpoints, key=str)]
|
|
593
|
+
|
|
594
|
+
def is_auto_run(self) -> bool:
|
|
595
|
+
"""Check if we are in auto-run mode.
|
|
596
|
+
|
|
597
|
+
Returns
|
|
598
|
+
-------
|
|
599
|
+
bool
|
|
600
|
+
False if we don't have any breakpoints and
|
|
601
|
+
we don't have any 'all' breakpoints, True otherwise.
|
|
602
|
+
"""
|
|
603
|
+
if not self._breakpoints:
|
|
604
|
+
return False
|
|
605
|
+
if any(bp.type.value == "all" for bp in self._breakpoints):
|
|
606
|
+
self._breakpoints.clear()
|
|
607
|
+
self._invalidate_cache()
|
|
608
|
+
return False
|
|
609
|
+
return True
|
|
610
|
+
|
|
611
|
+
def import_breakpoints(
|
|
612
|
+
self, breakpoint_specs: list[str]
|
|
613
|
+
) -> tuple[int, list[str]]:
|
|
614
|
+
"""Import breakpoints from a list of string specifications.
|
|
615
|
+
|
|
616
|
+
Parameters
|
|
617
|
+
----------
|
|
618
|
+
breakpoint_specs : list[str]
|
|
619
|
+
List of breakpoint specifications as strings.
|
|
620
|
+
|
|
621
|
+
Returns
|
|
622
|
+
-------
|
|
623
|
+
tuple[int, list[str]]
|
|
624
|
+
Tuple of (successful_imports, error_messages).
|
|
625
|
+
"""
|
|
626
|
+
successful = 0
|
|
627
|
+
errors: list[str] = []
|
|
628
|
+
|
|
629
|
+
for spec in breakpoint_specs:
|
|
630
|
+
# pylint: disable=too-many-try-statements
|
|
631
|
+
try:
|
|
632
|
+
breakpoint_obj = WaldiezBreakpoint.from_string(spec)
|
|
633
|
+
if breakpoint_obj not in self._breakpoints:
|
|
634
|
+
self._breakpoints.add(breakpoint_obj)
|
|
635
|
+
successful += 1
|
|
636
|
+
else:
|
|
637
|
+
errors.append(f"Breakpoint '{spec}' already exists")
|
|
638
|
+
except ValueError as e:
|
|
639
|
+
errors.append(f"Invalid breakpoint '{spec}': {e}")
|
|
640
|
+
|
|
641
|
+
if successful > 0:
|
|
642
|
+
self._invalidate_cache()
|
|
643
|
+
|
|
644
|
+
return successful, errors
|