diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4e7300438a..ff0957efae 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -80,6 +80,7 @@ jobs: praisonai, langgraph, phidata, + google, ] runs-on: ${{ matrix.os }} steps: diff --git a/docs/framework/google.mdx b/docs/framework/google.mdx new file mode 100644 index 0000000000..6b9d3dc00a --- /dev/null +++ b/docs/framework/google.mdx @@ -0,0 +1,119 @@ +--- +title: "Using Composio With Google AI" +sidebarTitle: "Google AI" +icon: "robot" +description: "Enable Google AI models to seamlessly interact with external apps via Composio for enhanced functionality" +--- + +**Composio enables** your **Google AI models** to **connect** with many **tools**! + +### Install Packages & Connect a Tool + + + Goal: Enable Google AI models to perform tasks like starring a repository on + GitHub via natural language commands + + +These steps prepare your environment to enable interactions between Google AI and GitHub through Composio. + + + ```bash Run Command + pip install composio-google + + # Connect your GitHub so models can interact with it + + composio add github + + # Check all supported apps + + composio apps + ``` + + + + + +Replace `{google_api_key}` with your actual API key. + + ```python Default Imports & Configuration + import dotenv + from composio_google import App, ComposioToolset + from vertexai.generative_models import GenerativeModel + + # Load environment variables from .env + dotenv.load_dotenv() + + # Initialize the Composio Toolset + composio_toolset = ComposioToolset() + + # Get GitHub tools that are pre-configured + tool = composio_toolset.get_tool(apps=[App.GITHUB]) + + # Initialize the Google AI Gemini model + model = GenerativeModel("gemini-1.5-pro", tools=[tool]) + ``` + + + + + + ```python Start Chat Session + # Start a chat session + chat = model.start_chat() + ``` + + + + + + ```python Execute Task + # Define task + task = "Star a repo composiohq/composio on GitHub" + + # Send a message to the model + response = chat.send_message(task) + + print("Model response:") + print(response) + ``` + + + + + + ```python Handle Tool Calls + result = composio_toolset.handle_response(response) + print("Function call result:") + print(result) + ``` + + + + + +### Use Specific Actions + + + ```python Filter Specific Action + # To restrict models from executing any actions, filter specific actions + actions = composio_toolset.get_tool(actions=[Action.GITHUB_CREATE_ISSUE]) + ``` + + +### Use Specific Apps + + + ```python Filter Specific App + # To restrict models from using all tools, filter specific tools + actions = composio_toolset.get_tool(apps=[App.ASANA, App.GITHUB]) + ``` + + +### Filter apps actions by tags + + + ```python Filter Actions by Tags + actions = composio_toolset.get_tool(apps=[App.ASANA], tags=[Tag.ASANA_TASKS]) + ``` + + diff --git a/docs/framework/julep.mdx b/docs/framework/julep.mdx index 5cb9a68b13..5fe1d98b75 100644 --- a/docs/framework/julep.mdx +++ b/docs/framework/julep.mdx @@ -1,7 +1,7 @@ --- title: "Using Composio With Julep" sidebarTitle: "Julep" -icon: "glass" +icon: "robot" description: "Integrate Composio with Julep agents to enhance their interaction with external apps" --- diff --git a/python/plugins/google/README.md b/python/plugins/google/README.md new file mode 100644 index 0000000000..17fd750581 --- /dev/null +++ b/python/plugins/google/README.md @@ -0,0 +1,66 @@ +## 🚀🔗 Integrating Composio with Google AI Python + +Streamline the integration of Composio with Google AI Python to enhance the capabilities of Gemini models, allowing them to interact directly with external applications and expanding their operational scope. + +### Objective + +- **Automate starring a GitHub repository** using conversational instructions via Google AI Python's Function Calling feature. + +### Installation and Setup + +Ensure you have the necessary packages installed and connect your GitHub account to allow your agents to utilize GitHub functionalities. + +```bash +# Install Composio LangChain package +pip install composio-google + +# Connect your GitHub account +composio-cli add github + +# View available applications you can connect with +composio-cli show-apps +``` + +### Usage Steps + +#### 1. Import Base Packages + +Prepare your environment by initializing necessary imports from Google AI Python and setting up your client. + +```python +from vertexai.generative_models import GenerativeModel + +# Initialize Google AI Python client +model = GenerativeModel("gemini-pro") +``` + +### Step 2: Integrating GitHub Tools with Composio + +This step involves fetching and integrating GitHub tools provided by Composio, enabling enhanced functionality for Google AI Python operations. +```python +from composio_google import App, ComposioToolset + +toolset = ComposioToolset() +actions = toolset.get_tools(apps=[App.GITHUB]) +``` + +### Step 3: Agent Execution + +This step involves configuring and executing the agent to carry out actions, such as starring a GitHub repository. + +```python +# Define task +task = "Star a repo composiohq/composio on GitHub" + +# Send a message to the model +response = chat.send_message(task) +``` + +### Step 4: Validate Execution Response + +Execute the following code to validate the response, ensuring that the intended task has been successfully completed. + +```python +result = composio_toolset.handle_response(response) +print("Function call result:", result) +``` diff --git a/python/plugins/google/composio_google/__init__.py b/python/plugins/google/composio_google/__init__.py new file mode 100644 index 0000000000..4cc89c427b --- /dev/null +++ b/python/plugins/google/composio_google/__init__.py @@ -0,0 +1,13 @@ +from composio_google.toolset import ComposioToolset + +from composio import Action, App, Tag, Trigger, WorkspaceType + + +__all__ = ( + "Action", + "App", + "Tag", + "Trigger", + "WorkspaceType", + "ComposioToolset", +) diff --git a/python/plugins/google/composio_google/toolset.py b/python/plugins/google/composio_google/toolset.py new file mode 100644 index 0000000000..6045e66cce --- /dev/null +++ b/python/plugins/google/composio_google/toolset.py @@ -0,0 +1,213 @@ +""" +Google AI Python Gemini tool spec. +""" + +import typing as t + +import typing_extensions as te +from proto.marshal.collections.maps import MapComposite +from vertexai.generative_models import ( + Content, + FunctionDeclaration, + GenerationResponse, + Part, + Tool, +) + +from composio import Action, ActionType, AppType, TagType +from composio.constants import DEFAULT_ENTITY_ID +from composio.tools import ComposioToolSet as BaseComposioToolSet +from composio.utils.shared import json_schema_to_model + + +class ComposioToolset( + BaseComposioToolSet, + runtime="google_ai", + description_char_limit=1024, +): + """ + Composio toolset for Google AI Python Gemini framework. + + Example: + ```python + import os + import dotenv + from vertexai.generative_models import GenerativeModel + from composio_google import ComposioToolSet, App + + # Load environment variables from .env + dotenv.load_dotenv() + + # Initialize tools + composio_toolset = ComposioToolSet() + + # Get GitHub tools that are pre-configured + tools = composio_toolset.get_tools(apps=[App.GITHUB]) + + # Initialize the Gemini model + model = GenerativeModel("gemini-pro", tools=tools) + + # Start a chat + chat = model.start_chat() + + # Define task + task = "Star a repo composiohq/composio on GitHub" + + # Send a message to the model + response = chat.send_message(task) + + print(response.text) + + # Handle function calls if any + result = composio_toolset.handle_response(response) + if result: + print(result) + ``` + """ + + def validate_entity_id(self, entity_id: str) -> str: + """Validate entity ID.""" + if ( + self.entity_id != DEFAULT_ENTITY_ID + and entity_id != DEFAULT_ENTITY_ID + and self.entity_id != entity_id + ): + raise ValueError( + "separate `entity_id` can not be provided during " + "initialization and handling tool calls" + ) + if self.entity_id != DEFAULT_ENTITY_ID: + entity_id = self.entity_id + return entity_id + + def _wrap_tool( + self, + schema: t.Dict[str, t.Any], + ) -> FunctionDeclaration: + """Wraps composio tool as Google AI Python Gemini FunctionDeclaration object.""" + action = schema["name"] + description = schema.get("description", action) + parameters = json_schema_to_model(schema["parameters"]) + + # Clean up properties by removing 'examples' field + properties = parameters.schema().get("properties", {}) + cleaned_properties = { + prop_name: {k: v for k, v in prop_schema.items() if k != "examples"} + for prop_name, prop_schema in properties.items() + } + + # Create cleaned parameters + cleaned_parameters = { + "type": "object", + "properties": cleaned_properties, + "required": parameters.schema().get("required", []), + } + + return FunctionDeclaration( + name=action, + description=description, + parameters=cleaned_parameters, + ) + + @te.deprecated("Use `ComposioToolSet.get_tools` instead") + def get_actions( + self, + actions: t.Sequence[ActionType], + entity_id: t.Optional[str] = None, + ) -> Tool: + """ + Get composio tools wrapped as Google AI Python Gemini FunctionDeclaration objects. + + :param actions: List of actions to wrap + :param entity_id: Entity ID for the function wrapper + + :return: Composio tools wrapped as `FunctionDeclaration` objects + """ + return self.get_tool(actions=actions, entity_id=entity_id) + + def get_tool( + self, + actions: t.Optional[t.Sequence[ActionType]] = None, + apps: t.Optional[t.Sequence[AppType]] = None, + tags: t.Optional[t.List[TagType]] = None, + entity_id: t.Optional[str] = None, + ) -> Tool: + """ + Get composio tools wrapped as Google AI Python Gemini FunctionDeclaration objects. + + :param actions: List of actions to wrap + :param apps: List of apps to wrap + :param tags: Filter the apps by given tags + :param entity_id: Entity ID for the function wrapper + + :return: Composio tools wrapped as `FunctionDeclaration` objects + """ + entity_id = self.validate_entity_id(entity_id or self.entity_id) + self.validate_tools(apps=apps, actions=actions, tags=tags) + return Tool( + function_declarations=[ + self._wrap_tool( + schema=tool.model_dump( + exclude_none=True, + ), + ) + for tool in self.get_action_schemas( + actions=actions, apps=apps, tags=tags + ) + ] + ) + + def execute_function_call( + self, + function_call: t.Any, + entity_id: t.Optional[str] = DEFAULT_ENTITY_ID, + ) -> t.Dict: + """ + Execute a function call. + + :param function_call: Function call metadata from Gemini model response. + :param entity_id: Entity ID to use for executing the function call. + :return: Object containing output data from the function call. + """ + entity_id = self.validate_entity_id(entity_id or self.entity_id) + + def convert_map_composite(obj): + if isinstance(obj, MapComposite): + return {k: convert_map_composite(v) for k, v in obj.items()} + if isinstance(obj, (list, tuple)): + return [convert_map_composite(item) for item in obj] + return obj + + args = convert_map_composite(function_call.args) + + return self.execute_action( + action=Action(value=function_call.name), + params=args, + entity_id=entity_id, + ) + + def handle_response( + self, + response: GenerationResponse, + entity_id: t.Optional[str] = None, + ) -> t.List[t.Dict]: + """ + Handle response from Google AI Python Gemini model. + + :param response: Generation response from the Gemini model. + :param entity_id: Entity ID to use for executing the function call. + :return: A list of output objects from the function calls. + """ + entity_id = self.validate_entity_id(entity_id or self.entity_id) + outputs = [] + for candidate in response.candidates: + if isinstance(candidate.content, Content) and candidate.content.parts: + for part in candidate.content.parts: + if isinstance(part, Part) and part.function_call: + outputs.append( + self.execute_function_call( + function_call=part.function_call, + entity_id=entity_id, + ) + ) + return outputs diff --git a/python/plugins/google/google_demo.py b/python/plugins/google/google_demo.py new file mode 100644 index 0000000000..1c6cf0bfa7 --- /dev/null +++ b/python/plugins/google/google_demo.py @@ -0,0 +1,41 @@ +""" +Google AI Python Gemini demo. +""" +import dotenv +from composio_google import App, ComposioToolset +from vertexai.generative_models import GenerativeModel + + +# Load environment variables from .env +dotenv.load_dotenv() + +# Initialize tools +composio_toolset = ComposioToolset() + +# Get GitHub tools that are pre-configured +tool = composio_toolset.get_tool(apps=[App.GITHUB]) + +# Initialize the Gemini model +model = GenerativeModel("gemini-1.5-pro", tools=[tool]) + +# Start a chat session +chat = model.start_chat() + + +def main(): + # Define task + task = "Star a repo composiohq/composio on GitHub" + + # Send a message to the model + response = chat.send_message(task) + + print("Model response:") + print(response) + + result = composio_toolset.handle_response(response) + print("Function call result:") + print(result) + + +if __name__ == "__main__": + main() diff --git a/python/plugins/google/setup.py b/python/plugins/google/setup.py new file mode 100644 index 0000000000..330625ded4 --- /dev/null +++ b/python/plugins/google/setup.py @@ -0,0 +1,30 @@ +""" +Setup configuration for Composio Google AI Python Gemini plugin +""" + +from pathlib import Path + +from setuptools import setup + + +setup( + name="composio_google", + version="0.5.25", + author="Assistant", + author_email="karan@composio.dev", + description="Use Composio to get an array of tools with your Google AI Python Gemini model.", + long_description=(Path(__file__).parent / "README.md").read_text(encoding="utf-8"), + long_description_content_type="text/markdown", + url="https://github.com/ComposioHQ/composio", + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + ], + python_requires=">=3.9,<4", + install_requires=[ + "composio_core==0.5.25", + "google-cloud-aiplatform>=1.38.0", + ], + include_package_data=True, +) diff --git a/python/scripts/bump.py b/python/scripts/bump.py index 61d9d4d950..e647e80d0b 100644 --- a/python/scripts/bump.py +++ b/python/scripts/bump.py @@ -34,6 +34,7 @@ class BumpType(Enum): "composio_langgraph==", "composio_praisonai==", "composio_camel==", + "composio_google==", )