waldiez 0.4.6__py3-none-any.whl → 0.4.8__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.

Files changed (244) hide show
  1. waldiez/__init__.py +5 -5
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +112 -73
  4. waldiez/exporter.py +61 -19
  5. waldiez/exporting/__init__.py +25 -6
  6. waldiez/exporting/agent/__init__.py +7 -3
  7. waldiez/exporting/agent/code_execution.py +114 -0
  8. waldiez/exporting/agent/exporter.py +354 -0
  9. waldiez/exporting/agent/extras/__init__.py +15 -0
  10. waldiez/exporting/agent/extras/captain_agent_extras.py +315 -0
  11. waldiez/exporting/agent/extras/group/target.py +178 -0
  12. waldiez/exporting/agent/extras/group_manager_agent_extas.py +500 -0
  13. waldiez/exporting/agent/extras/group_member_extras.py +181 -0
  14. waldiez/exporting/agent/extras/handoffs/__init__.py +19 -0
  15. waldiez/exporting/agent/extras/handoffs/after_work.py +78 -0
  16. waldiez/exporting/agent/extras/handoffs/available.py +74 -0
  17. waldiez/exporting/agent/extras/handoffs/condition.py +158 -0
  18. waldiez/exporting/agent/extras/handoffs/handoff.py +171 -0
  19. waldiez/exporting/agent/extras/handoffs/target.py +189 -0
  20. waldiez/exporting/agent/extras/rag/__init__.py +10 -0
  21. waldiez/exporting/agent/{utils/rag_user/chroma_utils.py → extras/rag/chroma_extras.py} +16 -15
  22. waldiez/exporting/agent/{utils/rag_user/mongo_utils.py → extras/rag/mongo_extras.py} +10 -10
  23. waldiez/exporting/agent/{utils/rag_user/pgvector_utils.py → extras/rag/pgvector_extras.py} +13 -13
  24. waldiez/exporting/agent/{utils/rag_user/qdrant_utils.py → extras/rag/qdrant_extras.py} +13 -13
  25. waldiez/exporting/agent/{utils/rag_user/vector_db.py → extras/rag/vector_db_extras.py} +59 -46
  26. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +245 -0
  27. waldiez/exporting/agent/extras/reasoning_agent_extras.py +88 -0
  28. waldiez/exporting/agent/factory.py +95 -0
  29. waldiez/exporting/agent/processor.py +150 -0
  30. waldiez/exporting/agent/system_message.py +36 -0
  31. waldiez/exporting/agent/termination.py +50 -0
  32. waldiez/exporting/chats/__init__.py +7 -3
  33. waldiez/exporting/chats/exporter.py +97 -0
  34. waldiez/exporting/chats/factory.py +65 -0
  35. waldiez/exporting/chats/processor.py +226 -0
  36. waldiez/exporting/chats/utils/__init__.py +6 -5
  37. waldiez/exporting/chats/utils/common.py +11 -45
  38. waldiez/exporting/chats/utils/group.py +55 -0
  39. waldiez/exporting/chats/utils/nested.py +37 -52
  40. waldiez/exporting/chats/utils/sequential.py +72 -61
  41. waldiez/exporting/chats/utils/{single_chat.py → single.py} +48 -50
  42. waldiez/exporting/core/__init__.py +196 -0
  43. waldiez/exporting/core/constants.py +17 -0
  44. waldiez/exporting/core/content.py +69 -0
  45. waldiez/exporting/core/context.py +244 -0
  46. waldiez/exporting/core/enums.py +89 -0
  47. waldiez/exporting/core/errors.py +19 -0
  48. waldiez/exporting/core/exporter.py +390 -0
  49. waldiez/exporting/core/exporters.py +67 -0
  50. waldiez/exporting/core/extras/__init__.py +39 -0
  51. waldiez/exporting/core/extras/agent_extras/__init__.py +27 -0
  52. waldiez/exporting/core/extras/agent_extras/captain_extras.py +57 -0
  53. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +102 -0
  54. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +53 -0
  55. waldiez/exporting/core/extras/agent_extras/reasoning_extras.py +68 -0
  56. waldiez/exporting/core/extras/agent_extras/standard_extras.py +263 -0
  57. waldiez/exporting/core/extras/base.py +241 -0
  58. waldiez/exporting/core/extras/chat_extras.py +118 -0
  59. waldiez/exporting/core/extras/flow_extras.py +70 -0
  60. waldiez/exporting/core/extras/model_extras.py +73 -0
  61. waldiez/exporting/core/extras/path_resolver.py +93 -0
  62. waldiez/exporting/core/extras/serializer.py +138 -0
  63. waldiez/exporting/core/extras/tool_extras.py +82 -0
  64. waldiez/exporting/core/protocols.py +259 -0
  65. waldiez/exporting/core/result.py +705 -0
  66. waldiez/exporting/core/types.py +329 -0
  67. waldiez/exporting/core/utils/__init__.py +11 -0
  68. waldiez/exporting/core/utils/comment.py +33 -0
  69. waldiez/exporting/core/utils/llm_config.py +117 -0
  70. waldiez/exporting/core/validation.py +96 -0
  71. waldiez/exporting/flow/__init__.py +6 -2
  72. waldiez/exporting/flow/execution_generator.py +193 -0
  73. waldiez/exporting/flow/exporter.py +107 -0
  74. waldiez/exporting/flow/factory.py +94 -0
  75. waldiez/exporting/flow/file_generator.py +214 -0
  76. waldiez/exporting/flow/merger.py +387 -0
  77. waldiez/exporting/flow/orchestrator.py +411 -0
  78. waldiez/exporting/flow/utils/__init__.py +9 -36
  79. waldiez/exporting/flow/utils/common.py +206 -0
  80. waldiez/exporting/flow/utils/importing.py +373 -0
  81. waldiez/exporting/flow/utils/linting.py +200 -0
  82. waldiez/exporting/flow/utils/{logging_utils.py → logging.py} +23 -9
  83. waldiez/exporting/models/__init__.py +3 -1
  84. waldiez/exporting/models/exporter.py +233 -0
  85. waldiez/exporting/models/factory.py +66 -0
  86. waldiez/exporting/models/processor.py +139 -0
  87. waldiez/exporting/tools/__init__.py +11 -0
  88. waldiez/exporting/tools/exporter.py +207 -0
  89. waldiez/exporting/tools/factory.py +57 -0
  90. waldiez/exporting/tools/processor.py +248 -0
  91. waldiez/exporting/tools/registration.py +133 -0
  92. waldiez/io/__init__.py +128 -0
  93. waldiez/io/_ws.py +199 -0
  94. waldiez/io/models/__init__.py +60 -0
  95. waldiez/io/models/base.py +66 -0
  96. waldiez/io/models/constants.py +78 -0
  97. waldiez/io/models/content/__init__.py +23 -0
  98. waldiez/io/models/content/audio.py +43 -0
  99. waldiez/io/models/content/base.py +45 -0
  100. waldiez/io/models/content/file.py +43 -0
  101. waldiez/io/models/content/image.py +96 -0
  102. waldiez/io/models/content/text.py +37 -0
  103. waldiez/io/models/content/video.py +43 -0
  104. waldiez/io/models/user_input.py +269 -0
  105. waldiez/io/models/user_response.py +215 -0
  106. waldiez/io/mqtt.py +681 -0
  107. waldiez/io/redis.py +782 -0
  108. waldiez/io/structured.py +419 -0
  109. waldiez/io/utils.py +184 -0
  110. waldiez/io/ws.py +298 -0
  111. waldiez/logger.py +481 -0
  112. waldiez/models/__init__.py +108 -51
  113. waldiez/models/agents/__init__.py +34 -70
  114. waldiez/models/agents/agent/__init__.py +10 -4
  115. waldiez/models/agents/agent/agent.py +466 -65
  116. waldiez/models/agents/agent/agent_data.py +119 -47
  117. waldiez/models/agents/agent/agent_type.py +13 -2
  118. waldiez/models/agents/agent/code_execution.py +12 -12
  119. waldiez/models/agents/agent/human_input_mode.py +8 -0
  120. waldiez/models/agents/agent/{linked_skill.py → linked_tool.py} +7 -7
  121. waldiez/models/agents/agent/nested_chat.py +35 -7
  122. waldiez/models/agents/agent/termination_message.py +30 -22
  123. waldiez/models/agents/{swarm_agent → agent}/update_system_message.py +22 -22
  124. waldiez/models/agents/agents.py +58 -63
  125. waldiez/models/agents/assistant/assistant.py +4 -4
  126. waldiez/models/agents/assistant/assistant_data.py +13 -1
  127. waldiez/models/agents/{captain_agent → captain}/captain_agent.py +5 -5
  128. waldiez/models/agents/{captain_agent → captain}/captain_agent_data.py +5 -5
  129. waldiez/models/agents/extra_requirements.py +11 -16
  130. waldiez/models/agents/group_manager/group_manager.py +103 -13
  131. waldiez/models/agents/group_manager/group_manager_data.py +36 -14
  132. waldiez/models/agents/group_manager/speakers.py +77 -24
  133. waldiez/models/agents/{rag_user → rag_user_proxy}/__init__.py +16 -16
  134. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +64 -0
  135. waldiez/models/agents/{rag_user/rag_user_data.py → rag_user_proxy/rag_user_proxy_data.py} +6 -5
  136. waldiez/models/agents/{rag_user → rag_user_proxy}/retrieve_config.py +182 -114
  137. waldiez/models/agents/{rag_user → rag_user_proxy}/vector_db_config.py +13 -13
  138. waldiez/models/agents/reasoning/reasoning_agent.py +6 -6
  139. waldiez/models/agents/reasoning/reasoning_agent_data.py +110 -63
  140. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +38 -10
  141. waldiez/models/agents/user_proxy/user_proxy.py +11 -7
  142. waldiez/models/agents/user_proxy/user_proxy_data.py +2 -2
  143. waldiez/models/chat/__init__.py +2 -1
  144. waldiez/models/chat/chat.py +166 -87
  145. waldiez/models/chat/chat_data.py +99 -136
  146. waldiez/models/chat/chat_message.py +33 -23
  147. waldiez/models/chat/chat_nested.py +31 -30
  148. waldiez/models/chat/chat_summary.py +10 -8
  149. waldiez/models/common/__init__.py +52 -2
  150. waldiez/models/common/ag2_version.py +1 -1
  151. waldiez/models/common/base.py +38 -7
  152. waldiez/models/common/dict_utils.py +42 -17
  153. waldiez/models/common/handoff.py +459 -0
  154. waldiez/models/common/id_generator.py +19 -0
  155. waldiez/models/common/method_utils.py +130 -68
  156. waldiez/{exporting/base/utils → models/common}/naming.py +38 -61
  157. waldiez/models/common/waldiez_version.py +37 -0
  158. waldiez/models/flow/__init__.py +9 -2
  159. waldiez/models/flow/connection.py +18 -0
  160. waldiez/models/flow/flow.py +311 -215
  161. waldiez/models/flow/flow_data.py +207 -40
  162. waldiez/models/flow/info.py +85 -0
  163. waldiez/models/flow/naming.py +131 -0
  164. waldiez/models/model/__init__.py +7 -1
  165. waldiez/models/model/extra_requirements.py +3 -12
  166. waldiez/models/model/model.py +76 -21
  167. waldiez/models/model/model_data.py +108 -20
  168. waldiez/models/tool/__init__.py +16 -0
  169. waldiez/models/tool/extra_requirements.py +36 -0
  170. waldiez/models/{skill/skill.py → tool/tool.py} +88 -88
  171. waldiez/models/tool/tool_data.py +51 -0
  172. waldiez/models/tool/tool_type.py +8 -0
  173. waldiez/models/waldiez.py +97 -80
  174. waldiez/runner.py +114 -49
  175. waldiez/running/__init__.py +1 -1
  176. waldiez/running/environment.py +49 -68
  177. waldiez/running/gen_seq_diagram.py +16 -14
  178. waldiez/running/running.py +53 -34
  179. waldiez/utils/__init__.py +0 -4
  180. waldiez/utils/cli_extras/jupyter.py +5 -3
  181. waldiez/utils/cli_extras/runner.py +6 -4
  182. waldiez/utils/cli_extras/studio.py +6 -4
  183. waldiez/utils/conflict_checker.py +15 -9
  184. waldiez/utils/flaml_warnings.py +5 -5
  185. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/METADATA +235 -91
  186. waldiez-0.4.8.dist-info/RECORD +200 -0
  187. waldiez/exporting/agent/agent_exporter.py +0 -297
  188. waldiez/exporting/agent/utils/__init__.py +0 -23
  189. waldiez/exporting/agent/utils/captain_agent.py +0 -263
  190. waldiez/exporting/agent/utils/code_execution.py +0 -65
  191. waldiez/exporting/agent/utils/group_manager.py +0 -220
  192. waldiez/exporting/agent/utils/rag_user/__init__.py +0 -7
  193. waldiez/exporting/agent/utils/rag_user/rag_user.py +0 -209
  194. waldiez/exporting/agent/utils/reasoning.py +0 -36
  195. waldiez/exporting/agent/utils/swarm_agent.py +0 -469
  196. waldiez/exporting/agent/utils/teachability.py +0 -41
  197. waldiez/exporting/agent/utils/termination_message.py +0 -44
  198. waldiez/exporting/base/__init__.py +0 -25
  199. waldiez/exporting/base/agent_position.py +0 -75
  200. waldiez/exporting/base/base_exporter.py +0 -118
  201. waldiez/exporting/base/export_position.py +0 -48
  202. waldiez/exporting/base/import_position.py +0 -23
  203. waldiez/exporting/base/mixin.py +0 -137
  204. waldiez/exporting/base/utils/__init__.py +0 -18
  205. waldiez/exporting/base/utils/comments.py +0 -96
  206. waldiez/exporting/base/utils/path_check.py +0 -68
  207. waldiez/exporting/base/utils/to_string.py +0 -84
  208. waldiez/exporting/chats/chats_exporter.py +0 -240
  209. waldiez/exporting/chats/utils/swarm.py +0 -210
  210. waldiez/exporting/flow/flow_exporter.py +0 -528
  211. waldiez/exporting/flow/utils/agent_utils.py +0 -204
  212. waldiez/exporting/flow/utils/chat_utils.py +0 -71
  213. waldiez/exporting/flow/utils/def_main.py +0 -77
  214. waldiez/exporting/flow/utils/flow_content.py +0 -202
  215. waldiez/exporting/flow/utils/flow_names.py +0 -116
  216. waldiez/exporting/flow/utils/importing_utils.py +0 -227
  217. waldiez/exporting/models/models_exporter.py +0 -199
  218. waldiez/exporting/models/utils.py +0 -174
  219. waldiez/exporting/skills/__init__.py +0 -9
  220. waldiez/exporting/skills/skills_exporter.py +0 -176
  221. waldiez/exporting/skills/utils.py +0 -369
  222. waldiez/models/agents/agent/teachability.py +0 -70
  223. waldiez/models/agents/rag_user/rag_user.py +0 -60
  224. waldiez/models/agents/swarm_agent/__init__.py +0 -50
  225. waldiez/models/agents/swarm_agent/after_work.py +0 -179
  226. waldiez/models/agents/swarm_agent/on_condition.py +0 -105
  227. waldiez/models/agents/swarm_agent/on_condition_available.py +0 -142
  228. waldiez/models/agents/swarm_agent/on_condition_target.py +0 -40
  229. waldiez/models/agents/swarm_agent/swarm_agent.py +0 -107
  230. waldiez/models/agents/swarm_agent/swarm_agent_data.py +0 -124
  231. waldiez/models/flow/utils.py +0 -232
  232. waldiez/models/skill/__init__.py +0 -16
  233. waldiez/models/skill/extra_requirements.py +0 -36
  234. waldiez/models/skill/skill_data.py +0 -53
  235. waldiez/models/skill/skill_type.py +0 -8
  236. waldiez/utils/pysqlite3_checker.py +0 -308
  237. waldiez/utils/rdps_checker.py +0 -122
  238. waldiez-0.4.6.dist-info/RECORD +0 -149
  239. /waldiez/models/agents/{captain_agent → captain}/__init__.py +0 -0
  240. /waldiez/models/agents/{captain_agent → captain}/captain_agent_lib_entry.py +0 -0
  241. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/WHEEL +0 -0
  242. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/entry_points.txt +0 -0
  243. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/licenses/LICENSE +0 -0
  244. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/licenses/NOTICE.md +0 -0
