Skip to content

Commit

Permalink
Adding new CrewAI+LangGraph example
Browse files Browse the repository at this point in the history
  • Loading branch information
João Moura committed Jan 23, 2024
1 parent 78e8433 commit d4c272f
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 0 deletions.
9 changes: 9 additions & 0 deletions CrewAI-LangGraph/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
OPENAI_API_KEY=...
TAVILY_API_KEY=... (tavily.com)
MY_EMAIL=... (your email)

# Optional
LANGCHAIN_TRACING_V2=...
LANGCHAIN_ENDPOINT=...
LANGCHAIN_API_KEY=...
LANGCHAIN_PROJECT=...
4 changes: 4 additions & 0 deletions CrewAI-LangGraph/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__
.env
credentials.json
token.json
Binary file added CrewAI-LangGraph/CrewAI-LangGraph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions CrewAI-LangGraph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# CrewAI + LangGraph

## Introduction
This is an example of how to use the [CrewAI](https://github.com/joaomdmoura/crewai) with LangChain and LangGraph to automate the process of automatically checking emails and creating drafts. CrewAI orchestrates autonomous AI agents, enabling them to collaborate and execute complex tasks efficiently.

![High level image](./CrewAI-LangGraph.png)


By [@joaomdmoura](https://x.com/joaomdmoura)

- [CrewAI Framework](#crewai-framework)
- [Running the code](#running-the-code)
- [Details & Explanation](#details--explanation)
- [Using Local Models with Ollama](#using-local-models-with-ollama)
- [License](#license)

## CrewAI Framework
CrewAI is designed to facilitate the collaboration of role-playing AI agents. In this example, these agents work together to give a complete stock analysis and investment recommendation

## Running the Code
This example uses GPT-4.

- **Configure Environment**: Copy ``.env.example` and set up the environment variable
- **Setup a credentials.json**: Follow the [google instructions](https://developers.google.com/gmail/api/quickstart/python#authorize_credentials_for_a_desktop_application), once you’ve downloaded the file, name it `credentials.json` and add to the root of the project,
- **Install Dependencies**: Run `pip install -r requirements.txt`
- **Execute the Script**: Run `python main.py`

## Details & Explanation
- **Running the Script**: Execute `python main.py`
- **Key Components**:
- `./src/graph.py`: Class defining the nodes and edges.
- `./src/nodes.py`: Class with the function for each node.
- `./src/state.py`: State declaration.
- `./src/crew/agents.py`: Class defining the CrewAI Agents.
- `./src/crew/taks.py`: Class definig the CrewAI Tasks.
- `./src/crew/crew.py`: Class defining the CrewAI Crew.
- `./src/crew/tools.py`: Class implementing the GmailDraft Tool.

## License
This project is released under the MIT License.
Empty file added CrewAI-LangGraph/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions CrewAI-LangGraph/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from src.graph import WorkFlow

app = WorkFlow().app
app.invoke({})
10 changes: 10 additions & 0 deletions CrewAI-LangGraph/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
crewai==0.1.32
langgraph==0.0.15
langchain-community==0.0.14
python-dotenv==1.0.0
google-search-results==2.1.0
google-api-python-client==2.114.0
google-auth-oauthlib==1.2.0
google-auth-httplib2==0.2.0
beautifulsoup4==4.12.3
tavily-python==0.3.1
Empty file.
58 changes: 58 additions & 0 deletions CrewAI-LangGraph/src/crew/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from langchain_community.agent_toolkits import GmailToolkit
from langchain_community.tools.gmail.get_thread import GmailGetThread
from langchain_community.tools.tavily_search import TavilySearchResults

from textwrap import dedent
from crewai import Agent
from .tools import CreateDraftTool

class EmailFilterAgents():
def __init__(self):
self.gmail = GmailToolkit()

def email_filter_agent(self):
return Agent(
role='Senior Email Analyst',
goal='Filter out non-essential emails like newsletters and promotional content',
backstory=dedent("""\
As a Senior Email Analyst, you have extensive experience in email content analysis.
You are adept at distinguishing important emails from spam, newsletters, and other
irrelevant content. Your expertise lies in identifying key patterns and markers that
signify the importance of an email."""),
verbose=True,
allow_delegation=False
)

def email_action_agent(self):

return Agent(
role='Email Action Specialist',
goal='Identify action-required emails and compile a list of their IDs',
backstory=dedent("""\
With a keen eye for detail and a knack for understanding context, you specialize
in identifying emails that require immediate action. Your skill set includes interpreting
the urgency and importance of an email based on its content and context."""),
tools=[
GmailGetThread(api_resource=self.gmail.api_resource),
TavilySearchResults()
],
verbose=True,
allow_delegation=False,
)

def email_response_writer(self):
return Agent(
role='Email Response Writer',
goal='Draft responses to action-required emails',
backstory=dedent("""\
You are a skilled writer, adept at crafting clear, concise, and effective email responses.
Your strength lies in your ability to communicate effectively, ensuring that each response is
tailored to address the specific needs and context of the email."""),
tools=[
TavilySearchResults(),
GmailGetThread(api_resource=self.gmail.api_resource),
CreateDraftTool.create_draft
],
verbose=True,
allow_delegation=False,
)
40 changes: 40 additions & 0 deletions CrewAI-LangGraph/src/crew/crew.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from crewai import Crew

from .agents import EmailFilterAgents
from .tasks import EmailFilterTasks

class EmailFilterCrew():
def __init__(self):
agents = EmailFilterAgents()
self.filter_agent = agents.email_filter_agent()
self.action_agent = agents.email_action_agent()
self.writer_agent = agents.email_response_writer()

def kickoff(self, state):
print("### Filtering emails")
tasks = EmailFilterTasks()
crew = Crew(
agents=[self.filter_agent, self.action_agent, self.writer_agent],
tasks=[
tasks.filter_emails_task(self.filter_agent, self._format_emails(state['emails'])),
tasks.action_required_emails_task(self.action_agent),
tasks.draft_responses_task(self.writer_agent)
],
verbose=True
)
result = crew.kickoff()
return {**state, "action_required_emails": result}

def _format_emails(self, emails):
emails_string = []
for email in emails:
print(email)
arr = [
f"ID: {email['id']}",
f"- Thread ID: {email['threadId']}",
f"- Snippet: {email['snippet']}",
f"- From: {email['sender']}",
f"--------"
]
emails_string.append("\n".join(arr))
return "\n".join(emails_string)
64 changes: 64 additions & 0 deletions CrewAI-LangGraph/src/crew/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from crewai import Task
from textwrap import dedent

class EmailFilterTasks:
def filter_emails_task(self, agent, emails):
return Task(
description=dedent(f"""\
Analyze a batch of emails and filter out
non-essential ones such as newsletters, promotional content and notifications.
Use your expertise in email content analysis to distinguish
important emails from the rest, pay attention to the sender and avoind invalid emails.
Make sure to filter for the messages actually directed at the user and avoid notifications.
EMAILS
-------
{emails}
Your final answer MUST be a the relevant thread_ids and the sender, use bullet points.
"""),
agent=agent
)

def action_required_emails_task(self, agent):
return Task(
description=dedent("""\
For each email thread, pull and analyze the complete threads using only the actual Thread ID.
understand the context, key points, and the overall sentiment
of the conversation.
Identify the main query or concerns that needs to be
addressed in the response for each
Your final answer MUST be a list for all emails with:
- the thread_id
- a summary of the email thread
- a highlighting with the main points
- identify the user and who he will be answering to
- communication style in the thread
- the sender's email address
"""),
agent=agent
)

def draft_responses_task(self, agent):
return Task(
description=dedent(f"""\
Based on the action-required emails identified, draft responses for each.
Ensure that each response is tailored to address the specific needs
and context outlined in the email.
Assume the persona of the user and mimic the communication style in the thread.
Use the tool provided to draft each of the responses.
When using the tool pass the following input:
- to (sender to be responded)
- subject
- message
You MUST create all drafts before sending your final answer.
Your final answer MUST be a confirmation that all responses have been drafted.
"""),
agent=agent
)
26 changes: 26 additions & 0 deletions CrewAI-LangGraph/src/crew/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from langchain_community.agent_toolkits import GmailToolkit
from langchain_community.tools.gmail.create_draft import GmailCreateDraft
from langchain.tools import tool

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, `[email protected]|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)
resutl = draft({
'to': [email],
'subject': subject,
'message': message
})
return f"\nDraft created: {resutl}\n"



30 changes: 30 additions & 0 deletions CrewAI-LangGraph/src/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from dotenv import load_dotenv
load_dotenv()

from langgraph.graph import StateGraph

from .state import EmailsState
from .nodes import Nodes
from .crew.crew import EmailFilterCrew

class WorkFlow():
def __init__(self):
nodes = Nodes()
workflow = StateGraph(EmailsState)

workflow.add_node("check_new_emails", nodes.check_email)
workflow.add_node("wait_next_run", nodes.wait_next_run)
workflow.add_node("draft_responses", EmailFilterCrew().kickoff)

workflow.set_entry_point("check_new_emails")
workflow.add_conditional_edges(
"check_new_emails",
nodes.new_emails,
{
"continue": 'draft_responses',
"end": 'wait_next_run'
}
)
workflow.add_edge('draft_responses', 'wait_next_run')
workflow.add_edge('wait_next_run', 'check_new_emails')
self.app = workflow.compile()
48 changes: 48 additions & 0 deletions CrewAI-LangGraph/src/nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import os
import time

from langchain_community.agent_toolkits import GmailToolkit
from langchain_community.tools.gmail.search import GmailSearch

class Nodes():
def __init__(self):
self.gmail = GmailToolkit()

def check_email(self, state):
print("# Checking for new emails")
search = GmailSearch(api_resource=self.gmail.api_resource)
emails = search('after:newer_than:1d')
checked_emails = state['checked_emails_ids'] if state['checked_emails_ids'] else []
thread = []
new_emails = []
for email in emails:
if (email['id'] not in checked_emails) and (email['threadId'] not in thread) and ( os.environ['MY_EMAIL'] not in email['sender']):
thread.append(email['threadId'])
new_emails.append(
{
"id": email['id'],
"threadId": email['threadId'],
"snippet": email['snippet'],
"sender": email["sender"]
}
)
checked_emails.extend([email['id'] for email in emails])
return {
**state,
"emails": new_emails,
"checked_emails_ids": checked_emails
}

def wait_next_run(self, state):
print("## Waiting for 180 seconds")
time.sleep(180)
return state

def new_emails(self, state):
if len(state['emails']) == 0:
print("## No new emails")
return "end"
else:
print("## New emails")
return "continue"

7 changes: 7 additions & 0 deletions CrewAI-LangGraph/src/state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import datetime
from typing import TypedDict

class EmailsState(TypedDict):
checked_emails_ids: list[str]
emails: list[dict]
action_required_emails: dict

0 comments on commit d4c272f

Please sign in to comment.