diff --git a/auto_gen/composio_autogen/autogen_toolspec.ipynb b/auto_gen/composio_autogen/autogen_toolspec.ipynb new file mode 100644 index 0000000000..be1fea5464 --- /dev/null +++ b/auto_gen/composio_autogen/autogen_toolspec.ipynb @@ -0,0 +1,326 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from inspect import Parameter, Signature\n", + "from typing import Annotated, get_type_hints" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "{'properties': {\n", + " 'body': {\n", + " 'default': '',\n", + " 'description': 'Body of the issue',\n", + " 'examples': ['The code is not working',\n", + " 'I would like to request '\n", + " 'a new feature'],\n", + " 'title': 'Body',\n", + " 'type': 'string'},\n", + " 'owner': {'description': 'Owner of the '\n", + " 'repository',\n", + " 'examples': ['openai', 'facebook'],\n", + " 'title': 'Owner',\n", + " 'type': 'string'},\n", + " 'repo': {'description': 'Name of the '\n", + " 'repository',\n", + " 'examples': ['gpt-3', 'react'],\n", + " 'title': 'Repo',\n", + " 'type': 'string'},\n", + " 'title': {'description': 'Title of the issue',\n", + " 'examples': ['Bug in the code',\n", + " 'Feature request'],\n", + " 'title': 'Title',\n", + " 'type': 'string'}},\n", + "'required': ['owner', 'repo', 'title'],\n", + "'title': 'CreateIssueRequest',\n", + "'type': 'object'}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "schema_type_python_type_dict = {\n", + " 'string': str,\n", + " 'number': float,\n", + " 'boolean': bool,\n", + " # 'array': list,\n", + " # 'object': dict\n", + "}\n", + "\n", + "fallback_values = {\n", + " 'string': \"\",\n", + " 'number': 0.0,\n", + " 'boolean': False,\n", + " 'object': {},\n", + " 'array': []\n", + "}\n", + "def pydantic_model_from_param_schema(param_title, param_schema):\n", + " fields = {}\n", + " for prop_name, prop_schema in param_schema.items():\n", + "\n", + " prop_name: (str, Field(..., title=prop_info['title'], default=prop_info.get('default')))\n", + " if prop_name in required else\n", + " (str, Field(None, title=prop_info['title'], default=prop_info.get('default')))\n", + "\n", + "\n", + " \n", + "\n", + "def get_signature_format_from_schema_params(\n", + " schema_params\n", + "):\n", + " parameters = []\n", + "\n", + " for param_name, param_schema in schema_params['properties'].items():\n", + " param_type = param_schema['type']\n", + " param_title = param_schema['title'].replace(\" \", \"\")\n", + " required_params = param_schema.get('required', [])\n", + "\n", + " if param_type in schema_type_python_type_dict:\n", + " signature_param_type = schema_type_python_type_dict[param_type]\n", + " else:\n", + " signature_param_type = get_signature_format_from_schema_params(param_title,\n", + " param_schema,\n", + " required_params)\n", + "\n", + " # param_type = schema_type_python_type_dict[param_schema['type']]\n", + " param_name = param_schema['name']\n", + " param_default = param_schema.get('default', fallback_values[param_type])\n", + " param_annotation = Annotated[signature_param_type, param_schema['description']]\n", + " param = Parameter(\n", + " name=param_name,\n", + " kind=Parameter.POSITIONAL_OR_KEYWORD,\n", + " annotation=param_annotation,\n", + " default=param_default\n", + " )\n", + " parameters.append(param)\n", + "\n", + "\n", + " \n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(base: typing.Annotated[str, 'Base currency: amount and currency symbol'], quote_currency: typing.Annotated[str, 'Quote currency symbol']) -> str>" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import types\n", + "\n", + "# Not supporting posiotonal arguments intentionally\n", + "# demo_func = lambda *args, **kwargs: print(args, kwargs)\n", + "\n", + "demo_func = lambda *args, **kwargs: print(args, kwargs)\n", + "\n", + "parameters = [\n", + " Parameter(\n", + " name=\"base\", \n", + " kind=Parameter.POSITIONAL_OR_KEYWORD, \n", + " annotation=Annotated[str, \"Base currency: amount and currency symbol\"], \n", + " # default=Parameter.empty if param in required_params else Parameter.default\n", + " ),\n", + " Parameter(\n", + " name=\"quote_currency\", \n", + " kind=Parameter.POSITIONAL_OR_KEYWORD, \n", + " annotation=Annotated[str, \"Quote currency symbol\"], \n", + " # default=Parameter.empty if param in required_params else Parameter.default\n", + " ),\n", + "]\n", + "new_sig = Signature(parameters, return_annotation=str)\n", + "\n", + "func = types.FunctionType(demo_func.__code__, \n", + " globals=globals(), \n", + " name=\"currency_calculator2\", \n", + " # closure=template_function.__closure__\n", + " )\n", + "func.__signature__ = new_sig\n", + "func\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(3, 5) {}\n" + ] + } + ], + "source": [ + "func(3, 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " str>" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inspect.signature(func)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import inspect\n", + "from pydantic import BaseModel, Field\n", + "from typing_extensions import Annotated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " str>" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def currency_calculator(\n", + " base: Annotated[str, \"Base currency: amount and currency symbol\"],\n", + " quote_currency: Annotated[str, \"Quote currency symbol\"] = \"USD\",\n", + ") -> str:\n", + " quote_amount = base + \" \" + quote_currency\n", + " return quote_amount\n", + "\n", + "currency_calculator" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "currency_calculator.__closure__" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " str>" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inspect.signature(currency_calculator)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "composio.autogen.register.slack()\n", + "composio.autogen.register.hubspot()\n", + "\n", + "composio.autogen.register(\n", + " [composio.slack.send_message.args(\n", + " caller=chatbot,\n", + " executor=user_proxy,\n", + " ),\n", + " composio.slack.block_one],\n", + " caller=chatbot,\n", + " executor=user_proxy,\n", + ")\n", + "\n", + "\n", + "autogen.agentchat.register_function(\n", + " composio.slack.send_message,\n", + " caller=chatbot,\n", + " executor=user_proxy,\n", + " name=\"sh\",\n", + " description=\"run a shell script and return the execution result.\",\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/auto_gen/composio_autogen/autogen_toolspec.py b/auto_gen/composio_autogen/autogen_toolspec.py new file mode 100644 index 0000000000..aff27b3e39 --- /dev/null +++ b/auto_gen/composio_autogen/autogen_toolspec.py @@ -0,0 +1,182 @@ +import json +import types +import logging +import requests +from inspect import Parameter, Signature +from typing import Union, List, Dict, Any, Type, Annotated + +import autogen +from composio import ComposioCore, App, Action +from pydantic import BaseModel, create_model, Field +from autogen.agentchat.conversable_agent import ConversableAgent + + +logger = logging.getLogger(__name__) + + +schema_type_python_type_dict = { + 'string': str, + 'number': float, + 'boolean': bool, + 'array': List, + 'integer': int + # 'object': dict +} + +fallback_values = { + 'string': "", + 'number': 0.0, + 'boolean': False, + 'object': {}, + 'array': [] +} + +def pydantic_model_from_param_schema(param_schema): + fields = {} + param_title = param_schema['title'].replace(" ", "") + required_props = param_schema.get('required', []) + for prop_name, prop_info in param_schema['properties'].items(): + prop_type = prop_info["type"] + prop_title = prop_info['title'].replace(" ", "") + prop_default = prop_info.get('default', fallback_values[prop_type]) + if prop_type in schema_type_python_type_dict: + signature_prop_type = schema_type_python_type_dict[prop_type] + else: + signature_prop_type = pydantic_model_from_param_schema(prop_info) + + if prop_name in required_props: + fields[prop_name] = (signature_prop_type, + Field(..., + title=prop_title, + description=prop_info.get('description', + prop_info.get('desc', + prop_title)) + )) + else: + fields[prop_name] = (signature_prop_type, + Field(title=prop_title, + default=prop_default + )) + fieldModel = create_model(param_title, **fields) + return fieldModel + + + + +def get_signature_format_from_schema_params( + schema_params +): + parameters = [] + required_params = schema_params.get('required', []) + + for param_name, param_schema in schema_params['properties'].items(): + param_type = param_schema['type'] + param_title = param_schema['title'].replace(" ", "") + + if param_type in schema_type_python_type_dict: + signature_param_type = schema_type_python_type_dict[param_type] + else: + signature_param_type = pydantic_model_from_param_schema(param_schema) + + # param_type = schema_type_python_type_dict[param_schema['type']] + # param_name = param_schema['name'] + param_default = param_schema.get('default', fallback_values[param_type]) + param_annotation = Annotated[signature_param_type, param_schema.get('description', + param_schema.get('desc', + param_title))] + param = Parameter( + name=param_name, + kind=Parameter.POSITIONAL_OR_KEYWORD, + annotation=param_annotation, + default=Parameter.empty if param_name in required_params else param_default + ) + parameters.append(param) + return parameters + + +class ComposioAutogenToolset: + def __init__(self, caller = None, executor = None): + self.caller = caller + self.executor = executor + self.client = ComposioCore() + + + def register_tools( + self, + tools: Union[App, List[App]], + caller: ConversableAgent = None, + executor: ConversableAgent = None + ): + if isinstance(tools, App): + tools = [tools] + assert caller or self.caller, "If caller hasn't been specified during initialization, has to be specified during registration" + assert executor or self.executor, "If executor hasn't been specified during initialization, has to be specified during registration" + + action_schemas = self.client.sdk.get_list_of_actions( + apps=tools) + + for schema in action_schemas: + self._register_schema_to_autogen(action_schema=schema, + caller = caller if caller else self.caller, + executor = executor if executor else self.executor) + + + print("Tools registered successfully!") + + def register_actions( + self, + actions: Union[Action, List[Action]], + caller: ConversableAgent = None, + executor: ConversableAgent = None + ): + if isinstance(actions, Action): + actions = [actions] + + assert caller or self.caller, "If caller hasn't been specified during initialization, has to be specified during registration" + assert executor or self.executor, "If executor hasn't been specified during initialization, has to be specified during registration" + + action_schemas = self.client.sdk.get_list_of_actions( + actions=actions) + + for schema in action_schemas: + self._register_schema_to_autogen(action_schema=schema, + caller = caller if caller else self.caller, + executor = executor if executor else self.executor) + + + print("Actions registered successfully!") + + def _register_schema_to_autogen(self, + action_schema, + caller: ConversableAgent, + executor: ConversableAgent): + + name = action_schema["name"] + appName = action_schema["appName"] + description = action_schema["description"] + + parameters = get_signature_format_from_schema_params( + action_schema["parameters"]) + action_signature = Signature(parameters=parameters) + + placeholder_function = lambda **kwargs: self.client.execute_action( + self.client.get_action_enum(name, appName), + kwargs) + action_func = types.FunctionType( + placeholder_function.__code__, + globals=globals(), + name=name, + closure=placeholder_function.__closure__ + ) + action_func.__signature__ = action_signature + action_func.__doc__ = description + + autogen.agentchat.register_function( + action_func, + caller=caller, + executor=executor, + name=name, + description=description + ) + + diff --git a/auto_gen/composio_autogen/requirements.txt b/auto_gen/composio_autogen/requirements.txt new file mode 100644 index 0000000000..d8df7a5253 --- /dev/null +++ b/auto_gen/composio_autogen/requirements.txt @@ -0,0 +1,2 @@ +pyautogen +composio-core \ No newline at end of file