@@ -1,18 +1,20 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ # pyright: reportCallIssue=false
3
4
  """Waldiez flow model."""
4
5
 
5
- import uuid
6
- from typing import List, Optional, Tuple
6
+ from functools import cached_property
7
+ from typing import Optional
7
8
 
8
9
  from pydantic import Field, model_validator
9
10
  from typing_extensions import Annotated, Literal, Self
10
11
 
11
- from ..agents import WaldiezAgent, WaldiezSwarmAgent
12
+ from ..agents import WaldiezAgent, WaldiezGroupManager
12
13
  from ..chat import WaldiezChat
13
- from ..common import WaldiezBase, now
14
+ from ..common import WaldiezBase, get_id, get_waldiez_version, now
15
+ from .connection import WaldiezAgentConnection
14
16
  from .flow_data import WaldiezFlowData
15
- from .utils import check_handoff_to_nested_chat, id_factory
17
+ from .naming import WaldiezUniqueNames, ensure_unique_names
16
18
 
17
19
 
18
20
  class WaldiezFlow(WaldiezBase):
@@ -28,9 +30,9 @@ class WaldiezFlow(WaldiezBase):
28
30
  The name of the flow.
29
31
  description : str
30
32
  The description of the flow.
31
- tags : List[str]
33
+ tags : list[str]
32
34
  The tags of the flow.
