Skip to content

Commit

Permalink
Merge pull request #169 from crewAIInc/flow-examples
Browse files Browse the repository at this point in the history
Flow examples
  • Loading branch information
joaomdmoura authored Oct 4, 2024
2 parents 8e6caa3 + 3bdee63 commit 660c7db
Show file tree
Hide file tree
Showing 54 changed files with 7,861 additions and 0 deletions.
3 changes: 3 additions & 0 deletions email_auto_responder_flow/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
__pycache__/
credentials.json
1,946 changes: 1,946 additions & 0 deletions email_auto_responder_flow/Automating_Tasks_with_CrewAI.md

Large diffs are not rendered by default.

Binary file added email_auto_responder_flow/Email_Flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions email_auto_responder_flow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Email Auto Responder Flow

Welcome to the Email Auto Responder Flow project, powered by [crewAI](https://crewai.com). This example demonstrates how you can leverage Flows from crewAI to automate the process of checking emails and creating draft responses. By utilizing Flows, the process becomes much simpler and more efficient.

## Background

In this project, we've taken one of our old example repositories, [CrewAI-LangGraph](https://github.com/crewAIInc/crewAI-examples/tree/main/CrewAI-LangGraph), and repurposed it to now use Flows. This showcases the power and simplicity of Flows in orchestrating AI agents to automate tasks like checking emails and creating drafts. Flows provide a more straightforward and powerful alternative to LangGraph, making it easier to build and manage complex workflows.

### High-Level Diagram

Below is a high-level diagram of the Email Auto Responder Flow:

![High-level Diagram](./Email_Flow.png)

This diagram illustrates the flow of tasks from fetching new emails to generating draft responses.

## Overview

This flow will guide you through the process of setting up an automated email responder. Here's a brief overview of what will happen in this flow:

1. **Fetch New Emails**: The flow starts by using the `EmailFilterCrew` to check for new emails. It updates the state with any new emails and their IDs.

2. **Generate Draft Responses**: Once new emails are fetched, the flow formats these emails and uses the `EmailFilterCrew` to generate draft responses for each email.

This flow is a great example of using Flows as a background worker that runs continuously to help you out. By following this flow, you can efficiently automate the process of checking emails and generating draft responses, leveraging the power of multiple AI agents to handle different aspects of the email processing workflow.

## Installation

Ensure you have Python >=3.10 <=3.13 installed on your system. This project uses [Poetry](https://python-poetry.org/) for dependency management and package handling, offering a seamless setup and execution experience.

First, if you haven't already, install Poetry:

```bash
pip install poetry
```

Next, navigate to your project directory and install the dependencies:

1. First lock the dependencies and then install them:

```bash
crewai install
```

### Customizing & Dependencies

**Add your `OPENAI_API_KEY` into the `.env` file**
**Add your `SERPER_API_KEY` into the `.env` file**
**Add your `TAVILY_API_KEY` into the `.env` file**
**Add your `MY_EMAIL` into the `.env` file**

To customize the behavior of the email auto responder, you can update the agents and tasks defined in the `EmailFilterCrew`. If you want to adjust the flow itself, you will need to modify the flow in `main.py`.

- **Agents and Tasks**: Modify `src/email_auto_responder_flow/crews/email_filter_crew/email_filter_crew.py` to define your agents and tasks. This is where you can customize how emails are filtered and how draft responses are generated.

- **Flow Adjustments**: Modify `src/email_auto_responder_flow/main.py` to adjust the flow. This is where you can change how the flow orchestrates the different crews and tasks.

### Setting Up Google Credentials

To enable the email auto responder to access your Gmail account, you need to set up a `credentials.json` file. Follow these steps:

1. **Set Up Google Account**: Follow the [Google instructions](https://developers.google.com/gmail/api/quickstart/python#authorize_credentials_for_a_desktop_application) to set up your Google account and obtain the `credentials.json` file.

2. **Download and Place `credentials.json`**: Once you’ve downloaded the file, name it `credentials.json` and place it in the root of the project.

## Running the Project

To kickstart your crew of AI agents and begin task execution, run this from the root folder of your project:

```bash
crewai run
```

This command initializes the email_auto_responder_flow, assembling the agents and assigning them tasks as defined in your configuration.

When you kickstart the flow, it will orchestrate multiple crews to perform the tasks. The flow will first fetch new emails, then create and run a crew to generate draft responses.

## Understanding Your Flow

The email_auto_responder_flow is composed of multiple AI agents, each with unique roles, goals, and tools. These agents collaborate on a series of tasks, defined in `config/tasks.yaml`, leveraging their collective skills to achieve complex objectives. The `config/agents.yaml` file outlines the capabilities and configurations of each agent in your flow.

### Flow Structure

1. **EmailFilterCrew**: This crew is responsible for checking for new emails and updating the state with any new emails and their IDs.

2. **Generate Draft Responses**: Once new emails are fetched, this step formats the emails and uses the `EmailFilterCrew` to generate draft responses for each email.

By understanding the flow structure, you can see how multiple crews are orchestrated to work together, each handling a specific part of the email processing workflow. This modular approach allows for efficient and scalable email automation.

## Support

For support, questions, or feedback regarding the Email Auto Responder Flow or crewAI:

- Visit our [documentation](https://docs.crewai.com)
- Reach out to us through our [GitHub repository](https://github.com/joaomdmoura/crewai)
- [Join our Discord](https://discord.com/invite/X4JWnZnxPb)
- [Chat with our docs](https://chatg.pt/DWjSBZn)

Let's create wonders together with the power and simplicity of crewAI.
23 changes: 23 additions & 0 deletions email_auto_responder_flow/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[tool.poetry]
name = "email_auto_responder_flow"
version = "0.1.0"
description = "email_auto_responder_flow using crewAI"
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = ">=3.10,<=3.13"
crewai = { extras = ["tools"], version = ">=0.67.1,<1.0.0" }
asyncio = "*"
langchain-tools = "^0.1.34"
crewai-tools = "^0.12.0"
google-auth-oauthlib = "^1.2.1"
google-api-python-client = "^2.145.0"

[tool.poetry.scripts]
email_auto_responder_flow = "email_auto_responder_flow.main:main"
run_flow = "email_auto_responder_flow.main:main"
plot_flow = "email_auto_responder_flow.main:plot"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
email_followup_agent:
role: >
HR Coordinator
goal: >
Compose personalized follow-up emails to candidates based on their bio and whether they are being pursued for the job.
If we are proceeding, request availability for a Zoom call. Otherwise, send a polite rejection email.
backstory: >
You are an HR professional with excellent communication skills and a talent for crafting personalized and thoughtful
emails to job candidates. You understand the importance of maintaining a positive and professional tone in all correspondence.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
send_followup_email:
description: >
Compose personalized follow-up emails for candidates who applied to a specific job.
You will use the candidate's name, bio, and whether the company wants to proceed with them to generate the email.
If the candidate is proceeding, ask them for their availability for a Zoom call in the upcoming days.
If not, send a polite rejection email.
CANDIDATE DETAILS
-----------------
Candidate ID: {candidate_id}
Name: {name}
Bio:
{bio}
PROCEEDING WITH CANDIDATE: {proceed_with_candidate}
ADDITIONAL INSTRUCTIONS
-----------------------
- If we are proceeding, ask for their availability for a Zoom call within the next few days.
- If we are not proceeding, send a polite rejection email, acknowledging their effort in applying and appreciating their time.
expected_output: >
A personalized email based on the candidate's information. It should be professional and respectful,
either inviting them for a Zoom call or letting them know we are pursuing other candidates.
agent: email_followup_agent
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import SerperDevTool
from langchain_community.tools.gmail.get_thread import GmailGetThread
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_openai import ChatOpenAI

from email_auto_responder_flow.tools.create_draft import CreateDraftTool


@CrewBase
class EmailFilterCrew:
"""Email Filter Crew"""

agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
llm = ChatOpenAI(model="gpt-4o")

@agent
def email_filter_agent(self) -> Agent:
search_tool = SerperDevTool()
return Agent(
config=self.agents_config["email_filter_agent"],
tools=[search_tool],
llm=self.llm,
verbose=True,
allow_delegation=True,
)

@agent
def email_action_agent(self) -> Agent:
gmail = GmailGetThread()
return Agent(
config=self.agents_config["email_action_agent"],
llm=self.llm,
verbose=True,
tools=[
GmailGetThread(api_resource=gmail.api_resource),
TavilySearchResults(),
],
)

@agent
def email_response_writer(self) -> Agent:
gmail = GmailGetThread()
return Agent(
config=self.agents_config["email_response_writer"],
llm=self.llm,
verbose=True,
tools=[
TavilySearchResults(),
GmailGetThread(api_resource=gmail.api_resource),
CreateDraftTool.create_draft,
],
)

@task
def filter_emails_task(self) -> Task:
return Task(config=self.tasks_config["filter_emails"])

@task
def action_required_emails_task(self) -> Task:
return Task(config=self.tasks_config["action_required_emails"])

@task
def draft_responses_task(self) -> Task:
return Task(config=self.tasks_config["draft_responses"])

@crew
def crew(self) -> Crew:
"""Creates the Email Filter Crew"""
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
)
73 changes: 73 additions & 0 deletions email_auto_responder_flow/src/email_auto_responder_flow/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python
import asyncio
import time
from typing import List

from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel

from email_auto_responder_flow.types import Email
from email_auto_responder_flow.utils.emails import check_email, format_emails

from .crews.email_filter_crew.email_filter_crew import EmailFilterCrew


class AutoResponderState(BaseModel):
emails: List[Email] = []
checked_emails_ids: set[str] = set()


class EmailAutoResponderFlow(Flow[AutoResponderState]):
initial_state = AutoResponderState

@start("wait_next_run")
def fetch_new_emails(self):
print("Kickoff the Email Filter Crew")
new_emails, updated_checked_email_ids = check_email(
checked_emails_ids=self.state.checked_emails_ids
)

self.state.emails = new_emails
self.state.checked_emails_ids = updated_checked_email_ids

@listen(fetch_new_emails)
def generate_draft_responses(self):
print("Current email queue: ", len(self.state.emails))
if len(self.state.emails) > 0:
print("Writing New emails")
emails = format_emails(self.state.emails)

EmailFilterCrew().crew().kickoff(inputs={"emails": emails})

self.state.emails = []

print("Waiting for 180 seconds")
time.sleep(180)


async def run_flow():
"""
Run the flow.
"""
email_auto_response_flow = EmailAutoResponderFlow()
email_auto_response_flow.kickoff()


async def plot_flow():
"""
Plot the flow.
"""
email_auto_response_flow = EmailAutoResponderFlow()
email_auto_response_flow.plot()


def main():
asyncio.run(run_flow())


def plot():
asyncio.run(plot_flow())


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from langchain.tools import tool
from langchain_community.agent_toolkits import GmailToolkit
from langchain_community.tools.gmail.create_draft import GmailCreateDraft


class CreateDraftTool:
@tool("Create Draft")
def create_draft(data):
"""
Useful to create an email draft.
The input to this tool should be a pipe (|) separated text
of length 3 (three), representing who to send the email to,
the subject of the email and the actual message.
For example, `lorem@ipsum.com|Nice To Meet You|Hey it was great to meet you.`.
"""
email, subject, message = data.split("|")
gmail = GmailToolkit()
draft = GmailCreateDraft(api_resource=gmail.api_resource)
result = draft({"to": [email], "subject": subject, "message": message})
return f"\nDraft created: {result}\n"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel


class Email(BaseModel):
id: str
threadId: str
snippet: str
sender: str
Loading

0 comments on commit 660c7db

Please sign in to comment.