Python SDK Integration

This guide walks you through the process of integrating third-party Python frameworks with Composio. By creating these integrations, you enable Composio’s tools to work seamlessly with popular AI/ML frameworks.

Understanding the integration pattern

When integrating a Python SDK with Composio, you’re essentially creating a bridge between:

  1. Composio’s tool schema and execution model - How Composio represents and calls tools
  2. The target framework’s tool representation - How the framework defines and calls tools

An integration primarily handles the transformation of tool schemas and execution flows between the two systems, while preserving all the capabilities and data.

Prerequisites

Before you begin, make sure you have:

  • Basic understanding of Python and object-oriented programming
  • Familiarity with the Composio platform
  • Knowledge of the target framework you want to integrate
  • Python 3.9+ installed on your development machine

Step-by-step integration process

1

Setup your environment

First, fork and clone the Composio repository:

$git clone https://github.com/ComposioHQ/composio.git

Then navigate to the plugins directory:

$cd composio/python/plugins

Create a new directory for your integration:

$mkdir composio_yourframework
>cd composio_yourframework
2

Create the basic structure

Every integration requires at least these files:

  • __init__.py - Exports your module’s components
  • toolset.py - Contains the core integration logic
  • setup.py - Package configuration for installation
  • README.md - Documentation for your integration
3

Implement the toolset.py File

This is the heart of your integration. Here’s a template to follow:

toolset.py
1import typing as t
2from typing import List, cast
3
4# Import from your target framework
5from targetframework import FrameworkTool, SomeFrameworkClass
6
7# Import from Composio
8from composio import ActionType, AppType, TagType
9from composio.tools import ComposioToolSet as BaseComposioToolSet
10from composio.tools.toolset import ProcessorsType
11from composio.utils.pydantic import parse_pydantic_error
12from composio.utils.shared import get_signature_format_from_schema_params
13
14class ComposioToolSet(BaseComposioToolSet):
15 """
16 Composio toolset for [Your Framework] integration.
17
18 Add usage examples here to help users understand how to use your integration.
19 """
20
21 def __init__(self, *args, **kwargs):
22 # Initialize with your framework-specific settings
23 super().__init__(*args, **kwargs,
24 runtime="your_framework_name",
25 description_char_limit=1024,
26 action_name_char_limit=64)
27
28 def _wrap_tool(
29 self,
30 schema: t.Dict[str, t.Any],
31 entity_id: t.Optional[str] = None,
32 ) -> FrameworkTool:
33 """Convert a Composio tool schema into a framework-specific tool."""
34 action = schema["name"]
35 description = schema["description"]
36 schema_params = schema["parameters"]
37
38 # Implementation specific to your framework
39 # This is where you adapt the Composio schema to your framework's format
40
41 # Example implementation (modify for your framework):
42 tool = FrameworkTool(
43 name=action,
44 description=description,
45 # Transform schema_params to match your framework's format
46 parameters=self._adapt_parameters(schema_params),
47 # Create a wrapper function that calls Composio's execute_action
48 function=lambda **kwargs: self.execute_action(
49 action=action,
50 params=kwargs,
51 entity_id=entity_id or self.entity_id,
52 )
53 )
54
55 return tool
56
57 def get_tools(
58 self,
59 actions: t.Optional[t.Sequence[ActionType]] = None,
60 apps: t.Optional[t.Sequence[AppType]] = None,
61 tags: t.Optional[t.List[TagType]] = None,
62 entity_id: t.Optional[str] = None,
63 *,
64 processors: t.Optional[ProcessorsType] = None,
65 check_connected_accounts: bool = True,
66 ) -> List[FrameworkTool]:
67 """
68 Get Composio tools as your framework's tool objects.
69
70 Args:
71 actions: List of specific actions to get
72 apps: List of apps to get tools from
73 tags: Filter tools by tags
74 entity_id: Entity ID to use for tool execution
75 processors: Optional request/response processors
76 check_connected_accounts: Whether to check for connected accounts
77
78 Returns:
79 A list of framework-compatible tools
80 """
81 # Validate and prepare
82 self.validate_tools(apps=apps, actions=actions, tags=tags)
83 if processors is not None:
84 self._processor_helpers.merge_processors(processors)
85
86 # Get action schemas from Composio
87 tools = [
88 self._wrap_tool(
89 schema=tool.model_dump(exclude_none=True),
90 entity_id=entity_id or self.entity_id,
91 )
92 for tool in self.get_action_schemas(
93 actions=actions,
94 apps=apps,
95 tags=tags,
96 check_connected_accounts=check_connected_accounts,
97 _populate_requested=True,
98 )
99 ]
100
101 return tools
4