33
- requirements : List[str]
35
+ requirements : list[str]
34
36
  The requirements of the flow.
35
37
  storage_id : str
36
38
  The storage ID of the flow (ignored, UI related).
@@ -47,9 +49,17 @@ class WaldiezFlow(WaldiezBase):
47
49
  Field(
48
50
  description="The ID of the flow",
49
51
  title="ID",
50
- default_factory=id_factory,
52
+ default_factory=get_id,
51
53
  ),
52
54
  ]
55
+ version: Annotated[
56
+ str,
57
+ Field(
58
+ default_factory=get_waldiez_version,
59
+ description="The version waldiez that was used to create the flow",
60
+ title="Version",
61
+ ),
62
+ ] = "0.0.0" # default to 0.0.0 if not found
53
63
  type: Annotated[
54
64
  Literal["flow"],
55
65
  Field(
@@ -75,7 +85,7 @@ class WaldiezFlow(WaldiezBase):
75
85
  ),
76
86
  ]
77
87
  tags: Annotated[
78
- List[str],
88
+ list[str],
79
89
  Field(
80
90
  description="The tags of the flow",
81
91
  title="Tags",
@@ -83,7 +93,7 @@ class WaldiezFlow(WaldiezBase):
83
93
  ),
84
94
  ]
85
95
  requirements: Annotated[
86
- List[str],
96
+ list[str],
87
97
  Field(
88
98
  description="The requirements of the flow",
89
99
  title="Requirements",
@@ -101,7 +111,7 @@ class WaldiezFlow(WaldiezBase):
101
111
  storage_id: Annotated[
102
112
  str,
103
113
  Field(
104
- uuid.uuid4(),
114
+ default_factory=get_id,
105
115
  description="The storage ID of the flow (ignored, UI related)",
106
116
  title="Storage ID",
107
117
  alias="storageId",
@@ -123,10 +133,9 @@ class WaldiezFlow(WaldiezBase):
123
133
  description="The date and time when the flow was last updated.",
124
134
  ),
125
135
  ]
126
- _ordered_flow: Optional[
127
- List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]]
128
- ] = None
136
+ _ordered_flow: Optional[list[WaldiezAgentConnection]] = None
129
137
  _single_agent_mode: bool = False
