waldiez 0.3.12__py3-none-any.whl → 0.4.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/_version.py +1 -1
- waldiez/cli.py +1 -3
- waldiez/exporting/agent/agent_exporter.py +5 -1
- waldiez/exporting/agent/utils/captain_agent.py +4 -8
- waldiez/exporting/agent/utils/swarm_agent.py +12 -7
- waldiez/exporting/base/utils/comments.py +1 -0
- waldiez/exporting/chats/utils/swarm.py +1 -1
- waldiez/exporting/flow/flow_exporter.py +2 -0
- waldiez/exporting/flow/utils/__init__.py +3 -6
- waldiez/exporting/flow/utils/flow_content.py +38 -0
- waldiez/exporting/flow/utils/importing_utils.py +64 -29
- waldiez/exporting/skills/skills_exporter.py +13 -6
- waldiez/exporting/skills/utils.py +92 -6
- waldiez/models/agents/agent/__init__.py +2 -1
- waldiez/models/agents/agent/agent.py +5 -8
- waldiez/models/agents/agent/agent_type.py +11 -0
- waldiez/models/agents/captain_agent/captain_agent.py +1 -1
- waldiez/models/agents/group_manager/speakers.py +3 -0
- waldiez/models/agents/rag_user/retrieve_config.py +3 -0
- waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +1 -0
- waldiez/models/agents/swarm_agent/after_work.py +13 -11
- waldiez/models/agents/swarm_agent/on_condition.py +3 -2
- waldiez/models/agents/swarm_agent/on_condition_available.py +1 -0
- waldiez/models/agents/swarm_agent/swarm_agent_data.py +3 -3
- waldiez/models/agents/swarm_agent/update_system_message.py +1 -0
- waldiez/models/chat/chat_message.py +1 -0
- waldiez/models/chat/chat_summary.py +1 -0
- waldiez/models/common/__init__.py +2 -0
- waldiez/models/common/method_utils.py +98 -0
- waldiez/models/model/extra_requirements.py +2 -0
- waldiez/models/model/model_data.py +1 -0
- waldiez/models/skill/__init__.py +4 -0
- waldiez/models/skill/extra_requirements.py +39 -0
- waldiez/models/skill/skill.py +157 -13
- waldiez/models/skill/skill_data.py +14 -0
- waldiez/models/skill/skill_type.py +8 -0
- waldiez/models/waldiez.py +36 -6
- waldiez/runner.py +19 -7
- waldiez/running/environment.py +30 -1
- waldiez/running/running.py +0 -6
- waldiez/utils/pysqlite3_checker.py +18 -5
- {waldiez-0.3.12.dist-info → waldiez-0.4.0.dist-info}/METADATA +24 -21
- {waldiez-0.3.12.dist-info → waldiez-0.4.0.dist-info}/RECORD +47 -44
- {waldiez-0.3.12.dist-info → waldiez-0.4.0.dist-info}/WHEEL +0 -0
- {waldiez-0.3.12.dist-info → waldiez-0.4.0.dist-info}/entry_points.txt +0 -0
- {waldiez-0.3.12.dist-info → waldiez-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.3.12.dist-info → waldiez-0.4.0.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -13,8 +13,11 @@ from ...common import WaldiezBase, check_function, generate_function
|
|
|
13
13
|
from .vector_db_config import WaldiezRagUserVectorDbConfig
|
|
14
14
|
|
|
15
15
|
WaldiezRagUserTask = Literal["code", "qa", "default"]
|
|
16
|
+
"""Possible tasks for the retrieve chat."""
|
|
16
17
|
WaldiezRagUserVectorDb = Literal["chroma", "pgvector", "mongodb", "qdrant"]
|
|
18
|
+
"""Possible vector dbs for the retrieve chat."""
|
|
17
19
|
WaldiezRagUserChunkMode = Literal["multi_lines", "one_line"]
|
|
20
|
+
"""Possible chunk modes for the retrieve chat."""
|
|
18
21
|
WaldiezRagUserModels: Dict[WaldiezRagUserVectorDb, str] = {
|
|
19
22
|
"chroma": "all-MiniLM-L6-v2",
|
|
20
23
|
"mongodb": "all-MiniLM-L6-v2",
|
|
@@ -17,16 +17,18 @@ from typing_extensions import Annotated, Literal, Self
|
|
|
17
17
|
from ...common import WaldiezBase, check_function, generate_function
|
|
18
18
|
|
|
19
19
|
WaldiezSwarmAfterWorkRecipientType = Literal["agent", "option", "callable"]
|
|
20
|
+
"""The possible AfterWork recipient types."""
|
|
20
21
|
WaldiezSwarmAfterWorkOption = Literal[
|
|
21
22
|
"TERMINATE", "REVERT_TO_USER", "STAY", "SWARM_MANAGER"
|
|
22
23
|
]
|
|
24
|
+
"""The possible AfterWork options."""
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
CUSTOM_AFTER_WORK = "custom_after_work"
|
|
26
28
|
CUSTOM_AFTER_WORK_ARGS = ["last_speaker", "messages", "groupchat"]
|
|
27
29
|
CUSTOM_AFTER_WORK_TYPES = (
|
|
28
|
-
["
|
|
29
|
-
"Union[AfterWorkOption,
|
|
30
|
+
["ConversableAgent", "List[Dict[str, Any]]", "GroupChat"],
|
|
31
|
+
"Union[AfterWorkOption, ConversableAgent, str]",
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
|
|
@@ -45,15 +47,15 @@ class WaldiezSwarmAfterWork(WaldiezBase):
|
|
|
45
47
|
recipient_type : WaldiezSwarmAfterWorkRecipientType
|
|
46
48
|
The type of recipient.
|
|
47
49
|
Can be 'agent', 'option', or 'callable'.
|
|
48
|
-
If 'agent', the recipient is a
|
|
50
|
+
If 'agent', the recipient is a Swarm Agent.
|
|
49
51
|
If 'option', the recipient is an AfterWorkOption :
|
|
50
52
|
('TERMINATE', 'REVERT_TO_USER', 'STAY', 'SWARM_MANAGER').
|
|
51
53
|
If 'callable', it should have the signature:
|
|
52
54
|
def custom_after_work(
|
|
53
|
-
last_speaker:
|
|
55
|
+
last_speaker: ConversableAgent,
|
|
54
56
|
messages: List[dict],
|
|
55
57
|
groupchat: GroupChat,
|
|
56
|
-
) -> Union[AfterWorkOption,
|
|
58
|
+
) -> Union[AfterWorkOption, ConversableAgent, str]:
|
|
57
59
|
|
|
58
60
|
"""
|
|
59
61
|
|
|
@@ -79,15 +81,15 @@ class WaldiezSwarmAfterWork(WaldiezBase):
|
|
|
79
81
|
description=(
|
|
80
82
|
"The type of recipient. "
|
|
81
83
|
"Can be 'agent', 'option', or 'callable'. "
|
|
82
|
-
"If 'agent', the recipient is a
|
|
84
|
+
"If 'agent', the recipient is a Swarm Agent. "
|
|
83
85
|
"If 'option', the recipient is an AfterWorkOption :"
|
|
84
86
|
" ('TERMINATE', 'REVERT_TO_USER', 'STAY', 'SWARM_MANAGER'). "
|
|
85
87
|
"If 'callable', it should have the signature: "
|
|
86
88
|
"def custom_after_work("
|
|
87
|
-
" last_speaker:
|
|
89
|
+
" last_speaker: ConversableAgent,"
|
|
88
90
|
" messages: List[Dict[str, Any]],"
|
|
89
91
|
" groupchat: GroupChat,"
|
|
90
|
-
") -> Union[AfterWorkOption,
|
|
92
|
+
") -> Union[AfterWorkOption, ConversableAgent, str]:"
|
|
91
93
|
),
|
|
92
94
|
),
|
|
93
95
|
]
|
|
@@ -117,13 +119,13 @@ class WaldiezSwarmAfterWork(WaldiezBase):
|
|
|
117
119
|
The recipient string and the function content if applicable.
|
|
118
120
|
"""
|
|
119
121
|
if self.recipient_type == "option":
|
|
120
|
-
return f"
|
|
122
|
+
return f"AfterWork(AfterWorkOption.{self.recipient})", ""
|
|
121
123
|
if self.recipient_type == "agent":
|
|
122
124
|
# the the recipient is passed as the agent name
|
|
123
125
|
# (and not its id), care should be taken to ensure
|
|
124
126
|
# the all the agents in the flow have unique names
|
|
125
127
|
agent_instance = agent_names.get(self.recipient, self.recipient)
|
|
126
|
-
return f"
|
|
128
|
+
return f"AfterWork({agent_instance})", ""
|
|
127
129
|
|
|
128
130
|
function_name = CUSTOM_AFTER_WORK
|
|
129
131
|
if name_prefix:
|
|
@@ -131,7 +133,7 @@ class WaldiezSwarmAfterWork(WaldiezBase):
|
|
|
131
133
|
if name_suffix:
|
|
132
134
|
function_name = f"{function_name}_{name_suffix}"
|
|
133
135
|
return (
|
|
134
|
-
f"
|
|
136
|
+
f"AfterWork({function_name})",
|
|
135
137
|
generate_function(
|
|
136
138
|
function_name=function_name,
|
|
137
139
|
function_args=CUSTOM_AFTER_WORK_ARGS,
|
|
@@ -12,6 +12,7 @@ from .on_condition_available import WaldiezSwarmOnConditionAvailable
|
|
|
12
12
|
from .on_condition_target import WaldiezSwarmOnConditionTarget
|
|
13
13
|
|
|
14
14
|
WaldiezSwarmOnConditionTargetType = Literal["agent", "nested_chat"]
|
|
15
|
+
"""Possible types for the target of the OnCondition."""
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class WaldiezSwarmOnCondition(WaldiezBase):
|
|
@@ -30,7 +31,7 @@ class WaldiezSwarmOnCondition(WaldiezBase):
|
|
|
30
31
|
The condition for transitioning to the target agent
|
|
31
32
|
|
|
32
33
|
available: str, optional
|
|
33
|
-
Optional condition to determine if this
|
|
34
|
+
Optional condition to determine if this OnCondition is available.
|
|
34
35
|
Can be a Callable or a string. If a string, it will look up the
|
|
35
36
|
value of the context variable with that name, which should be a bool.
|
|
36
37
|
|
|
@@ -73,7 +74,7 @@ class WaldiezSwarmOnCondition(WaldiezBase):
|
|
|
73
74
|
default_factory=WaldiezSwarmOnConditionAvailable,
|
|
74
75
|
title="Available",
|
|
75
76
|
description=(
|
|
76
|
-
"Optional condition to determine if this
|
|
77
|
+
"Optional condition to determine if this OnCondition "
|
|
77
78
|
"is available."
|
|
78
79
|
),
|
|
79
80
|
),
|
|
@@ -12,6 +12,7 @@ from ...common import WaldiezBase, check_function, generate_function
|
|
|
12
12
|
WaldiezSwarmOnConditionAvailableCheckType = Literal[
|
|
13
13
|
"string", "callable", "none"
|
|
14
14
|
]
|
|
15
|
+
"""Possible types for the `available` check."""
|
|
15
16
|
|
|
16
17
|
CUSTOM_ON_CONDITION_AVAILABLE = "custom_on_condition_available"
|
|
17
18
|
CUSTOM_ON_CONDITION_AVAILABLE_ARGS = ["agent", "message"]
|
|
@@ -29,7 +29,7 @@ class WaldiezSwarmAgentData(WaldiezAgentData):
|
|
|
29
29
|
A list of functions (skill ids) to register with the agent.
|
|
30
30
|
|
|
31
31
|
update_agent_state_before_reply : List[str]
|
|
32
|
-
A list of functions, including `
|
|
32
|
+
A list of functions, including `UpdateSystemMessage`,
|
|
33
33
|
called to update the agent's state before it replies. Each function
|
|
34
34
|
is called when the agent is selected and before it speaks.
|
|
35
35
|
|
|
@@ -67,10 +67,10 @@ class WaldiezSwarmAgentData(WaldiezAgentData):
|
|
|
67
67
|
title="Update Agent State Before Reply",
|
|
68
68
|
alias="updateAgentStateBeforeReply",
|
|
69
69
|
description=(
|
|
70
|
-
"A list of functions, including
|
|
70
|
+
"A list of functions, including UpdateSystemMessage,"
|
|
71
71
|
"called to update the agent's state before it replies. "
|
|
72
72
|
" Each function is called when the agent is selected "
|
|
73
|
-
"and before it speaks. If not an
|
|
73
|
+
"and before it speaks. If not an UpdateSystemMessage, "
|
|
74
74
|
"it should be a skill id."
|
|
75
75
|
),
|
|
76
76
|
default_factory=list,
|
|
@@ -10,6 +10,7 @@ from typing_extensions import Annotated, Literal, Self
|
|
|
10
10
|
from ...common import WaldiezBase, check_function, generate_function
|
|
11
11
|
|
|
12
12
|
WaldiezSwarmUpdateFunctionType = Literal["string", "callable"]
|
|
13
|
+
"""Possible types for the update function."""
|
|
13
14
|
|
|
14
15
|
CUSTOM_UPDATE_SYSTEM_MESSAGE = "custom_update_system_message"
|
|
15
16
|
CUSTOM_UPDATE_SYSTEM_MESSAGE_ARGS = ["agent", "messages"]
|
|
@@ -12,6 +12,7 @@ from ..common import WaldiezBase, check_function, update_dict
|
|
|
12
12
|
WaldiezChatMessageType = Literal[
|
|
13
13
|
"string", "method", "rag_message_generator", "none"
|
|
14
14
|
]
|
|
15
|
+
"""Possible types for the message."""
|
|
15
16
|
|
|
16
17
|
CALLABLE_MESSAGE = "callable_message"
|
|
17
18
|
CALLABLE_MESSAGE_ARGS = ["sender", "recipient", "context"]
|
|
@@ -8,6 +8,7 @@ from .date_utils import now
|
|
|
8
8
|
from .dict_utils import update_dict
|
|
9
9
|
from .method_utils import (
|
|
10
10
|
check_function,
|
|
11
|
+
gather_code_imports,
|
|
11
12
|
generate_function,
|
|
12
13
|
get_function,
|
|
13
14
|
parse_code_string,
|
|
@@ -17,6 +18,7 @@ __all__ = [
|
|
|
17
18
|
"WaldiezBase",
|
|
18
19
|
"now",
|
|
19
20
|
"check_function",
|
|
21
|
+
"gather_code_imports",
|
|
20
22
|
"get_autogen_version",
|
|
21
23
|
"get_function",
|
|
22
24
|
"generate_function",
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
"""Function related utilities."""
|
|
4
4
|
|
|
5
5
|
import ast
|
|
6
|
+
import importlib.util
|
|
7
|
+
import sys
|
|
8
|
+
import sysconfig
|
|
9
|
+
from pathlib import Path
|
|
6
10
|
from typing import List, Optional, Tuple
|
|
7
11
|
|
|
8
12
|
import parso
|
|
@@ -12,6 +16,35 @@ import parso.tree
|
|
|
12
16
|
MAX_VAR_NAME_LENGTH = 64
|
|
13
17
|
|
|
14
18
|
|
|
19
|
+
def is_standard_library(module_name: str) -> bool:
|
|
20
|
+
"""Check if the module is part of the standard library.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
module_name : str
|
|
25
|
+
The module name.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
bool
|
|
30
|
+
True if the module is part of the standard library.
|
|
31
|
+
"""
|
|
32
|
+
if module_name in sys.builtin_module_names:
|
|
33
|
+
return True
|
|
34
|
+
try:
|
|
35
|
+
spec = importlib.util.find_spec(module_name)
|
|
36
|
+
except BaseException: # pylint: disable=broad-except
|
|
37
|
+
return False
|
|
38
|
+
if spec is None or not spec.origin:
|
|
39
|
+
return False
|
|
40
|
+
if "site-packages" in spec.origin:
|
|
41
|
+
return False
|
|
42
|
+
if spec.origin.startswith(sys.prefix) or spec.origin == "frozen":
|
|
43
|
+
return True
|
|
44
|
+
stdlib_path = str(Path(sysconfig.get_path("stdlib")).resolve())
|
|
45
|
+
return spec.origin.startswith(stdlib_path)
|
|
46
|
+
|
|
47
|
+
|
|
15
48
|
def parse_code_string(
|
|
16
49
|
code_string: str,
|
|
17
50
|
) -> Tuple[Optional[str], Optional[ast.Module]]:
|
|
@@ -38,6 +71,71 @@ def parse_code_string(
|
|
|
38
71
|
return None, tree
|
|
39
72
|
|
|
40
73
|
|
|
74
|
+
def gather_code_imports(
|
|
75
|
+
code_string: str,
|
|
76
|
+
is_interop: bool,
|
|
77
|
+
) -> Tuple[List[str], List[str]]:
|
|
78
|
+
"""Gather the imports from the code string.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
code_string : str
|
|
83
|
+
The code string.
|
|
84
|
+
is_interop : bool
|
|
85
|
+
If True, make sure the interoperability import is present.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
Tuple[List[str], List[str]]
|
|
90
|
+
The standard library imports and the third party imports.
|
|
91
|
+
"""
|
|
92
|
+
standard_lib_imports: List[str] = []
|
|
93
|
+
third_party_imports: List[str] = []
|
|
94
|
+
tree = parso.parse(code_string) # type: ignore
|
|
95
|
+
for node in tree.iter_imports():
|
|
96
|
+
if node.type == "import_name":
|
|
97
|
+
full_import_statement = node.get_code().strip()
|
|
98
|
+
module_name = (
|
|
99
|
+
node.get_code().replace("import", "").strip().split(" ")[0]
|
|
100
|
+
)
|
|
101
|
+
if not module_name:
|
|
102
|
+
continue
|
|
103
|
+
if is_standard_library(module_name):
|
|
104
|
+
standard_lib_imports.append(full_import_statement)
|
|
105
|
+
else:
|
|
106
|
+
third_party_imports.append(full_import_statement)
|
|
107
|
+
elif node.type == "import_from":
|
|
108
|
+
full_import_statement = node.get_code().strip()
|
|
109
|
+
module_name = (
|
|
110
|
+
node.get_code().replace("from", "").strip().split(" ")[0]
|
|
111
|
+
)
|
|
112
|
+
if not module_name:
|
|
113
|
+
continue
|
|
114
|
+
if is_standard_library(module_name):
|
|
115
|
+
standard_lib_imports.append(full_import_statement)
|
|
116
|
+
else:
|
|
117
|
+
third_party_imports.append(full_import_statement)
|
|
118
|
+
if is_interop and (
|
|
119
|
+
"from autogen.interop import Interoperability"
|
|
120
|
+
not in third_party_imports
|
|
121
|
+
):
|
|
122
|
+
third_party_imports.append(
|
|
123
|
+
"from autogen.interop import Interoperability"
|
|
124
|
+
)
|
|
125
|
+
# sorted_standard_lib_imports = # first import x, then from a import b
|
|
126
|
+
sorted_standard_lib_imports = sorted(
|
|
127
|
+
[stmt for stmt in standard_lib_imports if stmt.startswith("import ")]
|
|
128
|
+
) + sorted(
|
|
129
|
+
[stmt for stmt in standard_lib_imports if stmt.startswith("from ")]
|
|
130
|
+
)
|
|
131
|
+
sorted_third_party_imports = sorted(
|
|
132
|
+
[stmt for stmt in third_party_imports if stmt.startswith("import ")]
|
|
133
|
+
) + sorted(
|
|
134
|
+
[stmt for stmt in third_party_imports if stmt.startswith("from ")]
|
|
135
|
+
)
|
|
136
|
+
return sorted_standard_lib_imports, sorted_third_party_imports
|
|
137
|
+
|
|
138
|
+
|
|
41
139
|
def check_function(
|
|
42
140
|
code_string: str,
|
|
43
141
|
function_name: str,
|
|
@@ -36,6 +36,8 @@ def get_models_extra_requirements(
|
|
|
36
36
|
"bedrock", # we might add this later
|
|
37
37
|
]
|
|
38
38
|
for model in models:
|
|
39
|
+
for requirement in model.requirements:
|
|
40
|
+
model_requirements.add(requirement)
|
|
39
41
|
if model.data.api_type == "google":
|
|
40
42
|
model_requirements.add(f"pyautogen[gemini]=={autogen_version}")
|
|
41
43
|
continue
|
waldiez/models/skill/__init__.py
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
3
|
"""Waldiez Skill related models."""
|
|
4
4
|
|
|
5
|
+
from .extra_requirements import get_skills_extra_requirements
|
|
5
6
|
from .skill import SHARED_SKILL_NAME, WaldiezSkill
|
|
6
7
|
from .skill_data import WaldiezSkillData
|
|
8
|
+
from .skill_type import WaldiezSkillType
|
|
7
9
|
|
|
8
10
|
__all__ = [
|
|
9
11
|
"SHARED_SKILL_NAME",
|
|
10
12
|
"WaldiezSkill",
|
|
11
13
|
"WaldiezSkillData",
|
|
14
|
+
"WaldiezSkillType",
|
|
15
|
+
"get_skills_extra_requirements",
|
|
12
16
|
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Waldiez skill extra requirements."""
|
|
4
|
+
|
|
5
|
+
from typing import Iterator, Set
|
|
6
|
+
|
|
7
|
+
from .skill import WaldiezSkill
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_skills_extra_requirements(
|
|
11
|
+
skills: Iterator[WaldiezSkill],
|
|
12
|
+
autogen_version: str,
|
|
13
|
+
) -> Set[str]:
|
|
14
|
+
"""Get the skills extra requirements.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
skills : List[WaldiezSkill]
|
|
19
|
+
The skills.
|
|
20
|
+
autogen_version : str
|
|
21
|
+
The ag2 version.
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
List[str]
|
|
25
|
+
The skills extra requirements.
|
|
26
|
+
"""
|
|
27
|
+
skill_requirements: Set[str] = set()
|
|
28
|
+
for skill in skills:
|
|
29
|
+
if skill.skill_type == "langchain":
|
|
30
|
+
skill_requirements.add(
|
|
31
|
+
f"pyautogen[interop-langchain]=={autogen_version}"
|
|
32
|
+
)
|
|
33
|
+
if skill.skill_type == "crewai":
|
|
34
|
+
skill_requirements.add(
|
|
35
|
+
f"pyautogen[interop-crewai]=={autogen_version}"
|
|
36
|
+
)
|
|
37
|
+
for requirement in skill.requirements:
|
|
38
|
+
skill_requirements.add(requirement)
|
|
39
|
+
return skill_requirements
|
waldiez/models/skill/skill.py
CHANGED
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
3
|
"""Waldiez Skill model."""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Tuple, Union
|
|
6
9
|
|
|
7
10
|
from pydantic import Field, model_validator
|
|
8
11
|
from typing_extensions import Annotated, Literal, Self
|
|
9
12
|
|
|
10
|
-
from ..common import
|
|
13
|
+
from ..common import (
|
|
14
|
+
WaldiezBase,
|
|
15
|
+
gather_code_imports,
|
|
16
|
+
get_function,
|
|
17
|
+
now,
|
|
18
|
+
parse_code_string,
|
|
19
|
+
)
|
|
11
20
|
from .skill_data import WaldiezSkillData
|
|
21
|
+
from .skill_type import WaldiezSkillType
|
|
12
22
|
|
|
13
23
|
SHARED_SKILL_NAME = "waldiez_shared"
|
|
14
24
|
|
|
@@ -97,6 +107,66 @@ class WaldiezSkill(WaldiezBase):
|
|
|
97
107
|
),
|
|
98
108
|
]
|
|
99
109
|
|
|
110
|
+
@staticmethod
|
|
111
|
+
def load(data_or_path: Union[str, Path, Dict[str, Any]]) -> "WaldiezSkill":
|
|
112
|
+
"""Load a skill from a read-only file.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
data_or_path : Union[str, Path, Dict[str, Any]]
|
|
117
|
+
The path to the read-only file or the loaded data.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
WaldiezSkill
|
|
122
|
+
The skill.
|
|
123
|
+
|
|
124
|
+
Raises
|
|
125
|
+
------
|
|
126
|
+
FileNotFoundError
|
|
127
|
+
If the file is not found.
|
|
128
|
+
ValueError
|
|
129
|
+
If the JSON is invalid or the data is invalid.
|
|
130
|
+
"""
|
|
131
|
+
if isinstance(data_or_path, dict):
|
|
132
|
+
return WaldiezSkill.model_validate(data_or_path)
|
|
133
|
+
if not isinstance(data_or_path, Path):
|
|
134
|
+
data_or_path = Path(data_or_path)
|
|
135
|
+
resolved = data_or_path.resolve()
|
|
136
|
+
if not resolved.is_file():
|
|
137
|
+
raise FileNotFoundError(f"File not found: {resolved}")
|
|
138
|
+
with resolved.open("r", encoding="utf-8") as file:
|
|
139
|
+
data_string = file.read()
|
|
140
|
+
try:
|
|
141
|
+
data_dict = json.loads(data_string)
|
|
142
|
+
except BaseException as exc: # pylint: disable=broad-except
|
|
143
|
+
raise ValueError(f"Invalid WaldiezSkill/JSON: {exc}") from exc
|
|
144
|
+
return WaldiezSkill.model_validate(data_dict)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def skill_type(self) -> WaldiezSkillType:
|
|
148
|
+
"""Get the skill type.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
WaldiezSkillType
|
|
153
|
+
The type of the skill:
|
|
154
|
+
[shared, custom, langchain, crewai].
|
|
155
|
+
"""
|
|
156
|
+
return self.data.skill_type
|
|
157
|
+
|
|
158
|
+
_skill_imports: Tuple[List[str], List[str]] = ([], [])
|
|
159
|
+
|
|
160
|
+
def get_imports(self) -> Tuple[List[str], List[str]]:
|
|
161
|
+
"""Get the skill imports.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
Tuple[List[str], List[str]]
|
|
166
|
+
The builtin and external imports.
|
|
167
|
+
"""
|
|
168
|
+
return self._skill_imports
|
|
169
|
+
|
|
100
170
|
@property
|
|
101
171
|
def is_shared(self) -> bool:
|
|
102
172
|
"""Check if the skill is shared.
|
|
@@ -106,7 +176,18 @@ class WaldiezSkill(WaldiezBase):
|
|
|
106
176
|
bool
|
|
107
177
|
True if the skill is shared, False otherwise.
|
|
108
178
|
"""
|
|
109
|
-
return self.name == SHARED_SKILL_NAME
|
|
179
|
+
return self.skill_type == "shared" or self.name == SHARED_SKILL_NAME
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def is_interop(self) -> bool:
|
|
183
|
+
"""Check if the skill is interoperability.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
bool
|
|
188
|
+
True if the skill is interoperability, False otherwise.
|
|
189
|
+
"""
|
|
190
|
+
return self.skill_type in ("langchain", "crewai")
|
|
110
191
|
|
|
111
192
|
def get_content(self) -> str:
|
|
112
193
|
"""Get the content of the skill.
|
|
@@ -116,11 +197,61 @@ class WaldiezSkill(WaldiezBase):
|
|
|
116
197
|
str
|
|
117
198
|
The content of the skill.
|
|
118
199
|
"""
|
|
119
|
-
if self.
|
|
120
|
-
# the whole content (globals)
|
|
200
|
+
if self.is_shared or self.is_interop:
|
|
121
201
|
return self.data.content
|
|
202
|
+
# if custom, only the function content
|
|
122
203
|
return get_function(self.data.content, self.name)
|
|
123
204
|
|
|
205
|
+
def _validate_interop_skill(self) -> None:
|
|
206
|
+
"""Validate the interoperability skill.
|
|
207
|
+
|
|
208
|
+
Raises
|
|
209
|
+
------
|
|
210
|
+
ValueError
|
|
211
|
+
If the skill name is not in the content.
|
|
212
|
+
"""
|
|
213
|
+
if self.is_interop:
|
|
214
|
+
# we expect sth like:
|
|
215
|
+
# with single or double quotes for type={skill_type}
|
|
216
|
+
# {skill_name} = *.convert_tool(..., type="{skill_type}", ...)
|
|
217
|
+
if f"{self.name} = " not in self.data.content:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"The skill name '{self.name}' is not in the content."
|
|
220
|
+
)
|
|
221
|
+
# we don't want the conversion to ag2 tool (we do it internally)
|
|
222
|
+
# or the skill registration (we do it after having the agent names)
|
|
223
|
+
# so no" .convert_tool(... type="...")
|
|
224
|
+
# or .register_for_llm(...), .register_for_execution(...)
|
|
225
|
+
to_exclude = [
|
|
226
|
+
r".convert_tool\(.+?type=",
|
|
227
|
+
rf"{self.name}.register_for_llm\(",
|
|
228
|
+
rf"{self.name}.register_for_execution\(",
|
|
229
|
+
]
|
|
230
|
+
for exclude in to_exclude:
|
|
231
|
+
if re.search(exclude, self.data.content):
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"Invalid skill content: '{exclude}' is not allowed."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _validate_custom_skill(self) -> None:
|
|
237
|
+
"""Validate a custom skill.
|
|
238
|
+
|
|
239
|
+
Raises
|
|
240
|
+
------
|
|
241
|
+
ValueError
|
|
242
|
+
If the skill name is not in the content.
|
|
243
|
+
If the skill content is invalid.
|
|
244
|
+
"""
|
|
245
|
+
search = f"def {self.name}("
|
|
246
|
+
if self.skill_type == "custom" and not self.is_shared:
|
|
247
|
+
if search not in self.data.content:
|
|
248
|
+
raise ValueError(
|
|
249
|
+
f"The skill name '{self.name}' is not in the content."
|
|
250
|
+
)
|
|
251
|
+
error, tree = parse_code_string(self.data.content)
|
|
252
|
+
if error is not None or tree is None:
|
|
253
|
+
raise ValueError(f"Invalid skill content: {error}")
|
|
254
|
+
|
|
124
255
|
@model_validator(mode="after")
|
|
125
256
|
def validate_data(self) -> Self:
|
|
126
257
|
"""Validate the data.
|
|
@@ -136,14 +267,27 @@ class WaldiezSkill(WaldiezBase):
|
|
|
136
267
|
If the skill name is not in the content.
|
|
137
268
|
If the skill content is invalid.
|
|
138
269
|
"""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
270
|
+
self._validate_custom_skill()
|
|
271
|
+
self._validate_interop_skill()
|
|
272
|
+
self._skill_imports = gather_code_imports(
|
|
273
|
+
self.data.content, self.is_interop
|
|
274
|
+
)
|
|
275
|
+
# remove the imports from the content
|
|
276
|
+
# we 'll place them at the top of the file
|
|
277
|
+
all_imports = self._skill_imports[0] + self._skill_imports[1]
|
|
278
|
+
code_lines = self.data.content.splitlines()
|
|
279
|
+
valid_lines = [
|
|
280
|
+
line
|
|
281
|
+
for line in code_lines
|
|
282
|
+
if not any(line.startswith(imp) for imp in all_imports)
|
|
283
|
+
]
|
|
284
|
+
# remove empty lines at the beginning and end
|
|
285
|
+
# of the content
|
|
286
|
+
while valid_lines and not valid_lines[0].strip():
|
|
287
|
+
valid_lines.pop(0)
|
|
288
|
+
while valid_lines and not valid_lines[-1].strip():
|
|
289
|
+
valid_lines.pop()
|
|
290
|
+
self.data.content = "\n".join(valid_lines)
|
|
147
291
|
return self
|
|
148
292
|
|
|
149
293
|
@property
|
|
@@ -8,6 +8,7 @@ from pydantic import Field
|
|
|
8
8
|
from typing_extensions import Annotated
|
|
9
9
|
|
|
10
10
|
from ..common import WaldiezBase
|
|
11
|
+
from .skill_type import WaldiezSkillType
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class WaldiezSkillData(WaldiezBase):
|
|
@@ -15,12 +16,25 @@ class WaldiezSkillData(WaldiezBase):
|
|
|
15
16
|
|
|
16
17
|
Attributes
|
|
17
18
|
----------
|
|
19
|
+
skill_type : WaldiezSkillType
|
|
20
|
+
The type of the skill: shared, custom, langchain, crewai.
|
|
18
21
|
content : str
|
|
19
22
|
The content (source code) of the skill.
|
|
20
23
|
secrets : Dict[str, str]
|
|
21
24
|
The secrets (environment variables) of the skill.
|
|
22
25
|
"""
|
|
23
26
|
|
|
27
|
+
skill_type: Annotated[
|
|
28
|
+
WaldiezSkillType,
|
|
29
|
+
Field(
|
|
30
|
+
"custom",
|
|
31
|
+
alias="skillType",
|
|
32
|
+
title="Skill Type",
|
|
33
|
+
description=(
|
|
34
|
+
"The type of the skill: shared, custom, langchain, crewai."
|
|
35
|
+
),
|
|
36
|
+
),
|
|
37
|
+
] = "custom"
|
|
24
38
|
content: Annotated[
|
|
25
39
|
str,
|
|
26
40
|
Field(
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Waldiez Skill types."""
|
|
4
|
+
|
|
5
|
+
from typing_extensions import Literal
|
|
6
|
+
|
|
7
|
+
WaldiezSkillType = Literal["shared", "custom", "langchain", "crewai"]
|
|
8
|
+
"""Possible types of a Waldiez Skill."""
|