Lately, my calendar has become so hectic that I could really use some help managing it. In fact, I realized it’s a very common problem among my colleagues at Composio.
We get tons of e-mails for multiple events, and managing them has become a nightmare.
I set out to create an automation that could read my incoming emails, understand their content, determine if an event needed to be scheduled, and finally draft an email with the invitation link.
Traditional methods wouldn’t be effective for this task, given the challenges involved:
- Requiring contextual understanding of emails.
- Needing seamless integration with Gmail and Google Calendar.
I opted for a large language model (LLM) to tackle the first challenge. The real complexity, however, lies in integrating Gmail and Calendar with LLMs, especially since these services can be notoriously finicky.
Now, imagine a library that handles Google OAuth for you, simplifying the integration of API endpoints with LLMs. It would be a game-changer, right?
That's precisely what we offer at Composio.
Composio - The First AI Integration Platform
Here’s a quick introduction about us.
Composio is an open-source tooling infrastructure for building robust and reliable AI applications. We provide over 100+ tools and integrations across industry verticals from CRM, HRM, and Sales to Productivity, Dev, and Social Media.
We handle user authentication and authorization for all these applications and make connecting all the API endpoints with various AI models and frameworks simple. So, you spend more time delivering shareholder value than troubleshooting unnecessary bugs.
Please help us with a star. 🥹
It would help us to create more articles like this 💖
Star the Composio.dev repository ⭐
LangGraph - Framework for Building Production-ready AI Apps
To complete the project, you will need another dependency, LangGraph.
LangGraph is an open-source framework for building stateful AI workflows. It defines workflows using a modular graphical architecture, making it easier to control each step in the workflow.
Key Components
-
State:
- A shared data structure that represents the current snapshot of your application.
- It can be any Python type, but is typically a
TypedDict
orPydantic
BaseModel
.
-
Nodes:
- Python functions that encode the logic of your agents.
- They receive the current State as input, perform some computation or side-effect, and return an updated State.
-
Edges:
- Python functions that determine which Node to execute next based on the current State.
- They can be conditional branches or fixed transitions.
It is highly recommended that you go through this guide before moving ahead.
In this article, you will build an end-to-end AI Bot that can read incoming emails, process them, decide if they need to be scheduled, and finally draft an invitation email to the recipient with a Calendar link.
Overall Workflow
Let's have a look at the overall workflow.
- Connect Gmail and Google Calendar with Composio.
- Enable trigger in Composio to receive mail.
- Create the AI bot with LangGraph.
- The bot polls Gmail for incoming emails.
- The emails are passed to the bot for further analysis.
- If the email contains event scheduling information:
- Yes: The bot fetches free slots from the Calendar and drafts a suitable email with a scheduled event invitation link.
- No: Ignores.
Prerequisites to Building the AI Bot
You will need the following requirements to complete the project.
- Composio user account. Create one here for free.
- OpenAI API credits. You may use open-source LLMs like Llama from Groq as well.
Let’s Build it!
Setting Up Environment
The first rule of Python development is always to create a virtual environment.
python -m venv gmail-bot
cd gmail-bot
source bit/activate
Now, install the dependencies.
pip install composio-core
pip install composio-langgraph
pip install python-dotenv
-
composio-core
: Core library for all Composio’s functions. -
composio-langgraph
: Composio’s LangGraph plug-in. -
python-dotenv
: To set environment variables from the.env
file.
Create a .env file and set the OpenAI API key.
OPENAI_API_KEY="YOUR_KEY"
Integrate Gmail and Google Calendar
To integrate Gmail and Calendar, we will use Composio’s dedicated CLI, but log in to your user account before that.
composio login
Finish the login flow to proceed further. Now, Refresh apps.
composio apps update
Integrate Gmail
composio add gmail
Integrate Google Calendaar
composio add googlecalendar
Finish the integration flow to add these services to your user account.
The best thing about Composio is you can monitor these integrations on your dashboard.
Next, enable the trigger to receive emails when someone sends them.
composio triggers enable gmail_new_gmail_message
You can even monitor triggers from the dashboard.
Building the AI Bot
As we discussed before, LangGraph defines workflows with Nodes and Edges.
This workflow will have four key components: START, END, a Tool Node and a Scheduler Node.
- START: Entry point to the workflow.
- Tool Node: Handles invoking the tools available to the LLM.
- Scheduler Node: Manages processing text inputs from emails, tool calls, and more.
- END: Exit point of the workflow.
When the Scheduler Node determines that a tool needs to be called, it hands over control to the Tool Node, which then executes the tool and sends the results and control back to the Scheduler Node.
Once the execution is complete, the Scheduler Node terminates the process.
Here is a high-level schematic diagram.
Now, let’s hop on to Coding the workflow.
Importing Libraries
First, import the required libraries and set the environment variable from the .env using.
from typing import Literal, Annotated, Sequence, TypedDict
import operator
import re
from datetime import datetime
from dotenv import load_dotenv
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, StateGraph
from langgraph.prebuilt import ToolNode
from composio_langgraph import Action, ComposioToolSet
from composio.client.collections import TriggerEventData
# Setup
load_dotenv()
Now, define the constants.
# Constants
SCHEDULER_AGENT_NAME = "Scheduler"
TOOL_NODE_NAME = "ToolNode"
DATE_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
Defining Tools
Next, we will define the tools and Tool Node that we will use to schedule events and draft emails.
# Initialize LLM and tools
llm = ChatOpenAI(model="gpt-4-turbo")
composio_toolset = ComposioToolSet()
schedule_tools = composio_toolset.get_actions(
actions=[
Action.GOOGLECALENDAR_FIND_FREE_SLOTS,
Action.GOOGLECALENDAR_CREATE_EVENT,
]
)
email_tools = composio_toolset.get_actions(actions=[Action.GMAIL_CREATE_EMAIL_DRAFT])
tools = [*schedule_tools, *email_tools]
tool_node = ToolNode(tools)
The above code block,
- Initializes a Large Language Model (LLM) using OpenAI's GPT-4 Turbo model.
- Creates an instance of the Composio toolset, which provides access to the Google service integrations.
- Fetches the actions to find free slots and create events from the Composio toolset related to Google Calendar.
- Fetches the action to create a draft email in Gmail.
- Creates a tool node with all the actions to be used in LangGraph.
Note: The Tool Node abstracts away the calling and handling tool calls by the LLM.
Defining Prompts
Next, define the system prompt. This is crucial as it will give the LLM the necessary context to handle incoming mail.
# System prompt
scheduler_system_prompt = f"""
You are an AI assistant specialized in analyzing emails, creating calendar events, and drafting response emails. Your primary tasks are:
1. Analyze email content:
a. Understand the email received from the sender.
b. Determine if an event should be created based on the email content.
c. Extract relevant information such as proposed meeting times, topics, and participants.
2. Manage calendar events:
a. If an event should be created, use the Google Calendar Find Free Slots action to identify available time slots.
b. Once a suitable slot is found, use the Google Calendar Create Event action to schedule the event.
c. Ensure to invite the email sender and any other relevant participants to the event.
3. Draft response emails:
a. If an event was created, draft a confirmation email for the sender.
b. The email should include:
- A clear subject line (e.g., "Meeting Scheduled")
- A brief description of the scheduled meeting's purpose
- The date, time, and duration of the event
- Any other relevant details or instructions for the participants
Remember:
- The current date and time is {DATE_TIME}.
- All conversations and scheduling occur in the IST timezone.
- Be courteous and professional in all communications.
- If you encounter any errors when making function calls, try passing an empty config ({{"config": {{}}}}).
- Always provide a FINAL ANSWER when you've completed all necessary tasks.
You aim to efficiently manage scheduling and communication, ensuring a smooth experience for all parties involved.
"""
We defined a clear and step-by-step system prompt to steer the LLM.
Defining AgentState and Helper Functions
In LangGraph, the AgentState keeps track of the state of the agentic workflow at any given point.
# Define AgentState
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
sender: str
# Helper functions
def create_agent_node(agent, name):
def agent_node(state):
result = agent.invoke(state)
if not isinstance(result, ToolMessage):
result = AIMessage(**result.dict(exclude={"type", "name"}), name=name)
return {"messages": [result], "sender": name}
return agent_node
def create_agent(system_prompt, tools):
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
MessagesPlaceholder(variable_name="messages"),
])
return prompt | llm.bind_tools(tools)
Here is what is going on,
-
AgentState(TypedDict)
: Defines a dictionary structure for an agent's state, containing a list of messages and the sender's name. -
create_agent_node(agent, name)
: Creates a function that processes an agent's state, invokes the agent, and formats the result as a message with the agent's name as the sender. -
create_agent(system_prompt, tools)
: This function constructs an agent by creating a prompt template from system instructions and messages and binding the LLM to the specified tools.
Create an agent using the system prompt, the tools, and the agent node using the scheduler_agent and the constant SCHEDULER_AGENT_NAME.
scheduler_agent = create_agent(scheduler_system_prompt, tools)
scheduler_node = create_agent_node(scheduler_agent, SCHEDULER_AGENT_NAME)
This node handles everything related to scheduling Google Calendar events, from checking slots to confirming events.
Defining the Nodes, Edges, and the Workflow
As we know, LangGraph follows a directed graph structure where each node contains an executable (a code that can be executed) and edges that direct the flow of information.
# Create workflow
workflow = StateGraph(AgentState)
workflow.add_node(SCHEDULER_AGENT_NAME, scheduler_node)
workflow.add_node(TOOL_NODE_NAME, tool_node)
In the above code block,
- We defined the workflow graph using
StateGraph(AgentState)
. The AgentState keeps track of state objects. - Two nodes, tool_node and scheduler_node, were added.
Now, define edges to direct the flow of information.
# Router function
def router(state) -> Literal["call_tool", "__end__", "continue"]:
last_message = state["messages"][-1]
if last_message.tool_calls:
return "call_tool"
if "FINAL ANSWER" in last_message.content:
return "__end__"
return "continue"
workflow.add_edge(START, SCHEDULER_AGENT_NAME)
workflow.add_edge(SCHEDULER_AGENT_NAME, END)
This is the code for defining the edges of our workflow.
- The router returns the Node name from the agent state. This helps in navigating the workflow. It can also transfer flow control from one node to another and end the workflow.
- We defined two normal edges that move from START→SCHEDULER_AGENT_NAME→END.
Conditional Edges
The workflow includes two key conditional edges:
-
Conditional Edge from
SCHEDULER_AGENT_NAME
to Various Nodes:pythonCopy code workflow.add_conditional_edges( SCHEDULER_AGENT_NAME, router, { "continue": SCHEDULER_AGENT_NAME, "call_tool": TOOL_NODE_NAME, }, )
- Purpose: This edge determines the next step after the scheduler agent processes the current state.
-
Condition: The
router
function checks the content of the last message in the state. -
Possible Outcomes:
-
"continue": If the
router
function returns"continue"
, the workflow stays in theSCHEDULER_AGENT_NAME
node, allowing the agent to keep processing. -
"call_tool": If the
router
function returns"call_tool"
, the workflow transitions to theTOOL_NODE_NAME
node, where the relevant tool (e.g., find free slots or create an event) is invoked.
-
"continue": If the
-
Conditional Edge from
TOOL_NODE_NAME
toSCHEDULER_AGENT_NAME
:pythonCopy code workflow.add_conditional_edges( TOOL_NODE_NAME, lambda x: x["sender"], {SCHEDULER_AGENT_NAME: SCHEDULER_AGENT_NAME}, )
- Purpose: This edge determines where to route the workflow after invoking a tool (like Google Calendar or Gmail).
-
Condition: The lambda function
lambda x: x["sender"]
checks the sender of the last message. -
Outcome: If the sender is the
SCHEDULER_AGENT_NAME
, the workflow returns to theSCHEDULER_AGENT_NAME
node to continue processing.
Finally, compile the workflow.
app = workflow.compile()
Setting Up the Event Listener
The next step is to define the event listener. This will be responsible for fetching mail when someone sends an email to your connected Gmail account.
def extract_sender_email(payload):
if not any(header.get("name") == "Delivered-To" and header.get("value") for header in payload["headers"]):
return None
for header in payload["headers"]:
if header["name"] == "From":
match = re.search(r"[\w\.-]+@[\w\.-]+", header["value"])
return match.group(0) if match else None
return None
def process_email(email_sender, email_content, thread_id):
final_state = app.invoke({
"messages": [
HumanMessage(content=f"""
Please process the email
Email sender: {email_sender}
Email content: {email_content}
Thread id: {thread_id}
""")
]
})
return final_state["messages"][-1].content
listener = composio_toolset.create_trigger_listener()
@listener.callback(filters={"trigger_name": "gmail_new_gmail_message"})
def callback_new_message(event: TriggerEventData) -> None:
payload = event.payload
thread_id = payload.get("threadId")
email_content = payload.get("snippet")
email_sender = extract_sender_email(payload["payload"])
if email_sender is None:
print("No sender email found")
return
print(f"Processing email from: {email_sender}")
output = process_email(email_sender, email_content, thread_id)
print("Final output:", output)
listener.listen()
So, here is the explanation of the above code block
-
extract_sender_email(payload)
: Extracts the sender's email address from the email payload headers, returning it if found orNone
if not. -
process_email(email_sender, email_content, thread_id)
: Invokes the workflow with the email details and returns the final processed message content. -
Listener Setup (
listener = composio_toolset.create_trigger_listener()
): Creates a listener to monitor for specific events, like new Gmail messages. -
@listener.callback(filters={"trigger_name": "gmail_new_gmail_message"})
: Defines a callback function that triggers when a new Gmail message event is detected, processing the email through the workflow. -
listener.listen()
: This command starts the listener and makes it actively listen for and respond to new Gmail messages by invoking the callback function.
After completing all the steps, run the Python file to set up the event listener.
The next time you email the configured Gmail account, the bot will process it and determine whether to schedule an event or pass it along. If an event is scheduled, a draft email will be prepared for your review, allowing you to send it if desired.
You can find the full code here: Scheduling Agent
Thank you for reading! 🎉
Help me out!
If you feel this article helped you better understand AI-powered event automation, I would be super happy if you could give us a star! Let me know in the comments.
Top comments (2)
It would be way better if you made a YouTube tutorial on how to implement the AI bot. I have no idea what to do with the copied code snippets
Some comments may only be visible to logged-in visitors. Sign in to view all comments.