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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
|
|
4
|
+
# pylint: disable=unused-argument
|
|
5
|
+
# pyright: reportImportCycles=false, reportUnusedParameter=false
|
|
6
|
+
|
|
7
|
+
"""Command handler for step-by-step execution."""
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Callable
|
|
10
|
+
|
|
11
|
+
from .step_by_step_models import (
|
|
12
|
+
HELP_MESSAGE,
|
|
13
|
+
WaldiezDebugError,
|
|
14
|
+
WaldiezDebugStepAction,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
# noinspection PyUnusedImports
|
|
19
|
+
from .step_by_step_runner import WaldiezStepByStepRunner
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# pylint: disable=too-few-public-methods
|
|
23
|
+
class CommandHandler:
|
|
24
|
+
"""Handler for debug commands to reduce complexity in main runner."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, runner: "WaldiezStepByStepRunner"):
|
|
27
|
+
self.runner = runner
|
|
28
|
+
self._command_map: dict[
|
|
29
|
+
str, Callable[[str | None], WaldiezDebugStepAction]
|
|
30
|
+
] = {
|
|
31
|
+
"c": self._handle_continue,
|
|
32
|
+
"s": self._handle_step,
|
|
33
|
+
"r": self._handle_run,
|
|
34
|
+
"q": self._handle_quit,
|
|
35
|
+
"i": self._handle_info,
|
|
36
|
+
"h": self._handle_help,
|
|
37
|
+
"st": self._handle_stats,
|
|
38
|
+
"ab": self._handle_add_breakpoint,
|
|
39
|
+
"rb": self._handle_remove_breakpoint,
|
|
40
|
+
"lb": self._handle_list_breakpoints,
|
|
41
|
+
"cb": self._handle_clear_breakpoints,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def handle_command(self, command_line: str) -> WaldiezDebugStepAction:
|
|
45
|
+
"""Handle a command line input.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
command_line : str
|
|
50
|
+
The command line input to handle.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
WaldiezDebugStepAction
|
|
55
|
+
The action to take for the command.
|
|
56
|
+
"""
|
|
57
|
+
if not command_line or not command_line.strip():
|
|
58
|
+
return self._handle_step(None) # Enter = step
|
|
59
|
+
parts = command_line.strip().split(maxsplit=1)
|
|
60
|
+
if not parts or not parts[0]:
|
|
61
|
+
return self._handle_unknown(None)
|
|
62
|
+
command = parts[0].lower()
|
|
63
|
+
args = parts[1] if len(parts) > 1 else None
|
|
64
|
+
|
|
65
|
+
handler = self._command_map.get(command, self._handle_unknown)
|
|
66
|
+
return handler(args)
|
|
67
|
+
|
|
68
|
+
def _handle_continue(self, args: str | None) -> WaldiezDebugStepAction:
|
|
69
|
+
self.runner.step_mode = True
|
|
70
|
+
return WaldiezDebugStepAction.CONTINUE
|
|
71
|
+
|
|
72
|
+
def _handle_step(self, args: str | None) -> WaldiezDebugStepAction:
|
|
73
|
+
self.runner.step_mode = True
|
|
74
|
+
return WaldiezDebugStepAction.STEP
|
|
75
|
+
|
|
76
|
+
def _handle_run(self, args: str | None) -> WaldiezDebugStepAction:
|
|
77
|
+
self.runner.step_mode = False
|
|
78
|
+
return WaldiezDebugStepAction.RUN
|
|
79
|
+
|
|
80
|
+
def _handle_quit(self, args: str | None) -> WaldiezDebugStepAction:
|
|
81
|
+
self.runner.set_stop_requested()
|
|
82
|
+
return WaldiezDebugStepAction.QUIT
|
|
83
|
+
|
|
84
|
+
def _handle_info(self, args: str | None) -> WaldiezDebugStepAction:
|
|
85
|
+
self.runner.show_event_info()
|
|
86
|
+
return WaldiezDebugStepAction.INFO
|
|
87
|
+
|
|
88
|
+
def _handle_help(self, args: str | None) -> WaldiezDebugStepAction:
|
|
89
|
+
self.runner.emit(HELP_MESSAGE)
|
|
90
|
+
return WaldiezDebugStepAction.HELP
|
|
91
|
+
|
|
92
|
+
def _handle_stats(self, args: str | None) -> WaldiezDebugStepAction:
|
|
93
|
+
self.runner.show_stats()
|
|
94
|
+
return WaldiezDebugStepAction.STATS
|
|
95
|
+
|
|
96
|
+
def _handle_add_breakpoint(
|
|
97
|
+
self, args: str | None
|
|
98
|
+
) -> WaldiezDebugStepAction:
|
|
99
|
+
if args:
|
|
100
|
+
self.runner.add_breakpoint(args)
|
|
101
|
+
return WaldiezDebugStepAction.ADD_BREAKPOINT
|
|
102
|
+
current_event = self.runner.current_event
|
|
103
|
+
if current_event and hasattr(current_event, "type"):
|
|
104
|
+
self.runner.add_breakpoint(getattr(current_event, "type", ""))
|
|
105
|
+
return WaldiezDebugStepAction.ADD_BREAKPOINT
|
|
106
|
+
# else:
|
|
107
|
+
self.runner.emit(
|
|
108
|
+
WaldiezDebugError(
|
|
109
|
+
error=(
|
|
110
|
+
"No breakpoint specification provided "
|
|
111
|
+
"and no current event available"
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
return WaldiezDebugStepAction.ADD_BREAKPOINT
|
|
116
|
+
|
|
117
|
+
def _handle_remove_breakpoint(
|
|
118
|
+
self, args: str | None
|
|
119
|
+
) -> WaldiezDebugStepAction:
|
|
120
|
+
if args:
|
|
121
|
+
self.runner.remove_breakpoint(args)
|
|
122
|
+
return WaldiezDebugStepAction.REMOVE_BREAKPOINT
|
|
123
|
+
current_event = self.runner.current_event
|
|
124
|
+
if current_event and hasattr(current_event, "type"):
|
|
125
|
+
self.runner.remove_breakpoint(getattr(current_event, "type", ""))
|
|
126
|
+
return WaldiezDebugStepAction.REMOVE_BREAKPOINT
|
|
127
|
+
# else:
|
|
128
|
+
self.runner.emit(
|
|
129
|
+
WaldiezDebugError(
|
|
130
|
+
error=(
|
|
131
|
+
"No breakpoint specification provided and "
|
|
132
|
+
"no current event available"
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
return WaldiezDebugStepAction.REMOVE_BREAKPOINT
|
|
137
|
+
|
|
138
|
+
def _handle_list_breakpoints(
|
|
139
|
+
self, args: str | None
|
|
140
|
+
) -> WaldiezDebugStepAction:
|
|
141
|
+
self.runner.list_breakpoints()
|
|
142
|
+
return WaldiezDebugStepAction.LIST_BREAKPOINTS
|
|
143
|
+
|
|
144
|
+
def _handle_clear_breakpoints(
|
|
145
|
+
self, args: str | None
|
|
146
|
+
) -> WaldiezDebugStepAction:
|
|
147
|
+
self.runner.clear_breakpoints()
|
|
148
|
+
return WaldiezDebugStepAction.CLEAR_BREAKPOINTS
|
|
149
|
+
|
|
150
|
+
def _handle_unknown(self, args: str | None) -> WaldiezDebugStepAction:
|
|
151
|
+
self.runner.emit(
|
|
152
|
+
WaldiezDebugError(error="Unknown command. Use 'h' for help")
|
|
153
|
+
)
|
|
154
|
+
return WaldiezDebugStepAction.UNKNOWN
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
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
|
+
|
|
9
|
+
"""Command handler for step-by-step execution."""
|
|
10
|
+
|
|
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
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from autogen.agentchat import ConversableAgent # type: ignore
|
|
18
|
+
from autogen.events.base_event import BaseEvent # type: ignore
|
|
19
|
+
from autogen.messages.base_message import BaseMessage # type: ignore
|
|
20
|
+
|
|
21
|
+
# noinspection PyUnusedImports
|
|
22
|
+
from .step_by_step_runner import WaldiezStepByStepRunner
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# pylint: disable=too-few-public-methods
|
|
26
|
+
class EventProcessor:
|
|
27
|
+
"""Processes events for the step-by-step runner."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, runner: "WaldiezStepByStepRunner"):
|
|
30
|
+
self.runner = runner
|
|
31
|
+
|
|
32
|
+
def process_event(
|
|
33
|
+
self,
|
|
34
|
+
event: Union["BaseEvent", "BaseMessage"],
|
|
35
|
+
agents: list["ConversableAgent"],
|
|
36
|
+
) -> dict[str, Any]:
|
|
37
|
+
"""Shared logic for both sync and async event processing.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
event : BaseEvent | BaseMessage
|
|
42
|
+
The event to process.
|
|
43
|
+
agents : list[ConversableAgent]
|
|
44
|
+
The workflow's known agents.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
dict[str, Any]
|
|
49
|
+
The result of processing the event.
|
|
50
|
+
"""
|
|
51
|
+
self.runner.event_plus_one()
|
|
52
|
+
self.runner.current_event = event
|
|
53
|
+
|
|
54
|
+
if self.runner.is_stop_requested():
|
|
55
|
+
return {"action": "stop", "reason": "stop_requested"}
|
|
56
|
+
|
|
57
|
+
event_info = self._create_event_info(event)
|
|
58
|
+
self._update_participant_info(event_info)
|
|
59
|
+
self._manage_event_history(event_info)
|
|
60
|
+
self._check_for_input_request(event_info)
|
|
61
|
+
self._add_agents_info(event_info, agents)
|
|
62
|
+
|
|
63
|
+
should_break = self.runner.should_break_on_event(
|
|
64
|
+
event, sender_only=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"action": "break" if should_break else "continue",
|
|
69
|
+
"event_info": event_info,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def _create_event_info(
|
|
73
|
+
self,
|
|
74
|
+
event: Union["BaseEvent", "BaseMessage"],
|
|
75
|
+
) -> dict[str, Any]:
|
|
76
|
+
"""Create event info dictionary from event object.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
event : BaseEvent | BaseMessage
|
|
81
|
+
The event to convert to info dict.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
dict[str, Any]
|
|
86
|
+
Event information dictionary.
|
|
87
|
+
"""
|
|
88
|
+
event_info = event.model_dump(
|
|
89
|
+
mode="json", exclude_none=True, fallback=str
|
|
90
|
+
)
|
|
91
|
+
event_info["count"] = self.runner.event_count
|
|
92
|
+
event_info["sender"] = getattr(event, "sender", self.runner.last_sender)
|
|
93
|
+
event_info["recipient"] = getattr(
|
|
94
|
+
event, "recipient", self.runner.last_recipient
|
|
95
|
+
)
|
|
96
|
+
return event_info
|
|
97
|
+
|
|
98
|
+
def _update_participant_info(self, event_info: dict[str, Any]) -> None:
|
|
99
|
+
"""Update sender and recipient information in event_info.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
event_info : dict[str, Any]
|
|
104
|
+
Event information dictionary to update.
|
|
105
|
+
"""
|
|
106
|
+
if not event_info["sender"] or not event_info["recipient"]:
|
|
107
|
+
self._extract_participants_from_content(event_info)
|
|
108
|
+
|
|
109
|
+
self._check_for_event_speaker(event_info)
|
|
110
|
+
self._extract_participants_from_direct_content(event_info)
|
|
111
|
+
|
|
112
|
+
# Update last known participants
|
|
113
|
+
self.runner.last_sender = event_info["sender"]
|
|
114
|
+
self.runner.last_recipient = event_info["recipient"]
|
|
115
|
+
|
|
116
|
+
def _extract_participants_from_content(
|
|
117
|
+
self, event_info: dict[str, Any]
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Extract sender/recipient from nested content structure.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
event_info : dict[str, Any]
|
|
124
|
+
Event information dictionary to update.
|
|
125
|
+
"""
|
|
126
|
+
content = event_info.get("content", {})
|
|
127
|
+
if (
|
|
128
|
+
isinstance(content, dict)
|
|
129
|
+
and "chat_info" in content
|
|
130
|
+
and isinstance(content["chat_info"], dict)
|
|
131
|
+
):
|
|
132
|
+
content = content.get("chat_info", {})
|
|
133
|
+
|
|
134
|
+
if not event_info["sender"] and "sender" in content:
|
|
135
|
+
event_info["sender"] = content["sender"]
|
|
136
|
+
if not event_info["recipient"] and "recipient" in content:
|
|
137
|
+
event_info["recipient"] = content["recipient"]
|
|
138
|
+
|
|
139
|
+
def _check_for_event_speaker(self, event_info: dict[str, Any]) -> None:
|
|
140
|
+
"""Handle speaker information for group chat events.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
event_info : dict[str, Any]
|
|
145
|
+
Event information dictionary to update.
|
|
146
|
+
"""
|
|
147
|
+
if "content" in event_info and isinstance(event_info["content"], dict):
|
|
148
|
+
content = event_info.get("content", {})
|
|
149
|
+
speaker = content.get("speaker")
|
|
150
|
+
if isinstance(speaker, str) and speaker:
|
|
151
|
+
event_info["sender"] = speaker
|
|
152
|
+
|
|
153
|
+
def _extract_participants_from_direct_content(
|
|
154
|
+
self, event_info: dict[str, Any]
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Extract sender/recipient directly from content dictionary.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
event_info : dict[str, Any]
|
|
161
|
+
Event information dictionary to update.
|
|
162
|
+
"""
|
|
163
|
+
if "content" in event_info and isinstance(event_info["content"], dict):
|
|
164
|
+
content = event_info.get("content", {})
|
|
165
|
+
|
|
166
|
+
sender = content.get("sender", "")
|
|
167
|
+
if isinstance(sender, str) and sender:
|
|
168
|
+
event_info["sender"] = sender
|
|
169
|
+
|
|
170
|
+
recipient = content.get("recipient", "")
|
|
171
|
+
if isinstance(recipient, str) and recipient:
|
|
172
|
+
event_info["recipient"] = recipient
|
|
173
|
+
|
|
174
|
+
def _manage_event_history(self, event_info: dict[str, Any]) -> None:
|
|
175
|
+
"""Add event to history and manage history size limits.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
event_info : dict[str, Any]
|
|
180
|
+
Event information to add to history.
|
|
181
|
+
"""
|
|
182
|
+
self.runner.add_to_history(event_info)
|
|
183
|
+
self._trim_history_if_needed()
|
|
184
|
+
|
|
185
|
+
def _trim_history_if_needed(self) -> None:
|
|
186
|
+
"""Remove oldest events from history if over size limit."""
|
|
187
|
+
current_history = self.runner.event_history
|
|
188
|
+
if len(current_history) > self.runner.max_event_history:
|
|
189
|
+
excess = len(current_history) - self.runner.max_event_history
|
|
190
|
+
for _ in range(excess):
|
|
191
|
+
self.runner.pop_event()
|
|
192
|
+
|
|
193
|
+
def _check_for_input_request(self, event_info: dict[str, Any]) -> None:
|
|
194
|
+
"""Swap participant names if we have an input request."""
|
|
195
|
+
if (
|
|
196
|
+
event_info["type"] in ("input_request", "debug_input_request")
|
|
197
|
+
and "sender" in event_info
|
|
198
|
+
and "recipient" in event_info
|
|
199
|
+
):
|
|
200
|
+
# swap them,
|
|
201
|
+
# before:
|
|
202
|
+
# "recipient" is the user (the one received the input request),
|
|
203
|
+
# make her the "sender" (the one typing...)
|
|
204
|
+
sender = event_info["sender"]
|
|
205
|
+
recipient = event_info["recipient"]
|
|
206
|
+
event_info["sender"] = recipient
|
|
207
|
+
event_info["recipient"] = sender
|
|
208
|
+
self.runner.last_sender = recipient
|
|
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
|