138
+ _is_group_chat: bool = False
130
139
 
131
140
  @property
132
141
  def is_async(self) -> bool:
@@ -150,19 +159,6 @@ class WaldiezFlow(WaldiezBase):
150
159
  """
151
160
  return self.data.cache_seed
152
161
 
153
- @property
154
- def is_swarm_flow(self) -> bool:
155
- """Check if the flow is a swarm flow.
156
-
157
- Returns
158
- -------
159
- bool
160
- True if the flow is a swarm flow, False otherwise.
161
- """
162
- return any(
163
- agent.agent_type == "swarm" for agent in self.data.agents.members
164
- )
165
-
166
162
  @property
167
163
  def is_single_agent_mode(self) -> bool:
168
164
  """Check if the flow is in single agent mode.
@@ -177,12 +173,30 @@ class WaldiezFlow(WaldiezBase):
177
173
  @property
178
174
  def ordered_flow(
179
175
  self,
180
- ) -> List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]]:
176
+ ) -> list[WaldiezAgentConnection]:
181
177
  """Get the ordered flow."""
182
- if not self._ordered_flow:
178
+ if self._ordered_flow is None:
183
179
  self._ordered_flow = self._get_flow_order()
184
180
  return self._ordered_flow
185
181
 
182
+ @cached_property
183
+ def unique_names(self) -> WaldiezUniqueNames:
184
+ """Get the unique names for the flow.
185
+
186
+ Returns
187
+ -------
188
+ WaldiezUniqueNames
189
+ The unique names for the flow.
190
+ """
191
+ return ensure_unique_names(
192
+ flow_id=self.id,
193
+ flow_name=self.name,
194
+ flow_agents=self.data.agents.members,
195
+ flow_chats=self.data.chats,
196
+ flow_tools=self.data.tools,
197
+ flow_models=self.data.models,
198
+ )
199
+
186
200
  def get_agent_by_id(self, agent_id: str) -> WaldiezAgent:
