diff --git a/requirements.txt b/requirements.txt index ae0a6d4a..5eab3687 100644 --- a/requirements.txt +++ b/requirements.txt @@ -71,3 +71,4 @@ nltk mkdocs mkdocs-material mkdocs-glightbox +ratelimit diff --git a/swarms/__init__.py b/swarms/__init__.py index 49aa5f53..c778c6f4 100644 --- a/swarms/__init__.py +++ b/swarms/__init__.py @@ -11,4 +11,4 @@ from swarms.swarms import * # noqa: E402, F403 from swarms.structs import * # noqa: E402, F403 from swarms.models import * # noqa: E402, F403 from swarms.chunkers import * # noqa: E402, F403 -from swarms.workers import * # noqa: E402, F403 +# from swarms.workers import * # noqa: E402, F403 diff --git a/swarms/models/__init__.py b/swarms/models/__init__.py index 62ff0a2d..8e5d0189 100644 --- a/swarms/models/__init__.py +++ b/swarms/models/__init__.py @@ -1,7 +1,5 @@ import sys -log_file = open("errors.txt", "w") -sys.stderr = log_file # LLMs from swarms.models.anthropic import Anthropic @@ -19,6 +17,10 @@ from swarms.models.idefics import Idefics from swarms.models.vilt import Vilt from swarms.models.nougat import Nougat from swarms.models.layoutlm_document_qa import LayoutLMDocumentQA +from swarms.models.gpt4v import GPT4Vision +from swarms.models.dalle3 import Dalle3 +from swarms.models.distilled_whisperx import DistilWhisperModel + # from swarms.models.gpt4v import GPT4Vision # from swarms.models.dalle3 import Dalle3 @@ -41,6 +43,7 @@ __all__ = [ "OpenAIChat", "Zephyr", "Idefics", + # "Kosmos", "Vilt", "Nougat", "LayoutLMDocumentQA", diff --git a/swarms/tools/apitool.py b/swarms/tools/apitool.py index 650cc109..414d8081 100644 --- a/swarms/tools/apitool.py +++ b/swarms/tools/apitool.py @@ -10,7 +10,7 @@ import aiohttp import http.client http.client._MAXLINE = 655360 -from swarms.utils import get_logger +from swarms.utils.tool_logging import get_logger logger = get_logger(__name__) diff --git a/swarms/tools/tool.py b/swarms/tools/tool.py index 4bbafdca..ac747026 100644 --- a/swarms/tools/tool.py +++ b/swarms/tools/tool.py @@ -9,64 +9,6 @@ class Tool(fastapi.FastAPI): - It can be easily mounted to another server - It has a list of sub-routes, each route is a function -from langchain.callbacks.base import BaseCallbackManager -from langchain.callbacks.manager import ( - AsyncCallbackManager, - AsyncCallbackManagerForToolRun, - CallbackManager, - CallbackManagerForToolRun, - Callbacks, -) - -from langchain.load.serializable import Serializable -from pydantic import ( - BaseModel, - Extra, - Field, - create_model, - root_validator, - validate_arguments, -) -from langchain.schema.runnable import Runnable, RunnableConfig, RunnableSerializable - - -class SchemaAnnotationError(TypeError): - """Raised when 'args_schema' is missing or has an incorrect type annotation.""" - - -def _create_subset_model( - name: str, model: BaseModel, field_names: list -) -> Type[BaseModel]: - """Create a pydantic model with only a subset of model's fields.""" - fields = {} - for field_name in field_names: - field = model.__fields__[field_name] - fields[field_name] = (field.outer_type_, field.field_info) - return create_model(name, **fields) # type: ignore - - -def _get_filtered_args( - inferred_model: Type[BaseModel], - func: Callable, -) -> dict: - """Get the arguments from a function's signature.""" - schema = inferred_model.schema()["properties"] - valid_keys = signature(func).parameters - return {k: schema[k] for k in valid_keys if k not in ("run_manager", "callbacks")} - - -class _SchemaConfig: - """Configuration for the pydantic model.""" - - extra: Any = Extra.forbid - arbitrary_types_allowed: bool = True - - -def create_schema_from_function( - model_name: str, - func: Callable, -) -> Type[BaseModel]: - """Create a pydantic schema from a function's signature. Args: - tool_name (str): The name of the tool. - description (str): The description of the tool. @@ -81,603 +23,20 @@ def create_schema_from_function( - version (str, optional): The version of the tool. Defaults to "0.1.0". """ - pass - - -class BaseTool(RunnableSerializable[Union[str, Dict], Any]): - """Interface LangChain tools must implement.""" - - def __init_subclass__(cls, **kwargs: Any) -> None: - """Create the definition of the new tool class.""" - super().__init_subclass__(**kwargs) - - args_schema_type = cls.__annotations__.get("args_schema", None) - - if args_schema_type is not None: - if args_schema_type is None or args_schema_type == BaseModel: - # Throw errors for common mis-annotations. - # TODO: Use get_args / get_origin and fully - # specify valid annotations. - typehint_mandate = """ -class ChildTool(BaseTool): - ... - args_schema: Type[BaseModel] = SchemaClass - ...""" - name = cls.__name__ - raise SchemaAnnotationError( - f"Tool definition for {name} must include valid type annotations" - " for argument 'args_schema' to behave as expected.\n" - "Expected annotation of 'Type[BaseModel]'" - f" but got '{args_schema_type}'.\n" - "Expected class looks like:\n" - f"{typehint_mandate}" - ) - - name: str - """The unique name of the tool that clearly communicates its purpose.""" - description: str - """Used to tell the model how/when/why to use the tool. - - You can provide few-shot examples as a part of the description. - """ - args_schema: Optional[Type[BaseModel]] = None - """Pydantic model class to validate and parse the tool's input arguments.""" - return_direct: bool = False - """Whether to return the tool's output directly. Setting this to True means - - that after the tool is called, the AgentExecutor will stop looping. - """ - verbose: bool = False - """Whether to log the tool's progress.""" - - callbacks: Callbacks = Field(default=None, exclude=True) - """Callbacks to be called during tool execution.""" - callback_manager: Optional[BaseCallbackManager] = Field(default=None, exclude=True) - """Deprecated. Please use callbacks instead.""" - tags: Optional[List[str]] = None - """Optional list of tags associated with the tool. Defaults to None - These tags will be associated with each call to this tool, - and passed as arguments to the handlers defined in `callbacks`. - You can use these to eg identify a specific instance of a tool with its use case. - """ - metadata: Optional[Dict[str, Any]] = None - """Optional metadata associated with the tool. Defaults to None - This metadata will be associated with each call to this tool, - and passed as arguments to the handlers defined in `callbacks`. - You can use these to eg identify a specific instance of a tool with its use case. - """ - - handle_tool_error: Optional[ - Union[bool, str, Callable[[ToolException], str]] - ] = False - """Handle the content of the ToolException thrown.""" - - class Config(Serializable.Config): - """Configuration for this pydantic object.""" - - arbitrary_types_allowed = True - - @property - def is_single_input(self) -> bool: - """Whether the tool only accepts a single input.""" - keys = {k for k in self.args if k != "kwargs"} - return len(keys) == 1 - - @property - def args(self) -> dict: - if self.args_schema is not None: - return self.args_schema.schema()["properties"] - else: - schema = create_schema_from_function(self.name, self._run) - return schema.schema()["properties"] - - # --- Runnable --- - - @property - def input_schema(self) -> Type[BaseModel]: - """The tool's input schema.""" - if self.args_schema is not None: - return self.args_schema - else: - return create_schema_from_function(self.name, self._run) - - def invoke( - self, - input: Union[str, Dict], - config: Optional[RunnableConfig] = None, - **kwargs: Any, - ) -> Any: - config = config or {} - return self.run( - input, - callbacks=config.get("callbacks"), - tags=config.get("tags"), - metadata=config.get("metadata"), - run_name=config.get("run_name"), - **kwargs, - ) - - async def ainvoke( - self, - input: Union[str, Dict], - config: Optional[RunnableConfig] = None, - **kwargs: Any, - ) -> Any: - config = config or {} - return await self.arun( - input, - callbacks=config.get("callbacks"), - tags=config.get("tags"), - metadata=config.get("metadata"), - run_name=config.get("run_name"), - **kwargs, - ) - - # --- Tool --- - - def _parse_input( - self, - tool_input: Union[str, Dict], - ) -> Union[str, Dict[str, Any]]: - """Convert tool input to pydantic model.""" - input_args = self.args_schema - if isinstance(tool_input, str): - if input_args is not None: - key_ = next(iter(input_args.__fields__.keys())) - input_args.validate({key_: tool_input}) - return tool_input - else: - if input_args is not None: - result = input_args.parse_obj(tool_input) - return {k: v for k, v in result.dict().items() if k in tool_input} - return tool_input - - @root_validator() - def raise_deprecation(cls, values: Dict) -> Dict: - """Raise deprecation warning if callback_manager is used.""" - if values.get("callback_manager") is not None: - warnings.warn( - "callback_manager is deprecated. Please use callbacks instead.", - DeprecationWarning, - ) - values["callbacks"] = values.pop("callback_manager", None) - return values - - @abstractmethod - def _run( - self, - *args: Any, - **kwargs: Any, - ) -> Any: - """Use the tool. - - Add run_manager: Optional[CallbackManagerForToolRun] = None - to child implementations to enable tracing, - """ - - async def _arun( - self, - *args: Any, - **kwargs: Any, - ) -> Any: - """Use the tool asynchronously. - - Add run_manager: Optional[AsyncCallbackManagerForToolRun] = None - to child implementations to enable tracing, - """ - return await asyncio.get_running_loop().run_in_executor( - None, - partial(self._run, **kwargs), - *args, - ) - - def _to_args_and_kwargs(self, tool_input: Union[str, Dict]) -> Tuple[Tuple, Dict]: - # For backwards compatibility, if run_input is a string, - # pass as a positional argument. - if isinstance(tool_input, str): - return (tool_input,), {} - else: - return (), tool_input - - def run( - self, - tool_input: Union[str, Dict], - verbose: Optional[bool] = None, - start_color: Optional[str] = "green", - color: Optional[str] = "green", - callbacks: Callbacks = None, - *, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, - run_name: Optional[str] = None, - **kwargs: Any, - ) -> Any: - """Run the tool.""" - parsed_input = self._parse_input(tool_input) - if not self.verbose and verbose is not None: - verbose_ = verbose - else: - verbose_ = self.verbose - callback_manager = CallbackManager.configure( - callbacks, - self.callbacks, - verbose_, - tags, - self.tags, - metadata, - self.metadata, - ) - # TODO: maybe also pass through run_manager is _run supports kwargs - new_arg_supported = signature(self._run).parameters.get("run_manager") - run_manager = callback_manager.on_tool_start( - {"name": self.name, "description": self.description}, - tool_input if isinstance(tool_input, str) else str(tool_input), - color=start_color, - name=run_name, - **kwargs, - ) - try: - tool_args, tool_kwargs = self._to_args_and_kwargs(parsed_input) - observation = ( - self._run(*tool_args, run_manager=run_manager, **tool_kwargs) - if new_arg_supported - else self._run(*tool_args, **tool_kwargs) - ) - except ToolException as e: - if not self.handle_tool_error: - run_manager.on_tool_error(e) - raise e - elif isinstance(self.handle_tool_error, bool): - if e.args: - observation = e.args[0] - else: - observation = "Tool execution error" - elif isinstance(self.handle_tool_error, str): - observation = self.handle_tool_error - elif callable(self.handle_tool_error): - observation = self.handle_tool_error(e) - else: - raise ValueError( - "Got unexpected type of `handle_tool_error`. Expected bool, str " - f"or callable. Received: {self.handle_tool_error}" - ) - run_manager.on_tool_end( - str(observation), color="red", name=self.name, **kwargs - ) - return observation - except (Exception, KeyboardInterrupt) as e: - run_manager.on_tool_error(e) - raise e - else: - run_manager.on_tool_end( - str(observation), color=color, name=self.name, **kwargs - ) - return observation - - async def arun( - self, - tool_input: Union[str, Dict], - verbose: Optional[bool] = None, - start_color: Optional[str] = "green", - color: Optional[str] = "green", - callbacks: Callbacks = None, - *, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, - run_name: Optional[str] = None, - **kwargs: Any, - ) -> Any: - """Run the tool asynchronously.""" - parsed_input = self._parse_input(tool_input) - if not self.verbose and verbose is not None: - verbose_ = verbose - else: - verbose_ = self.verbose - callback_manager = AsyncCallbackManager.configure( - callbacks, - self.callbacks, - verbose_, - tags, - self.tags, - metadata, - self.metadata, - ) - new_arg_supported = signature(self._arun).parameters.get("run_manager") - run_manager = await callback_manager.on_tool_start( - {"name": self.name, "description": self.description}, - tool_input if isinstance(tool_input, str) else str(tool_input), - color=start_color, - name=run_name, - **kwargs, - ) - try: - # We then call the tool on the tool input to get an observation - tool_args, tool_kwargs = self._to_args_and_kwargs(parsed_input) - observation = ( - await self._arun(*tool_args, run_manager=run_manager, **tool_kwargs) - if new_arg_supported - else await self._arun(*tool_args, **tool_kwargs) - ) - except ToolException as e: - if not self.handle_tool_error: - await run_manager.on_tool_error(e) - raise e - elif isinstance(self.handle_tool_error, bool): - if e.args: - observation = e.args[0] - else: - observation = "Tool execution error" - elif isinstance(self.handle_tool_error, str): - observation = self.handle_tool_error - elif callable(self.handle_tool_error): - observation = self.handle_tool_error(e) - else: - raise ValueError( - "Got unexpected type of `handle_tool_error`. Expected bool, str " - f"or callable. Received: {self.handle_tool_error}" - ) - await run_manager.on_tool_end( - str(observation), color="red", name=self.name, **kwargs - ) - return observation - except (Exception, KeyboardInterrupt) as e: - await run_manager.on_tool_error(e) - raise e - else: - await run_manager.on_tool_end( - str(observation), color=color, name=self.name, **kwargs - ) - return observation - - def __call__(self, tool_input: str, callbacks: Callbacks = None) -> str: - """Make tool callable.""" - return self.run(tool_input, callbacks=callbacks) - - -class Tool(BaseTool): - """Tool that takes in function or coroutine directly.""" - - description: str = "" - func: Optional[Callable[..., str]] - """The function to run when the tool is called.""" - coroutine: Optional[Callable[..., Awaitable[str]]] = None - """The asynchronous version of the function.""" - - # --- Runnable --- - async def ainvoke( - self, - input: Union[str, Dict], - config: Optional[RunnableConfig] = None, - **kwargs: Any, - ) -> Any: - if not self.coroutine: - # If the tool does not implement async, fall back to default implementation - return await asyncio.get_running_loop().run_in_executor( - None, partial(self.invoke, input, config, **kwargs) - ) - - return await super().ainvoke(input, config, **kwargs) - - # --- Tool --- - - @property - def args(self) -> dict: - """The tool's input arguments.""" - if self.args_schema is not None: - return self.args_schema.schema()["properties"] - # For backwards compatibility, if the function signature is ambiguous, - # assume it takes a single string input. - return {"tool_input": {"type": "string"}} - - def _to_args_and_kwargs(self, tool_input: Union[str, Dict]) -> Tuple[Tuple, Dict]: - """Convert tool input to pydantic model.""" - args, kwargs = super()._to_args_and_kwargs(tool_input) - # For backwards compatibility. The tool must be run with a single input - all_args = list(args) + list(kwargs.values()) - if len(all_args) != 1: - raise ToolException( - f"Too many arguments to single-input tool {self.name}. Args: {all_args}" - ) - return tuple(all_args), {} - - def _run( - self, - *args: Any, - run_manager: Optional[CallbackManagerForToolRun] = None, - **kwargs: Any, - ) -> Any: - """Use the tool.""" - if self.func: - new_argument_supported = signature(self.func).parameters.get("callbacks") - return ( - self.func( - *args, - callbacks=run_manager.get_child() if run_manager else None, - **kwargs, - ) - if new_argument_supported - else self.func(*args, **kwargs) - ) - raise NotImplementedError("Tool does not support sync") - - async def _arun( - self, - *args: Any, - run_manager: Optional[AsyncCallbackManagerForToolRun] = None, - **kwargs: Any, - ) -> Any: - """Use the tool asynchronously.""" - if self.coroutine: - new_argument_supported = signature(self.coroutine).parameters.get( - "callbacks" - ) - return ( - await self.coroutine( - *args, - callbacks=run_manager.get_child() if run_manager else None, - **kwargs, - ) - if new_argument_supported - else await self.coroutine(*args, **kwargs) - ) - else: - return await asyncio.get_running_loop().run_in_executor( - None, partial(self._run, run_manager=run_manager, **kwargs), *args - ) - - # TODO: this is for backwards compatibility, remove in future def __init__( - self, name: str, func: Optional[Callable], description: str, **kwargs: Any - ) -> None: - """Initialize tool.""" - super(Tool, self).__init__( - name=name, func=func, description=description, **kwargs - ) - - @classmethod - def from_function( - cls, - func: Optional[Callable], - name: str, # We keep these required to support backwards compatibility - description: str, - return_direct: bool = False, - args_schema: Optional[Type[BaseModel]] = None, - coroutine: Optional[ - Callable[..., Awaitable[Any]] - ] = None, # This is last for compatibility, but should be after func - **kwargs: Any, - ) -> Tool: - """Initialize tool from a function.""" - if func is None and coroutine is None: - raise ValueError("Function and/or coroutine must be provided") - return cls( - name=name, - func=func, - coroutine=coroutine, - description=description, - return_direct=return_direct, - args_schema=args_schema, - **kwargs, - ) - - -class StructuredTool(BaseTool): - """Tool that can operate on any number of inputs.""" - - description: str = "" - args_schema: Type[BaseModel] = Field(..., description="The tool schema.") - """The input arguments' schema.""" - func: Optional[Callable[..., Any]] - """The function to run when the tool is called.""" - coroutine: Optional[Callable[..., Awaitable[Any]]] = None - """The asynchronous version of the function.""" - - # --- Runnable --- - async def ainvoke( - self, - input: Union[str, Dict], - config: Optional[RunnableConfig] = None, - **kwargs: Any, - ) -> Any: - if not self.coroutine: - # If the tool does not implement async, fall back to default implementation - return await asyncio.get_running_loop().run_in_executor( - None, partial(self.invoke, input, config, **kwargs) - ) - - return await super().ainvoke(input, config, **kwargs) - - # --- Tool --- - - @property - def args(self) -> dict: - """The tool's input arguments.""" - return self.args_schema.schema()["properties"] - - def _run( - self, - *args: Any, - run_manager: Optional[CallbackManagerForToolRun] = None, - **kwargs: Any, - ) -> Any: - """Use the tool.""" - if self.func: - new_argument_supported = signature(self.func).parameters.get("callbacks") - return ( - self.func( - *args, - callbacks=run_manager.get_child() if run_manager else None, - **kwargs, - ) - if new_argument_supported - else self.func(*args, **kwargs) - ) - raise NotImplementedError("Tool does not support sync") - - async def _arun( - self, - *args: Any, - run_manager: Optional[AsyncCallbackManagerForToolRun] = None, - **kwargs: Any, - ) -> str: - """Use the tool asynchronously.""" - if self.coroutine: - new_argument_supported = signature(self.coroutine).parameters.get( - "callbacks" - ) - return ( - await self.coroutine( - *args, - callbacks=run_manager.get_child() if run_manager else None, - **kwargs, - ) - if new_argument_supported - else await self.coroutine(*args, **kwargs) - ) - return await asyncio.get_running_loop().run_in_executor( - None, - partial(self._run, run_manager=run_manager, **kwargs), - *args, - ) - - @classmethod - def from_function( - cls, - func: Optional[Callable] = None, - coroutine: Optional[Callable[..., Awaitable[Any]]] = None, - name: Optional[str] = None, - description: Optional[str] = None, - return_direct: bool = False, - args_schema: Optional[Type[BaseModel]] = None, - infer_schema: bool = True, - **kwargs: Any, - ) -> StructuredTool: - """Create tool from a given function. - - A classmethod that helps to create a tool from a function. - - Args: - func: The function from which to create a tool - coroutine: The async function from which to create a tool - name: The name of the tool. Defaults to the function name - description: The description of the tool. Defaults to the function docstring - return_direct: Whether to return the result directly or as a callback - args_schema: The schema of the tool's input arguments - infer_schema: Whether to infer the schema from the function's signature - **kwargs: Additional arguments to pass to the tool - - Returns: - The tool - - Examples: - - .. code-block:: python - - def add(a: int, b: int) -> int: - \"\"\"Add two numbers\"\"\" - return a + b - tool = StructuredTool.from_function(add) - tool.run(1, 2) # 3 + self, + tool_name : str, + description : str, + name_for_human : Optional[str] = None, + name_for_model : Optional[str] = None, + description_for_human : Optional[str] = None, + description_for_model : Optional[str] = None, + logo_url : Optional[str] = None, + author_github : Optional[str] = None, + contact_email : str = "", + legal_info_url : str = "", + version : str = "0.1.0", + ): """ Diagram: Root API server (ToolServer object) diff --git a/swarms/utils/__init__.py b/swarms/utils/__init__.py index da323121..b43c3a83 100644 --- a/swarms/utils/__init__.py +++ b/swarms/utils/__init__.py @@ -2,6 +2,7 @@ from swarms.utils.display_markdown import display_markdown_message from swarms.utils.futures import execute_futures_dict from swarms.utils.code_interpreter import SubprocessCodeInterpreter from swarms.utils.parse_code import extract_code_in_backticks_in_string +from swarms.utils.tool_logging import get_logger __all__ = [ "display_markdown_message",