mirror of
https://github.com/crewAIInc/crewAI-examples.git
synced 2026-01-10 06:17:58 -05:00
Adding new CrewAI+LangGraph example
This commit is contained in:
9
CrewAI-LangGraph/.env.example
Normal file
9
CrewAI-LangGraph/.env.example
Normal file
@@ -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
CrewAI-LangGraph/.gitignore
vendored
Normal file
4
CrewAI-LangGraph/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
.env
|
||||
credentials.json
|
||||
token.json
|
||||
BIN
CrewAI-LangGraph/CrewAI-LangGraph.png
Normal file
BIN
CrewAI-LangGraph/CrewAI-LangGraph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
40
CrewAI-LangGraph/README.md
Normal file
40
CrewAI-LangGraph/README.md
Normal file
@@ -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.
|
||||
|
||||

|
||||
|
||||
|
||||
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.
|
||||
0
CrewAI-LangGraph/__init__.py
Normal file
0
CrewAI-LangGraph/__init__.py
Normal file
4
CrewAI-LangGraph/main.py
Normal file
4
CrewAI-LangGraph/main.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from src.graph import WorkFlow
|
||||
|
||||
app = WorkFlow().app
|
||||
app.invoke({})
|
||||
10
CrewAI-LangGraph/requirements.txt
Normal file
10
CrewAI-LangGraph/requirements.txt
Normal file
@@ -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
|
||||
0
CrewAI-LangGraph/src/__init__.py
Normal file
0
CrewAI-LangGraph/src/__init__.py
Normal file
58
CrewAI-LangGraph/src/crew/agents.py
Normal file
58
CrewAI-LangGraph/src/crew/agents.py
Normal file
@@ -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
CrewAI-LangGraph/src/crew/crew.py
Normal file
40
CrewAI-LangGraph/src/crew/crew.py
Normal file
@@ -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
CrewAI-LangGraph/src/crew/tasks.py
Normal file
64
CrewAI-LangGraph/src/crew/tasks.py
Normal file
@@ -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
CrewAI-LangGraph/src/crew/tools.py
Normal file
26
CrewAI-LangGraph/src/crew/tools.py
Normal file
@@ -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, `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)
|
||||
resutl = draft({
|
||||
'to': [email],
|
||||
'subject': subject,
|
||||
'message': message
|
||||
})
|
||||
return f"\nDraft created: {resutl}\n"
|
||||
|
||||
|
||||
|
||||
30
CrewAI-LangGraph/src/graph.py
Normal file
30
CrewAI-LangGraph/src/graph.py
Normal file
@@ -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
CrewAI-LangGraph/src/nodes.py
Normal file
48
CrewAI-LangGraph/src/nodes.py
Normal file
@@ -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
CrewAI-LangGraph/src/state.py
Normal file
7
CrewAI-LangGraph/src/state.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user