187
201
  """Get the agent by ID.
188
202
 
@@ -206,89 +220,51 @@ class WaldiezFlow(WaldiezBase):
206
220
  return agent
207
221
  raise ValueError(f"Agent with ID {agent_id} not found.")
208
222
 
209
- def _get_flow_order(
210
- self,
211
- ) -> List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]]:
212
- """Get the ordered flow."""
213
- # in the chats, there is the 'order' field, we use this,
214
- # we only keep the ones with order >=0
215
- # and sort them by this property
216
- ordered_flow: List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]] = []
217
- # if swarm, we only keep the first chat
218
- if self.is_swarm_flow:
219
- ordered_flow = self._get_swarm_flow()
220
- if ordered_flow:
221
- return ordered_flow
222
- for chat in self.data.chats:
223
- if chat.data.order < 0:
224
- continue
225
- source = self.get_agent_by_id(chat.source)
226
- target = self.get_agent_by_id(chat.target)
227
- ordered_flow.append((chat, source, target))
228
- if not ordered_flow:
229
- if len(self.data.chats) == 1:
230
- chat = self.data.chats[0]
231
- source = self.get_agent_by_id(chat.source)
232
- target = self.get_agent_by_id(chat.target)
233
- ordered_flow.append((chat, source, target))
234
- return ordered_flow
223
+ @classmethod
224
+ def default(cls) -> "WaldiezFlow":
225
+ """Get the default flow.
235
226
 
