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
waldiez/models/flow/flow.py
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
1
3
|
"""Waldiez flow model."""
|
|
2
4
|
|
|
3
5
|
import uuid
|
|
4
|
-
from datetime import datetime, timezone
|
|
5
6
|
from typing import List, Optional, Tuple
|
|
6
7
|
|
|
7
8
|
from pydantic import Field, model_validator
|
|
8
9
|
from typing_extensions import Annotated, Literal, Self
|
|
9
10
|
|
|
10
|
-
from ..agents import WaldiezAgent
|
|
11
|
+
from ..agents import WaldiezAgent, WaldiezSwarmAgent
|
|
11
12
|
from ..chat import WaldiezChat
|
|
12
13
|
from ..common import WaldiezBase, now
|
|
13
14
|
from .flow_data import WaldiezFlowData
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def id_factory() -> str:
|
|
17
|
-
"""Generate a unique ID.
|
|
18
|
-
|
|
19
|
-
Returns
|
|
20
|
-
-------
|
|
21
|
-
str
|
|
22
|
-
The unique ID.
|
|
23
|
-
"""
|
|
24
|
-
now_td = datetime.now(timezone.utc)
|
|
25
|
-
now_str = now_td.strftime("%Y%m%d%H%M%S%f")
|
|
26
|
-
return f"{now_str}-{uuid.uuid4().hex}"
|
|
15
|
+
from .utils import check_handoff_to_nested_chat, id_factory
|
|
27
16
|
|
|
28
17
|
|
|
29
18
|
class WaldiezFlow(WaldiezBase):
|
|
@@ -138,6 +127,41 @@ class WaldiezFlow(WaldiezBase):
|
|
|
138
127
|
List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]]
|
|
139
128
|
] = None
|
|
140
129
|
|
|
130
|
+
@property
|
|
131
|
+
def is_async(self) -> bool:
|
|
132
|
+
"""Check if the flow is asynchronous.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
bool
|
|
137
|
+
True if the flow is asynchronous, False otherwise.
|
|
138
|
+
"""
|
|
139
|
+
return self.data.is_async
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def has_shared_skill(self) -> bool:
|
|
143
|
+
"""Check if the flow has a skill with shared variables (global).
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
bool
|
|
148
|
+
True if the flow has shared skills, False otherwise.
|
|
149
|
+
"""
|
|
150
|
+
return any(skill.is_shared for skill in self.data.skills)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def is_swarm_flow(self) -> bool:
|
|
154
|
+
"""Check if the flow is a swarm flow.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
bool
|
|
159
|
+
True if the flow is a swarm flow, False otherwise.
|
|
160
|
+
"""
|
|
161
|
+
return any(
|
|
162
|
+
agent.agent_type == "swarm" for agent in self.data.agents.members
|
|
163
|
+
)
|
|
164
|
+
|
|
141
165
|
@property
|
|
142
166
|
def ordered_flow(
|
|
143
167
|
self,
|
|
@@ -165,18 +189,9 @@ class WaldiezFlow(WaldiezBase):
|
|
|
165
189
|
ValueError
|
|
166
190
|
If the agent with the given ID is not found.
|
|
167
191
|
"""
|
|
168
|
-
for
|
|
169
|
-
if
|
|
170
|
-
return
|
|
171
|
-
for assistant in self.data.agents.assistants:
|
|
172
|
-
if assistant.id == agent_id:
|
|
173
|
-
return assistant
|
|
174
|
-
for manager in self.data.agents.managers:
|
|
175
|
-
if manager.id == agent_id:
|
|
176
|
-
return manager
|
|
177
|
-
for rag_user in self.data.agents.rag_users:
|
|
178
|
-
if rag_user.id == agent_id:
|
|
179
|
-
return rag_user
|
|
192
|
+
for agent in self.data.agents.members:
|
|
193
|
+
if agent.id == agent_id:
|
|
194
|
+
return agent
|
|
180
195
|
raise ValueError(f"Agent with ID {agent_id} not found.")
|
|
181
196
|
|
|
182
197
|
def _get_flow_order(
|
|
@@ -187,10 +202,12 @@ class WaldiezFlow(WaldiezBase):
|
|
|
187
202
|
# we only keep the ones with order >=0
|
|
188
203
|
# and sort them by this property
|
|
189
204
|
ordered_flow: List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]] = []
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
205
|
+
# if swarm, we only keep the first chat
|
|
206
|
+
if self.is_swarm_flow:
|
|
207
|
+
ordered_flow = self._get_swarm_flow()
|
|
208
|
+
if ordered_flow:
|
|
209
|
+
return ordered_flow
|
|
210
|
+
for chat in self.data.chats:
|
|
194
211
|
if chat.data.order < 0:
|
|
195
212
|
continue
|
|
196
213
|
source = self.get_agent_by_id(chat.source)
|
|
@@ -204,6 +221,51 @@ class WaldiezFlow(WaldiezBase):
|
|
|
204
221
|
ordered_flow.append((chat, source, target))
|
|
205
222
|
return ordered_flow
|
|
206
223
|
|
|
224
|
+
def _get_swarm_flow(
|
|
225
|
+
self,
|
|
226
|
+
) -> List[Tuple[WaldiezChat, WaldiezAgent, WaldiezAgent]]:
|
|
227
|
+
# valid "first" chat:
|
|
228
|
+
# - source is a user|rag_user and target is a swarm
|
|
229
|
+
# - source is a swarm and target is a (and source.is_initial)
|
|
230
|
+
valid_chats: List[WaldiezChat] = []
|
|
231
|
+
for chat in self.data.chats:
|
|
232
|
+
target = self.get_agent_by_id(chat.target)
|
|
233
|
+
source = self.get_agent_by_id(chat.source)
|
|
234
|
+
if (
|
|
235
|
+
source.agent_type in ["user", "rag_user"]
|
|
236
|
+
and target.agent_type == "swarm"
|
|
237
|
+
):
|
|
238
|
+
return [(chat, source, target)]
|
|
239
|
+
if source.agent_type == "swarm" and target.agent_type == "swarm":
|
|
240
|
+
if isinstance(source, WaldiezSwarmAgent) and source.is_initial:
|
|
241
|
+
return [(chat, source, target)]
|
|
242
|
+
valid_chats.append(chat)
|
|
243
|
+
if not valid_chats:
|
|
244
|
+
return []
|
|
245
|
+
first_chat: Optional[WaldiezChat] = None
|
|
246
|
+
# first check the order
|
|
247
|
+
by_order = sorted(
|
|
248
|
+
filter(lambda edge: edge.data.order >= 0, valid_chats),
|
|
249
|
+
key=lambda edge: edge.data.order,
|
|
250
|
+
)
|
|
251
|
+
if not by_order:
|
|
252
|
+
# let's order by position
|
|
253
|
+
by_position = sorted(
|
|
254
|
+
valid_chats,
|
|
255
|
+
key=lambda chat: chat.data.position,
|
|
256
|
+
)
|
|
257
|
+
if by_position:
|
|
258
|
+
first_chat = by_position[0]
|
|
259
|
+
else:
|
|
260
|
+
first_chat = valid_chats[0]
|
|
261
|
+
else:
|
|
262
|
+
first_chat = by_order[0]
|
|
263
|
+
if first_chat:
|
|
264
|
+
source = self.get_agent_by_id(first_chat.source)
|
|
265
|
+
target = self.get_agent_by_id(first_chat.target)
|
|
266
|
+
return [(first_chat, source, target)]
|
|
267
|
+
return []
|
|
268
|
+
|
|
207
269
|
def get_agent_connections(
|
|
208
270
|
self, agent_id: str, all_chats: bool = True
|
|
209
271
|
) -> List[str]:
|
|
@@ -222,7 +284,7 @@ class WaldiezFlow(WaldiezBase):
|
|
|
222
284
|
List[str]
|
|
223
285
|
The list of agent ids that the agent with the given ID connects to.
|
|
224
286
|
"""
|
|
225
|
-
connections = []
|
|
287
|
+
connections: List[str] = []
|
|
226
288
|
if all_chats:
|
|
227
289
|
for chat in self.data.chats:
|
|
228
290
|
if chat.source == agent_id:
|
|
@@ -255,9 +317,81 @@ class WaldiezFlow(WaldiezBase):
|
|
|
255
317
|
agent = self.get_agent_by_id(group_manager_id)
|
|
256
318
|
if agent.agent_type != "manager":
|
|
257
319
|
return []
|
|
258
|
-
connections = self.get_agent_connections(
|
|
320
|
+
connections = self.get_agent_connections(
|
|
321
|
+
group_manager_id,
|
|
322
|
+
all_chats=True,
|
|
323
|
+
)
|
|
259
324
|
return [self.get_agent_by_id(member_id) for member_id in connections]
|
|
260
325
|
|
|
326
|
+
def get_initial_swarm_agent(self) -> Optional[WaldiezAgent]:
|
|
327
|
+
"""Get the initial swarm agent.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
Optional[WaldiezAgent]
|
|
332
|
+
The initial swarm agent if found, None otherwise.
|
|
333
|
+
"""
|
|
334
|
+
fallback_agent = None
|
|
335
|
+
for chat in self.data.chats:
|
|
336
|
+
source_agent = self.get_agent_by_id(chat.source)
|
|
337
|
+
target_agent = self.get_agent_by_id(chat.target)
|
|
338
|
+
if (
|
|
339
|
+
target_agent.agent_type == "swarm"
|
|
340
|
+
and source_agent.agent_type != "swarm"
|
|
341
|
+
):
|
|
342
|
+
return target_agent
|
|
343
|
+
if (
|
|
344
|
+
source_agent.agent_type == "swarm"
|
|
345
|
+
and target_agent.agent_type == "swarm"
|
|
346
|
+
):
|
|
347
|
+
fallback_agent = source_agent
|
|
348
|
+
break
|
|
349
|
+
for swarm_agent in self.data.agents.swarm_agents:
|
|
350
|
+
if swarm_agent.is_initial:
|
|
351
|
+
return swarm_agent
|
|
352
|
+
return fallback_agent
|
|
353
|
+
|
|
354
|
+
def get_swarm_chat_members(
|
|
355
|
+
self,
|
|
356
|
+
initial_agent: WaldiezAgent,
|
|
357
|
+
) -> Tuple[List[WaldiezAgent], Optional[WaldiezAgent]]:
|
|
358
|
+
"""Get the swarm chat members.
|
|
359
|
+
|
|
360
|
+
Parameters
|
|
361
|
+
----------
|
|
362
|
+
initial_agent : WaldiezAgent
|
|
363
|
+
The initial agent.
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
Tuple[List[WaldiezAgent], Optional[WaldiezAgent]]
|
|
368
|
+
The list of swarm chat members and the user agent if any.
|
|
369
|
+
"""
|
|
370
|
+
if initial_agent.agent_type != "swarm":
|
|
371
|
+
return [], None
|
|
372
|
+
members: List[WaldiezAgent] = [initial_agent]
|
|
373
|
+
user_agent: Optional[WaldiezAgent] = None
|
|
374
|
+
visited_agents = set()
|
|
375
|
+
visited_agents.add(initial_agent.id)
|
|
376
|
+
connections = self.get_agent_connections(
|
|
377
|
+
initial_agent.id,
|
|
378
|
+
all_chats=True,
|
|
379
|
+
)
|
|
380
|
+
while connections:
|
|
381
|
+
agent_id = connections.pop()
|
|
382
|
+
if agent_id in visited_agents:
|
|
383
|
+
continue
|
|
384
|
+
agent = self.get_agent_by_id(agent_id)
|
|
385
|
+
visited_agents.add(agent_id)
|
|
386
|
+
if agent.agent_type == "swarm":
|
|
387
|
+
members.append(agent)
|
|
388
|
+
connections.extend(
|
|
389
|
+
self.get_agent_connections(agent_id, all_chats=True)
|
|
390
|
+
)
|
|
391
|
+
if agent.agent_type in ["user", "rag_user"] and not user_agent:
|
|
392
|
+
user_agent = agent
|
|
393
|
+
return members, user_agent
|
|
394
|
+
|
|
261
395
|
def _validate_agent_connections(self) -> None:
|
|
262
396
|
for agent in self.data.agents.members:
|
|
263
397
|
if not any(
|
|
@@ -268,13 +402,6 @@ class WaldiezFlow(WaldiezBase):
|
|
|
268
402
|
f"Agent {agent.id} ({agent.name}) "
|
|
269
403
|
"does not connect to any other node."
|
|
270
404
|
)
|
|
271
|
-
# already covered above
|
|
272
|
-
# if agent.agent_type == "manager":
|
|
273
|
-
# chat_member_ids = self.get_agent_connections(agent.id)
|
|
274
|
-
# if not chat_member_ids:
|
|
275
|
-
# raise ValueError(
|
|
276
|
-
# f"Manager's {agent.id} group chat has no members."
|
|
277
|
-
# )
|
|
278
405
|
|
|
279
406
|
@model_validator(mode="after")
|
|
280
407
|
def validate_flow(self) -> Self:
|
|
@@ -288,6 +415,8 @@ class WaldiezFlow(WaldiezBase):
|
|
|
288
415
|
- all the managers have at least one member in the chat group
|
|
289
416
|
- the ordered flow (chats with position >=0) is not empty
|
|
290
417
|
- all agents' code execution config functions exist in the flow skills
|
|
418
|
+
- if swarm flow, there is at least one swarm agent
|
|
419
|
+
- if swarm flow, there is an initial swarm agent
|
|
291
420
|
|
|
292
421
|
Returns
|
|
293
422
|
-------
|
|
@@ -313,4 +442,11 @@ class WaldiezFlow(WaldiezBase):
|
|
|
313
442
|
raise ValueError("Skill IDs must be unique.")
|
|
314
443
|
self.data.agents.validate_flow(model_ids, skills_ids)
|
|
315
444
|
self._validate_agent_connections()
|
|
445
|
+
if self.is_swarm_flow:
|
|
446
|
+
for swarm_agent in self.data.agents.swarm_agents:
|
|
447
|
+
check_handoff_to_nested_chat(
|
|
448
|
+
swarm_agent,
|
|
449
|
+
all_agents=list(self.data.agents.members),
|
|
450
|
+
all_chats=self.data.chats,
|
|
451
|
+
)
|
|
316
452
|
return self
|
waldiez/models/flow/flow_data.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
1
3
|
"""Waldiez flow data."""
|
|
2
4
|
|
|
3
5
|
from typing import Any, Dict, List
|
|
4
6
|
|
|
5
|
-
from pydantic import Field
|
|
6
|
-
from typing_extensions import Annotated
|
|
7
|
+
from pydantic import Field, model_validator
|
|
8
|
+
from typing_extensions import Annotated, Self
|
|
7
9
|
|
|
8
10
|
from ..agents import WaldiezAgents
|
|
9
11
|
from ..chat import WaldiezChat
|
|
@@ -36,6 +38,8 @@ class WaldiezFlowData(WaldiezBase):
|
|
|
36
38
|
The skills of the flow. See `WaldiezSkill`.
|
|
37
39
|
chats : List[WaldiezChat]
|
|
38
40
|
The chats of the flow. See `WaldiezChat`.
|
|
41
|
+
is_async : bool
|
|
42
|
+
Whether the flow is asynchronous or not.
|
|
39
43
|
"""
|
|
40
44
|
|
|
41
45
|
# the ones below (nodes,edges, viewport) we ignore
|
|
@@ -97,3 +101,60 @@ class WaldiezFlowData(WaldiezBase):
|
|
|
97
101
|
default_factory=list,
|
|
98
102
|
),
|
|
99
103
|
]
|
|
104
|
+
is_async: Annotated[
|
|
105
|
+
bool,
|
|
106
|
+
Field(
|
|
107
|
+
False,
|
|
108
|
+
description="Whether the flow is asynchronous or not",
|
|
109
|
+
title="Is Async",
|
|
110
|
+
),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
@model_validator(mode="after")
|
|
114
|
+
def validate_flow_chats(self) -> Self:
|
|
115
|
+
"""Validate the flow chats.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
WaldiezFlowData
|
|
120
|
+
The flow data.
|
|
121
|
+
|
|
122
|
+
Raises
|
|
123
|
+
------
|
|
124
|
+
ValueError
|
|
125
|
+
If there is a chat with a prerequisite that does not exist.
|
|
126
|
+
"""
|
|
127
|
+
self.chats = sorted(self.chats, key=lambda x: x.order)
|
|
128
|
+
# in async, ag2 uses the "chat_id" field (and it must be an int):
|
|
129
|
+
# ```
|
|
130
|
+
# prerequisites = []
|
|
131
|
+
# for chat_info in chat_queue:
|
|
132
|
+
# if "chat_id" not in chat_info:
|
|
133
|
+
# raise ValueError(
|
|
134
|
+
# "Each chat must have a unique id for "
|
|
135
|
+
# "async multi-chat execution."
|
|
136
|
+
# )
|
|
137
|
+
# chat_id = chat_info["chat_id"]
|
|
138
|
+
# pre_chats = chat_info.get("prerequisites", [])
|
|
139
|
+
# for pre_chat_id in pre_chats:
|
|
140
|
+
# if not isinstance(pre_chat_id, int):
|
|
141
|
+
# raise ValueError("Prerequisite chat id is not int.")
|
|
142
|
+
# prerequisites.append((chat_id, pre_chat_id))
|
|
143
|
+
# return prerequisites
|
|
144
|
+
# ```
|
|
145
|
+
id_to_chat_id: Dict[str, int] = {}
|
|
146
|
+
for index, chat in enumerate(self.chats):
|
|
147
|
+
id_to_chat_id[chat.id] = index
|
|
148
|
+
chat.set_chat_id(index)
|
|
149
|
+
# also update the chat prerequisites
|
|
150
|
+
# we have ids(str), not chat_ids(int)
|
|
151
|
+
for chat in self.chats:
|
|
152
|
+
chat_prerequisites = []
|
|
153
|
+
for chat_id in chat.data.prerequisites:
|
|
154
|
+
if chat_id not in id_to_chat_id:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"Chat with id {chat_id} not found in the flow."
|
|
157
|
+
)
|
|
158
|
+
chat_prerequisites.append(id_to_chat_id[chat_id])
|
|
159
|
+
chat.set_prerequisites(chat_prerequisites)
|
|
160
|
+
return self
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Helpers for the flow model."""
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from ..agents import (
|
|
10
|
+
WaldiezAgent,
|
|
11
|
+
WaldiezAgentNestedChat,
|
|
12
|
+
WaldiezAgentNestedChatMessage,
|
|
13
|
+
WaldiezSwarmAgent,
|
|
14
|
+
WaldiezSwarmOnCondition,
|
|
15
|
+
)
|
|
16
|
+
from ..chat import WaldiezChat
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def id_factory() -> str:
|
|
20
|
+
"""Generate a unique ID.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
str
|
|
25
|
+
The unique ID.
|
|
26
|
+
"""
|
|
27
|
+
now_td = datetime.now(timezone.utc)
|
|
28
|
+
now_str = now_td.strftime("%Y%m%d%H%M%S%f")
|
|
29
|
+
return f"{now_str}-{uuid.uuid4().hex}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check_handoff_to_nested_chat(
|
|
33
|
+
agent: WaldiezSwarmAgent,
|
|
34
|
+
all_agents: List[WaldiezAgent],
|
|
35
|
+
all_chats: List[WaldiezChat],
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Check the handoffs to a nested chat.
|
|
38
|
+
|
|
39
|
+
If we have one and the agent does not have nested_chats,
|
|
40
|
+
we should generate them with the `handoff.target.id`
|
|
41
|
+
as the first (chat's) message.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
agent : WaldiezSwarmAgent
|
|
46
|
+
The swarm agent.
|
|
47
|
+
all_agents : List[WaldiezAgent]
|
|
48
|
+
All agents.
|
|
49
|
+
all_chats : List[WaldiezChat]
|
|
50
|
+
All chats.
|
|
51
|
+
|
|
52
|
+
Raises
|
|
53
|
+
------
|
|
54
|
+
ValueError
|
|
55
|
+
If the agent has a handoff to a nested chat,
|
|
56
|
+
but no chat found with it as a source.
|
|
57
|
+
"""
|
|
58
|
+
# pylint: disable=too-complex
|
|
59
|
+
for handoff in agent.handoffs:
|
|
60
|
+
if not isinstance(handoff, WaldiezSwarmOnCondition):
|
|
61
|
+
continue
|
|
62
|
+
is_nested_chat = handoff.target_type == "nested_chat"
|
|
63
|
+
if is_nested_chat:
|
|
64
|
+
# check if the agent already has nested_chats
|
|
65
|
+
# but only to get the order (and the first chat/message)
|
|
66
|
+
# either way, we must include all the connections that
|
|
67
|
+
# are swarm => non-swarm
|
|
68
|
+
# if we have the orders, ok, else we get them from the
|
|
69
|
+
# edges positions
|
|
70
|
+
all_connections = sorted(
|
|
71
|
+
get_nested_chat_swarm_connections(agent, all_agents, all_chats),
|
|
72
|
+
key=lambda x: x.data.order,
|
|
73
|
+
)
|
|
74
|
+
if not all_connections:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Agent {agent.name} has a handoff to a nested chat, "
|
|
77
|
+
"but no chat found with it as a source."
|
|
78
|
+
)
|
|
79
|
+
agent_nested_chats = agent.nested_chats
|
|
80
|
+
if not agent_nested_chats or not agent_nested_chats[0].messages:
|
|
81
|
+
agent.data.nested_chats = [
|
|
82
|
+
WaldiezAgentNestedChat(
|
|
83
|
+
triggered_by=[],
|
|
84
|
+
messages=[
|
|
85
|
+
WaldiezAgentNestedChatMessage(
|
|
86
|
+
id=chat.id,
|
|
87
|
+
is_reply=False,
|
|
88
|
+
)
|
|
89
|
+
for chat in all_connections
|
|
90
|
+
],
|
|
91
|
+
)
|
|
92
|
+
]
|
|
93
|
+
break
|
|
94
|
+
agent.data.nested_chats = merge_nested_chat_messages(
|
|
95
|
+
agent_nested_chats[0].messages, all_connections
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_nested_chat_swarm_connections(
|
|
100
|
+
agent: WaldiezSwarmAgent,
|
|
101
|
+
all_agents: List[WaldiezAgent],
|
|
102
|
+
all_chats: List[WaldiezChat],
|
|
103
|
+
) -> List[WaldiezChat]:
|
|
104
|
+
"""Get the nested chat connections.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
agent : WaldiezSwarmAgent
|
|
109
|
+
The swarm agent.
|
|
110
|
+
all_agents : Iterator[WaldiezAgent]
|
|
111
|
+
All agents.
|
|
112
|
+
all_chats : List[WaldiezChat]
|
|
113
|
+
All chats.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
List[WaldiezAgentNestedChat]
|
|
118
|
+
The nested chat connections.
|
|
119
|
+
"""
|
|
120
|
+
connections_with_non_swarm_targets = []
|
|
121
|
+
for chat in all_chats:
|
|
122
|
+
if chat.source != agent.id:
|
|
123
|
+
continue
|
|
124
|
+
target_agent = next(
|
|
125
|
+
(a for a in all_agents if a.id == chat.target), None
|
|
126
|
+
)
|
|
127
|
+
if not target_agent or target_agent.agent_type != "swarm":
|
|
128
|
+
connections_with_non_swarm_targets.append(chat)
|
|
129
|
+
return connections_with_non_swarm_targets
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def merge_nested_chat_messages(
|
|
133
|
+
agent_nested_chat_messages: List[WaldiezAgentNestedChatMessage],
|
|
134
|
+
all_connections: List[WaldiezChat],
|
|
135
|
+
) -> List[WaldiezAgentNestedChat]:
|
|
136
|
+
"""Merge the nested chat messages.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
all_connections : List[WaldiezChat]
|
|
141
|
+
The connections.
|
|
142
|
+
agent_nested_chat_messages : List[WaldiezAgentNestedChatMessage]
|
|
143
|
+
The agent's nested chat messages.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
List[WaldiezAgentNestedChat]
|
|
148
|
+
The merged nested chat with all the messages.
|
|
149
|
+
"""
|
|
150
|
+
nested_chat = WaldiezAgentNestedChat(triggered_by=[], messages=[])
|
|
151
|
+
chat_ids_added: List[str] = []
|
|
152
|
+
for message in agent_nested_chat_messages:
|
|
153
|
+
chat = next((c for c in all_connections if c.id == message.id), None)
|
|
154
|
+
if chat and chat.id not in chat_ids_added:
|
|
155
|
+
nested_chat.messages.append(
|
|
156
|
+
WaldiezAgentNestedChatMessage(
|
|
157
|
+
id=chat.id,
|
|
158
|
+
is_reply=False,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
chat_ids_added.append(chat.id)
|
|
162
|
+
for chat in all_connections:
|
|
163
|
+
if chat.id not in chat_ids_added:
|
|
164
|
+
nested_chat.messages.append(
|
|
165
|
+
WaldiezAgentNestedChatMessage(
|
|
166
|
+
id=chat.id,
|
|
167
|
+
is_reply=False,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
chat_ids_added.append(chat.id)
|
|
171
|
+
nested_chat.messages.sort(key=lambda x: chat_ids_added.index(x.id))
|
|
172
|
+
return [nested_chat]
|
waldiez/models/model/__init__.py
CHANGED
waldiez/models/model/model.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Waldiez model model."""
|
|
2
4
|
|
|
3
5
|
import os
|
|
4
6
|
from typing import Any, Dict, List, Optional
|
|
@@ -102,6 +104,27 @@ class WaldiezModel(WaldiezBase):
|
|
|
102
104
|
Field(..., title="Data", description="The data of the model."),
|
|
103
105
|
]
|
|
104
106
|
|
|
107
|
+
@property
|
|
108
|
+
def api_key_env_key(self) -> str:
|
|
109
|
+
"""Get the model's api key environment key to check.
|
|
110
|
+
|
|
111
|
+
- openai: 'OPENAI_API_KEY',
|
|
112
|
+
- azure: 'AZURE_API_KEY',
|
|
113
|
+
- google: 'GOOGLE_GEMINI_API_KEY',
|
|
114
|
+
- anthropic: 'ANTHROPIC_API_KEY',
|
|
115
|
+
- mistral: 'MISTRAL_API_KEY',
|
|
116
|
+
- groq: 'GROQ_API_KEY',
|
|
117
|
+
- together: 'TOGETHER_API_KEY',
|
|
118
|
+
- nim: 'NIM_API_KEY',
|
|
119
|
+
- other: 'OPENAI_API_KEY'
|
|
120
|
+
"""
|
|
121
|
+
env_key = "OPENAI_API_KEY"
|
|
122
|
+
if self.data.api_type == "google":
|
|
123
|
+
env_key = "GOOGLE_GEMINI_API_KEY"
|
|
124
|
+
elif self.data.api_type not in ["openai", "other"]:
|
|
125
|
+
env_key = f"{self.data.api_type.upper()}_API_KEY"
|
|
126
|
+
return env_key
|
|
127
|
+
|
|
105
128
|
@property
|
|
106
129
|
def api_key(self) -> str:
|
|
107
130
|
"""Get the model's api key.
|
|
@@ -120,11 +143,7 @@ class WaldiezModel(WaldiezBase):
|
|
|
120
143
|
"""
|
|
121
144
|
if self.data.api_key:
|
|
122
145
|
return self.data.api_key
|
|
123
|
-
env_key =
|
|
124
|
-
if self.data.api_type == "google":
|
|
125
|
-
env_key = "GOOGLE_GEMINI_API_KEY"
|
|
126
|
-
elif self.data.api_type not in ["openai", "other"]:
|
|
127
|
-
env_key = f"{self.data.api_type.upper()}_API_KEY"
|
|
146
|
+
env_key = self.api_key_env_key
|
|
128
147
|
api_key = os.environ.get(env_key, "")
|
|
129
148
|
return api_key
|
|
130
149
|
|
waldiez/models/skill/__init__.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
1
3
|
"""Waldiez Skill related models."""
|
|
2
4
|
|
|
3
|
-
from .skill import WaldiezSkill
|
|
5
|
+
from .skill import SHARED_SKILL_NAME, WaldiezSkill
|
|
4
6
|
from .skill_data import WaldiezSkillData
|
|
5
7
|
|
|
6
8
|
__all__ = [
|
|
9
|
+
"SHARED_SKILL_NAME",
|
|
7
10
|
"WaldiezSkill",
|
|
8
11
|
"WaldiezSkillData",
|
|
9
12
|
]
|
waldiez/models/skill/skill.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
1
3
|
"""Waldiez Skill model."""
|
|
2
4
|
|
|
3
5
|
from typing import Dict, List
|
|
@@ -5,9 +7,11 @@ from typing import Dict, List
|
|
|
5
7
|
from pydantic import Field, model_validator
|
|
6
8
|
from typing_extensions import Annotated, Literal, Self
|
|
7
9
|
|
|
8
|
-
from ..common import WaldiezBase, now, parse_code_string
|
|
10
|
+
from ..common import WaldiezBase, get_function, now, parse_code_string
|
|
9
11
|
from .skill_data import WaldiezSkillData
|
|
10
12
|
|
|
13
|
+
SHARED_SKILL_NAME = "waldiez_shared"
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
class WaldiezSkill(WaldiezBase):
|
|
13
17
|
"""Waldiez Skill.
|
|
@@ -93,6 +97,30 @@ class WaldiezSkill(WaldiezBase):
|
|
|
93
97
|
),
|
|
94
98
|
]
|
|
95
99
|
|
|
100
|
+
@property
|
|
101
|
+
def is_shared(self) -> bool:
|
|
102
|
+
"""Check if the skill is shared.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
bool
|
|
107
|
+
True if the skill is shared, False otherwise.
|
|
108
|
+
"""
|
|
109
|
+
return self.name == SHARED_SKILL_NAME
|
|
110
|
+
|
|
111
|
+
def get_content(self) -> str:
|
|
112
|
+
"""Get the content of the skill.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
str
|
|
117
|
+
The content of the skill.
|
|
118
|
+
"""
|
|
119
|
+
if self.name == SHARED_SKILL_NAME:
|
|
120
|
+
# the whole content (globals)
|
|
121
|
+
return self.data.content
|
|
122
|
+
return get_function(self.data.content, self.name)
|
|
123
|
+
|
|
96
124
|
@model_validator(mode="after")
|
|
97
125
|
def validate_data(self) -> Self:
|
|
98
126
|
"""Validate the data.
|
|
@@ -109,7 +137,7 @@ class WaldiezSkill(WaldiezBase):
|
|
|
109
137
|
If the skill content is invalid.
|
|
110
138
|
"""
|
|
111
139
|
search = f"def {self.name}("
|
|
112
|
-
if search not in self.data.content:
|
|
140
|
+
if self.name != SHARED_SKILL_NAME and search not in self.data.content:
|
|
113
141
|
raise ValueError(
|
|
114
142
|
f"The skill name '{self.name}' is not in the content."
|
|
115
143
|
)
|