waldiez 0.2.2__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of waldiez might be problematic. Click here for more details.
- waldiez/__init__.py +2 -0
- waldiez/__main__.py +2 -0
- waldiez/_version.py +3 -1
- waldiez/cli.py +13 -3
- waldiez/cli_extras.py +4 -3
- waldiez/conflict_checker.py +4 -3
- waldiez/exporter.py +28 -105
- waldiez/exporting/__init__.py +8 -9
- waldiez/exporting/agent/__init__.py +7 -0
- waldiez/exporting/agent/agent_exporter.py +279 -0
- waldiez/exporting/agent/utils/__init__.py +23 -0
- waldiez/exporting/agent/utils/agent_class_name.py +34 -0
- waldiez/exporting/agent/utils/agent_imports.py +50 -0
- waldiez/exporting/{agents → agent/utils}/code_execution.py +9 -11
- waldiez/exporting/{agents → agent/utils}/group_manager.py +47 -35
- waldiez/exporting/{agents → agent/utils}/rag_user/__init__.py +2 -0
- waldiez/exporting/{agents → agent/utils}/rag_user/chroma_utils.py +22 -17
- waldiez/exporting/{agents → agent/utils}/rag_user/mongo_utils.py +14 -10
- waldiez/exporting/{agents → agent/utils}/rag_user/pgvector_utils.py +12 -8
- waldiez/exporting/{agents → agent/utils}/rag_user/qdrant_utils.py +11 -8
- waldiez/exporting/{agents → agent/utils}/rag_user/rag_user.py +78 -55
- waldiez/exporting/{agents → agent/utils}/rag_user/vector_db.py +10 -8
- waldiez/exporting/agent/utils/swarm_agent.py +463 -0
- waldiez/exporting/{agents → agent/utils}/teachability.py +10 -6
- waldiez/exporting/{agents → agent/utils}/termination_message.py +7 -8
- waldiez/exporting/base/__init__.py +25 -0
- waldiez/exporting/base/agent_position.py +75 -0
- waldiez/exporting/base/base_exporter.py +118 -0
- waldiez/exporting/base/export_position.py +48 -0
- waldiez/exporting/base/import_position.py +23 -0
- waldiez/exporting/base/mixin.py +134 -0
- waldiez/exporting/base/utils/__init__.py +18 -0
- waldiez/exporting/{utils → base/utils}/comments.py +12 -55
- waldiez/exporting/{utils → base/utils}/naming.py +14 -4
- waldiez/exporting/base/utils/path_check.py +68 -0
- waldiez/exporting/{utils/object_string.py → base/utils/to_string.py} +21 -20
- waldiez/exporting/chats/__init__.py +5 -12
- waldiez/exporting/chats/chats_exporter.py +240 -0
- waldiez/exporting/chats/utils/__init__.py +15 -0
- waldiez/exporting/chats/utils/common.py +81 -0
- waldiez/exporting/chats/{nested.py → utils/nested.py} +125 -86
- waldiez/exporting/chats/utils/sequential.py +244 -0
- waldiez/exporting/chats/utils/single_chat.py +313 -0
- waldiez/exporting/chats/utils/swarm.py +207 -0
- waldiez/exporting/flow/__init__.py +5 -3
- waldiez/exporting/flow/flow_exporter.py +503 -0
- waldiez/exporting/flow/utils/__init__.py +47 -0
- waldiez/exporting/flow/utils/agent_utils.py +204 -0
- waldiez/exporting/flow/utils/chat_utils.py +71 -0
- waldiez/exporting/flow/utils/def_main.py +62 -0
- waldiez/exporting/flow/utils/flow_content.py +112 -0
- waldiez/exporting/flow/utils/flow_names.py +115 -0
- waldiez/exporting/flow/utils/importing_utils.py +179 -0
- waldiez/exporting/{utils → flow/utils}/logging_utils.py +34 -31
- waldiez/exporting/models/__init__.py +7 -242
- waldiez/exporting/models/models_exporter.py +192 -0
- waldiez/exporting/models/utils.py +166 -0
- waldiez/exporting/skills/__init__.py +7 -161
- waldiez/exporting/skills/skills_exporter.py +169 -0
- waldiez/exporting/skills/utils.py +281 -0
- waldiez/models/__init__.py +25 -7
- waldiez/models/agents/__init__.py +70 -0
- waldiez/models/agents/agent/__init__.py +11 -1
- waldiez/models/agents/agent/agent.py +9 -4
- waldiez/models/agents/agent/agent_data.py +3 -1
- waldiez/models/agents/agent/code_execution.py +2 -0
- waldiez/models/agents/agent/linked_skill.py +2 -0
- waldiez/models/agents/agent/nested_chat.py +2 -0
- waldiez/models/agents/agent/teachability.py +2 -0
- waldiez/models/agents/agent/termination_message.py +49 -13
- waldiez/models/agents/agents.py +15 -3
- waldiez/models/agents/assistant/__init__.py +2 -0
- waldiez/models/agents/assistant/assistant.py +2 -0
- waldiez/models/agents/assistant/assistant_data.py +2 -0
- waldiez/models/agents/group_manager/__init__.py +9 -1
- waldiez/models/agents/group_manager/group_manager.py +2 -0
- waldiez/models/agents/group_manager/group_manager_data.py +2 -0
- waldiez/models/agents/group_manager/speakers.py +49 -13
- waldiez/models/agents/rag_user/__init__.py +21 -4
- waldiez/models/agents/rag_user/rag_user.py +3 -1
- waldiez/models/agents/rag_user/rag_user_data.py +2 -0
- waldiez/models/agents/rag_user/retrieve_config.py +268 -17
- waldiez/models/agents/rag_user/vector_db_config.py +5 -3
- waldiez/models/agents/swarm_agent/__init__.py +49 -0
- waldiez/models/agents/swarm_agent/after_work.py +178 -0
- waldiez/models/agents/swarm_agent/on_condition.py +103 -0
- waldiez/models/agents/swarm_agent/on_condition_available.py +140 -0
- waldiez/models/agents/swarm_agent/on_condition_target.py +40 -0
- waldiez/models/agents/swarm_agent/swarm_agent.py +107 -0
- waldiez/models/agents/swarm_agent/swarm_agent_data.py +125 -0
- waldiez/models/agents/swarm_agent/update_system_message.py +144 -0
- waldiez/models/agents/user_proxy/__init__.py +2 -0
- waldiez/models/agents/user_proxy/user_proxy.py +2 -0
- waldiez/models/agents/user_proxy/user_proxy_data.py +2 -0
- waldiez/models/chat/__init__.py +21 -3
- waldiez/models/chat/chat.py +241 -7
- waldiez/models/chat/chat_data.py +192 -48
- waldiez/models/chat/chat_message.py +153 -144
- waldiez/models/chat/chat_nested.py +33 -53
- waldiez/models/chat/chat_summary.py +2 -0
- waldiez/models/common/__init__.py +6 -6
- waldiez/models/common/base.py +4 -1
- waldiez/models/common/method_utils.py +163 -83
- waldiez/models/flow/__init__.py +2 -0
- waldiez/models/flow/flow.py +176 -40
- waldiez/models/flow/flow_data.py +63 -2
- waldiez/models/flow/utils.py +172 -0
- waldiez/models/model/__init__.py +2 -0
- waldiez/models/model/model.py +25 -6
- waldiez/models/model/model_data.py +3 -1
- waldiez/models/skill/__init__.py +4 -1
- waldiez/models/skill/skill.py +30 -2
- waldiez/models/skill/skill_data.py +2 -0
- waldiez/models/waldiez.py +28 -4
- waldiez/runner.py +142 -228
- waldiez/running/__init__.py +33 -0
- waldiez/running/environment.py +83 -0
- waldiez/running/gen_seq_diagram.py +185 -0
- waldiez/running/running.py +300 -0
- {waldiez-0.2.2.dist-info → waldiez-0.3.0.dist-info}/METADATA +32 -26
- waldiez-0.3.0.dist-info/RECORD +125 -0
- waldiez-0.3.0.dist-info/licenses/LICENSE +201 -0
- waldiez/exporting/agents/__init__.py +0 -5
- waldiez/exporting/agents/agent.py +0 -236
- waldiez/exporting/agents/agent_skills.py +0 -67
- waldiez/exporting/agents/llm_config.py +0 -53
- waldiez/exporting/chats/chats.py +0 -46
- waldiez/exporting/chats/helpers.py +0 -420
- waldiez/exporting/flow/def_main.py +0 -32
- waldiez/exporting/flow/flow.py +0 -189
- waldiez/exporting/utils/__init__.py +0 -36
- waldiez/exporting/utils/importing.py +0 -265
- waldiez/exporting/utils/method_utils.py +0 -35
- waldiez/exporting/utils/path_check.py +0 -51
- waldiez-0.2.2.dist-info/RECORD +0 -92
- waldiez-0.2.2.dist-info/licenses/LICENSE +0 -21
- {waldiez-0.2.2.dist-info → waldiez-0.3.0.dist-info}/WHEEL +0 -0
- {waldiez-0.2.2.dist-info → waldiez-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Swarm after work model.
|
|
4
|
+
|
|
5
|
+
Handles the next step in the conversation when an
|
|
6
|
+
agent doesn't suggest a tool call or a handoff.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# pylint: disable=line-too-long
|
|
11
|
+
|
|
12
|
+
from typing import Dict, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
from pydantic import Field, model_validator
|
|
15
|
+
from typing_extensions import Annotated, Literal, Self
|
|
16
|
+
|
|
17
|
+
from ...common import WaldiezBase, check_function, generate_function
|
|
18
|
+
|
|
19
|
+
WaldiezSwarmAfterWorkRecipientType = Literal["agent", "option", "callable"]
|
|
20
|
+
WaldiezSwarmAfterWorkOption = Literal[
|
|
21
|
+
"TERMINATE", "REVERT_TO_USER", "STAY", "SWARM_MANAGER"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
CUSTOM_AFTER_WORK = "custom_after_work"
|
|
26
|
+
CUSTOM_AFTER_WORK_ARGS = ["last_speaker", "messages", "groupchat"]
|
|
27
|
+
CUSTOM_AFTER_WORK_TYPES = (
|
|
28
|
+
["SwarmAgent", "List[Dict[str, Any]]", "GroupChat"],
|
|
29
|
+
"Union[AfterWorkOption, SwarmAgent, str]",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WaldiezSwarmAfterWork(WaldiezBase):
|
|
34
|
+
"""Swarm after work.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Attributes
|
|
38
|
+
----------
|
|
39
|
+
recipient : str
|
|
40
|
+
The agent_id to hand off to, an AfterWork option,
|
|
41
|
+
or the custom after work method.
|
|
42
|
+
If it is an AfterWork option, it can be one of
|
|
43
|
+
('TERMINATE', 'REVERT_TO_USER', 'STAY', 'SWARM_MANAGER').
|
|
44
|
+
|
|
45
|
+
recipient_type : WaldiezSwarmAfterWorkRecipientType
|
|
46
|
+
The type of recipient.
|
|
47
|
+
Can be 'agent', 'option', or 'callable'.
|
|
48
|
+
If 'agent', the recipient is a SwarmAgent.
|
|
49
|
+
If 'option', the recipient is an AfterWorkOption :
|
|
50
|
+
('TERMINATE', 'REVERT_TO_USER', 'STAY', 'SWARM_MANAGER').
|
|
51
|
+
If 'callable', it should have the signature:
|
|
52
|
+
def custom_after_work(
|
|
53
|
+
last_speaker: SwarmAgent,
|
|
54
|
+
messages: List[dict],
|
|
55
|
+
groupchat: GroupChat,
|
|
56
|
+
) -> Union[AfterWorkOption, SwarmAgent, str]:
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
recipient: Annotated[
|
|
61
|
+
str,
|
|
62
|
+
Field(
|
|
63
|
+
"TERMINATE",
|
|
64
|
+
title="Recipient",
|
|
65
|
+
description=(
|
|
66
|
+
"The agent_id to hand off to, an AfterWork option, "
|
|
67
|
+
"or the custom after work method. "
|
|
68
|
+
"If it is an AfterWork option, it can be one of "
|
|
69
|
+
"('TERMINATE', 'REVERT_TO_USER', 'STAY')"
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
]
|
|
73
|
+
recipient_type: Annotated[
|
|
74
|
+
WaldiezSwarmAfterWorkRecipientType,
|
|
75
|
+
Field(
|
|
76
|
+
"option",
|
|
77
|
+
alias="recipientType",
|
|
78
|
+
title="Recipient Type",
|
|
79
|
+
description=(
|
|
80
|
+
"The type of recipient. "
|
|
81
|
+
"Can be 'agent', 'option', or 'callable'. "
|
|
82
|
+
"If 'agent', the recipient is a SwarmAgent. "
|
|
83
|
+
"If 'option', the recipient is an AfterWorkOption :"
|
|
84
|
+
" ('TERMINATE', 'REVERT_TO_USER', 'STAY', 'SWARM_MANAGER'). "
|
|
85
|
+
"If 'callable', it should have the signature: "
|
|
86
|
+
"def custom_after_work("
|
|
87
|
+
" last_speaker: SwarmAgent,"
|
|
88
|
+
" messages: List[Dict[str, Any]],"
|
|
89
|
+
" groupchat: GroupChat,"
|
|
90
|
+
") -> Union[AfterWorkOption, SwarmAgent, str]:"
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
_recipient_string: str = ""
|
|
96
|
+
|
|
97
|
+
def get_recipient(
|
|
98
|
+
self,
|
|
99
|
+
agent_names: Dict[str, str],
|
|
100
|
+
name_prefix: Optional[str] = None,
|
|
101
|
+
name_suffix: Optional[str] = None,
|
|
102
|
+
) -> Tuple[str, str]:
|
|
103
|
+
"""Get the recipient string.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
agent_names : Dict[str, str]
|
|
108
|
+
A mapping of agent id to agent name.
|
|
109
|
+
name_prefix : Optional[str], optional
|
|
110
|
+
The prefix for the function name, by default None.
|
|
111
|
+
name_suffix : Optional[str], optional
|
|
112
|
+
The suffix for the function name, by default None.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
Tuple[str, str]
|
|
117
|
+
The recipient string and the function content if applicable.
|
|
118
|
+
"""
|
|
119
|
+
if self.recipient_type == "option":
|
|
120
|
+
return f"AFTER_WORK(AfterWorkOption.{self.recipient})", ""
|
|
121
|
+
if self.recipient_type == "agent":
|
|
122
|
+
# the the recipient is passed as the agent name
|
|
123
|
+
# (and not its id), care should be taken to ensure
|
|
124
|
+
# the all the agents in the flow have unique names
|
|
125
|
+
agent_instance = agent_names.get(self.recipient, self.recipient)
|
|
126
|
+
return f"AFTER_WORK({agent_instance})", ""
|
|
127
|
+
|
|
128
|
+
function_name = CUSTOM_AFTER_WORK
|
|
129
|
+
if name_prefix:
|
|
130
|
+
function_name = f"{name_prefix}_{function_name}"
|
|
131
|
+
if name_suffix:
|
|
132
|
+
function_name = f"{function_name}_{name_suffix}"
|
|
133
|
+
return (
|
|
134
|
+
f"AFTER_WORK({function_name})",
|
|
135
|
+
generate_function(
|
|
136
|
+
function_name=function_name,
|
|
137
|
+
function_args=CUSTOM_AFTER_WORK_ARGS,
|
|
138
|
+
function_body=self._recipient_string,
|
|
139
|
+
function_types=CUSTOM_AFTER_WORK_TYPES,
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@model_validator(mode="after")
|
|
144
|
+
def validate_recipient(self) -> Self:
|
|
145
|
+
"""Validate the recipient.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
WaldiezSwarmAfterWork
|
|
150
|
+
The validated after work model.
|
|
151
|
+
|
|
152
|
+
Raises
|
|
153
|
+
------
|
|
154
|
+
ValueError
|
|
155
|
+
If the validation fails.
|
|
156
|
+
"""
|
|
157
|
+
self._recipient_string = self.recipient
|
|
158
|
+
if self.recipient_type == "callable":
|
|
159
|
+
is_valid, error_or_body = check_function(
|
|
160
|
+
code_string=self.recipient,
|
|
161
|
+
function_name=CUSTOM_AFTER_WORK,
|
|
162
|
+
function_args=CUSTOM_AFTER_WORK_ARGS,
|
|
163
|
+
)
|
|
164
|
+
if not is_valid or not error_or_body:
|
|
165
|
+
# pylint: disable=inconsistent-quotes
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"Invalid custom method: {error_or_body or 'no content'}"
|
|
168
|
+
)
|
|
169
|
+
self._recipient_string = error_or_body
|
|
170
|
+
elif self.recipient_type == "option":
|
|
171
|
+
if self.recipient not in [
|
|
172
|
+
"TERMINATE",
|
|
173
|
+
"REVERT_TO_USER",
|
|
174
|
+
"STAY",
|
|
175
|
+
"SWARM_MANAGER",
|
|
176
|
+
]:
|
|
177
|
+
raise ValueError("Invalid option.")
|
|
178
|
+
return self
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Swarm condition model for handoff."""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
from typing_extensions import Annotated, Literal
|
|
9
|
+
|
|
10
|
+
from ...common import WaldiezBase
|
|
11
|
+
from .on_condition_available import WaldiezSwarmOnConditionAvailable
|
|
12
|
+
from .on_condition_target import WaldiezSwarmOnConditionTarget
|
|
13
|
+
|
|
14
|
+
WaldiezSwarmOnConditionTargetType = Literal["agent", "nested_chat"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WaldiezSwarmOnCondition(WaldiezBase):
|
|
18
|
+
"""Swarm condition to handle handoff.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
target : WaldiezSwarmOnConditionTarget
|
|
23
|
+
The agent or nested chat configuration to hand off to.
|
|
24
|
+
|
|
25
|
+
target_type: Literal["agent", "nested_chat"]
|
|
26
|
+
The type of the target. Can be either 'agent' or 'nested_chat'.
|
|
27
|
+
Default is 'agent'.
|
|
28
|
+
|
|
29
|
+
condition : str
|
|
30
|
+
The condition for transitioning to the target agent
|
|
31
|
+
|
|
32
|
+
available: str, optional
|
|
33
|
+
Optional condition to determine if this ON_CONDITION is available.
|
|
34
|
+
Can be a Callable or a string. If a string, it will look up the
|
|
35
|
+
value of the context variable with that name, which should be a bool.
|
|
36
|
+
|
|
37
|
+
available_check_type : Literal["string", "callable", "none"]
|
|
38
|
+
The type of the `available` property to check. Default is "none".
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
target: Annotated[
|
|
42
|
+
WaldiezSwarmOnConditionTarget,
|
|
43
|
+
Field(
|
|
44
|
+
title="Target",
|
|
45
|
+
description=(
|
|
46
|
+
"The agent or nested chat configuration to hand off to."
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
target_type: Annotated[
|
|
51
|
+
WaldiezSwarmOnConditionTargetType,
|
|
52
|
+
Field(
|
|
53
|
+
"agent",
|
|
54
|
+
alias="targetType",
|
|
55
|
+
title="Target Type",
|
|
56
|
+
description=(
|
|
57
|
+
"The type of the target. "
|
|
58
|
+
"Can be either 'agent' or 'nested_chat'.Default is 'agent'."
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
] = "agent"
|
|
62
|
+
condition: Annotated[
|
|
63
|
+
str,
|
|
64
|
+
Field(
|
|
65
|
+
...,
|
|
66
|
+
title="Condition",
|
|
67
|
+
description="The condition for transitioning to the target agent",
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
available: Annotated[
|
|
71
|
+
WaldiezSwarmOnConditionAvailable,
|
|
72
|
+
Field(
|
|
73
|
+
default_factory=WaldiezSwarmOnConditionAvailable,
|
|
74
|
+
title="Available",
|
|
75
|
+
description=(
|
|
76
|
+
"Optional condition to determine if this ON_CONDITION "
|
|
77
|
+
"is available."
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
def get_available(
|
|
83
|
+
self,
|
|
84
|
+
name_prefix: Optional[str] = None,
|
|
85
|
+
name_suffix: Optional[str] = None,
|
|
86
|
+
) -> Tuple[str, str]:
|
|
87
|
+
"""Get the available string.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
name_prefix : str, optional
|
|
92
|
+
The prefix to add to the function name. Default is None.
|
|
93
|
+
name_suffix : str, optional
|
|
94
|
+
The suffix to add to the function name. Default is None.
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
Tuple[str, str]
|
|
98
|
+
The available string or function name and code if available.
|
|
99
|
+
"""
|
|
100
|
+
return self.available.get_available(
|
|
101
|
+
name_prefix,
|
|
102
|
+
name_suffix,
|
|
103
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Waldiez Swarm Agent ON_CONDITION Available Model."""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from pydantic import Field, model_validator
|
|
8
|
+
from typing_extensions import Annotated, Literal, Self
|
|
9
|
+
|
|
10
|
+
from ...common import WaldiezBase, check_function, generate_function
|
|
11
|
+
|
|
12
|
+
WaldiezSwarmOnConditionAvailableCheckType = Literal[
|
|
13
|
+
"string", "callable", "none"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
CUSTOM_ON_CONDITION_AVAILABLE = "custom_on_condition_available"
|
|
17
|
+
CUSTOM_ON_CONDITION_AVAILABLE_ARGS = ["agent", "message"]
|
|
18
|
+
CUSTOM_ON_CONDITION_AVAILABLE_TYPES = (
|
|
19
|
+
["ConversableAgent", "Dict[str, Any]"],
|
|
20
|
+
"bool",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# In ag2 it is used as:
|
|
24
|
+
#
|
|
25
|
+
# if on_condition.available is not None:
|
|
26
|
+
# if isinstance(on_condition.available, Callable):
|
|
27
|
+
# is_available = on_condition.available(
|
|
28
|
+
# agent, next(iter(agent.chat_messages.values()))
|
|
29
|
+
# )
|
|
30
|
+
# elif isinstance(on_condition.available, str):
|
|
31
|
+
# is_available = agent.get_context(on_condition.available) or False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WaldiezSwarmOnConditionAvailable(WaldiezBase):
|
|
35
|
+
"""Swarm condition availability check."""
|
|
36
|
+
|
|
37
|
+
type: Annotated[
|
|
38
|
+
WaldiezSwarmOnConditionAvailableCheckType,
|
|
39
|
+
Field(
|
|
40
|
+
"none",
|
|
41
|
+
alias="availableCheckType",
|
|
42
|
+
title="Available Check Type",
|
|
43
|
+
description=("The type of the `available` property to check. "),
|
|
44
|
+
),
|
|
45
|
+
] = "none"
|
|
46
|
+
value: Annotated[
|
|
47
|
+
Optional[str],
|
|
48
|
+
Field(
|
|
49
|
+
None,
|
|
50
|
+
title="Available",
|
|
51
|
+
description=(
|
|
52
|
+
"Optional condition to determine if this ON_CONDITION "
|
|
53
|
+
"is available. Can be a Callable or a string. If a string, "
|
|
54
|
+
" it will look up the value of the context variable with that "
|
|
55
|
+
"name, which should be a bool."
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
_available_string: str = ""
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def available_string(self) -> str:
|
|
64
|
+
"""Get the available string.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
str
|
|
69
|
+
The available string.
|
|
70
|
+
"""
|
|
71
|
+
return self._available_string
|
|
72
|
+
|
|
73
|
+
def get_available(
|
|
74
|
+
self,
|
|
75
|
+
name_prefix: Optional[str] = None,
|
|
76
|
+
name_suffix: Optional[str] = None,
|
|
77
|
+
) -> Tuple[str, str]:
|
|
78
|
+
"""Get the available string.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
name_prefix : str, optional
|
|
83
|
+
The prefix to add to the function name. Default is None.
|
|
84
|
+
name_suffix : str, optional
|
|
85
|
+
The suffix to add to the function name. Default is None.
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
Tuple[str, str]
|
|
89
|
+
The available string or function name and code if available.
|
|
90
|
+
"""
|
|
91
|
+
if self.type == "none" or not self.value:
|
|
92
|
+
return "", ""
|
|
93
|
+
if self.type == "string":
|
|
94
|
+
return self._available_string, ""
|
|
95
|
+
function_name = CUSTOM_ON_CONDITION_AVAILABLE
|
|
96
|
+
if name_prefix:
|
|
97
|
+
function_name = f"{name_prefix}_{function_name}"
|
|
98
|
+
if name_suffix:
|
|
99
|
+
function_name = f"{function_name}_{name_suffix}"
|
|
100
|
+
return function_name, generate_function(
|
|
101
|
+
function_name=function_name,
|
|
102
|
+
function_args=CUSTOM_ON_CONDITION_AVAILABLE_ARGS,
|
|
103
|
+
function_types=CUSTOM_ON_CONDITION_AVAILABLE_TYPES,
|
|
104
|
+
function_body=self._available_string,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="after")
|
|
108
|
+
def validate_available(self) -> Self:
|
|
109
|
+
"""Validate the available check.
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Self
|
|
114
|
+
The swarm agent's on condition available model.
|
|
115
|
+
|
|
116
|
+
Raises
|
|
117
|
+
------
|
|
118
|
+
ValueError
|
|
119
|
+
If the available check fails.
|
|
120
|
+
"""
|
|
121
|
+
if self.type == "callable":
|
|
122
|
+
if not self.value:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
"A callable is expected, but no value was provided."
|
|
125
|
+
)
|
|
126
|
+
is_valid, error_or_body = check_function(
|
|
127
|
+
code_string=self.value,
|
|
128
|
+
function_name=CUSTOM_ON_CONDITION_AVAILABLE,
|
|
129
|
+
function_args=CUSTOM_ON_CONDITION_AVAILABLE_ARGS,
|
|
130
|
+
)
|
|
131
|
+
if not is_valid:
|
|
132
|
+
raise ValueError(error_or_body)
|
|
133
|
+
self._available_string = error_or_body
|
|
134
|
+
if self.type == "string":
|
|
135
|
+
if not self.value:
|
|
136
|
+
raise ValueError(
|
|
137
|
+
"A string is expected, but no value was provided."
|
|
138
|
+
)
|
|
139
|
+
self._available_string = self.value
|
|
140
|
+
return self
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Swarm condition model for handoff."""
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from typing_extensions import Annotated
|
|
7
|
+
|
|
8
|
+
from ...common import WaldiezBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WaldiezSwarmOnConditionTarget(WaldiezBase):
|
|
12
|
+
"""Swarm condition target.
|
|
13
|
+
|
|
14
|
+
If the condition's target is "agent", the id refers to the agent's ID.
|
|
15
|
+
If the condition's target is "nested_chat", the id refers to the edge's ID.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
id : str
|
|
20
|
+
The ID of the target agent or edge.
|
|
21
|
+
order : int
|
|
22
|
+
The order of the target agent or edge.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
id: Annotated[
|
|
26
|
+
str,
|
|
27
|
+
Field(
|
|
28
|
+
...,
|
|
29
|
+
title="ID",
|
|
30
|
+
description="The ID of the target agent or edge.",
|
|
31
|
+
),
|
|
32
|
+
]
|
|
33
|
+
order: Annotated[
|
|
34
|
+
int,
|
|
35
|
+
Field(
|
|
36
|
+
...,
|
|
37
|
+
title="Order",
|
|
38
|
+
description="The order of the target agent or edge.",
|
|
39
|
+
),
|
|
40
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Swarm agent."""
|
|
4
|
+
|
|
5
|
+
from typing import List, Union
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
from typing_extensions import Annotated, Literal
|
|
9
|
+
|
|
10
|
+
from ..agent import WaldiezAgent, WaldiezAgentNestedChat
|
|
11
|
+
from .after_work import WaldiezSwarmAfterWork
|
|
12
|
+
from .on_condition import WaldiezSwarmOnCondition
|
|
13
|
+
from .swarm_agent_data import WaldiezSwarmAgentData
|
|
14
|
+
from .update_system_message import WaldiezSwarmUpdateSystemMessage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WaldiezSwarmAgent(WaldiezAgent):
|
|
18
|
+
"""Swarm agent.
|
|
19
|
+
|
|
20
|
+
It extends a user agent and has swarm related parameters.
|
|
21
|
+
|
|
22
|
+
Attributes
|
|
23
|
+
----------
|
|
24
|
+
agent_type : Literal["swarm"]
|
|
25
|
+
The agent type: 'swarm' for a swarm agent.
|
|
26
|
+
data : WaldiezSwarmAgentData
|
|
27
|
+
The swarm agent's data.
|
|
28
|
+
See `WaldiezSwarmAgentData` for more info.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
agent_type: Annotated[
|
|
32
|
+
Literal["swarm"],
|
|
33
|
+
Field(
|
|
34
|
+
"swarm",
|
|
35
|
+
title="Agent type",
|
|
36
|
+
description="The agent type: 'swarm' for a swarm agent.",
|
|
37
|
+
alias="agentType",
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
data: Annotated[
|
|
42
|
+
WaldiezSwarmAgentData,
|
|
43
|
+
Field(
|
|
44
|
+
title="Data",
|
|
45
|
+
description="The swarm agent's data",
|
|
46
|
+
default_factory=WaldiezSwarmAgentData,
|
|
47
|
+
),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def functions(self) -> List[str]:
|
|
52
|
+
"""Get the functions that the agent can use.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
List[str]
|
|
57
|
+
The functions that the agent can use.
|
|
58
|
+
"""
|
|
59
|
+
return self.data.functions
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def update_agent_state_before_reply(
|
|
63
|
+
self,
|
|
64
|
+
) -> List[Union[str, WaldiezSwarmUpdateSystemMessage]]:
|
|
65
|
+
"""Get the functions that update the agent's state before it replies.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
List[str]
|
|
70
|
+
The functions that update the agent's state before it replies.
|
|
71
|
+
"""
|
|
72
|
+
return self.data.update_agent_state_before_reply
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def handoffs(
|
|
76
|
+
self,
|
|
77
|
+
) -> List[Union[WaldiezSwarmOnCondition, WaldiezSwarmAfterWork]]:
|
|
78
|
+
"""Get the hand offs to register.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
List[str]
|
|
83
|
+
The hand offs to register.
|
|
84
|
+
"""
|
|
85
|
+
return self.data.handoffs
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def nested_chats(self) -> List[WaldiezAgentNestedChat]:
|
|
89
|
+
"""Get the nested chats.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
List[WaldiezChat]
|
|
94
|
+
The nested chats.
|
|
95
|
+
"""
|
|
96
|
+
return self.data.nested_chats
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def is_initial(self) -> bool:
|
|
100
|
+
"""Check if the agent is the initial agent.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
bool
|
|
105
|
+
Whether the agent is the initial agent.
|
|
106
|
+
"""
|
|
107
|
+
return self.data.is_initial
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
# https://docs.ag2.ai/docs/reference/agentchat/contrib/swarm_agent
|
|
4
|
+
"""Swarm agent data."""
|
|
5
|
+
|
|
6
|
+
from typing import List, Union
|
|
7
|
+
|
|
8
|
+
from pydantic import Field, model_validator
|
|
9
|
+
from typing_extensions import Annotated, Self
|
|
10
|
+
|
|
11
|
+
from ..agent import WaldiezAgentData
|
|
12
|
+
from .after_work import WaldiezSwarmAfterWork
|
|
13
|
+
from .on_condition import WaldiezSwarmOnCondition
|
|
14
|
+
from .update_system_message import WaldiezSwarmUpdateSystemMessage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# flake8: noqa: E501
|
|
18
|
+
# pylint: disable=line-too-long
|
|
19
|
+
class WaldiezSwarmAgentData(WaldiezAgentData):
|
|
20
|
+
"""Swarm agent data.
|
|
21
|
+
|
|
22
|
+
Attributes
|
|
23
|
+
----------
|
|
24
|
+
is_initial: bool
|
|
25
|
+
Whether the agent is the initial agent.
|
|
26
|
+
functions : List[str]
|
|
27
|
+
A list of functions (skill ids) to register with the agent.
|
|
28
|
+
|
|
29
|
+
update_agent_state_before_reply : List[str]
|
|
30
|
+
A list of functions, including `UPDATE_SYSTEM_MESSAGE`,
|
|
31
|
+
called to update the agent's state before it replies. Each function
|
|
32
|
+
is called when the agent is selected and before it speaks.
|
|
33
|
+
|
|
34
|
+
handoffs : List[Union[WaldiezSwarmOnCondition, WaldiezSwarmAfterWork]]
|
|
35
|
+
A list of hand offs to register.
|
|
36
|
+
|
|
37
|
+
Notes
|
|
38
|
+
-----
|
|
39
|
+
Each agent should have at most one `AfterWork` and (if any) it should be
|
|
40
|
+
at the end the list of hand offs.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
is_initial: Annotated[
|
|
44
|
+
bool,
|
|
45
|
+
Field(
|
|
46
|
+
False,
|
|
47
|
+
title="Is Initial",
|
|
48
|
+
alias="isInitial",
|
|
49
|
+
description=("Whether the agent is the initial agent."),
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
functions: Annotated[
|
|
53
|
+
List[str],
|
|
54
|
+
Field(
|
|
55
|
+
title="Functions",
|
|
56
|
+
description=(
|
|
57
|
+
"A list of functions (skill ids) to register with the agent."
|
|
58
|
+
),
|
|
59
|
+
default_factory=list,
|
|
60
|
+
),
|
|
61
|
+
]
|
|
62
|
+
update_agent_state_before_reply: Annotated[
|
|
63
|
+
List[Union[str, WaldiezSwarmUpdateSystemMessage]],
|
|
64
|
+
Field(
|
|
65
|
+
title="Update Agent State Before Reply",
|
|
66
|
+
alias="updateAgentStateBeforeReply",
|
|
67
|
+
description=(
|
|
68
|
+
"A list of functions, including UPDATE_SYSTEM_MESSAGEs,"
|
|
69
|
+
"called to update the agent's state before it replies. "
|
|
70
|
+
" Each function is called when the agent is selected "
|
|
71
|
+
"and before it speaks. If not an UPDATE_SYSTEM_MESSAGE, "
|
|
72
|
+
"it should be a skill id."
|
|
73
|
+
),
|
|
74
|
+
default_factory=list,
|
|
75
|
+
),
|
|
76
|
+
]
|
|
77
|
+
handoffs: Annotated[
|
|
78
|
+
List[Union[WaldiezSwarmOnCondition, WaldiezSwarmAfterWork]],
|
|
79
|
+
Field(
|
|
80
|
+
title="Handoffs",
|
|
81
|
+
description=(
|
|
82
|
+
"A list of hand offs to register. "
|
|
83
|
+
"There should only be at most one `AfterWork` per agent"
|
|
84
|
+
"And (if any) it should be at the end of the list."
|
|
85
|
+
),
|
|
86
|
+
default_factory=list,
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
@model_validator(mode="after")
|
|
91
|
+
def validate_handoffs(self) -> Self:
|
|
92
|
+
"""Validate the hand offs.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
Self
|
|
97
|
+
The swarm agent data.
|
|
98
|
+
|
|
99
|
+
Raises
|
|
100
|
+
------
|
|
101
|
+
ValueError
|
|
102
|
+
If there are more than one `AfterWork`s.
|
|
103
|
+
"""
|
|
104
|
+
after_works: List[WaldiezSwarmAfterWork] = [
|
|
105
|
+
hand_off
|
|
106
|
+
for hand_off in self.handoffs
|
|
107
|
+
if isinstance(hand_off, WaldiezSwarmAfterWork)
|
|
108
|
+
]
|
|
109
|
+
if len(after_works) > 1:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"Each agent should have at most one `AfterWork` "
|
|
112
|
+
"and (if any) it should be at the end of the list."
|
|
113
|
+
)
|
|
114
|
+
on_conditions: List[WaldiezSwarmOnCondition] = [
|
|
115
|
+
hand_off
|
|
116
|
+
for hand_off in self.handoffs
|
|
117
|
+
if isinstance(hand_off, WaldiezSwarmOnCondition)
|
|
118
|
+
]
|
|
119
|
+
on_conditions = sorted(on_conditions, key=lambda x: x.target.order)
|
|
120
|
+
handoffs = on_conditions + after_works
|
|
121
|
+
if after_works and after_works[0] != handoffs[-1]:
|
|
122
|
+
handoffs.remove(after_works[0])
|
|
123
|
+
handoffs.append(after_works[0])
|
|
124
|
+
self.handoffs = handoffs
|
|
125
|
+
return self
|