Create the __init__.py File

This file exports the necessary components:

__init__.py
1from composio import Action, App, Tag, Trigger, WorkspaceType, action
2
3from composio_yourframework.toolset import ComposioToolSet
4
5__all__ = (
6 "Action",
7 "App",
8 "Tag",
9 "Trigger",
10 "WorkspaceType",
11 "action",
12 "ComposioToolSet",
13)
5

Create a demo file

Create a demonstration file that shows your integration in action:

framework_demo.py
1"""
2Example demonstrating how to use the [Your Framework] integration with Composio.
3"""
4
5import os
6import dotenv
7
8# Import from your target framework
9from targetframework import Agent, Runner
10
11# Import from your integration
12from composio_yourframework import App, Action, ComposioToolSet
13
14# Set up environment
15dotenv.load_dotenv()
16
17def main():
18 # Initialize your toolset
19 toolset = ComposioToolSet()
20
21 # Get specific tools
22 tools = toolset.get_tools(actions=[Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER])
23
24 # Create a framework agent with the tools
25 agent = Agent(
26 name="Demo Agent",
27 tools=tools,
28 # Other framework-specific parameters
29 )
30
31 # Run the agent
32 result = Runner.run(
33 agent,
34 "Perform an action using the integrated tools"
35 )
36
37 print(result)
38
39if __name__ == "__main__":
40 main()
6

Create the setup.py File

This configures your package for installation:

setup.py
1"""
2Setup configuration for Composio [Your Framework] plugin
3"""
4
5from pathlib import Path
6from setuptools import setup, find_packages
7
8setup(
9 name="composio_yourframework",
10 version="0.1.0",
11 author="Your Name",
12 author_email="your.email@example.com",
13 description="Use Composio with [Your Framework]",
14 long_description=(Path(__file__).parent / "README.md").read_text(encoding="utf-8"),
15 long_description_content_type="text/markdown",
16 url="https://github.com/ComposioHQ/composio",
17 classifiers=[
18 "Programming Language :: Python :: 3",
19 "License :: OSI Approved :: Apache Software License",
20 "Operating System :: OS Independent",
21 ],
22 python_requires=">=3.9,<4",
23 packages=find_packages(),
24 install_requires=[
25 "composio_core>=0.7.0,<0.8.0",
26 "your-framework>=X.Y.Z", # Replace with actual dependency
27 "pydantic>=2.0.0",
28 "typing-extensions>=4.0.0",
29 ],
30 include_package_data=True,
31)

Code Standards and Quality

For comprehensive guidance on code formatting, linting, testing, and development practices, please refer to the Development.md file in the Python project’s docs directory. This file contains detailed instructions on environment setup, code conventions, and all the commands needed for maintaining code quality.

Common challenges and solutions

When creating an integration, you might encounter these challenges:

Pay special attention to data type conversions between systems. This is the most common source of bugs in integrations.

Type compatibility issues

Problem: The framework expects different data types than what Composio provides.

Solution:

1def _adapt_parameters(self, schema_params):
2 """Convert Composio parameter schema to framework-specific format"""
3 # Implement type conversions here
4 return converted_schema

Schema format differences

Problem: The framework has a different JSON schema format than Composio.

Solution: Add required properties to match the framework’s expectations. For example:

1# Adding additionalProperties: false for OpenAI-compatible frameworks
2modified_schema = schema_params.copy()
3modified_schema["additionalProperties"] = False

String vs. object returns

Problem: Many frameworks expect string outputs from tool calls, while you want to preserve structured data.

Solution: Use JSON serialization to preserve structure:

