With Composio's managed authentication and tool calling, it's easy to build AI agents that interact with the real world while reducing boilerplate for setup and authentication management. This cookbook will guide you through building and serving agents using Composio
, OpenAI
, and FastAPI
.
- Python3.x
- UV
- Composio API key
- OpenAI API key
- Basic knowledge of OAuth
- Understanding of building HTTP services (preferably using FastAPI)
Let's start with building a simple AI agent embedded with tools from Composio that let's the agent interact with the gmail
service.
from openai import OpenAI
from composio import Composio
from composio_openai import OpenAIProvider
def run_gmail_agent(
composio_client: Composio[OpenAIProvider],
openai_client: OpenAI,
user_id: str, # Composio uses the User ID to store and access user-level authentication tokens.
prompt: str,
):
"""
Run the Gmail agent using composio and openai clients.
"""
# Step 1: Fetch the necessary Gmail tools list with Composio
tools = composio_client.tools.get(
user_id=user_id,
tools=[
"GMAIL_FETCH_EMAILS",
"GMAIL_SEND_EMAIL",
"GMAIL_CREATE_EMAIL_DRAFT"
]
)
# Step 2: Use OpenAI to generate a response based on the prompt and available tools
response = openai_client.chat.completions.create(
model="gpt-4.1",
tools=tools,
messages=[{"role": "user", "content": prompt}],
)
# Step 3: Handle tool calls with Composio and return the result
result = composio_client.provider.handle_tool_calls(response=response, user_id=user_id)
return result
NOTE: This is a simple agent without state management and agentic loop implementation, so the agent can't perform complicated tasks. If you want to understand how composio can be used with agentic loops, check other cookbooks with more agentic frameworks.
To invoke this agent, authenticate your users with Composio's managed authentication service.
To authenticate your users with Composio you need an authentication config for the given app, In this case you need one for gmail. To create an authentication config for gmail
you need client_id
and client_secret
from your Google OAuth Console. Once you have the required credentials you can use the following piece of code to set up authentication for gmail
.
from composio import Composio
from composio_openai import OpenAIProvider
def create_auth_config(composio_client: Composio[OpenAIProvider]):
"""
Create a auth config for the gmail toolkit.
"""
client_id = os.getenv("GMAIL_CLIENT_ID")
client_secret = os.getenv("GMAIL_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET must be set")
return composio_client.auth_configs.create(
toolkit="GMAIL",
options={
"name": "default_gmail_auth_config",
"type": "use_custom_auth",
"auth_scheme": "OAUTH2",
"credentials": {
"client_id": client_id,
"client_secret": client_secret,
},
},
)
This will create a Gmail authentication config to authenticate your app’s users. Ideally, create one authentication object per project, so check for an existing auth config before creating a new one.
def fetch_auth_config(composio_client: Composio[OpenAIProvider]):
"""
Fetch the auth config for a given user id.
"""
auth_configs = composio_client.auth_configs.list()
for auth_config in auth_configs.items:
if auth_config.toolkit == "GMAIL":
return auth_config
return None
NOTE: Composio platform provides composio managed authentication for some apps to fast-track your development,
gmail
being one of them. You can use these default auth configs for development, but for production, always use your own oauth app configuration.
Once you have authentication management in place, we can start with connecting your users to your gmail
app. Let's implement a function to connect users to your gmail
app via composio.
from fastapi import FastAPI
# Function to initiate a connected account
def create_connection(composio_client: Composio[OpenAIProvider], user_id: str):
"""
Create a connection for a given user id and auth config id.
"""
# Fetch or create the auth config for the gmail toolkit
auth_config = fetch_auth_config(composio_client=composio_client)
if not auth_config:
auth_config = create_auth_config(composio_client=composio_client)
# Create a connection for the user
return composio_client.connected_accounts.initiate(
user_id=user_id,
auth_config_id=auth_config.id,
)
# Setup FastAPI
app = FastAPI()
# Connection initiation endpoint
@app.post("/connection/create")
def _create_connection(
request: CreateConnectionRequest,
composio_client: ComposioClient,
) -> dict:
"""
Create a connection for a given user id.
"""
# For demonstration, using a default user_id. Replace with real user logic in production.
user_id = "default"
# Create a new connection for the user
connection_request = create_connection(composio_client=composio_client, user_id=user_id)
return {
"connection_id": connection_request.id,
"redirect_url": connection_request.redirect_url,
}
Now, you can make a request to this endpoint on your client app, and your user will get a URL which they can use to authenticate.
We will use FastApi
to build an HTTP service that authenticates your users and lets them interact with your agent. This guide will provide best practices for using composio client in production environments.
FastAPI allows dependency injection to simplify the usage of SDK clients that must be singletons. We recommend using composio SDK client as singleton.
import os
import typing_extensions as te
from composio import Composio
from composio_openai import OpenAIProvider
from fastapi import Depends
_composio_client: Composio[OpenAIProvider] | None = None
def provide_composio_client():
"""
Provide a Composio client.
"""
global _composio_client
if _composio_client is None:
_composio_client = Composio(provider=OpenAIProvider())
return _composio_client
ComposioClient = te.Annotated[Composio, Depends(provide_composio_client)]
"""
A Composio client dependency.
"""
Check dependencies module for more details.
When invoking an agent, make sure you validate the user_id
.
def check_connected_account_exists(
composio_client: Composio[OpenAIProvider],
user_id: str,
):
"""
Check if a connected account exists for a given user id.
"""
# Fetch all connected accounts for the user
connected_accounts = composio_client.connected_accounts.list(user_ids=[user_id])
# Check if there's an active connected account
for account in connected_accounts.items:
if account.status == "ACTIVE":
return True
# Ideally you should not have inactive accounts, but if you do, delete them.
print(f"[warning] inactive account {account.id} found for user id: {user_id}")
return False
def validate_user_id(user_id: str, composio_client: ComposioClient):
"""
Validate the user id, if no connected account is found, create a new connection.
"""
if check_connected_account_exists(composio_client=composio_client, user_id=user_id):
return user_id
raise HTTPException(
status_code=404, detail={"error": "No connected account found for the user id"}
)
# Endpoint: Run the Gmail agent for a given user id and prompt
@app.post("/agent")
def _run_gmail_agent(
request: RunGmailAgentRequest,
composio_client: ComposioClient,
openai_client: OpenAIClient, # OpenAI client will be injected as dependency
) -> List[ToolExecutionResponse]:
"""
Run the Gmail agent for a given user id and prompt.
"""
# For demonstration, using a default user_id. Replace with real user logic in production.
user_id = "default"
# Validate the user id before proceeding
user_id = validate_user_id(user_id=user_id, composio_client=composio_client)
# Run the Gmail agent using Composio and OpenAI
result = run_gmail_agent(
composio_client=composio_client,
openai_client=openai_client,
user_id=user_id,
prompt=request.prompt,
)
return result
NOTE: Check server module for service implementation
So far, we have created an agent with ability to interact with gmail
using the composio
SDK, functions to manage connected accounts for users and a FastAPI service. Now let's run the service.
Before proceeding, check the code for utility endpoints not discussed in the cookbook
-
Clone the repository
git clone git@github.com:composiohq/composio-fastapi cd composio-fastapi/
-
Setup environment
cp .env.example .env
Fill the api keys
COMPOSIO_API_KEY= OPENAI_API_KEY=
Create the virtual env
make env source .venv/bin/activate
-
Run the HTTP server
uvicorn simple_gmail_agent.server.api:create_app --factory
Composio reduces boilerplate for building AI agents that access and use various apps. In this cookbook, to build Gmail integration without Composio, you would have to write code to
- manage Gmail OAuth app
- manage user connections
- tools for your agents to interact with Gmail
Using Composio simplifies all of the above to a few lines of code as shown in the cookbook.
🎯 Effective Prompts:
- Be specific: "Send email to john@company.com about tomorrow's 2pm meeting" works better than "send email"
- Include context: "Reply to Sarah's email about the budget with our approval"
- Use natural language: The agent understands conversational requests
🔒 User Management:
- Use unique, consistent
user_id
values for each person - Each user maintains their own Gmail connection
- User IDs can be email addresses, usernames, or any unique identifier
Connection Issues:
- Ensure your
.env
file has validCOMPOSIO_API_KEY
andOPENAI_API_KEY
- Check if the user has completed Gmail authorization.
- Verify the user_id matches exactly between requests
API Errors:
- Check the server logs for detailed error messages
- Ensure request payloads match the expected format
- Visit
/docs
endpoint for API schema validation
Gmail API Limits:
- Gmail has rate limits; the agent will handle these gracefully
- For high-volume usage, consider implementing request queuing
Assuming the server is running locally on http://localhost:8000
.
curl -X POST http://localhost:8000/connection/exists
Note: The body fields are required by the API schema, but are ignored internally in this example service.
curl -X POST http://localhost:8000/connection/create \
-H "Content-Type: application/json" \
-d '{
"user_id": "default",
"auth_config_id": "AUTH_CONFIG_ID_FOR_GMAIL_FROM_THE_COMPOSIO_DASHBOARD"
}'
Response includes connection_id
and redirect_url
. Complete the OAuth flow at the redirect_url
.
Use the connection_id
returned from the create step.
curl -X POST http://localhost:8000/connection/status \
-H "Content-Type: application/json" \
-d '{
"user_id": "default",
"connection_id": "CONNECTION_ID_FROM_CREATE_RESPONSE"
}'
Requires an active connected account for the default
user.
curl -X POST http://localhost:8000/agent \
-H "Content-Type: application/json" \
-d '{
"user_id": "default",
"prompt": "Summarize my latest unread emails from the last 24 hours."
}'
curl -X POST http://localhost:8000/actions/fetch_emails \
-H "Content-Type: application/json" \
-d '{
"user_id": "default",
"limit": 5
}'
These examples are intended solely for testing purposes.