From cb1779d9bd4e0017d429547645d2ffe2e8cddfea Mon Sep 17 00:00:00 2001 From: MichaelDecent Date: Fri, 20 Dec 2024 13:26:00 +0100 Subject: [PATCH 1/3] swarm - Refactor agent APIs: move to deprecated directory, add new AgentAPI and IAgentAPI implementations --- pkgs/core/swarmauri_core/ComponentBase.py | 3 + .../IAgentCommands.py | 0 .../IAgentRouterCRUD.py | 0 .../agent_apis(deprecated)/__init__.py | 4 + .../swarmauri_core/agent_apis/IAgentAPI.py | 15 ++++ .../swarmauri_core/agent_apis/__init__.py | 4 - .../swarmauri/swarmauri/agent_apis/__init_.py | 0 .../swarmauri/agent_apis/base/AgentAPIBase.py | 31 +++++++ .../swarmauri/agent_apis/base/__init__.py | 0 .../swarmauri/agent_apis/concrete/AgentAPI.py | 10 +++ .../swarmauri/agent_apis/concrete/__init__.py | 0 .../concrete/MaxSystemContextConversation.py | 40 ++++++--- .../factories/concrete/AgentFactory.py | 5 +- .../swarmauri/utils/_get_subclasses.py | 85 +++++-------------- .../unit/agent_apis/AgentAPI_unit_test.py | 80 +++++++++++++++++ .../unit/factories/AgentFactory_unit_test.py | 23 +++-- .../tests/unit/factories/Factory_unit_test.py | 5 +- 17 files changed, 210 insertions(+), 95 deletions(-) rename pkgs/core/swarmauri_core/{agent_apis => agent_apis(deprecated)}/IAgentCommands.py (100%) rename pkgs/core/swarmauri_core/{agent_apis => agent_apis(deprecated)}/IAgentRouterCRUD.py (100%) create mode 100644 pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py create mode 100644 pkgs/core/swarmauri_core/agent_apis/IAgentAPI.py create mode 100644 pkgs/swarmauri/swarmauri/agent_apis/__init_.py create mode 100644 pkgs/swarmauri/swarmauri/agent_apis/base/AgentAPIBase.py create mode 100644 pkgs/swarmauri/swarmauri/agent_apis/base/__init__.py create mode 100644 pkgs/swarmauri/swarmauri/agent_apis/concrete/AgentAPI.py create mode 100644 pkgs/swarmauri/swarmauri/agent_apis/concrete/__init__.py create mode 100644 pkgs/swarmauri/tests/unit/agent_apis/AgentAPI_unit_test.py diff --git a/pkgs/core/swarmauri_core/ComponentBase.py b/pkgs/core/swarmauri_core/ComponentBase.py index f4cc811da..f25bf5f38 100644 --- a/pkgs/core/swarmauri_core/ComponentBase.py +++ b/pkgs/core/swarmauri_core/ComponentBase.py @@ -64,10 +64,13 @@ class ResourceTypes(Enum): CONTROL_PANEL = "ControlPanel" TASK_MGT_STRATEGY = "TaskMgtStrategy" MAS = "Mas" + AGENT_API = "AgentAPI" + def generate_id() -> str: return str(uuid4()) + class ComponentBase(BaseModel): name: Optional[str] = None id: str = Field(default_factory=generate_id) diff --git a/pkgs/core/swarmauri_core/agent_apis/IAgentCommands.py b/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentCommands.py similarity index 100% rename from pkgs/core/swarmauri_core/agent_apis/IAgentCommands.py rename to pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentCommands.py diff --git a/pkgs/core/swarmauri_core/agent_apis/IAgentRouterCRUD.py b/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentRouterCRUD.py similarity index 100% rename from pkgs/core/swarmauri_core/agent_apis/IAgentRouterCRUD.py rename to pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentRouterCRUD.py diff --git a/pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py b/pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py new file mode 100644 index 000000000..27f8d7f14 --- /dev/null +++ b/pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py @@ -0,0 +1,4 @@ +from .IAgentCommands import IAgentCommands +from .IAgentRouterCRUD import IAgentRouterCRUD + +__all__ = ['IAgentCommands', 'IAgentRouterCRUD'] \ No newline at end of file diff --git a/pkgs/core/swarmauri_core/agent_apis/IAgentAPI.py b/pkgs/core/swarmauri_core/agent_apis/IAgentAPI.py new file mode 100644 index 000000000..79a90d748 --- /dev/null +++ b/pkgs/core/swarmauri_core/agent_apis/IAgentAPI.py @@ -0,0 +1,15 @@ +from abc import ABC, abstractmethod +from typing import Any, Coroutine, Dict + + +class IAgentAPI(ABC): + + @abstractmethod + def invoke(self, agent_id: str, **kwargs: Dict[str, Any]) -> Any: + """Invoke an agent synchronously.""" + pass + + @abstractmethod + async def ainvoke(self, agent_id: str, **kwargs: Dict[str, Any]) -> Any: + """Invoke an agent asynchronously.""" + pass diff --git a/pkgs/core/swarmauri_core/agent_apis/__init__.py b/pkgs/core/swarmauri_core/agent_apis/__init__.py index 27f8d7f14..e69de29bb 100644 --- a/pkgs/core/swarmauri_core/agent_apis/__init__.py +++ b/pkgs/core/swarmauri_core/agent_apis/__init__.py @@ -1,4 +0,0 @@ -from .IAgentCommands import IAgentCommands -from .IAgentRouterCRUD import IAgentRouterCRUD - -__all__ = ['IAgentCommands', 'IAgentRouterCRUD'] \ No newline at end of file diff --git a/pkgs/swarmauri/swarmauri/agent_apis/__init_.py b/pkgs/swarmauri/swarmauri/agent_apis/__init_.py new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/swarmauri/swarmauri/agent_apis/base/AgentAPIBase.py b/pkgs/swarmauri/swarmauri/agent_apis/base/AgentAPIBase.py new file mode 100644 index 000000000..8c2cb8ad4 --- /dev/null +++ b/pkgs/swarmauri/swarmauri/agent_apis/base/AgentAPIBase.py @@ -0,0 +1,31 @@ +from typing import Any, Dict, Literal, Optional + +from pydantic import ConfigDict, Field +from swarmauri_core.ComponentBase import ComponentBase, ResourceTypes +from swarmauri_core.agent_apis.IAgentAPI import IAgentAPI +from swarmauri.service_registries.concrete.ServiceRegistry import ServiceRegistry + + +class AgentAPIBase(IAgentAPI, ComponentBase): + + resource: Optional[str] = Field(default=ResourceTypes.AGENT_API.value, frozen=True) + model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True) + type: Literal["AgentAPIBase"] = "AgentAPIBase" + + agent_registry: ServiceRegistry + + def invoke(self, agent_id: str, **kwargs: Dict[str, Any]) -> Any: + agent = self.agent_registry.get_service(agent_id) + if not agent: + raise ValueError(f"Agent with ID {agent_id} not found.") + return agent.exec(**kwargs) + + async def ainvoke(self, agent_id: str, **kwargs: Dict[str, Any]) -> Any: + agent = self.agent_registry.get_service(agent_id) + if not agent: + raise ValueError(f"Agent with ID {agent_id} not found.") + if not hasattr(agent, "aexec"): + raise NotImplementedError( + f"Agent with ID {agent_id} does not support async execution." + ) + return await agent.aexec(**kwargs) diff --git a/pkgs/swarmauri/swarmauri/agent_apis/base/__init__.py b/pkgs/swarmauri/swarmauri/agent_apis/base/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/swarmauri/swarmauri/agent_apis/concrete/AgentAPI.py b/pkgs/swarmauri/swarmauri/agent_apis/concrete/AgentAPI.py new file mode 100644 index 000000000..312f9156a --- /dev/null +++ b/pkgs/swarmauri/swarmauri/agent_apis/concrete/AgentAPI.py @@ -0,0 +1,10 @@ +from typing import Literal +from swarmauri.agent_apis.base.AgentAPIBase import AgentAPIBase + + +class AgentAPI(AgentAPIBase): + """ + Concrete implementation of the AgentAPIBase. + """ + + type: Literal["AgentAPI"] = "AgentAPI" diff --git a/pkgs/swarmauri/swarmauri/agent_apis/concrete/__init__.py b/pkgs/swarmauri/swarmauri/agent_apis/concrete/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/swarmauri/swarmauri/conversations/concrete/MaxSystemContextConversation.py b/pkgs/swarmauri/swarmauri/conversations/concrete/MaxSystemContextConversation.py index 723f4b49a..4fed78122 100644 --- a/pkgs/swarmauri/swarmauri/conversations/concrete/MaxSystemContextConversation.py +++ b/pkgs/swarmauri/swarmauri/conversations/concrete/MaxSystemContextConversation.py @@ -3,22 +3,29 @@ from swarmauri_core.messages.IMessage import IMessage from swarmauri_core.conversations.IMaxSize import IMaxSize from swarmauri.conversations.base.ConversationBase import ConversationBase -from swarmauri.conversations.base.ConversationSystemContextMixin import ConversationSystemContextMixin -from swarmauri.messages.concrete import SystemMessage, AgentMessage, HumanMessage +from swarmauri.conversations.base.ConversationSystemContextMixin import ( + ConversationSystemContextMixin, +) +from swarmauri.messages.concrete.SystemMessage import SystemMessage +from swarmauri.messages.concrete.AgentMessage import AgentMessage +from swarmauri.messages.concrete.HumanMessage import HumanMessage from swarmauri.exceptions.concrete import IndexErrorWithContext -class MaxSystemContextConversation(IMaxSize, ConversationSystemContextMixin, ConversationBase): + +class MaxSystemContextConversation( + IMaxSize, ConversationSystemContextMixin, ConversationBase +): system_context: Optional[SystemMessage] = SystemMessage(content="") max_size: int = Field(default=2, gt=1) - model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True) - type: Literal['MaxSystemContextConversation'] = 'MaxSystemContextConversation' - - @field_validator('system_context', mode='before') + model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True) + type: Literal["MaxSystemContextConversation"] = "MaxSystemContextConversation" + + @field_validator("system_context", mode="before") def set_system_context(cls, value: Union[str, SystemMessage]) -> SystemMessage: if isinstance(value, str): return SystemMessage(content=value) return value - + @property def history(self) -> List[IMessage]: """ @@ -41,11 +48,16 @@ def history(self) -> List[IMessage]: # Build history from the first 'user' message ensuring alternating roles. res.append(self.system_context) alternating = True - count = 0 + count = 0 for message in self._history[user_start_index:]: - if count >= self.max_size: # max size + if count >= self.max_size: # max size break - if alternating and isinstance(message, HumanMessage) or not alternating and isinstance(message, AgentMessage): + if ( + alternating + and isinstance(message, HumanMessage) + or not alternating + and isinstance(message, AgentMessage) + ): res.append(message) alternating = not alternating count += 1 @@ -63,13 +75,15 @@ def add_message(self, message: IMessage): Adds a message to the conversation history and ensures history does not exceed the max size. """ if isinstance(message, SystemMessage): - raise ValueError(f"System context cannot be set through this method on {self.__class_name__}.") + raise ValueError( + f"System context cannot be set through this method on {self.__class_name__}." + ) elif isinstance(message, IMessage): self._history.append(message) else: raise ValueError(f"Must use a subclass of IMessage") self._enforce_max_size_limit() - + def _enforce_max_size_limit(self): """ Remove messages from the beginning of the conversation history if the limit is exceeded. diff --git a/pkgs/swarmauri/swarmauri/factories/concrete/AgentFactory.py b/pkgs/swarmauri/swarmauri/factories/concrete/AgentFactory.py index 0649ca928..6cf722eaf 100644 --- a/pkgs/swarmauri/swarmauri/factories/concrete/AgentFactory.py +++ b/pkgs/swarmauri/swarmauri/factories/concrete/AgentFactory.py @@ -1,5 +1,7 @@ +import logging from typing import Any, Callable, Dict, Literal from swarmauri.factories.base.FactoryBase import FactoryBase +from swarmauri.utils._get_subclasses import get_classes_from_module class AgentFactory(FactoryBase): @@ -8,7 +10,7 @@ class AgentFactory(FactoryBase): """ type: Literal["AgentFactory"] = "AgentFactory" - _registry: Dict[str, Callable] = {} + _registry: Dict[str, Callable] = get_classes_from_module("Agent") def register(self, type: str, resource_class: Callable) -> None: """ @@ -22,6 +24,7 @@ def create(self, type: str, *args: Any, **kwargs: Any) -> Any: """ Create an instance of the class associated with the given type name. """ + logging.info(self._registry) if type not in self._registry: raise ValueError(f"Type '{type}' is not registered.") diff --git a/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py b/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py index adf100601..4f105eb75 100644 --- a/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py +++ b/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py @@ -1,72 +1,25 @@ -import importlib -import re +from swarmauri.utils.LazyLoader import LazyLoader -def get_classes_from_module(module_name: str): - """ - Dynamically imports a module and retrieves a dictionary of class names and their corresponding class objects. +def get_classes_from_module(module): + import inspect - :param module_name: The name of the module (e.g., "parsers", "agent"). - :return: A dictionary with class names as keys and class objects as values. - """ - # Convert module name to lowercase to ensure consistency - module_name_lower = module_name.lower() + classes = {} + for name, obj in inspect.getmembers(module): + if isinstance(obj, LazyLoader): + obj = obj._load_class() # Load the class from LazyLoader + if inspect.isclass(obj): + classes[name] = obj + return classes - # Construct the full module path dynamically - full_module_path = f"swarmauri.{module_name_lower}s.concrete" - try: - # Import the module dynamically - module = importlib.import_module(full_module_path) +def get_class_from_module(module, class_name): + import inspect - # Get the list of class names from __all__ - class_names = getattr(module, "__all__", []) - - # Create a dictionary with class names and their corresponding class objects - classes_dict = { - class_name: getattr(module, class_name) for class_name in class_names - } - - return classes_dict - except ImportError as e: - print(f"Error importing module {full_module_path}: {e}") - raise ModuleNotFoundError(f"Resource '{module_name}' is not registered.") - except AttributeError as e: - print(f"Error accessing class in {full_module_path}: {e}") - raise e - - -def get_class_from_module(module_name: str, class_name: str): - """ - Dynamically imports a module and retrieves the class name of the module. - - :param module_name: The name of the module (e.g., "parsers", "agent"). - :return: The class name of the module. - """ - # Convert module name to lowercase to ensure consistency - module_name_lower = module_name.lower() - - # Construct the full module path dynamically - full_module_path = f"swarmauri.{module_name_lower}s.concrete" - - try: - # Import the module dynamically - module = importlib.import_module(full_module_path) - - # Get the list of class names from __all__ - class_names = getattr(module, "__all__", []) - - if not class_names: - raise AttributeError(f"No classes found in module {full_module_path}") - - for cls_name in class_names: - if cls_name == class_name: - return getattr(module, class_name) - return None - - except ImportError as e: - print(f"Error importing module {full_module_path}: {e}") - raise ModuleNotFoundError(f"Resource '{module_name}' is not found.") - except AttributeError as e: - print(f"Error accessing class in {full_module_path}: {e}") - raise e + if hasattr(module, class_name): + obj = getattr(module, class_name) + if isinstance(obj, LazyLoader): + obj = obj._load_class() + if inspect.isclass(obj): + return obj + return None diff --git a/pkgs/swarmauri/tests/unit/agent_apis/AgentAPI_unit_test.py b/pkgs/swarmauri/tests/unit/agent_apis/AgentAPI_unit_test.py new file mode 100644 index 000000000..810a66da1 --- /dev/null +++ b/pkgs/swarmauri/tests/unit/agent_apis/AgentAPI_unit_test.py @@ -0,0 +1,80 @@ +import logging +import os +import pytest +from swarmauri.agent_apis.concrete.AgentAPI import AgentAPI +from swarmauri.factories.concrete.AgentFactory import AgentFactory +from swarmauri.llms.concrete.GroqModel import GroqModel +from swarmauri.service_registries.concrete.ServiceRegistry import ServiceRegistry +from dotenv import load_dotenv + +load_dotenv() + + +@pytest.fixture(scope="module") +def groq_model(): + API_KEY = os.getenv("GROQ_API_KEY") + if not API_KEY: + pytest.skip("Skipping due to environment variable not set") + llm = GroqModel(api_key=API_KEY) + return llm + + +@pytest.fixture(scope="module") +def agent_api(groq_model): + agent = AgentFactory().create("QAAgent", llm=groq_model) + agent_registry = ServiceRegistry() + agent_registry.register_service(agent, "agent1") + return AgentAPI(agent_registry=agent_registry) + + +@pytest.mark.unit +def test_ubc_resource(agent_api): + assert agent_api.resource == "AgentAPI" + + +@pytest.mark.unit +def test_ubc_type(agent_api): + assert agent_api.type == "AgentAPI" + + +@pytest.mark.unit +def test_serialization(agent_api): + assert agent_api.id == AgentAPI.model_validate_json(agent_api.model_dump_json()).id + + +def test_invoke(agent_api): + agent_id = "agent1" + + result = agent_api.invoke(agent_id, input_str="Hello") + + logging.info(result) + + assert isinstance(result, str) + + +def test_invoke_agent_not_found(agent_api): + agent_id = "nonexistent_agent" + + with pytest.raises(ValueError) as exc_info: + agent_api.invoke(agent_id) + + assert str(exc_info.value) == f"Agent with ID {agent_id} not found." + + +@pytest.mark.asyncio +async def test_ainvoke(agent_api): + agent_id = "agent1" + + result = await agent_api.ainvoke(agent_id, param="value") + + assert isinstance(result, str) + + +@pytest.mark.asyncio +async def test_ainvoke_agent_not_found(agent_api): + agent_id = "nonexistent_agent" + + with pytest.raises(ValueError) as exc_info: + await agent_api.ainvoke(agent_id) + + assert str(exc_info.value) == f"Agent with ID {agent_id} not found." diff --git a/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py b/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py index 0aac46614..37d641f17 100644 --- a/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py +++ b/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py @@ -1,13 +1,22 @@ +from typing import Literal import pytest from swarmauri.factories.concrete.AgentFactory import AgentFactory import os from swarmauri.llms.concrete.GroqModel import GroqModel -from swarmauri.agents.concrete.QAAgent import QAAgent +from swarmauri.utils._get_subclasses import get_classes_from_module + from dotenv import load_dotenv load_dotenv() +class TestAgent: + type: Literal["TestAgent"] = "TestAgent" + + def exec(self, **kwargs): + return "TestAgent execution result" + + @pytest.fixture(scope="module") def groq_model(): API_KEY = os.getenv("GROQ_API_KEY") @@ -43,12 +52,12 @@ def test_serialization(agent_factory): @pytest.mark.unit def test_agent_factory_register_and_create(agent_factory, groq_model): - agent_factory.register(type="QAAgent", resource_class=QAAgent) + agent_factory.register(type="TestAgent", resource_class=TestAgent) # Create an instance - instance = agent_factory.create(type="QAAgent", llm=groq_model) - assert isinstance(instance, QAAgent) - assert instance.type == "QAAgent" + instance = agent_factory.create(type="TestAgent") + assert isinstance(instance, TestAgent) + assert instance.type == "TestAgent" @pytest.mark.unit @@ -61,6 +70,4 @@ def test_agent_factory_create_unregistered_type(agent_factory): @pytest.mark.unit def test_agent_factory_get_agents(agent_factory): - - assert agent_factory.get() == ["QAAgent"] - assert len(agent_factory.get()) == 1 + assert len(agent_factory.get()) == len(get_classes_from_module("Agent").keys()) + 1 diff --git a/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py b/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py index 11695f7ec..f3048d289 100644 --- a/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py +++ b/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py @@ -1,10 +1,9 @@ import pytest from swarmauri.factories.concrete.Factory import Factory from swarmauri.parsers.concrete.BeautifulSoupElementParser import ( - BeautifulSoupElementParser, + BeautifulSoupElementParser ) - @pytest.fixture(scope="module") def factory(): return Factory() @@ -46,7 +45,7 @@ def test_factory_create_unregistered_resource(factory): # Attempt to create an instance of an unregistered resource with pytest.raises( - ModuleNotFoundError, match="Resource 'UnknownResource' is not registered." + ValueError, match="Type 'BeautifulSoupElementParser' is not registered under resource 'UnknownResource'." ): factory.create("UnknownResource", "BeautifulSoupElementParser") From 799558af5556ccce44b92ae2b4fda59bd56df750 Mon Sep 17 00:00:00 2001 From: MichaelDecent Date: Fri, 20 Dec 2024 17:09:34 +0100 Subject: [PATCH 2/3] Enhance Factory and get_classes_from_module --- .../swarmauri/factories/concrete/Factory.py | 9 ++++--- .../swarmauri/utils/_get_subclasses.py | 26 +++++++++++++++---- .../unit/factories/AgentFactory_unit_test.py | 13 ++++++++-- .../tests/unit/factories/Factory_unit_test.py | 19 ++++++++++++-- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/pkgs/swarmauri/swarmauri/factories/concrete/Factory.py b/pkgs/swarmauri/swarmauri/factories/concrete/Factory.py index d3bca5af5..7908dddae 100644 --- a/pkgs/swarmauri/swarmauri/factories/concrete/Factory.py +++ b/pkgs/swarmauri/swarmauri/factories/concrete/Factory.py @@ -1,7 +1,6 @@ +import logging from typing import Any, Callable, Dict, Literal from swarmauri.factories.base.FactoryBase import FactoryBase -from swarmauri.utils._get_subclasses import get_classes_from_module - class Factory(FactoryBase): """ @@ -15,6 +14,7 @@ def register(self, resource: str, type: str, resource_class: Callable) -> None: """ Register a resource class under a specific resource. """ + from swarmauri.utils._get_subclasses import get_classes_from_module if type in self._resource_registry.get(resource, {}): raise ValueError( f"Type '{type}' is already registered under resource '{resource}'." @@ -30,10 +30,13 @@ def create(self, resource: str, type: str, *args: Any, **kwargs: Any) -> Any: """ Create an instance of the class associated with the given resource and type. """ + from swarmauri.utils._get_subclasses import get_classes_from_module + if resource not in self._resource_registry: self._resource_registry[resource] = get_classes_from_module(resource) + logging.info(self._resource_registry) - if type not in self._resource_registry[resource]: + if type not in self._resource_registry[resource].keys(): raise ValueError( f"Type '{type}' is not registered under resource '{resource}'." ) diff --git a/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py b/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py index 4f105eb75..0a74e860c 100644 --- a/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py +++ b/pkgs/swarmauri/swarmauri/utils/_get_subclasses.py @@ -1,20 +1,36 @@ +import importlib +import inspect +import logging + from swarmauri.utils.LazyLoader import LazyLoader -def get_classes_from_module(module): - import inspect +def get_classes_from_module(resource_name: str): + """ + Pass something like 'llms' to import 'swarmauri.llms.concrete' + and retrieve all loaded classes. + """ + resource_name = resource_name.lower() + + full_module = f"swarmauri.{resource_name}s.concrete" + module = importlib.import_module(full_module) classes = {} for name, obj in inspect.getmembers(module): if isinstance(obj, LazyLoader): - obj = obj._load_class() # Load the class from LazyLoader + obj = obj._load_class() if inspect.isclass(obj): classes[name] = obj + + logging.info(f"Classes found in module {module}: {classes}") return classes -def get_class_from_module(module, class_name): - import inspect +def get_class_from_module(resource_name, class_name): + resource_name = resource_name.lower() + + full_module = f"swarmauri.{resource_name}s.concrete" + module = importlib.import_module(full_module) if hasattr(module, class_name): obj = getattr(module, class_name) diff --git a/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py b/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py index 37d641f17..6ec7fc117 100644 --- a/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py +++ b/pkgs/swarmauri/tests/unit/factories/AgentFactory_unit_test.py @@ -3,7 +3,7 @@ from swarmauri.factories.concrete.AgentFactory import AgentFactory import os from swarmauri.llms.concrete.GroqModel import GroqModel -from swarmauri.utils._get_subclasses import get_classes_from_module +from swarmauri.agents.concrete.QAAgent import QAAgent from dotenv import load_dotenv @@ -60,6 +60,15 @@ def test_agent_factory_register_and_create(agent_factory, groq_model): assert instance.type == "TestAgent" +@pytest.mark.unit +def test_agent_factory_create(agent_factory, groq_model): + + # Create an instance + instance = agent_factory.create(type="QAAgent", llm=groq_model) + assert isinstance(instance, QAAgent) + assert instance.type == "QAAgent" + + @pytest.mark.unit def test_agent_factory_create_unregistered_type(agent_factory): @@ -70,4 +79,4 @@ def test_agent_factory_create_unregistered_type(agent_factory): @pytest.mark.unit def test_agent_factory_get_agents(agent_factory): - assert len(agent_factory.get()) == len(get_classes_from_module("Agent").keys()) + 1 + assert len(agent_factory.get()) == len(agent_factory._registry) diff --git a/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py b/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py index f3048d289..0a1165e8e 100644 --- a/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py +++ b/pkgs/swarmauri/tests/unit/factories/Factory_unit_test.py @@ -1,9 +1,10 @@ import pytest from swarmauri.factories.concrete.Factory import Factory from swarmauri.parsers.concrete.BeautifulSoupElementParser import ( - BeautifulSoupElementParser + BeautifulSoupElementParser, ) + @pytest.fixture(scope="module") def factory(): return Factory() @@ -40,12 +41,26 @@ def test_factory_register_create_resource(factory): assert instance.type == "BeautifulSoupElementParser" +@pytest.mark.unit +def test_factory_create_resource(factory): + + html_content = "

Sample HTML content

" + + # Create an instance of a registered resource + instance = factory.create( + "Parser", "BeautifulSoupElementParser", element=html_content + ) + assert isinstance(instance, BeautifulSoupElementParser) + assert instance.type == "BeautifulSoupElementParser" + + @pytest.mark.unit def test_factory_create_unregistered_resource(factory): # Attempt to create an instance of an unregistered resource with pytest.raises( - ValueError, match="Type 'BeautifulSoupElementParser' is not registered under resource 'UnknownResource'." + ModuleNotFoundError, + match="No module named 'swarmauri.unknownresources'", ): factory.create("UnknownResource", "BeautifulSoupElementParser") From 334a1d491be9183fcb9b56989398dae316086368 Mon Sep 17 00:00:00 2001 From: MichaelDecent Date: Fri, 20 Dec 2024 17:12:23 +0100 Subject: [PATCH 3/3] Remove deprecated agent API interfaces and related initialization --- .../agent_apis(deprecated)/IAgentCommands.py | 83 ------------------- .../IAgentRouterCRUD.py | 56 ------------- .../agent_apis(deprecated)/__init__.py | 4 - 3 files changed, 143 deletions(-) delete mode 100644 pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentCommands.py delete mode 100644 pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentRouterCRUD.py delete mode 100644 pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py diff --git a/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentCommands.py b/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentCommands.py deleted file mode 100644 index 175848a6c..000000000 --- a/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentCommands.py +++ /dev/null @@ -1,83 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Callable, Any, List - -class IAgentCommands(ABC): - """ - Interface for the API object that enables a SwarmAgent to host various API routes. - """ - - - @abstractmethod - def invoke(self, request: Any) -> Any: - """ - Handles invocation requests synchronously. - - Parameters: - request (Any): The incoming request payload. - - Returns: - Any: The response payload. - """ - pass - - @abstractmethod - async def ainvoke(self, request: Any) -> Any: - """ - Handles invocation requests asynchronously. - - Parameters: - request (Any): The incoming request payload. - - Returns: - Any: The response payload. - """ - pass - - @abstractmethod - def batch(self, requests: List[Any]) -> List[Any]: - """ - Handles batched invocation requests synchronously. - - Parameters: - requests (List[Any]): A list of incoming request payloads. - - Returns: - List[Any]: A list of responses. - """ - pass - - @abstractmethod - async def abatch(self, requests: List[Any]) -> List[Any]: - """ - Handles batched invocation requests asynchronously. - - Parameters: - requests (List[Any]): A list of incoming request payloads. - - Returns: - List[Any]: A list of responses. - """ - pass - - @abstractmethod - def stream(self, request: Any) -> Any: - """ - Handles streaming requests. - - Parameters: - request (Any): The incoming request payload. - - Returns: - Any: A streaming response. - """ - pass - - @abstractmethod - def get_schema_config(self) -> dict: - """ - Retrieves the schema configuration for the API. - - Returns: - dict: The schema configuration. - """ - pass \ No newline at end of file diff --git a/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentRouterCRUD.py b/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentRouterCRUD.py deleted file mode 100644 index 91eecbc4e..000000000 --- a/pkgs/core/swarmauri_core/agent_apis(deprecated)/IAgentRouterCRUD.py +++ /dev/null @@ -1,56 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Callable, Any, Dict - -class IAgentRouterCRUD(ABC): - """ - Interface for managing API routes within a SwarmAgent. - """ - - @abstractmethod - def create_route(self, path: str, method: str, handler: Callable[[Any], Any]) -> None: - """ - Create a new route for the API. - - Parameters: - - path (str): The URL path for the route. - - method (str): The HTTP method (e.g., 'GET', 'POST'). - - handler (Callable[[Any], Any]): The function that handles requests to this route. - """ - pass - - @abstractmethod - def read_route(self, path: str, method: str) -> Dict: - """ - Retrieve information about a specific route. - - Parameters: - - path (str): The URL path for the route. - - method (str): The HTTP method. - - Returns: - - Dict: Information about the route, including path, method, and handler. - """ - pass - - @abstractmethod - def update_route(self, path: str, method: str, new_handler: Callable[[Any], Any]) -> None: - """ - Update the handler function for an existing route. - - Parameters: - - path (str): The URL path for the route. - - method (str): The HTTP method. - - new_handler (Callable[[Any], Any]): The new function that handles requests to this route. - """ - pass - - @abstractmethod - def delete_route(self, path: str, method: str) -> None: - """ - Delete a specific route from the API. - - Parameters: - - path (str): The URL path for the route. - - method (str): The HTTP method. - """ - pass \ No newline at end of file diff --git a/pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py b/pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py deleted file mode 100644 index 27f8d7f14..000000000 --- a/pkgs/core/swarmauri_core/agent_apis(deprecated)/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .IAgentCommands import IAgentCommands -from .IAgentRouterCRUD import IAgentRouterCRUD - -__all__ = ['IAgentCommands', 'IAgentRouterCRUD'] \ No newline at end of file