236
- def _get_swarm_flow(
237
- self,
238
- ) -> List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]]:
239
- # valid "first" chat:
240
- # - source is a user|rag_user and target is a swarm
241
- # - source is a swarm and target is a (and source.is_initial)
242
- valid_chats: List[WaldiezChat] = []
243
- for chat in self.data.chats:
244
- target = self.get_agent_by_id(chat.target)
245
- source = self.get_agent_by_id(chat.source)
246
- if (
247
- source.agent_type in ["user", "rag_user"]
248
- and target.agent_type == "swarm"
249
- ):
250
- return [(chat, source, target)]
251
- if source.agent_type == "swarm" and target.agent_type == "swarm":
252
- valid_chats.append(chat)
253
- if not valid_chats:
254
- return []
255
- for valid_chat in valid_chats:
256
- source = self.get_agent_by_id(valid_chat.source)
257
- if isinstance(source, WaldiezSwarmAgent) and source.is_initial:
258
- return [
259
- (
260
- valid_chat,
261
- source,
262
- self.get_agent_by_id(valid_chat.target),
263
- )
264
- ]
265
- first_chat: Optional[WaldiezChat] = None
266
- # first check the order
267
- by_order = sorted(
268
- filter(lambda edge: edge.data.order >= 0, valid_chats),
269
- key=lambda edge: edge.data.order,
227
+ Returns
228
+ -------
229
+ WaldiezFlow
230
+ The default flow.
231
+ """
232
+ an_id = get_id()
233
+ return cls(
234
+ id=an_id,
235
+ storage_id=an_id,
236
+ created_at=now(),
237
+ updated_at=now(),
238
+ type="flow",
239
+ name="Default Flow",
240
+ description="Default Flow",
241
+ tags=[],
242
+ requirements=[],
243
+ data=WaldiezFlowData.default(),
270
244
  )
271
- if not by_order:
272
- # let's order by position
273
- by_position = sorted(
274
- valid_chats,
275
- key=lambda chat: chat.data.position,
276
- )
277
- if by_position:
278
- first_chat = by_position[0]
279
- else:
280
- first_chat = valid_chats[0]
281
- else:
282
- first_chat = by_order[0]
283
- if first_chat:
284
- source = self.get_agent_by_id(first_chat.source)
285
- target = self.get_agent_by_id(first_chat.target)
286
- return [(first_chat, source, target)]
287
- return []
245
+
246
+ def get_group_members(self, group_id: str) -> list[WaldiezAgent]:
247
+ """Get the group members.
248
+
249
+ Parameters
250
+ ----------
251
+ group_id : str
252
+ The ID of the group.
253
+
254
+ Returns
255
+ -------
256
+ list[WaldiezAgent]
257
+ The list of group members.
258
+ """
259
+ return [
260
+ agent
261
+ for agent in self.data.agents.members
262
+ if agent.data.parent_id == group_id
263
+ ]
288
264
 
289
265
  def get_agent_connections(
290
266
  self, agent_id: str, all_chats: bool = True
291
- ) -> List[str]:
267
+ ) -> list[str]:
292
268
  """Get the agent connections.
293
269
 
294
270
  Parameters
@@ -301,10 +277,10 @@ class WaldiezFlow(WaldiezBase):
301
277
 
302
278
  Returns
303
279
  -------
304
- List[str]
280
+ list[str]
305
281
  The list of agent ids that the agent with the given ID connects to.
306
282
  """
307
- connections: List[str] = []
283
+ connections: list[str] = []
308
284
  if all_chats:
309
285
  for chat in self.data.chats:
310
286
  if chat.source == agent_id:
@@ -312,7 +288,9 @@ class WaldiezFlow(WaldiezBase):
312
288
  if chat.target == agent_id:
313
289
  connections.append(chat.source)
314
290
  else:
315
- for _, source, target in self.ordered_flow:
291
+ for entry in self.ordered_flow:
292
+ source = entry["source"]
293
+ target = entry["target"]
316
294
  if source.id == agent_id:
317
295
  connections.append(target.id)
318
296
  if target.id == agent_id:
@@ -321,7 +299,7 @@ class WaldiezFlow(WaldiezBase):
321
299
 
322
300
  def get_group_chat_members(
323
301
  self, group_manager_id: str
324
- ) -> List[WaldiezAgent]:
302
+ ) -> list[WaldiezAgent]:
325
303
  """Get the group chat members.
326
304
 
327
305
  Parameters
@@ -331,89 +309,142 @@ class WaldiezFlow(WaldiezBase):
331
309
 
332
310
  Returns
333
311
  -------
334
- List[WaldiezAgent]
335
- The list of group chat
312
+ list[WaldiezAgent]
313
+ The list of group chat members.
336
314
  """
337
315
  agent = self.get_agent_by_id(group_manager_id)
338
- if agent.agent_type != "manager":
316
+ if not agent.is_group_manager or not isinstance(
317
+ agent, WaldiezGroupManager
318
+ ):
339
319
  return []
340
- connections = self.get_agent_connections(
341
- group_manager_id,
342
- all_chats=True,
343
- )
344
- return [self.get_agent_by_id(member_id) for member_id in connections]
320
+ members = [
321
+ agent
322
+ for agent in self.data.agents.members
323
+ if agent.data.parent_id == group_manager_id
324
+ ]
325
+ if agent.data.speakers.selection_method != "round_robin":
326
+ return members
327
+ ordered_ids = agent.get_speakers_order()
328
+ if not ordered_ids:
329
+ return members
330
+ members_dict = {member.id: member for member in members}
331
+ ordered_members = [
332
+ members_dict[member_id]
333
+ for member_id in ordered_ids
334
+ if member_id in members_dict
335
+ ]
336
+ return ordered_members
345
337
 
346
- def get_initial_swarm_agent(self) -> Optional[WaldiezAgent]:
347
- """Get the initial swarm agent.
338
+ def _get_flow_order(
339
+ self,
340
+ ) -> list[WaldiezAgentConnection]:
341
+ """Get the ordered flow."""
342
+ if self._is_group_chat:
343
+ return self._get_group_chat_flow()
344
+ # in the chats, there is the 'order' field, we use this,
345
+ # we only keep the ones with order >=0
346
+ # and sort them by this property
347
+ ordered_flow: list[WaldiezAgentConnection] = []
348
+ for chat in self.data.chats:
349
+ if chat.data.order < 0:
350
+ continue
351
+ source = self.get_agent_by_id(chat.source)
352
+ target = self.get_agent_by_id(chat.target)
353
+ ordered_flow.append(
354
+ {
355
+ "source": source,
356
+ "target": target,
357
+ "chat": chat,
358
+ }
359
+ )
360
+ # ordered_flow.append((chat, source, target))
361
+ if not ordered_flow:
362
+ if len(self.data.chats) == 1:
363
+ chat = self.data.chats[0]
364
+ source = self.get_agent_by_id(chat.source)
365
+ target = self.get_agent_by_id(chat.target)
366
+ ordered_flow.append(
367
+ {
368
+ "source": source,
369
+ "target": target,
370
+ "chat": chat,
371
+ }
372
+ )
373
+ return ordered_flow
374
+
375
+ def get_root_group_manager(self) -> WaldiezGroupManager:
376
+ """Get the root group manager.
348
377
 
349
378
  Returns
350
379
  -------
351
- Optional[WaldiezAgent]
352
- The initial swarm agent if found, None otherwise.
380
+ WaldiezGroupManager
381
+ The root group manager.
382
+
383
+ Raises
384
+ ------
385
+ ValueError
386
+ If no group manager is found.
353
387
  """
354
- fallback_agent = None
355
- for chat in self.data.chats:
356
- source_agent = self.get_agent_by_id(chat.source)
357
- target_agent = self.get_agent_by_id(chat.target)
358
- if (
359
- target_agent.agent_type == "swarm"
360
- and source_agent.agent_type != "swarm"
361
- ):
362
- return target_agent
363
- if (
364
- source_agent.agent_type == "swarm"
365
- and target_agent.agent_type == "swarm"
366
- ):
367
- fallback_agent = source_agent
368
- break
369
- for swarm_agent in self.data.agents.swarm_agents:
370
- if swarm_agent.is_initial:
371
- return swarm_agent
372
- return fallback_agent
373
-
374
- def get_swarm_chat_members(
375
- self,
376
- initial_agent: WaldiezAgent,
377
- ) -> Tuple[List[WaldiezAgent], Optional[WaldiezAgent]]:
378
- """Get the swarm chat members.
388
+ for agent in self.data.agents.groupManagerAgents:
389
+ if agent.data.parent_id is None:
390
+ return agent
391
+ raise ValueError("No group manager found.")
379
392
 
380
- Parameters
381
- ----------
382
- initial_agent : WaldiezAgent
383
- The initial agent.
393
+ def _get_group_chat_flow(
394
+ self,
395
+ ) -> list[WaldiezAgentConnection]:
396
+ """Get the ordered flow for group chat.
384
397
 
385
398
  Returns
386
399
  -------
387
- Tuple[List[WaldiezAgent], Optional[WaldiezAgent]]
388
- The list of swarm chat members and the user agent if any.
400
+ list[tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]]
401
+ The ordered flow for group chat.
389
402
  """
390
- if initial_agent.agent_type != "swarm":
391
- return [], None
392
- members: List[WaldiezAgent] = [initial_agent]
403
+ # in a group chat there is no "order", the group manager
404
+ # handles the conversation (using the group "pattern")
405
+ # the only thin to check is if there is a user agent that connects
406
+ # to the group manager agent (so that would be the first chat)
407
+ # if found, we must then check if the message from the user
408
+ # to the group manager is:
409
+ # - "text" or "none" => no need to create a group manager on ag2
410
+ # - "function/method" => create a group manager and a group chat on ag2
411
+ # in the first case, the chat would be:
412
+ # result, context, last_agent = initiate_group_chat(
413
+ # pattern=pattern,
414
+ # messages=...,
415
+ # max_rounds=10
416
+ # )
417
+ # in the second case, the chat would be:
418
+ # user.initiate_chat(manager, ...)
393
419
  user_agent: Optional[WaldiezAgent] = None
394
- visited_agents = set()
395
- visited_agents.add(initial_agent.id)
396
- connections = self.get_agent_connections(
397
- initial_agent.id,
398
- all_chats=True,
399
- )
400
- while connections:
401
- agent_id = connections.pop()
402
- if agent_id in visited_agents:
403
- continue
404
- agent = self.get_agent_by_id(agent_id)
405
- visited_agents.add(agent_id)
406
- if agent.agent_type == "swarm":
407
- members.append(agent)
408
- connections.extend(
409
- self.get_agent_connections(agent_id, all_chats=True)
410
- )
411
- if agent.agent_type in ["user", "rag_user"] and not user_agent:
412
- user_agent = agent
413
- return members, user_agent
420
+ to_root_manager: Optional[WaldiezChat] = None
421
+ root_manager: WaldiezGroupManager = self.get_root_group_manager()
422
+ for chat in self.data.chats:
423
+ if chat.target == root_manager.id:
424
+ # check if the source is a user agent
425
+ source = self.get_agent_by_id(chat.source)
426
+ if source.is_user:
427
+ user_agent = source
428
+ to_root_manager = chat
429
+ break
430
+ if not to_root_manager or not user_agent:
431
+ return []
432
+ return [
433
+ {
434
+ "source": user_agent,
435
+ "target": root_manager,
436
+ "chat": to_root_manager,
437
+ }
438
+ ]
414
439
 
415
440
  def _validate_agent_connections(self) -> None:
416
441
  for agent in self.data.agents.members:
442
+ if agent.is_group_member:
443
+ # group members are allowed
444
+ # to not connect to any other node
445
+ # the group manager will take care of
446
+ # the agent/speaker connections
447
+ continue
417
448
  if not any(
418
449
  agent.id in (chat.source, chat.target)
419
450
  for chat in self.data.chats
@@ -429,15 +460,15 @@ class WaldiezFlow(WaldiezBase):
429
460
 
430
461
  - unique node ids
431
462
  - there are at least two agents
432
- - (or a single agent but not a group manager or a swarm agent)
463
+ - (or a single agent but not a group manager)
433
464
  - all the agents connect to at least one other agent
434
- - all the linked agent skills are found in the flow
465
+ - all the linked agent tools are found in the flow
435
466
  - all the linked agent models are found in the flow
436
467
  - all the managers have at least one member in the chat group
437
468
  - the ordered flow (chats with position >=0) is not empty
438
- - all agents' code execution config functions exist in the flow skills
439
- - if swarm flow, there is at least one swarm agent
440
- - if swarm flow, there is an initial swarm agent
469
+ - all agents' code execution config functions exist in the flow tools
470
+ - if group chat flow, there is at least one group manager agent
471
+ - if group chat flow, there is an initial group member agent
441
472
 
442
473
  Returns
443
474
  -------
@@ -449,34 +480,35 @@ class WaldiezFlow(WaldiezBase):
449
480
  ValueError
450
481
  If the ordered flow is empty.
451
482
  If the model IDs are not unique.
452
- If the skill IDs are not unique.
483
+ If the tool IDs are not unique.
453
484
  If the agents do not connect to any other node.
454
485
  If the manager's group chat has no members.
455
486
  """
