Skip to content

Commit

Permalink
Merge pull request #1093 from MichaelDecent/tools_pkg
Browse files Browse the repository at this point in the history
Add Swarmauri community packages for Zapier Hook, Web Scraping, and Sentiment Analysis tools
  • Loading branch information
cobycloud authored Jan 15, 2025
2 parents 71363d8 + fd34dcd commit fa21fb1
Show file tree
Hide file tree
Showing 20 changed files with 884 additions and 1 deletion.
4 changes: 4 additions & 0 deletions pkgs/community/swarmauri_tool_communitygithub/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ GithubIssueTool = "swarmauri_tool_communitygithub.GithubIssueTool"
GithubPRTool = "swarmauri_tool_communitygithub.GithubPRTool"
GithubTool = "swarmauri_tool_communitygithub.GithubTool"
GithubRepoTool = "swarmauri_tool_communitygithub.GithubRepoTool"

[tool.poetry.plugins."swarmauri.toolkits"]
GithubToolkit = "swarmauri_tool_communitygithub.GithubToolkit"

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# File: swarmauri/standard/toolkits/concrete/GithubToolkit.py
from typing import Literal, Optional

from pydantic import BaseModel

from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
from swarmauri_tool_communitygithub.GithubRepoTool import GithubRepoTool
from swarmauri_tool_communitygithub.GithubIssueTool import GithubIssueTool
from swarmauri_tool_communitygithub.GithubPRTool import GithubPRTool
from swarmauri_tool_communitygithub.GithubBranchTool import GithubBranchTool
from swarmauri_tool_communitygithub.GithubCommitTool import GithubCommitTool

from dotenv import load_dotenv

load_dotenv()


class GithubToolkit(ToolkitBase):
type: Literal["GithubToolkit"] = "GithubToolkit"
resource: str = "GithubToolkit"

token: str = None
# Explicitly define the tools as fields
github_repo_tool: Optional[GithubRepoTool] = None
github_issue_tool: Optional[GithubIssueTool] = None
github_pr_tool: Optional[GithubPRTool] = None
github_branch_tool: Optional[GithubBranchTool] = None
github_commit_tool: Optional[GithubCommitTool] = None

def __init__(self, token: str, **kwargs):
super().__init__(**kwargs)

if not token:
raise ValueError("Invalid Token or Missing token")

self.token = token

self.github_repo_tool = GithubRepoTool(token=self.token)
self.github_issue_tool = GithubIssueTool(token=self.token)
self.github_pr_tool = GithubPRTool(token=self.token)
self.github_branch_tool = GithubBranchTool(token=self.token)
self.github_commit_tool = GithubCommitTool(token=self.token)

self.add_tool(self.github_repo_tool)
self.add_tool(self.github_issue_tool)
self.add_tool(self.github_pr_tool)
self.add_tool(self.github_commit_tool)
self.add_tool(self.github_branch_tool)

# @model_validator(mode="wrap")
# @classmethod
# def validate_model(cls, values: Any, handler: Any):
# # Extract the tools and validate their types manually
# tools = values.get("tools", {})

# for tool_name, tool_data in tools.items():
# if isinstance(tool_data, dict):
# tool_type = tool_data.get("type")
# tool_id = tool_data.get("id") # Preserve the ID if it exists

# try:
# tool_class = next(
# sub_cls
# for sub_cls in SubclassUnion.__swm__get_subclasses__(ToolBase)
# if sub_cls.__name__ == tool_type
# )

# # # Create an instance of the tool class
# tools[tool_name] = tool_class(**tool_data)
# tools[tool_name].id = (
# tool_id # Ensure the tool ID is not changed unintentionally
# )
# except StopIteration:
# raise ValueError(f"Unknown tool type: {tool_type}")

# values["tools"] = tools
# return handler(values)
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import os
from unittest.mock import patch, MagicMock
import pytest
import logging
from swarmauri_standard.tools.AdditionTool import AdditionTool
from swarmauri_tool_communitygithub.GithubRepoTool import GithubRepoTool
from swarmauri_tool_communitygithub.GithubToolkit import GithubToolkit as Toolkit
from dotenv import load_dotenv

load_dotenv()


@pytest.fixture(scope="module")
def github_token():
token = os.getenv("GITHUBTOOL_TEST_TOKEN")
if not token:
pytest.skip("Skipping due to GITHUBTOOL_TEST_TOKEN not set")
return token


@pytest.fixture(scope="module")
def github_toolkit(github_token):
return Toolkit(token=github_token)


@pytest.mark.unit
def test_ubc_resource(github_toolkit):
assert github_toolkit.resource == "GithubToolkit"


@pytest.mark.unit
def test_ubc_type(github_toolkit):
assert github_toolkit.type == "GithubToolkit"


@pytest.mark.unit
def test_serialization(github_toolkit):
serialized_data = github_toolkit.model_dump_json()
logging.info(serialized_data)
deserialized_toolkit = Toolkit.model_validate_json(serialized_data)
assert github_toolkit.id == deserialized_toolkit.id


@pytest.mark.unit
def test_tool_count(github_toolkit):
assert len(github_toolkit.get_tools()) == 5


@pytest.mark.unit
def test_add_tool(github_toolkit):
tool = AdditionTool()
github_toolkit.add_tool(tool)
assert len(github_toolkit.get_tools()) == 6


@pytest.mark.parametrize(
"action, kwargs, method_called",
[
("create_repo", {"repo_name": "test-repo"}, "create_repo"),
("delete_repo", {"repo_name": "test-repo"}, "delete_repo"),
("get_repo", {"repo_name": "test-repo"}, "get_repo"),
("list_repos", {}, "list_repos"),
("update_repo", {"repo_name": "test-repo"}, "update_repo"),
("invalid_action", {}, None),
],
)
@pytest.mark.unit
@patch("swarmauri_community.tools.concrete.GithubRepoTool.Github")
def test_call_github_repo_tool(
mock_github, github_toolkit, action, kwargs, method_called
):
expected_keys = {action}
tool_name = "GithubRepoTool"

