swarmauri_agent_skill 0.11.0.dev1__tar.gz → 0.11.0.dev5__tar.gz
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.
- {swarmauri_agent_skill-0.11.0.dev1 → swarmauri_agent_skill-0.11.0.dev5}/PKG-INFO +5 -5
- {swarmauri_agent_skill-0.11.0.dev1 → swarmauri_agent_skill-0.11.0.dev5}/pyproject.toml +5 -5
- {swarmauri_agent_skill-0.11.0.dev1 → swarmauri_agent_skill-0.11.0.dev5}/swarmauri_agent_skill/SkillAgent.py +108 -42
- {swarmauri_agent_skill-0.11.0.dev1 → swarmauri_agent_skill-0.11.0.dev5}/LICENSE +0 -0
- {swarmauri_agent_skill-0.11.0.dev1 → swarmauri_agent_skill-0.11.0.dev5}/README.md +0 -0
- {swarmauri_agent_skill-0.11.0.dev1 → swarmauri_agent_skill-0.11.0.dev5}/swarmauri_agent_skill/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarmauri_agent_skill
|
|
3
|
-
Version: 0.11.0.
|
|
3
|
+
Version: 0.11.0.dev5
|
|
4
4
|
Summary: Skill-aware agent wrapper for Swarmauri.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -16,10 +16,10 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Requires-Dist: swarmauri_base
|
|
20
|
-
Requires-Dist: swarmauri_core
|
|
21
|
-
Requires-Dist: swarmauri_standard
|
|
22
|
-
Requires-Dist: swarmauri_tool_skill_execution
|
|
19
|
+
Requires-Dist: swarmauri_base (>=0.11.0.dev6,<0.12)
|
|
20
|
+
Requires-Dist: swarmauri_core (>=0.11.0.dev6,<0.12)
|
|
21
|
+
Requires-Dist: swarmauri_standard (>=0.11.0.dev8,<0.12)
|
|
22
|
+
Requires-Dist: swarmauri_tool_skill_execution (>=0.11.0.dev6,<0.12)
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
25
25
|
# Swarmauri Skill Agent
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "swarmauri_agent_skill"
|
|
3
|
-
version = "0.11.0.
|
|
3
|
+
version = "0.11.0.dev5"
|
|
4
4
|
description = "Skill-aware agent wrapper for Swarmauri."
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
readme = "README.md"
|
|
@@ -18,10 +18,10 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
20
20
|
dependencies = [
|
|
21
|
-
"swarmauri_core",
|
|
22
|
-
"swarmauri_base",
|
|
23
|
-
"swarmauri_standard",
|
|
24
|
-
"swarmauri_tool_skill_execution",
|
|
21
|
+
"swarmauri_core>=0.11.0.dev6,<0.12",
|
|
22
|
+
"swarmauri_base>=0.11.0.dev6,<0.12",
|
|
23
|
+
"swarmauri_standard>=0.11.0.dev8,<0.12",
|
|
24
|
+
"swarmauri_tool_skill_execution>=0.11.0.dev6,<0.12",
|
|
25
25
|
]
|
|
26
26
|
keywords = ["swarmauri", "sdk", "standards", "skills", "agents"]
|
|
27
27
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
|
|
4
|
+
|
|
3
5
|
import asyncio
|
|
4
6
|
import inspect
|
|
5
|
-
from pathlib import Path
|
|
6
7
|
from typing import (
|
|
7
8
|
Any,
|
|
8
9
|
Dict,
|
|
@@ -14,7 +15,7 @@ from typing import (
|
|
|
14
15
|
Union,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
|
-
from pydantic import ConfigDict, Field
|
|
18
|
+
from pydantic import ConfigDict, Field, PrivateAttr
|
|
18
19
|
|
|
19
20
|
from swarmauri_base.ComponentBase import ComponentBase, SubclassUnion
|
|
20
21
|
from swarmauri_base.agents.AgentBase import AgentBase
|
|
@@ -25,8 +26,10 @@ from swarmauri_base.agents.AgentSystemContextMixin import (
|
|
|
25
26
|
from swarmauri_base.conversations.ConversationBase import ConversationBase
|
|
26
27
|
from swarmauri_base.llms.LLMBase import LLMBase
|
|
27
28
|
from swarmauri_base.skills.SkillBase import SkillBase
|
|
29
|
+
from swarmauri_base.skills.SkillMetadata import SkillMetadata
|
|
28
30
|
from swarmauri_base.tools.ToolBase import ToolBase
|
|
29
31
|
from swarmauri_core.messages.IMessage import IMessage
|
|
32
|
+
from swarmauri_core.skills.ISkillLoader import ISkillLoader
|
|
30
33
|
from swarmauri_standard.conversations.MaxSystemContextConversation import (
|
|
31
34
|
MaxSystemContextConversation,
|
|
32
35
|
)
|
|
@@ -46,9 +49,13 @@ class SkillAgent(AgentSystemContextMixin, AgentConversationMixin, AgentBase):
|
|
|
46
49
|
)
|
|
47
50
|
system_context: SystemMessage | str = SystemMessage(content="")
|
|
48
51
|
skills: List[SubclassUnion[SkillBase]] = Field(default_factory=list)
|
|
52
|
+
skill_metadata: List[SkillMetadata] = Field(default_factory=list)
|
|
53
|
+
skill_loader: ISkillLoader | None = None
|
|
54
|
+
_skill_roots: List[str] = PrivateAttr(default_factory=list)
|
|
49
55
|
skill_execution_tool: SubclassUnion[ToolBase] = Field(
|
|
50
56
|
default_factory=SkillExecutionTool
|
|
51
57
|
)
|
|
58
|
+
skillset_toolkit: ToolkitBase | None = Field(default=None, exclude=True)
|
|
52
59
|
turn_mode: Literal["single", "multi"] = "multi"
|
|
53
60
|
execution_mode: Literal["sequential", "concurrent"] = "sequential"
|
|
54
61
|
require_skill: bool = False
|
|
@@ -57,6 +64,10 @@ class SkillAgent(AgentSystemContextMixin, AgentConversationMixin, AgentBase):
|
|
|
57
64
|
|
|
58
65
|
def model_post_init(self, __context: Any) -> None:
|
|
59
66
|
self.skills = [self._hydrate_skill(item) for item in self.skills]
|
|
67
|
+
if not self.skill_metadata:
|
|
68
|
+
self.skill_metadata = [
|
|
69
|
+
self._metadata_for_skill(skill) for skill in self.skills
|
|
70
|
+
]
|
|
60
71
|
|
|
61
72
|
@staticmethod
|
|
62
73
|
def _hydrate_skill(value: Any) -> Any:
|
|
@@ -120,38 +131,98 @@ class SkillAgent(AgentSystemContextMixin, AgentConversationMixin, AgentBase):
|
|
|
120
131
|
)
|
|
121
132
|
return results
|
|
122
133
|
|
|
134
|
+
@classmethod
|
|
135
|
+
def from_skill_roots(
|
|
136
|
+
cls,
|
|
137
|
+
llm: LLMBase,
|
|
138
|
+
roots: Iterable[str],
|
|
139
|
+
loader_cls: Any,
|
|
140
|
+
**kwargs: Any,
|
|
141
|
+
) -> "SkillAgent":
|
|
142
|
+
"""Discover metadata now and activate only selected skills later."""
|
|
143
|
+
root_list = list(roots)
|
|
144
|
+
metadata = loader_cls.discover(root_list)
|
|
145
|
+
agent = cls(llm=llm, skill_metadata=metadata, **kwargs)
|
|
146
|
+
agent._skill_roots = [str(root) for root in root_list]
|
|
147
|
+
if isinstance(loader_cls, type) and issubclass(loader_cls, SkillBase):
|
|
148
|
+
# Shipped filesystem/local skill classes expose classmethod loader
|
|
149
|
+
# APIs but cannot be instantiated without skill fields.
|
|
150
|
+
agent.skill_loader = loader_cls
|
|
151
|
+
elif isinstance(loader_cls, type):
|
|
152
|
+
agent.skill_loader = loader_cls()
|
|
153
|
+
else:
|
|
154
|
+
agent.skill_loader = loader_cls
|
|
155
|
+
return agent
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _metadata_for_skill(skill: SkillBase) -> SkillMetadata:
|
|
159
|
+
return SkillMetadata(
|
|
160
|
+
name=skill.name,
|
|
161
|
+
description=skill.description,
|
|
162
|
+
license=skill.license,
|
|
163
|
+
compatibility=skill.compatibility,
|
|
164
|
+
metadata=skill.metadata,
|
|
165
|
+
source="loaded",
|
|
166
|
+
location=str(getattr(skill, "root_path", "")),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _activate_metadata(
|
|
170
|
+
self, selected_metadata: Sequence[SkillMetadata]
|
|
171
|
+
) -> List[SkillBase]:
|
|
172
|
+
if not self.skill_loader:
|
|
173
|
+
return [
|
|
174
|
+
skill
|
|
175
|
+
for skill in self.skills
|
|
176
|
+
if skill.name in {item.name for item in selected_metadata}
|
|
177
|
+
]
|
|
178
|
+
loaded = {skill.name: skill for skill in self.skills}
|
|
179
|
+
for metadata in selected_metadata:
|
|
180
|
+
if metadata.name in loaded:
|
|
181
|
+
continue
|
|
182
|
+
if hasattr(self.skill_loader, "from_name"):
|
|
183
|
+
skill = self.skill_loader.from_name(
|
|
184
|
+
metadata.name, roots=self._skill_roots
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
skill = self.skill_loader.load(
|
|
188
|
+
metadata.location, skill_name=metadata.name
|
|
189
|
+
)
|
|
190
|
+
loaded[metadata.name] = skill
|
|
191
|
+
self.skills.append(skill)
|
|
192
|
+
return [loaded[item.name] for item in selected_metadata]
|
|
193
|
+
|
|
123
194
|
def select_skills(
|
|
124
195
|
self,
|
|
125
196
|
input_data: Union[str, IMessage],
|
|
126
197
|
skill_names: Optional[Sequence[str]] = None,
|
|
127
198
|
) -> List[SkillBase]:
|
|
199
|
+
catalog = self.skill_metadata
|
|
128
200
|
if skill_names:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
for
|
|
132
|
-
if skill.name in set(skill_names)
|
|
201
|
+
requested = set(skill_names)
|
|
202
|
+
selected_metadata = [
|
|
203
|
+
item for item in catalog if item.name in requested
|
|
133
204
|
]
|
|
134
205
|
missing = sorted(
|
|
135
|
-
|
|
206
|
+
requested - {item.name for item in selected_metadata}
|
|
136
207
|
)
|
|
137
208
|
if missing:
|
|
138
209
|
raise ValueError(
|
|
139
210
|
f"Unknown skill name(s): {', '.join(missing)}"
|
|
140
211
|
)
|
|
141
|
-
return
|
|
212
|
+
return self._activate_metadata(selected_metadata)
|
|
142
213
|
|
|
143
|
-
if len(
|
|
144
|
-
return
|
|
214
|
+
if len(catalog) == 1:
|
|
215
|
+
return self._activate_metadata(catalog)
|
|
145
216
|
|
|
146
217
|
input_text = self._input_text(input_data).lower()
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
for
|
|
150
|
-
if self._skill_matches_input(
|
|
218
|
+
selected_metadata = [
|
|
219
|
+
item
|
|
220
|
+
for item in catalog
|
|
221
|
+
if self._skill_matches_input(item, input_text)
|
|
151
222
|
]
|
|
152
|
-
if not
|
|
223
|
+
if not selected_metadata and self.require_skill:
|
|
153
224
|
raise ValueError("No matching skill found for input")
|
|
154
|
-
return
|
|
225
|
+
return self._activate_metadata(selected_metadata)
|
|
155
226
|
|
|
156
227
|
def _exec_one(
|
|
157
228
|
self,
|
|
@@ -248,9 +319,26 @@ class SkillAgent(AgentSystemContextMixin, AgentConversationMixin, AgentBase):
|
|
|
248
319
|
selected_skills = list(skills if skills is not None else self.skills)
|
|
249
320
|
self.skill_execution_tool.skills = selected_skills
|
|
250
321
|
toolkit = Toolkit()
|
|
322
|
+
if self.skillset_toolkit is not None:
|
|
323
|
+
toolkit.add_tools(self.skillset_toolkit.available_tools())
|
|
251
324
|
toolkit.add_tool(self.skill_execution_tool)
|
|
252
325
|
return toolkit
|
|
253
326
|
|
|
327
|
+
def load_skill_resource(
|
|
328
|
+
self, skill_name: str, relative_path: str
|
|
329
|
+
) -> bytes:
|
|
330
|
+
"""Load one selected skill resource without eager context injection."""
|
|
331
|
+
for skill in self.skills:
|
|
332
|
+
if skill.name == skill_name:
|
|
333
|
+
reader = getattr(skill, "read_resource", None)
|
|
334
|
+
if reader is None:
|
|
335
|
+
raise ValueError(
|
|
336
|
+
f"Skill '{skill_name}' does not support "
|
|
337
|
+
"filesystem resources"
|
|
338
|
+
)
|
|
339
|
+
return reader(relative_path)
|
|
340
|
+
raise ValueError(f"Unknown skill name: {skill_name}")
|
|
341
|
+
|
|
254
342
|
def _llm_kwargs(
|
|
255
343
|
self,
|
|
256
344
|
selected_skills: Iterable[SkillBase],
|
|
@@ -285,7 +373,8 @@ class SkillAgent(AgentSystemContextMixin, AgentConversationMixin, AgentBase):
|
|
|
285
373
|
(
|
|
286
374
|
"Available tool: SkillExecutionTool. Use this single "
|
|
287
375
|
"tool "
|
|
288
|
-
)
|
|
376
|
+
),
|
|
377
|
+
(
|
|
289
378
|
"to run skill-local argv command arrays when execution " # noqa: E501
|
|
290
379
|
"is "
|
|
291
380
|
"needed."
|
|
@@ -299,39 +388,16 @@ class SkillAgent(AgentSystemContextMixin, AgentConversationMixin, AgentBase):
|
|
|
299
388
|
def _build_resource_context(cls, skill: SkillBase) -> str:
|
|
300
389
|
lines = ["## Skill Resources"]
|
|
301
390
|
has_resources = False
|
|
302
|
-
for field_name in
|
|
303
|
-
values = list(getattr(skill, field_name))
|
|
391
|
+
for field_name in ("references", "scripts", "assets"):
|
|
392
|
+
values = list(getattr(skill, field_name, []))
|
|
304
393
|
if not values:
|
|
305
394
|
continue
|
|
306
395
|
has_resources = True
|
|
307
396
|
lines.append(f"- {field_name}: {', '.join(values)}")
|
|
308
|
-
if field_name in {"agents", "references"}:
|
|
309
|
-
for value in values:
|
|
310
|
-
content = cls._read_resource_content(skill, value)
|
|
311
|
-
if content:
|
|
312
|
-
lines.append(f"\n### {field_name}/{value}\n{content}")
|
|
313
397
|
if not has_resources:
|
|
314
398
|
lines.append("- none")
|
|
315
399
|
return "\n".join(lines)
|
|
316
400
|
|
|
317
|
-
@staticmethod
|
|
318
|
-
def _read_resource_content(skill: SkillBase, resource_path: str) -> str:
|
|
319
|
-
root_path = getattr(skill, "root_path", None)
|
|
320
|
-
if not root_path:
|
|
321
|
-
return ""
|
|
322
|
-
root = Path(root_path).expanduser().resolve()
|
|
323
|
-
path = (root / resource_path).resolve()
|
|
324
|
-
try:
|
|
325
|
-
path.relative_to(root)
|
|
326
|
-
except ValueError:
|
|
327
|
-
return ""
|
|
328
|
-
if (
|
|
329
|
-
path.suffix.lower() not in {".md", ".yaml", ".yml"}
|
|
330
|
-
or not path.is_file()
|
|
331
|
-
):
|
|
332
|
-
return ""
|
|
333
|
-
return path.read_text(encoding="utf-8").strip()
|
|
334
|
-
|
|
335
401
|
@classmethod
|
|
336
402
|
def _skill_matches_input(cls, skill: SkillBase, input_text: str) -> bool:
|
|
337
403
|
candidates = [skill.name, skill.description]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|