456
487
  all_members = list(self.data.agents.members)
488
+ all_chats = list(self.data.chats)
489
+ for agent in all_members:
490
+ agent.gather_nested_chats(
491
+ all_agents=all_members, all_chats=all_chats
492
+ )
493
+ agent.gather_handoffs(all_agents=all_members, all_chats=all_chats)
494
+ self._validate_group_chat(all_members)
457
495
  if len(all_members) == 1:
458
- return self.validate_single_agent_mode(all_members[0])
459
- if not self.ordered_flow:
496
+ return self._validate_single_agent_mode(all_members[0])
497
+ ordered_flow = self.ordered_flow # could be empty (if group chat)
498
+ if not ordered_flow and self._ordered_flow is None:
460
499
  raise ValueError("The ordered flow is empty.")
461
- model_ids = self.validate_flow_models()
462
- skills_ids = self.validate_flow_skills()
463
- self.data.agents.validate_flow(model_ids, skills_ids)
500
+ model_ids = self._validate_flow_models()
501
+ tools_ids = self._validate_flow_tools()
502
+ self.data.agents.validate_flow(model_ids, tools_ids)
464
503
  self._validate_agent_connections()
465
- if self.is_swarm_flow:
466
- for swarm_agent in self.data.agents.swarm_agents:
467
- check_handoff_to_nested_chat(
468
- swarm_agent,
469
- all_agents=list(self.data.agents.members),
470
- all_chats=self.data.chats,
471
- )
472
504
  return self
