waldiez 0.6.0__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 +18 -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 +9 -10
- 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 +34 -23
- waldiez/exporting/agent/extras/group_member_extras.py +6 -5
- waldiez/exporting/agent/extras/handoffs/after_work.py +1 -1
- waldiez/exporting/agent/extras/handoffs/available.py +1 -1
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
- waldiez/exporting/agent/extras/handoffs/handoff.py +1 -1
- waldiez/exporting/agent/extras/handoffs/target.py +6 -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/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/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 +2 -2
- waldiez/exporting/core/protocols.py +6 -5
- waldiez/exporting/core/result.py +25 -28
- waldiez/exporting/core/types.py +10 -10
- waldiez/exporting/core/utils/llm_config.py +2 -2
- waldiez/exporting/core/validation.py +10 -11
- waldiez/exporting/flow/execution_generator.py +98 -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 +5 -5
- waldiez/exporting/flow/utils/importing.py +6 -7
- waldiez/exporting/flow/utils/linting.py +25 -9
- waldiez/exporting/flow/utils/logging.py +2 -2
- 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 +8 -4
- waldiez/io/_ws.py +10 -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 +52 -53
- waldiez/io/utils.py +3 -0
- waldiez/io/ws.py +5 -1
- waldiez/logger.py +16 -3
- waldiez/models/agents/__init__.py +3 -0
- waldiez/models/agents/agent/agent.py +23 -16
- 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 +27 -20
- waldiez/models/chat/chat_data.py +22 -19
- 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/dict_utils.py +8 -6
- waldiez/models/common/handoff.py +18 -17
- waldiez/models/common/method_utils.py +7 -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 +5 -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 +1 -0
- waldiez/models/tool/predefined/_google.py +1 -0
- waldiez/models/tool/predefined/_perplexity.py +1 -0
- waldiez/models/tool/predefined/_searxng.py +1 -0
- waldiez/models/tool/predefined/_tavily.py +1 -0
- waldiez/models/tool/predefined/_wikipedia.py +1 -0
- 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 +117 -264
- 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 +22 -25
- waldiez/running/step_by_step/breakpoints_mixin.py +192 -60
- waldiez/running/step_by_step/command_handler.py +3 -0
- waldiez/running/step_by_step/events_processor.py +194 -14
- waldiez/running/step_by_step/step_by_step_models.py +110 -43
- waldiez/running/step_by_step/step_by_step_runner.py +107 -57
- waldiez/running/subprocess_runner/__base__.py +9 -1
- waldiez/running/subprocess_runner/_async_runner.py +5 -3
- waldiez/running/subprocess_runner/_sync_runner.py +6 -2
- waldiez/running/subprocess_runner/runner.py +39 -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/_file_handler.py +18 -18
- waldiez/ws/_mock.py +2 -1
- waldiez/ws/cli.py +36 -12
- waldiez/ws/client_manager.py +35 -27
- waldiez/ws/errors.py +3 -0
- waldiez/ws/models.py +43 -52
- waldiez/ws/reloader.py +12 -4
- waldiez/ws/server.py +85 -55
- waldiez/ws/session_manager.py +8 -9
- waldiez/ws/session_stats.py +1 -1
- waldiez/ws/utils.py +4 -1
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/METADATA +82 -93
- waldiez-0.6.1.dist-info/RECORD +254 -0
- waldiez/running/post_run.py +0 -186
- waldiez/running/pre_run.py +0 -281
- waldiez/running/run_results.py +0 -14
- waldiez/running/utils.py +0 -625
- waldiez-0.6.0.dist-info/RECORD +0 -251
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
3
4
|
# pylint: disable=unused-argument,no-self-use
|
|
5
|
+
# pyright: reportMissingTypeStubs=false, reportImportCycles=false
|
|
6
|
+
# pyright: reportDeprecated=false, reportUnknownMemberType=false
|
|
7
|
+
# pyright: reportUnknownVariableType=false, reportUnknownArgumentType=false
|
|
8
|
+
|
|
4
9
|
"""Command handler for step-by-step execution."""
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
import inspect
|
|
12
|
+
from collections.abc import Mapping, Sequence
|
|
13
|
+
from dataclasses import asdict, is_dataclass
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
7
15
|
|
|
8
16
|
if TYPE_CHECKING:
|
|
17
|
+
from autogen.agentchat import ConversableAgent # type: ignore
|
|
9
18
|
from autogen.events.base_event import BaseEvent # type: ignore
|
|
10
19
|
from autogen.messages.base_message import BaseMessage # type: ignore
|
|
11
20
|
|
|
@@ -21,7 +30,9 @@ class EventProcessor:
|
|
|
21
30
|
self.runner = runner
|
|
22
31
|
|
|
23
32
|
def process_event(
|
|
24
|
-
self,
|
|
33
|
+
self,
|
|
34
|
+
event: Union["BaseEvent", "BaseMessage"],
|
|
35
|
+
agents: list["ConversableAgent"],
|
|
25
36
|
) -> dict[str, Any]:
|
|
26
37
|
"""Shared logic for both sync and async event processing.
|
|
27
38
|
|
|
@@ -29,6 +40,8 @@ class EventProcessor:
|
|
|
29
40
|
----------
|
|
30
41
|
event : BaseEvent | BaseMessage
|
|
31
42
|
The event to process.
|
|
43
|
+
agents : list[ConversableAgent]
|
|
44
|
+
The workflow's known agents.
|
|
32
45
|
|
|
33
46
|
Returns
|
|
34
47
|
-------
|
|
@@ -45,19 +58,20 @@ class EventProcessor:
|
|
|
45
58
|
self._update_participant_info(event_info)
|
|
46
59
|
self._manage_event_history(event_info)
|
|
47
60
|
self._check_for_input_request(event_info)
|
|
61
|
+
self._add_agents_info(event_info, agents)
|
|
48
62
|
|
|
49
63
|
should_break = self.runner.should_break_on_event(
|
|
50
|
-
event,
|
|
64
|
+
event, sender_only=True
|
|
51
65
|
)
|
|
52
66
|
|
|
53
67
|
return {
|
|
54
|
-
"action": "continue",
|
|
55
|
-
"should_break": should_break,
|
|
68
|
+
"action": "break" if should_break else "continue",
|
|
56
69
|
"event_info": event_info,
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
def _create_event_info(
|
|
60
|
-
self,
|
|
73
|
+
self,
|
|
74
|
+
event: Union["BaseEvent", "BaseMessage"],
|
|
61
75
|
) -> dict[str, Any]:
|
|
62
76
|
"""Create event info dictionary from event object.
|
|
63
77
|
|
|
@@ -92,7 +106,7 @@ class EventProcessor:
|
|
|
92
106
|
if not event_info["sender"] or not event_info["recipient"]:
|
|
93
107
|
self._extract_participants_from_content(event_info)
|
|
94
108
|
|
|
95
|
-
self.
|
|
109
|
+
self._check_for_event_speaker(event_info)
|
|
96
110
|
self._extract_participants_from_direct_content(event_info)
|
|
97
111
|
|
|
98
112
|
# Update last known participants
|
|
@@ -115,14 +129,14 @@ class EventProcessor:
|
|
|
115
129
|
and "chat_info" in content
|
|
116
130
|
and isinstance(content["chat_info"], dict)
|
|
117
131
|
):
|
|
118
|
-
content = content.get("chat_info", {})
|
|
132
|
+
content = content.get("chat_info", {})
|
|
119
133
|
|
|
120
134
|
if not event_info["sender"] and "sender" in content:
|
|
121
135
|
event_info["sender"] = content["sender"]
|
|
122
136
|
if not event_info["recipient"] and "recipient" in content:
|
|
123
137
|
event_info["recipient"] = content["recipient"]
|
|
124
138
|
|
|
125
|
-
def
|
|
139
|
+
def _check_for_event_speaker(self, event_info: dict[str, Any]) -> None:
|
|
126
140
|
"""Handle speaker information for group chat events.
|
|
127
141
|
|
|
128
142
|
Parameters
|
|
@@ -130,11 +144,7 @@ class EventProcessor:
|
|
|
130
144
|
event_info : dict[str, Any]
|
|
131
145
|
Event information dictionary to update.
|
|
132
146
|
"""
|
|
133
|
-
if (
|
|
134
|
-
event_info.get("type") == "group_chat_run_chat"
|
|
135
|
-
and "content" in event_info
|
|
136
|
-
and isinstance(event_info["content"], dict)
|
|
137
|
-
):
|
|
147
|
+
if "content" in event_info and isinstance(event_info["content"], dict):
|
|
138
148
|
content = event_info.get("content", {})
|
|
139
149
|
speaker = content.get("speaker")
|
|
140
150
|
if isinstance(speaker, str) and speaker:
|
|
@@ -197,3 +207,173 @@ class EventProcessor:
|
|
|
197
207
|
event_info["recipient"] = sender
|
|
198
208
|
self.runner.last_sender = recipient
|
|
199
209
|
self.runner.last_recipient = sender
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _get_agent_dump(
|
|
213
|
+
agent: Optional["ConversableAgent"],
|
|
214
|
+
) -> dict[str, Any] | None:
|
|
215
|
+
if not agent:
|
|
216
|
+
return None
|
|
217
|
+
dump = {
|
|
218
|
+
name: _trimmed(value)
|
|
219
|
+
for name, value in vars(agent).items()
|
|
220
|
+
if not name.startswith("_")
|
|
221
|
+
and not inspect.ismethod(value)
|
|
222
|
+
and not inspect.isfunction(value)
|
|
223
|
+
and name.upper() != name
|
|
224
|
+
and name != "chat_messages"
|
|
225
|
+
}
|
|
226
|
+
for attr_key in ["name", "description", "system_message"]:
|
|
227
|
+
attr_val = getattr(agent, attr_key)
|
|
228
|
+
if attr_val:
|
|
229
|
+
dump[attr_key] = _trimmed(attr_val)
|
|
230
|
+
try:
|
|
231
|
+
dump["chat_messages"] = {
|
|
232
|
+
_to_json_key(agent): [_trimmed(message) for message in messages]
|
|
233
|
+
for _agent, messages in agent.chat_messages.items()
|
|
234
|
+
}
|
|
235
|
+
except BaseException: # pylint: disable=broad-exception-caught
|
|
236
|
+
pass
|
|
237
|
+
dump["cost"] = {
|
|
238
|
+
"actual": agent.get_actual_usage(),
|
|
239
|
+
"total": agent.get_total_usage(),
|
|
240
|
+
}
|
|
241
|
+
return dump
|
|
242
|
+
|
|
243
|
+
def _add_agents_info(
|
|
244
|
+
self, event_info: dict[str, Any], agents: list["ConversableAgent"]
|
|
245
|
+
) -> None:
|
|
246
|
+
"""Add agents info."""
|
|
247
|
+
sender = event_info.get("sender", self.runner.last_sender)
|
|
248
|
+
recipient = event_info.get("recipient", self.runner.last_recipient)
|
|
249
|
+
|
|
250
|
+
agent_map = {agent.name: agent for agent in agents}
|
|
251
|
+
|
|
252
|
+
sender_agent = agent_map.get(sender)
|
|
253
|
+
recipient_agent = agent_map.get(recipient)
|
|
254
|
+
|
|
255
|
+
ordered_agents: list["ConversableAgent"] = []
|
|
256
|
+
seen: set[str] = set()
|
|
257
|
+
|
|
258
|
+
for a in (sender_agent, recipient_agent):
|
|
259
|
+
if a and a.name not in seen:
|
|
260
|
+
ordered_agents.append(a)
|
|
261
|
+
seen.add(a.name)
|
|
262
|
+
|
|
263
|
+
for a in agents:
|
|
264
|
+
if a.name not in seen:
|
|
265
|
+
ordered_agents.append(a)
|
|
266
|
+
seen.add(a.name)
|
|
267
|
+
|
|
268
|
+
event_info["agents"] = {
|
|
269
|
+
"sender": self._get_agent_dump(sender_agent),
|
|
270
|
+
"recipient": self._get_agent_dump(recipient_agent),
|
|
271
|
+
"all": [self._get_agent_dump(a) for a in ordered_agents if a],
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# pylint: disable=too-complex,too-many-return-statements,
|
|
276
|
+
# pylint: disable=broad-exception-caught,too-complex,too-many-branches
|
|
277
|
+
def _trimmed( # noqa: C901
|
|
278
|
+
value: Any, max_len: int = 200, _depth: int = 0, _max_depth: int = 10
|
|
279
|
+
) -> Any:
|
|
280
|
+
"""Recursively trim values for serialization."""
|
|
281
|
+
if _depth >= _max_depth:
|
|
282
|
+
# Hard stop
|
|
283
|
+
s = str(value)
|
|
284
|
+
return (s[:max_len] + "...") if len(s) > max_len else s
|
|
285
|
+
|
|
286
|
+
if isinstance(value, str):
|
|
287
|
+
return value[:max_len] + "..." if len(value) > max_len else value
|
|
288
|
+
|
|
289
|
+
if value is None or isinstance(value, (int, float, bool)):
|
|
290
|
+
return value
|
|
291
|
+
|
|
292
|
+
if isinstance(value, (bytes, bytearray)):
|
|
293
|
+
try:
|
|
294
|
+
s = value.decode("utf-8", errors="replace")
|
|
295
|
+
return s[:max_len] + "..." if len(s) > max_len else s
|
|
296
|
+
except Exception:
|
|
297
|
+
return f"<{type(value).__name__}> {len(value)} bytes"
|
|
298
|
+
|
|
299
|
+
if callable(value):
|
|
300
|
+
# pylint: disable=too-many-try-statements
|
|
301
|
+
try:
|
|
302
|
+
name = getattr(value, "__name__", repr(value))
|
|
303
|
+
module = getattr(value, "__module__", "")
|
|
304
|
+
if module:
|
|
305
|
+
s = f"<callable> {module}.{name}"
|
|
306
|
+
else:
|
|
307
|
+
s = f"<callable> {name}"
|
|
308
|
+
return s[:max_len] + "..." if len(s) > max_len else s
|
|
309
|
+
except Exception:
|
|
310
|
+
return "<callable>"
|
|
311
|
+
|
|
312
|
+
if _is_dataclass_instance(value):
|
|
313
|
+
try:
|
|
314
|
+
value = asdict(value)
|
|
315
|
+
except Exception:
|
|
316
|
+
s = str(value)
|
|
317
|
+
return (s[:max_len] + "...") if len(s) > max_len else s
|
|
318
|
+
|
|
319
|
+
if isinstance(value, Mapping):
|
|
320
|
+
out: dict[str, Any] = {}
|
|
321
|
+
for k, v in value.items():
|
|
322
|
+
jk = _to_json_key(k, max_len)
|
|
323
|
+
out[jk] = _trimmed(v, max_len, _depth + 1, _max_depth)
|
|
324
|
+
return out
|
|
325
|
+
if isinstance(value, (set, frozenset)):
|
|
326
|
+
items = [_trimmed(it, max_len, _depth + 1, _max_depth) for it in value]
|
|
327
|
+
try:
|
|
328
|
+
items.sort(key=repr)
|
|
329
|
+
except Exception:
|
|
330
|
+
pass
|
|
331
|
+
return items
|
|
332
|
+
|
|
333
|
+
if isinstance(value, Sequence) and not isinstance(
|
|
334
|
+
value, (str, bytes, bytearray)
|
|
335
|
+
):
|
|
336
|
+
return [_trimmed(it, max_len, _depth + 1, _max_depth) for it in value]
|
|
337
|
+
if hasattr(value, "__dict__"):
|
|
338
|
+
try:
|
|
339
|
+
return _trimmed(
|
|
340
|
+
{
|
|
341
|
+
k: v
|
|
342
|
+
for k, v in vars(value).items()
|
|
343
|
+
if not str(k).startswith("_") and str(k) != "key"
|
|
344
|
+
},
|
|
345
|
+
max_len,
|
|
346
|
+
_depth + 1,
|
|
347
|
+
_max_depth,
|
|
348
|
+
)
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
# Fallback: stringify and trim
|
|
353
|
+
try:
|
|
354
|
+
s = str(value)
|
|
355
|
+
return s[:max_len] + "..." if len(s) > max_len else s
|
|
356
|
+
except Exception:
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _is_dataclass_instance(obj: Any) -> bool:
|
|
361
|
+
"""Check if an object is an instance of a dataclass."""
|
|
362
|
+
return is_dataclass(obj) and not isinstance(obj, type)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _to_json_key(key: Any, max_len: int = 200) -> str:
|
|
366
|
+
"""Convert dict key to a JSON-legal key."""
|
|
367
|
+
if key is None or isinstance(key, (bool, int, float)):
|
|
368
|
+
return str(key)
|
|
369
|
+
|
|
370
|
+
if isinstance(key, str):
|
|
371
|
+
return key[:max_len] + "..." if len(key) > max_len else key
|
|
372
|
+
|
|
373
|
+
for _key in ["name", "_name", "__name__"]:
|
|
374
|
+
if hasattr(key, _key):
|
|
375
|
+
return str(getattr(key, _key))
|
|
376
|
+
|
|
377
|
+
# Non-primitive key: include type to avoid collisions like 1 vs "1"
|
|
378
|
+
s = f"<{type(key).__name__}> {repr(key)}"
|
|
379
|
+
return s[:max_len] + "..." if len(s) > max_len else s
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0.
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
3
4
|
# pylint: disable=unused-argument,disable=line-too-long
|
|
5
|
+
# pyright: reportUnusedParameter=false, reportUnnecessaryIsInstance=false
|
|
6
|
+
# pyright: reportDeprecated=false
|
|
4
7
|
# flake8: noqa: E501
|
|
5
8
|
"""Step-by-step execution models for Waldiez."""
|
|
6
9
|
|
|
@@ -9,6 +12,7 @@ from enum import Enum
|
|
|
9
12
|
from typing import Annotated, Any, Literal, Union
|
|
10
13
|
|
|
11
14
|
from pydantic import BaseModel, Field, ValidationInfo, field_validator
|
|
15
|
+
from typing_extensions import override
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class WaldiezBreakpointType(Enum):
|
|
@@ -58,7 +62,7 @@ class WaldiezBreakpoint(BaseModel):
|
|
|
58
62
|
|
|
59
63
|
type: WaldiezBreakpointType
|
|
60
64
|
event_type: str | None = None # Required for EVENT and AGENT_EVENT
|
|
61
|
-
|
|
65
|
+
agent: str | None = None # Required for AGENT and AGENT_EVENT
|
|
62
66
|
description: str | None = None # Human-readable description
|
|
63
67
|
|
|
64
68
|
# noinspection PyNestedDecorators,PyUnusedLocal
|
|
@@ -93,48 +97,50 @@ class WaldiezBreakpoint(BaseModel):
|
|
|
93
97
|
|
|
94
98
|
# Basic validation - event types should be alphanumeric with underscores
|
|
95
99
|
if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", v):
|
|
96
|
-
|
|
100
|
+
msg = (
|
|
97
101
|
"Invalid breakpoint format. Event type must start with a letter and contain only "
|
|
98
102
|
"letters, numbers, and underscores"
|
|
99
103
|
)
|
|
104
|
+
raise ValueError(msg)
|
|
100
105
|
return v
|
|
101
106
|
|
|
102
107
|
# noinspection PyNestedDecorators,PyUnusedLocal
|
|
103
|
-
@field_validator("
|
|
108
|
+
@field_validator("agent")
|
|
104
109
|
@classmethod
|
|
105
|
-
def
|
|
110
|
+
def validate_agent(
|
|
106
111
|
cls,
|
|
107
112
|
v: str | None,
|
|
108
113
|
info: ValidationInfo,
|
|
109
114
|
) -> str | None:
|
|
110
|
-
"""Validate agent name format.
|
|
115
|
+
"""Validate agent name/id format.
|
|
111
116
|
|
|
112
117
|
Parameters
|
|
113
118
|
----------
|
|
114
119
|
v : str | None
|
|
115
|
-
The agent name to validate.
|
|
120
|
+
The agent name or id to validate.
|
|
116
121
|
info : ValidationInfo
|
|
117
122
|
Validation context information.
|
|
118
123
|
|
|
119
124
|
Returns
|
|
120
125
|
-------
|
|
121
126
|
str | None
|
|
122
|
-
The validated agent
|
|
127
|
+
The validated agent or None if not provided.
|
|
123
128
|
|
|
124
129
|
Raises
|
|
125
130
|
------
|
|
126
131
|
ValueError
|
|
127
|
-
If the agent
|
|
132
|
+
If the agent format is invalid.
|
|
128
133
|
"""
|
|
129
134
|
if v is None:
|
|
130
135
|
return v
|
|
131
136
|
|
|
132
|
-
# Agent
|
|
137
|
+
# Agent name/id should not be empty or just whitespace
|
|
133
138
|
if not v.strip():
|
|
134
|
-
raise ValueError("Agent
|
|
139
|
+
raise ValueError("Agent cannot be empty or just whitespace")
|
|
135
140
|
|
|
136
141
|
return v.strip()
|
|
137
142
|
|
|
143
|
+
@override
|
|
138
144
|
def model_post_init(self, __context: Any, /) -> None:
|
|
139
145
|
"""Validate breakpoint consistency after initialization.
|
|
140
146
|
|
|
@@ -146,28 +152,31 @@ class WaldiezBreakpoint(BaseModel):
|
|
|
146
152
|
if self.type == WaldiezBreakpointType.EVENT and not self.event_type:
|
|
147
153
|
raise ValueError("EVENT breakpoints require an event_type")
|
|
148
154
|
|
|
149
|
-
if self.type == WaldiezBreakpointType.AGENT and not self.
|
|
155
|
+
if self.type == WaldiezBreakpointType.AGENT and not self.agent:
|
|
150
156
|
raise ValueError("AGENT breakpoints require an agent_name")
|
|
151
157
|
|
|
152
158
|
if self.type == WaldiezBreakpointType.AGENT_EVENT:
|
|
153
|
-
if not self.event_type or not self.
|
|
154
|
-
|
|
159
|
+
if not self.event_type or not self.agent:
|
|
160
|
+
msg = (
|
|
155
161
|
"AGENT_EVENT breakpoints require both"
|
|
156
162
|
" event_type and agent_name"
|
|
157
163
|
)
|
|
164
|
+
raise ValueError(msg)
|
|
158
165
|
|
|
166
|
+
@override
|
|
159
167
|
def __hash__(self) -> int:
|
|
160
168
|
"""Get the hash value for the breakpoint."""
|
|
161
|
-
return hash((self.type, self.event_type, self.
|
|
169
|
+
return hash((self.type, self.event_type, self.agent))
|
|
162
170
|
|
|
171
|
+
@override
|
|
163
172
|
def __str__(self) -> str:
|
|
164
173
|
"""Get the string representation for display."""
|
|
165
174
|
if self.type == WaldiezBreakpointType.EVENT:
|
|
166
175
|
return f"event:{self.event_type}"
|
|
167
176
|
if self.type == WaldiezBreakpointType.AGENT:
|
|
168
|
-
return f"agent:{self.
|
|
177
|
+
return f"agent:{self.agent}"
|
|
169
178
|
if self.type == WaldiezBreakpointType.AGENT_EVENT:
|
|
170
|
-
return f"{self.
|
|
179
|
+
return f"{self.agent}:{self.event_type}"
|
|
171
180
|
# else: # ALL
|
|
172
181
|
return "all"
|
|
173
182
|
|
|
@@ -194,7 +203,7 @@ class WaldiezBreakpoint(BaseModel):
|
|
|
194
203
|
ValueError
|
|
195
204
|
If the breakpoint string format is invalid.
|
|
196
205
|
"""
|
|
197
|
-
if not breakpoint_str or not isinstance(
|
|
206
|
+
if not breakpoint_str or not isinstance(
|
|
198
207
|
breakpoint_str,
|
|
199
208
|
str,
|
|
200
209
|
):
|
|
@@ -213,50 +222,109 @@ class WaldiezBreakpoint(BaseModel):
|
|
|
213
222
|
event_type = breakpoint_str[6:] # Remove "event:" prefix
|
|
214
223
|
if not event_type:
|
|
215
224
|
raise ValueError("Event type cannot be empty after 'event:'")
|
|
216
|
-
return cls(
|
|
225
|
+
return cls(
|
|
226
|
+
type=WaldiezBreakpointType.EVENT,
|
|
227
|
+
event_type=event_type,
|
|
228
|
+
)
|
|
217
229
|
|
|
218
230
|
if breakpoint_str.startswith("agent:"):
|
|
219
|
-
|
|
220
|
-
if not
|
|
221
|
-
raise ValueError(
|
|
222
|
-
|
|
231
|
+
agent = breakpoint_str[6:] # Remove "agent:" prefix
|
|
232
|
+
if not agent:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
"Agent identifier cannot be empty after 'agent:'"
|
|
235
|
+
)
|
|
236
|
+
return cls(
|
|
237
|
+
type=WaldiezBreakpointType.AGENT,
|
|
238
|
+
agent=agent,
|
|
239
|
+
)
|
|
223
240
|
|
|
224
241
|
if ":" in breakpoint_str and not breakpoint_str.startswith(
|
|
225
242
|
("event:", "agent:")
|
|
226
243
|
):
|
|
227
|
-
# Format: "
|
|
244
|
+
# Format: "agent:event_type"
|
|
228
245
|
parts = breakpoint_str.split(":", 1)
|
|
229
246
|
if len(parts) != 2:
|
|
230
247
|
raise ValueError("Invalid agent:event format")
|
|
231
248
|
|
|
232
|
-
|
|
233
|
-
if not
|
|
249
|
+
agent, event_type = parts
|
|
250
|
+
if not agent or not event_type:
|
|
234
251
|
raise ValueError(
|
|
235
|
-
"Both agent
|
|
252
|
+
"Both agent identifier and event type must be specified"
|
|
236
253
|
)
|
|
237
254
|
|
|
238
255
|
return cls(
|
|
239
256
|
type=WaldiezBreakpointType.AGENT_EVENT,
|
|
240
|
-
|
|
257
|
+
agent=agent,
|
|
241
258
|
event_type=event_type,
|
|
242
259
|
)
|
|
243
|
-
|
|
244
|
-
# Default to event type - but validate it's reasonable
|
|
245
260
|
if ":" in breakpoint_str:
|
|
246
|
-
|
|
261
|
+
msg = (
|
|
247
262
|
"Invalid breakpoint format. Use 'event:type', 'agent:name', "
|
|
248
263
|
"'agent:event', or 'all'"
|
|
249
264
|
)
|
|
265
|
+
raise ValueError(msg)
|
|
266
|
+
|
|
267
|
+
return cls(
|
|
268
|
+
type=WaldiezBreakpointType.EVENT,
|
|
269
|
+
event_type=breakpoint_str,
|
|
270
|
+
)
|
|
250
271
|
|
|
251
|
-
|
|
272
|
+
def matches_agent(
|
|
273
|
+
self,
|
|
274
|
+
event: dict[str, Any],
|
|
275
|
+
agent_id_to_name: dict[str, str],
|
|
276
|
+
sender_only: bool,
|
|
277
|
+
) -> bool:
|
|
278
|
+
"""Check if the event's sender or recipient matches the breakpoint's agent.
|
|
252
279
|
|
|
253
|
-
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
event : dict[str, Any]
|
|
283
|
+
The event to check against.
|
|
284
|
+
agent_id_to_name : dict[str, str]
|
|
285
|
+
The mapping between an agent's id and its name.
|
|
286
|
+
sender_only : dict[str, Any]
|
|
287
|
+
Only check for sender match.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
bool
|
|
292
|
+
True if the event's sender or recipient matches the breakpoint's agent.
|
|
293
|
+
"""
|
|
294
|
+
if not self.agent:
|
|
295
|
+
return False
|
|
296
|
+
# Normalize both the event's sender/recipient and the breakpoint's agent
|
|
297
|
+
_event_sender = event.get("sender", "")
|
|
298
|
+
event_sender = agent_id_to_name.get(_event_sender, _event_sender)
|
|
299
|
+
_event_recipient = event.get("recipient", "")
|
|
300
|
+
event_recipient = agent_id_to_name.get(
|
|
301
|
+
_event_recipient, _event_recipient
|
|
302
|
+
)
|
|
303
|
+
breakpoint_agent = (
|
|
304
|
+
agent_id_to_name.get(self.agent, self.agent) if self.agent else ""
|
|
305
|
+
)
|
|
306
|
+
if not breakpoint_agent:
|
|
307
|
+
return False
|
|
308
|
+
if sender_only:
|
|
309
|
+
return breakpoint_agent == event_sender
|
|
310
|
+
return breakpoint_agent in (event_sender, event_recipient)
|
|
311
|
+
|
|
312
|
+
def matches(
|
|
313
|
+
self,
|
|
314
|
+
event: dict[str, Any],
|
|
315
|
+
agent_id_to_name: dict[str, str],
|
|
316
|
+
sender_only: bool,
|
|
317
|
+
) -> bool:
|
|
254
318
|
"""Check if this breakpoint matches the given event.
|
|
255
319
|
|
|
256
320
|
Parameters
|
|
257
321
|
----------
|
|
258
322
|
event : dict[str, Any]
|
|
259
323
|
The event to check against.
|
|
324
|
+
agent_id_to_name : dict[str, str]
|
|
325
|
+
The mapping between an agent's id and its name.
|
|
326
|
+
sender_only : dict[str, Any]
|
|
327
|
+
On agent events, only check for sender match.
|
|
260
328
|
|
|
261
329
|
Returns
|
|
262
330
|
-------
|
|
@@ -270,22 +338,22 @@ class WaldiezBreakpoint(BaseModel):
|
|
|
270
338
|
return event.get("type") == self.event_type
|
|
271
339
|
|
|
272
340
|
if self.type == WaldiezBreakpointType.AGENT:
|
|
273
|
-
return (
|
|
274
|
-
event
|
|
275
|
-
or event.get("recipient") == self.agent_name
|
|
341
|
+
return self.matches_agent(
|
|
342
|
+
event, agent_id_to_name, sender_only=sender_only
|
|
276
343
|
)
|
|
277
344
|
|
|
278
345
|
if self.type == WaldiezBreakpointType.AGENT_EVENT:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
346
|
+
event_type = event.get("type", "")
|
|
347
|
+
if event_type != self.event_type:
|
|
348
|
+
return False
|
|
349
|
+
return self.matches_agent(
|
|
350
|
+
event, agent_id_to_name, sender_only=sender_only
|
|
282
351
|
)
|
|
283
352
|
|
|
284
353
|
# noinspection PyUnreachableCode
|
|
285
354
|
return False
|
|
286
355
|
|
|
287
356
|
|
|
288
|
-
# Enhanced configuration class for runtime settings
|
|
289
357
|
class WaldiezDebugConfig(BaseModel):
|
|
290
358
|
"""Configuration for debug session settings."""
|
|
291
359
|
|
|
@@ -296,7 +364,6 @@ class WaldiezDebugConfig(BaseModel):
|
|
|
296
364
|
command_timeout_seconds: float = Field(default=300.0, gt=0)
|
|
297
365
|
|
|
298
366
|
|
|
299
|
-
# Rest of the existing message classes remain the same...
|
|
300
367
|
class WaldiezDebugBreakpointsList(BaseModel):
|
|
301
368
|
"""Debug breakpoints message."""
|
|
302
369
|
|
|
@@ -314,7 +381,6 @@ class WaldiezDebugBreakpointsList(BaseModel):
|
|
|
314
381
|
try:
|
|
315
382
|
result.append(WaldiezBreakpoint.from_string(bp))
|
|
316
383
|
except ValueError:
|
|
317
|
-
# Skip invalid breakpoints rather than failing
|
|
318
384
|
continue
|
|
319
385
|
else:
|
|
320
386
|
result.append(bp)
|
|
@@ -325,7 +391,7 @@ class WaldiezDebugBreakpointAdded(BaseModel):
|
|
|
325
391
|
"""Debug breakpoint added message."""
|
|
326
392
|
|
|
327
393
|
type: Literal["debug_breakpoint_added"] = "debug_breakpoint_added"
|
|
328
|
-
breakpoint:
|
|
394
|
+
breakpoint: str | WaldiezBreakpoint
|
|
329
395
|
|
|
330
396
|
@property
|
|
331
397
|
def breakpoint_object(self) -> WaldiezBreakpoint:
|
|
@@ -339,7 +405,7 @@ class WaldiezDebugBreakpointRemoved(BaseModel):
|
|
|
339
405
|
"""Debug breakpoint removed message."""
|
|
340
406
|
|
|
341
407
|
type: Literal["debug_breakpoint_removed"] = "debug_breakpoint_removed"
|
|
342
|
-
breakpoint:
|
|
408
|
+
breakpoint: str | WaldiezBreakpoint
|
|
343
409
|
|
|
344
410
|
@property
|
|
345
411
|
def breakpoint_object(self) -> WaldiezBreakpoint:
|
|
@@ -423,6 +489,7 @@ class WaldiezDebugBreakpointCleared(BaseModel):
|
|
|
423
489
|
message: str
|
|
424
490
|
|
|
425
491
|
|
|
492
|
+
# pylint: disable=invalid-name
|
|
426
493
|
WaldiezDebugMessage = Annotated[
|
|
427
494
|
Union[
|
|
428
495
|
WaldiezDebugPrint,
|