1import json
2
3# When returning tool results
4if isinstance(result, dict):
5 return json.dumps(result) # For frameworks that expect strings
6else:
7 return json.dumps({"result": result})

Tool naming restrictions

Problem: The framework has restrictions on tool names or descriptions.

Solution: Implement transformation logic:

1def _sanitize_tool_name(self, name):
2 """Ensure tool name meets framework requirements"""
3 # Replace invalid characters, truncate if needed, etc.
4 return sanitized_name

Real-world example: OpenAI Agents Integration

Below is a concrete example of integrating the OpenAI Agents framework with Composio:

The OpenAI Agents framework expects strings for tool outputs, has specific JSON schema requirements, and needs custom wrapping of tool functions.

Here’s the key implementation for _wrap_tool in this integration:

1def _wrap_tool(
2 self,
3 schema: t.Dict[str, t.Any],
4 entity_id: t.Optional[str] = None,
5) -> FunctionTool:
6 """Wraps composio tool as OpenAI Agents FunctionTool object."""
7 action = schema["name"]
8 description = schema["description"]
9 schema_params = schema["parameters"]
10
11 # Create a function that accepts explicit JSON string for parameters
12 def execute_action_wrapper(ctx, args_json):
13 """Execute Composio action with the given arguments."""
14 try:
15 # Parse the args_json into a dict
16 import json
17 kwargs = json.loads(args_json) if args_json else {}
18
19 result = self.execute_action(
20 action=action,
21 params=kwargs,
22 entity_id=entity_id or self.entity_id,
23 _check_requested_actions=True,
24 )
25
26 # Serialize result to JSON string for OpenAI API
27 if not isinstance(result, dict):
28 result_dict = {"result": result}
29 else:
30 result_dict = result
31
32 return json.dumps(result_dict)
33
34 except Exception as e:
35 # Handle errors consistently
36 return json.dumps({
37 "successful": False,
38 "error": str(e),
39 "data": None,
40 })
41
42 # Add required schema properties for OpenAI
43 modified_schema = schema_params.copy()
44 modified_schema["additionalProperties"] = False
45
46 # Create a framework-specific tool
47 tool = FunctionTool(
48 name=action,
49 description=description,
50 params_json_schema=modified_schema,
51 on_invoke_tool=execute_action_wrapper,
52 strict_json_schema=True,
53 )
54
55 return tool

Testing your integration

Always test your integration thoroughly:

  1. Unit Tests: Test individual components like parameter transformation
  2. Integration Tests: Test the full flow from Composio to framework and back
  3. End-to-End Testing: Run a complete scenario with a real API call

Example test structure:

1def test_schema_transformation():
2 """Test that Composio schemas are correctly transformed to framework schemas"""
3 toolset = ComposioToolSet()
4 composio_schema = {...} # Sample Composio schema
5 framework_tool = toolset._wrap_tool(composio_schema)
6
7 # Assert framework tool has expected properties
8 assert framework_tool.name == composio_schema["name"]
9 assert framework_tool.description == composio_schema["description"]
10 # ... more assertions

Best practices for SDK integration

  • Preserve Data Fidelity: Ensure data structures are properly converted between systems
  • Handle Errors Gracefully: Provide clear error messages and follow both systems’ error patterns
  • Document Edge Cases: Note any limitations or special considerations in your README
  • Type Safety: Use proper type annotations and handle type conversions carefully
  • Minimal Dependencies: Don’t add unnecessary dependencies to your integration
  • Comprehensive Examples: Include examples for all common use cases

The best integrations are those that feel native to both systems, requiring minimal special handling by users.

Contribution and maintenance

After creating your integration:

  1. Documentation: Write clear documentation with examples
  2. Tests: Ensure comprehensive test coverage
  3. Pull Request: Submit a PR to the Composio repository
  4. Maintenance: Monitor compatibility with new versions of both Composio and the target framework

Conclusion

Creating a Python SDK integration for Composio allows users to leverage Composio’s rich tool ecosystem within their preferred frameworks. By following this guide, you can create robust, type-safe, and user-friendly integrations that enhance the power of both systems.

Remember that a good integration should feel natural to users of both Composio and the target framework, requiring minimal adaptation of their existing code.