473
505
 
474
- def validate_flow_models(self) -> List[str]:
506
+ def _validate_flow_models(self) -> list[str]:
475
507
  """Validate the flow models.
476
508
 
477
509
  Returns
478
510
  -------
479
- List[str]
511
+ list[str]
480
512
  The list of model IDs.
481
513
 
482
514
  Raises
@@ -489,25 +521,25 @@ class WaldiezFlow(WaldiezBase):
489
521
  raise ValueError("Model IDs must be unique.")
490
522
  return model_ids
491
523
 
492
- def validate_flow_skills(self) -> List[str]:
493
- """Validate the flow skills.
524
+ def _validate_flow_tools(self) -> list[str]:
525
+ """Validate the flow tools.
494
526
 
495
527
  Returns
496
528
  -------
497
- List[str]
498
- The list of skill IDs.
529
+ list[str]
530
+ The list of tool IDs.
499
531
 
500
532
  Raises
501
533
  ------
502
534
  ValueError
503
- If the skill IDs are not unique.
535
+ If the tool IDs are not unique.
504
536
  """
505
- skill_ids = [skill.id for skill in self.data.skills]
506
- if len(skill_ids) != len(set(skill_ids)):
507
- raise ValueError("Skill IDs must be unique.")
508
- return skill_ids
537
+ tool_ids = [tool.id for tool in self.data.tools]
538
+ if len(tool_ids) != len(set(tool_ids)):
539
+ raise ValueError("Tool IDs must be unique.")
540
+ return tool_ids
509
541
 
510
- def validate_single_agent_mode(self, member: WaldiezAgent) -> Self:
542
+ def _validate_single_agent_mode(self, member: WaldiezAgent) -> Self:
511
543
  """Flow validation for single agent mode.
512
544
 
513
545
  Parameters
@@ -523,17 +555,81 @@ class WaldiezFlow(WaldiezBase):
523
555
  Raises
524
556
  ------
525
557
  ValueError
526
- - If the only agent is a group manager or a swarm agent.
558
+ - If the only agent is a group manager.
527
559
  - If the model IDs are not unique.
528
- - If the skill IDs are not unique.
560
+ - If the tool IDs are not unique.
529
561
  """
530
- if member.agent_type in ["manager", "swarm"]:
562
+ if member.is_group_manager:
531
563
  raise ValueError(
532
- "In single agent mode, "
533
- "the agent must not be a group manager or a swarm agent."
564
+ "In single agent mode, the agent must not be a group manager."
534
565
  )
535
- model_ids = self.validate_flow_models()
536
- skills_ids = self.validate_flow_skills()
537
- self.data.agents.validate_flow(model_ids, skills_ids)
566
+ model_ids = self._validate_flow_models()
567
+ tools_ids = self._validate_flow_tools()
568
+ self.data.agents.validate_flow(model_ids, tools_ids)
538
569
  self._single_agent_mode = True
539
570
  return self
571
+
572
+ def _validate_group_manager(
573
+ self, group_manager: WaldiezGroupManager, all_member_ids: list[str]
574
+ ) -> None:
575
+ """Validate the group manager agents.
576
+
577
+ Raises
578
+ ------
579
+ ValueError
580
+ If there are no group manager agents.
581
+ """
582
+ if not group_manager.data.initial_agent_id:
583
+ raise ValueError(
584
+ "The flow is a group chat but the group manager agent "
585
+ f"{group_manager.id} has no initial agent ID."
586
+ )
587
+ if group_manager.data.initial_agent_id not in all_member_ids:
588
+ raise ValueError(
589
+ "The flow is a group chat but the initial agent ID "
590
+ f"{group_manager.data.initial_agent_id} is not in the flow."
591
+ )
592
+ group_members = self.get_group_members(group_manager.id)
593
+ if not group_members:
594
+ raise ValueError(
595
+ "The flow is a group chat but the group manager agent "
596
+ f"{group_manager.id} has no members in the group."
597
+ )
598
+ group_manager.set_speakers_order(
599
+ [member.id for member in group_members]
600
+ )
601
+
602
+ def _validate_group_chat(self, all_members: list[WaldiezAgent]) -> None:
603
+ """Check if the flow is a group chat and validate it.
604
+
605
+ Raises
606
+ ------
607
+ ValueError
608
+ If the flow is a group chat and there is no group manager agent,
609
+ if the group has no members,
610
+ or if the group has no initial member agent.
611
+ """
612
+ if not self.data.agents.groupManagerAgents:
613
+ # no group manager agents, not a group chat
614
+ return
615
+ self._is_group_chat = True
616
+ if not any(agent.is_group_member for agent in self.data.agents.members):
617
+ raise ValueError(
618
+ "The flow is a group chat but has no members in the group."
619
+ )
620
+ # check if the group manager agents are the flow
621
+ group_manager_ids = [
622
+ agent.id for agent in self.data.agents.groupManagerAgents
623
+ ]
624
+ all_member_ids = [agent.id for agent in all_members]
625
+ if not all(
626
+ group_manager_id in all_member_ids
627
+ for group_manager_id in group_manager_ids
628
+ ):
629
+ raise ValueError(
630
+ "The flow is a group chat but not all group manager agents are "
631
+ "in the flow."
632
+ )
633
+ # check the initial_agent_id for each group
634
+ for group_manager in self.data.agents.groupManagerAgents:
635
+ self._validate_group_manager(group_manager, all_member_ids)