mock_github.return_value = MagicMock()

if method_called is not None:
with patch.object(
GithubRepoTool,
method_called,
return_value="performed a test action successfully",
) as mock_method:
result = github_toolkit.get_tool_by_name(tool_name)(action=action, **kwargs)

mock_method.assert_called_once_with(**kwargs)

assert isinstance(
result, dict
), f"Expected dict, but got {type(result).__name__}"
assert expected_keys.issubset(
result.keys()
), f"Expected keys {expected_keys} but got {result.keys()}"
assert isinstance(
result.get(action), str
), f"Expected str, but got {type(result.get(action)).__name__}"
assert result == {f"{action}": "performed a test action successfully"}
else:
with pytest.raises(ValueError, match=f"Action '{action}' is not supported."):
github_toolkit.get_tool_by_name(tool_name)(action=action, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Swarmauri Example Community Package
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[tool.poetry]
name = "swarmauri_tool_communitysentimentanalysis"
version = "0.6.0.dev1"
description = "Sentiment Analysis Tool"
authors = ["Jacob Stewart <[email protected]>"]
license = "Apache-2.0"
readme = "README.md"
repository = "http://github.com/swarmauri/swarmauri-sdk"
classifiers = [
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12"
]

[tool.poetry.dependencies]
python = ">=3.10,<3.13"

# Swarmauri
swarmauri_core = { path = "../../core" }
swarmauri_base = { path = "../../base" }


[tool.poetry.group.dev.dependencies]
flake8 = "^7.0"
pytest = "^8.0"
pytest-asyncio = ">=0.24.0"
pytest-xdist = "^3.6.1"
pytest-json-report = "^1.5.0"
python-dotenv = "*"
requests = "^2.32.3"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
norecursedirs = ["combined", "scripts"]

markers = [
"test: standard test",
"unit: Unit tests",
"integration: Integration tests",
"acceptance: Acceptance tests",
"experimental: Experimental tests"
]
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(asctime)s [%(levelname)s] %(message)s"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_default_fixture_loop_scope = "function"

[tool.poetry.plugins."swarmauri.tools"]
SentimentAnalysisTool = "swm_example_community_package.SentimentAnalysisTool:SentimentAnalysisTool"
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from transformers import pipeline
from transformers import logging as hf_logging
from typing import List, Literal, Dict
from swarmauri.tools.base.ToolBase import ToolBase
from swarmauri.tools.concrete.Parameter import Parameter
from pydantic import Field

hf_logging.set_verbosity_error()


class SentimentAnalysisTool(ToolBase):
"""
A tool for analyzing the sentiment of the given text using Hugging Face's transformers.
"""

version: str = "1.0.0"
parameters: List[Parameter] = Field(
default_factory=lambda: [
Parameter(
name="text",
type="string",
description="The text for sentiment analysis",
required=True,
)
]
)

name: str = "SentimentAnalysisTool"
description: str = "Analyzes the sentiment of the given text."
type: Literal["SentimentAnalysisTool"] = "SentimentAnalysisTool"

def __call__(self, text: str) -> Dict[str, str]:
"""
Performs sentiment analysis on the given text.
Args:
text (str): The text to analyze.
Returns:
Dict[str, str]: A dictionary containing the sentiment analysis result.
The dictionary has a single key-value pair, where the key is "sentiment"
and the value is the sentiment label.
Raises:
RuntimeError: If sentiment analysis fails.
"""
analyzer = None
try:
analyzer = pipeline("sentiment-analysis")
result = analyzer(text)
return {"sentiment": result[0]["label"]}
except Exception as e:
raise RuntimeError(f"Sentiment analysis failed: {str(e)}")
finally:
if analyzer is not None:
del analyzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from .SentimentAnalysisTool import SentimentAnalysisTool

__version__ = "0.6.0.dev26"
__long_desc__ = """
# Swarmauri SentimentAnalysis Tool Plugin
Visit us at: https://swarmauri.com
Follow us at: https://github.com/swarmauri
Star us at: https://github.com/swarmauri/swarmauri-sdk
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import pytest
from swarmauri_tool_communitysentimentanalysis.SentimentAnalysisTool import (
SentimentAnalysisTool as Tool,
)


@pytest.mark.unit
def test_ubc_resource():
tool = Tool()
assert tool.resource == "Tool"


@pytest.mark.unit
def test_ubc_type():
assert Tool().type == "SentimentAnalysisTool"


@pytest.mark.unit
def test_initialization():
tool = Tool()
assert type(tool.id) == str


@pytest.mark.unit
def test_serialization():
tool = Tool()
assert tool.id == Tool.model_validate_json(tool.model_dump_json()).id


@pytest.mark.parametrize(
"text, expected_labels",
[
("I love this product!", ["POSITIVE", "NEGATIVE", "NEUTRAL"]),
("I hate this product!", ["NEGATIVE", "POSITIVE", "NEUTRAL"]),
("This product is okay.", ["NEUTRAL", "POSITIVE", "NEGATIVE"]),
],
)
@pytest.mark.unit
def test_call(text, expected_labels):
tool = Tool()
result = tool(text)

assert result["sentiment"] in expected_labels

assert isinstance(result, dict)
assert "sentiment" in result

assert not hasattr(tool, "analyzer")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Swarmauri Example Community Package
Loading

0 comments on commit fa21fb1

Please sign in to comment.