mirror of
https://github.com/crewAIInc/crewAI-examples.git
synced 2026-04-23 03:00:08 -04:00
3
email_auto_responder_flow/.gitignore
vendored
Normal file
3
email_auto_responder_flow/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.env
|
||||
__pycache__/
|
||||
credentials.json
|
||||
1946
email_auto_responder_flow/Automating_Tasks_with_CrewAI.md
Normal file
1946
email_auto_responder_flow/Automating_Tasks_with_CrewAI.md
Normal file
File diff suppressed because it is too large
Load Diff
BIN
email_auto_responder_flow/Email_Flow.png
Normal file
BIN
email_auto_responder_flow/Email_Flow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
99
email_auto_responder_flow/README.md
Normal file
99
email_auto_responder_flow/README.md
Normal file
@@ -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:
|
||||
|
||||

|
||||
|
||||
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
email_auto_responder_flow/pyproject.toml
Normal file
23
email_auto_responder_flow/pyproject.toml
Normal file
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
@@ -0,0 +1,8 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Email(BaseModel):
|
||||
id: str
|
||||
threadId: str
|
||||
snippet: str
|
||||
sender: str
|
||||
@@ -0,0 +1,65 @@
|
||||
import os
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
from langchain_community.agent_toolkits import GmailToolkit
|
||||
from langchain_community.tools.gmail.search import GmailSearch
|
||||
|
||||
from email_auto_responder_flow.types import Email
|
||||
|
||||
|
||||
def check_email(checked_emails_ids: set[str]) -> tuple[list[Email], set[str]]:
|
||||
print("# Checking for new emails")
|
||||
|
||||
gmail = GmailToolkit()
|
||||
search = GmailSearch(api_resource=gmail.api_resource)
|
||||
emails = search("after:newer_than:1d")
|
||||
thread = []
|
||||
new_emails: List[Email] = []
|
||||
for email in emails:
|
||||
if (
|
||||
(email["id"] not in checked_emails_ids)
|
||||
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_ids.update([email["id"] for email in emails])
|
||||
return new_emails, checked_emails_ids
|
||||
|
||||
|
||||
def wait_next_run(state):
|
||||
print("## Waiting for 180 seconds")
|
||||
time.sleep(180)
|
||||
return state
|
||||
|
||||
|
||||
def new_emails(state):
|
||||
if len(state["emails"]) == 0:
|
||||
print("## No new emails")
|
||||
return "end"
|
||||
else:
|
||||
print("## New emails")
|
||||
return "continue"
|
||||
|
||||
|
||||
def format_emails(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']}",
|
||||
"--------",
|
||||
]
|
||||
emails_string.append("\n".join(arr))
|
||||
return "\n".join(emails_string)
|
||||
2
lead-score-flow/.gitignore
vendored
Normal file
2
lead-score-flow/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
__pycache__/
|
||||
1946
lead-score-flow/Automating_Tasks_with_CrewAI.md
Normal file
1946
lead-score-flow/Automating_Tasks_with_CrewAI.md
Normal file
File diff suppressed because it is too large
Load Diff
87
lead-score-flow/README.md
Normal file
87
lead-score-flow/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Lead Score Flow
|
||||
|
||||
Welcome to the Lead Score Flow project, powered by [crewAI](https://crewai.com). This example demonstrates how you can leverage Flows from crewAI to automate the process of scoring leads, including data collection, analysis, and scoring. By utilizing Flows, the process becomes much simpler and more efficient.
|
||||
|
||||
## Overview
|
||||
|
||||
This flow will guide you through the process of setting up an automated lead scoring system. Here's a brief overview of what will happen in this flow:
|
||||
|
||||
1. **Load Leads**: The flow starts by loading lead data from a CSV file named `leads.csv`.
|
||||
|
||||
2. **Score Leads**: The `LeadScoreCrew` is kicked off to score the loaded leads based on predefined criteria.
|
||||
|
||||
3. **Human in the Loop**: The top 3 candidates are presented for human review, allowing for additional feedback or proceeding with writing emails.
|
||||
|
||||
4. **Write and Save Emails**: Emails are generated and saved for all leads, with special attention to the top 3 candidates.
|
||||
|
||||
By following this flow, you can efficiently automate the process of scoring leads, leveraging the power of multiple AI agents to handle different aspects of the lead scoring 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**
|
||||
|
||||
To customize the behavior of the lead score flow, you can update the agents and tasks defined in the `LeadDataCollectionCrew`, `LeadAnalysisCrew`, and `LeadScoringCrew`. If you want to adjust the flow itself, you will need to modify the flow in `main.py`.
|
||||
|
||||
- **Agents and Tasks**: Modify `src/lead_score_flow/config/agents.yaml` to define your agents and `src/lead_score_flow/config/tasks.yaml` to define your tasks. This is where you can customize how lead data is collected, analyzed, and scored.
|
||||
|
||||
- **Flow Adjustments**: Modify `src/lead_score_flow/main.py` to adjust the flow. This is where you can change how the flow orchestrates the different crews and tasks.
|
||||
|
||||
## 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 lead_score_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 collect lead data, then analyze the data, score the leads, save the scores to a CSV file, and generate email drafts.
|
||||
|
||||
## Understanding Your Flow
|
||||
|
||||
The lead_score_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. **Collect Lead Data**: This step collects lead data from various sources.
|
||||
|
||||
2. **Analyze Lead Data**: The `LeadAnalysisCrew` is kicked off to analyze the collected lead data.
|
||||
|
||||
3. **Score Leads**: The analyzed data is then used to score the leads based on predefined criteria.
|
||||
|
||||
4. **Save Lead Scores**: The lead scores are saved to a CSV file named `lead_scores.csv`.
|
||||
|
||||
5. **Write and Save Emails**: Emails are generated and saved for all leads, with special attention to the top 3 candidates.
|
||||
|
||||
By understanding the flow structure, you can see how multiple crews are orchestrated to work together, each handling a specific part of the lead scoring process. This modular approach allows for efficient and scalable lead scoring automation.
|
||||
|
||||
## Support
|
||||
|
||||
For support, questions, or feedback regarding the Lead Score 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.
|
||||
25
lead-score-flow/pyproject.toml
Normal file
25
lead-score-flow/pyproject.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[tool.poetry]
|
||||
name = "lead_score_flow"
|
||||
version = "0.1.0"
|
||||
description = "lead_score_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"
|
||||
pyvis = "^0.3.2"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
lead_score_flow = "lead_score_flow.main:main"
|
||||
run_flow = "lead_score_flow.main:main"
|
||||
plot_flow = "lead_score_flow.main:plot"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
0
lead-score-flow/src/lead_score_flow/__init__.py
Normal file
0
lead-score-flow/src/lead_score_flow/__init__.py
Normal file
47
lead-score-flow/src/lead_score_flow/constants.py
Normal file
47
lead-score-flow/src/lead_score_flow/constants.py
Normal file
@@ -0,0 +1,47 @@
|
||||
JOB_DESCRIPTION = """
|
||||
# Junior React Developer
|
||||
|
||||
**Position:** Junior React Developer
|
||||
**Duration:** 12-month contract with the possibility of extension based on performance and project needs.
|
||||
|
||||
We are seeking a motivated Junior React Developer to join our team and assist in the development of our cutting-edge Next.js web application. This project integrates the Vercel AI SDK to enhance user experience with advanced AI-driven features.
|
||||
|
||||
**Key Responsibilities:**
|
||||
- Develop and maintain React components and Next.js applications.
|
||||
- Integrate AI-driven features using the Vercel AI SDK.
|
||||
- Collaborate with senior developers to design and implement new features.
|
||||
- Optimize application performance and ensure responsiveness across different devices.
|
||||
- Participate in code reviews and contribute to best practices.
|
||||
- Troubleshoot and debug issues to ensure the highest quality of the web application.
|
||||
|
||||
**Qualifications:**
|
||||
- 1-2 years of experience in front-end development with React and Next.js.
|
||||
- Proficiency in JavaScript, TypeScript, CSS, and HTML.
|
||||
- Experience with Git and RESTful APIs.
|
||||
- Familiarity with Vercel AI SDK is a plus.
|
||||
- Strong problem-solving skills and attention to detail.
|
||||
- Excellent communication and teamwork abilities.
|
||||
- Ability to work independently and take initiative on projects.
|
||||
|
||||
**What We Offer:**
|
||||
- Opportunity to work with cutting-edge technologies and AI integration.
|
||||
- Collaborative and supportive work environment.
|
||||
- Mentorship from senior developers to help grow your skills.
|
||||
- Potential for role extension and career advancement within the company.
|
||||
- Flexible working hours and the possibility of remote work.
|
||||
|
||||
This role is ideal for someone looking to grow their skills in Next.js, React, and AI-powered web applications while contributing to impactful projects.
|
||||
"""
|
||||
|
||||
SKILLS = [
|
||||
"React",
|
||||
"Next.js",
|
||||
"JavaScript",
|
||||
"TypeScript",
|
||||
"Vercel AI SDK",
|
||||
"CSS",
|
||||
"HTML",
|
||||
"Git",
|
||||
"REST APIs",
|
||||
"CrewAI",
|
||||
]
|
||||
@@ -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 named Sarah who works at CrewAI 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.
|
||||
@@ -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
|
||||
@@ -0,0 +1,35 @@
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.project import CrewBase, agent, crew, task
|
||||
|
||||
|
||||
@CrewBase
|
||||
class LeadResponseCrew:
|
||||
"""Lead Response Crew"""
|
||||
|
||||
agents_config = "config/agents.yaml"
|
||||
tasks_config = "config/tasks.yaml"
|
||||
|
||||
@agent
|
||||
def email_followup_agent(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config["email_followup_agent"],
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
)
|
||||
|
||||
@task
|
||||
def send_followup_email_task(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["send_followup_email"],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the Lead Response Crew"""
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
hr_evaluation_agent:
|
||||
role: >
|
||||
Senior HR Evaluation Expert
|
||||
goal: >
|
||||
Analyze candidates' qualifications and compare them against the job description to provide a score and reasoning.
|
||||
backstory: >
|
||||
As a Senior HR Evaluation Expert, you have extensive experience in assessing candidate profiles. You excel at
|
||||
evaluating how well candidates match job descriptions by analyzing their skills, experience, cultural fit, and
|
||||
growth potential. Your professional background allows you to provide comprehensive evaluations with clear reasoning.
|
||||
@@ -0,0 +1,32 @@
|
||||
evaluate_candidate:
|
||||
description: >
|
||||
Evaluate a candidate's bio based on the provided job description.
|
||||
|
||||
Use your expertise to carefully assess how well the candidate fits the job requirements. Consider key factors such as:
|
||||
- Skill match
|
||||
- Relevant experience
|
||||
- Cultural fit
|
||||
- Growth potential
|
||||
|
||||
CANDIDATE BIO
|
||||
-------------
|
||||
Candidate ID: {candidate_id}
|
||||
Name: {name}
|
||||
Bio:
|
||||
{bio}
|
||||
|
||||
JOB DESCRIPTION
|
||||
---------------
|
||||
{job_description}
|
||||
|
||||
ADDITIONAL INSTRUCTIONS
|
||||
-----------------------
|
||||
Your final answer MUST include:
|
||||
- The candidates unique ID
|
||||
- A score between 1 and 100. Don't use numbers like 100, 75, or 50. Instead, use specific numbers like 87, 63, or 42.
|
||||
- A detailed reasoning, considering the candidate’s skill match, experience, cultural fit, and growth potential.
|
||||
{additional_instructions}
|
||||
|
||||
expected_output: >
|
||||
A very specific score from 1 to 100 for the candidate, along with a detailed reasoning explaining why you assigned this score.
|
||||
agent: hr_evaluation_agent
|
||||
@@ -0,0 +1,35 @@
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.project import CrewBase, agent, crew, task
|
||||
from lead_score_flow.types import CandidateScore
|
||||
|
||||
|
||||
@CrewBase
|
||||
class LeadScoreCrew:
|
||||
"""Lead Score Crew"""
|
||||
|
||||
agents_config = "config/agents.yaml"
|
||||
tasks_config = "config/tasks.yaml"
|
||||
|
||||
@agent
|
||||
def hr_evaluation_agent(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config["hr_evaluation_agent"],
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
@task
|
||||
def evaluate_candidate_task(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["evaluate_candidate"],
|
||||
output_pydantic=CandidateScore,
|
||||
)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the Lead Score Crew"""
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
31
lead-score-flow/src/lead_score_flow/leads.csv
Normal file
31
lead-score-flow/src/lead_score_flow/leads.csv
Normal file
@@ -0,0 +1,31 @@
|
||||
id,name,email,bio,skills
|
||||
1,John Doe,johndoe@example.com,"John is a passionate junior developer with a strong background in front-end technologies. He recently completed a web development bootcamp, where he built multiple responsive websites using React, CSS, and HTML. John is eager to apply his skills in a real-world setting and grow as a developer. He is highly detail-oriented and a fast learner.","React, CSS, HTML, Git"
|
||||
2,Sarah Lee,sarahlee@example.com,"Sarah is a self-taught developer who has been building personal projects for the past year. Her portfolio includes a fully responsive personal blog and a task manager app, both built using React and Next.js. She enjoys the challenge of problem-solving and is constantly expanding her skill set through online courses. Sarah thrives in collaborative environments and is excited to contribute to team projects.","React, Next.js, JavaScript, CSS, HTML"
|
||||
3,Michael Young,michaelyoung@example.com,"Michael recently graduated from a coding bootcamp and is skilled in web development, particularly in React-based applications. During his bootcamp, he worked on a capstone project where he developed a social media dashboard using React, JavaScript, and Git for version control. Michael is passionate about technology and is looking for an opportunity to apply his skills in a dynamic team environment.","React, JavaScript, CSS, Git"
|
||||
4,Linda Smith,lindasmith@example.com,"Linda is an experienced full-stack developer with a focus on front-end technologies. She has over three years of experience working with React and Next.js. Recently, she has been exploring AI integrations, using the Vercel AI SDK and CrewAI to enhance user experiences in web applications. Linda excels at creating seamless, user-friendly interfaces and optimizing performance.","React, Next.js, JavaScript, Vercel AI SDK, CrewAI, REST APIs"
|
||||
5,Tom Brown,tombrown@example.com,"Tom is a mid-level developer with hands-on experience working on commercial projects using React and Next.js. He has built several customer-facing websites and eCommerce platforms, focusing on performance optimization and SEO. He is proficient in REST APIs and enjoys building scalable web solutions that improve user engagement.","React, Next.js, CSS, HTML, Git, REST APIs"
|
||||
6,Kate Adams,kateadams@example.com,"Kate is an AI enthusiast with a solid background in front-end development and experience using the Vercel AI SDK and CrewAI. She has contributed to projects that leverage AI to improve user interaction and personalization. Kate is passionate about staying at the cutting edge of technology and is excited to work on projects that push the boundaries of what's possible.","React, Next.js, Vercel AI SDK, CrewAI, JavaScript, Git"
|
||||
7,Robert Johnson,robertjohnson@example.com,"Robert is a highly skilled front-end developer with a deep knowledge of React and AI-powered applications. He has worked on multiple projects that integrate AI to provide enhanced user experiences, including a recommendation engine built using the Vercel AI SDK. Robert is known for his attention to detail and ability to produce high-quality, maintainable code.","React, Next.js, JavaScript, Vercel AI SDK, CrewAI, CSS, Git, REST APIs"
|
||||
8,Emily Davis,emilydavis@example.com,"Emily is a beginner developer with a passion for learning and growing within the web development space. She has been building her foundation in HTML, CSS, and JavaScript by working on personal projects and completing online tutorials. Emily is looking for an opportunity to work on real-world projects and contribute to a team while continuing to develop her technical skills.","HTML, CSS, JavaScript"
|
||||
9,James Wilson,jameswilson@example.com,"James is an intermediate developer with experience working primarily with React. He is eager to explore AI-driven projects and has recently started experimenting with Next.js and the Vercel AI SDK. James enjoys learning new technologies and applying them to solve real-world problems. He is looking for a role that will allow him to grow his skills and work on innovative projects.","React, JavaScript, Next.js, Git"
|
||||
10,Amy White,amywhite@example.com,"Amy is a senior developer with extensive experience in full-stack development and AI integrations. She has led multiple projects, working with AI-powered tools such as CrewAI and the Vercel AI SDK to create dynamic, personalized web applications. Amy is passionate about mentoring junior developers and has a proven track record of delivering high-quality products.","React, Next.js, JavaScript, Vercel AI SDK, CrewAI, REST APIs, Git"
|
||||
11,Jessica Brown,jessicabrown@example.com,"Jessica is a motivated junior developer with a strong foundation in React and Next.js. She has recently completed an internship where she worked on a team to build a responsive web application for a local business. Jessica is eager to continue building her skills and gain more experience in AI technologies.","React, Next.js, JavaScript, CSS"
|
||||
12,Daniel Martinez,danielmartinez@example.com,"Daniel is a front-end developer with several years of experience working with React. He has recently started integrating AI-driven features into his projects using the Vercel AI SDK and CrewAI. Daniel enjoys working on projects that challenge him to think outside the box and is always looking for ways to improve the user experience.","React, Next.js, Vercel AI SDK, CrewAI, JavaScript, Git"
|
||||
13,Olivia Clark,oliviaclark@example.com,"Olivia is a self-starter with a passion for web development. She has built several personal projects using React, focusing on improving her problem-solving skills. Olivia has a basic understanding of AI integrations and is eager to apply what she's learned in a professional setting.","React, JavaScript, CSS, HTML, Git"
|
||||
14,Matthew Evans,matthewevans@example.com,"Matthew is an experienced developer with expertise in full-stack development. He has worked on several AI-driven front-end applications, integrating advanced technologies such as the Vercel AI SDK and CrewAI. Matthew is passionate about creating user-friendly, high-performance web applications and enjoys tackling complex technical challenges.","React, Next.js, JavaScript, Vercel AI SDK, CrewAI, Git, REST APIs"
|
||||
15,Sophia Baker,sophiabaker@example.com,"Sophia is an entry-level developer who has recently started learning React and building personal projects. She is excited to continue growing her skills and has a particular interest in AI-powered web applications. Sophia is eager to learn from more experienced developers and contribute to a collaborative team.","React, CSS, HTML, Git"
|
||||
16,Joshua Harris,joshuaharris@example.com,"Joshua is a mid-level developer specializing in React and Next.js. He has experience building scalable web applications for eCommerce platforms and is familiar with REST APIs. Joshua is always looking for new ways to improve his code and enjoys working in a fast-paced, dynamic environment.","React, Next.js, JavaScript, REST APIs, Git"
|
||||
17,Chloe Turner,chloeturner@example.com,"Chloe is a developer passionate about AI-powered applications. She has strong skills in both CrewAI and the Vercel AI SDK, which she has used to create personalized, data-driven user experiences. Chloe is looking to work on cutting-edge projects where she can continue to grow her skills and contribute to innovative solutions.","React, Next.js, JavaScript, Vercel AI SDK, CrewAI, REST APIs, CSS"
|
||||
18,Luke Roberts,lukeroberts@example.com,"Luke is a junior developer focused on building user-friendly React applications with clean, efficient code. He has a strong understanding of front-end fundamentals and is eager to continue building his skills in more advanced technologies, such as Next.js and AI integrations.","React, JavaScript, CSS, HTML"
|
||||
19,Emma Mitchell,emmamitchell@example.com,"Emma is a web developer with strong skills in integrating AI solutions into front-end projects. She has experience working with both the Vercel AI SDK and CrewAI and enjoys creating applications that provide a seamless user experience. Emma is looking to join a team where she can contribute her technical skills and continue to grow as a developer.","React, Next.js, JavaScript, Vercel AI SDK, CrewAI, REST APIs"
|
||||
20,Henry Garcia,henrygarcia@example.com,"Henry is a recent graduate with a focus on AI-powered web applications. He has completed several projects, including a Next.js app that leverages the Vercel AI SDK for personalized recommendations. Henry is passionate about applying AI to solve real-world problems and is eager to join a team that shares his enthusiasm.","React, JavaScript, Vercel AI SDK, CrewAI, Next.js, Git"
|
||||
21,Grace Hill,gracehill@example.com,"Grace is a junior developer with a strong foundation in web development and a growing interest in AI technologies. She has built multiple personal projects using React and JavaScript, including a weather app and a blog site. Grace is eager to join a team where she can expand her knowledge of Next.js and AI-driven solutions.","React, JavaScript, CSS, HTML, Git"
|
||||
22,David Foster,davidfoster@example.com,"David is a mid-level React developer with experience in building responsive web applications using Next.js. He has worked on several eCommerce projects and is proficient in modern front-end technologies. David has recently started exploring AI integrations using the Vercel AI SDK and is looking for a role where he can apply these skills.","React, Next.js, JavaScript, CSS, HTML, Git"
|
||||
23,Isabella Russell,isabellarussell@example.com,"Isabella is an entry-level developer with basic React knowledge and a passion for learning new technologies. She has completed several online courses and built personal projects focused on improving her front-end development skills. Isabella is looking for an opportunity to gain hands-on experience and contribute to a real-world project.","React, CSS, HTML, JavaScript"
|
||||
24,Ethan Reed,ethanreed@example.com,"Ethan is a self-taught web developer with experience in full-stack development and AI-driven solutions. He has built several web applications using React, Next.js, and the Vercel AI SDK. Ethan enjoys tackling complex problems and is excited to continue working on AI-powered projects that enhance user experience.","React, Next.js, JavaScript, CrewAI, Vercel AI SDK, REST APIs"
|
||||
25,Abigail Ward,abigailward@example.com,"Abigail is an intermediate developer with strong React skills and experience in integrating AI into web applications. She has worked on projects that leverage the Vercel AI SDK to deliver personalized content to users. Abigail is passionate about user experience and enjoys building applications that are both functional and visually appealing.","React, Next.js, JavaScript, Vercel AI SDK, CSS, Git"
|
||||
26,Aiden Bailey,aidenbailey@example.com,"Aiden is an experienced full-stack developer with a focus on front-end technologies and AI-powered tools. He has led several projects that use CrewAI and the Vercel AI SDK to provide advanced AI-driven features. Aiden is excited to join a team where he can continue building innovative solutions and mentor junior developers.","React, Next.js, JavaScript, CrewAI, Vercel AI SDK, REST APIs"
|
||||
27,Lily King,lilyking@example.com,"Lily is a junior developer with a solid understanding of React. She has worked on a few personal projects, including a to-do list app and a portfolio website. Lily is eager to contribute to AI-driven projects and is currently learning Next.js to improve her front-end development skills.","React, JavaScript, CSS, HTML, Git"
|
||||
28,Benjamin Turner,benjaminturner@example.com,"Benjamin is a front-end developer with experience in modern web technologies and AI integrations. He has contributed to several projects that use the Vercel AI SDK and CrewAI to provide personalized user experiences. Benjamin is passionate about building scalable web applications and improving his skills in AI-powered solutions.","React, Next.js, Vercel AI SDK, CrewAI, JavaScript, CSS"
|
||||
29,Zoe Barnes,zoebarnes@example.com,"Zoe is a junior developer looking to grow her skills in React and AI-driven web applications. She has built several personal projects and is constantly learning new technologies to improve her code. Zoe is excited to work on real-world projects where she can continue developing her skills and gain experience in AI integrations.","React, CSS, HTML, JavaScript, Git"
|
||||
30,Alexander Cook,alexandercook@example.com,"Alexander is an experienced developer specializing in building high-performance web applications using React and Next.js. He has worked on projects that leverage CrewAI and the Vercel AI SDK to enhance user engagement through personalized features. Alexander enjoys working in fast-paced environments and collaborating with teams to build innovative solutions.","React, Next.js, JavaScript, CrewAI, Vercel AI SDK, REST APIs"
|
||||
|
214
lead-score-flow/src/lead_score_flow/main.py
Normal file
214
lead-score-flow/src/lead_score_flow/main.py
Normal file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python
|
||||
import asyncio
|
||||
from typing import List
|
||||
|
||||
from crewai.flow.flow import Flow, listen, or_, router, start
|
||||
from pydantic import BaseModel
|
||||
|
||||
from lead_score_flow.constants import JOB_DESCRIPTION
|
||||
from lead_score_flow.crews.lead_response_crew.lead_response_crew import LeadResponseCrew
|
||||
from lead_score_flow.crews.lead_score_crew.lead_score_crew import LeadScoreCrew
|
||||
from lead_score_flow.types import Candidate, CandidateScore, ScoredCandidate
|
||||
from lead_score_flow.utils.candidateUtils import combine_candidates_with_scores
|
||||
|
||||
|
||||
class LeadScoreState(BaseModel):
|
||||
candidates: List[Candidate] = []
|
||||
candidate_score: List[CandidateScore] = []
|
||||
hydrated_candidates: List[ScoredCandidate] = []
|
||||
scored_leads_feedback: str = ""
|
||||
|
||||
|
||||
class LeadScoreFlow(Flow[LeadScoreState]):
|
||||
initial_state = LeadScoreState
|
||||
|
||||
@start()
|
||||
def load_leads(self):
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
# Get the path to leads.csv in the same directory
|
||||
current_dir = Path(__file__).parent
|
||||
csv_file = current_dir / "leads.csv"
|
||||
|
||||
candidates = []
|
||||
with open(csv_file, mode="r", newline="", encoding="utf-8") as file:
|
||||
reader = csv.DictReader(file)
|
||||
for row in reader:
|
||||
# Create a Candidate object for each row
|
||||
print("Row:", row)
|
||||
candidate = Candidate(**row)
|
||||
candidates.append(candidate)
|
||||
|
||||
# Update the state with the loaded candidates
|
||||
self.state.candidates = candidates
|
||||
|
||||
@listen(or_(load_leads, "scored_leads_feedback"))
|
||||
async def score_leads(self):
|
||||
print("Scoring leads")
|
||||
tasks = []
|
||||
|
||||
async def score_single_candidate(candidate: Candidate):
|
||||
result = await (
|
||||
LeadScoreCrew()
|
||||
.crew()
|
||||
.kickoff_async(
|
||||
inputs={
|
||||
"candidate_id": candidate.id,
|
||||
"name": candidate.name,
|
||||
"bio": candidate.bio,
|
||||
"job_description": JOB_DESCRIPTION,
|
||||
"additional_instructions": self.state.scored_leads_feedback,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
self.state.candidate_score.append(result.pydantic)
|
||||
|
||||
for candidate in self.state.candidates:
|
||||
print("Scoring candidate:", candidate.name)
|
||||
task = asyncio.create_task(score_single_candidate(candidate))
|
||||
tasks.append(task)
|
||||
|
||||
candidate_scores = await asyncio.gather(*tasks)
|
||||
print("Finished scoring leads: ", len(candidate_scores))
|
||||
|
||||
@router(score_leads)
|
||||
def human_in_the_loop(self):
|
||||
print("Finding the top 3 candidates for human to review")
|
||||
|
||||
# Combine candidates with their scores using the helper function
|
||||
self.state.hydrated_candidates = combine_candidates_with_scores(
|
||||
self.state.candidates, self.state.candidate_score
|
||||
)
|
||||
|
||||
# Sort the scored candidates by their score in descending order
|
||||
sorted_candidates = sorted(
|
||||
self.state.hydrated_candidates, key=lambda c: c.score, reverse=True
|
||||
)
|
||||
self.state.hydrated_candidates = sorted_candidates
|
||||
|
||||
# Select the top 3 candidates
|
||||
top_candidates = sorted_candidates[:3]
|
||||
|
||||
print("Here are the top 3 candidates:")
|
||||
for candidate in top_candidates:
|
||||
print(
|
||||
f"ID: {candidate.id}, Name: {candidate.name}, Score: {candidate.score}, Reason: {candidate.reason}"
|
||||
)
|
||||
|
||||
# Present options to the user
|
||||
print("\nPlease choose an option:")
|
||||
print("1. Quit")
|
||||
print("2. Redo lead scoring with additional feedback")
|
||||
print("3. Proceed with writing emails to all leads")
|
||||
|
||||
choice = input("Enter the number of your choice: ")
|
||||
|
||||
if choice == "1":
|
||||
print("Exiting the program.")
|
||||
exit()
|
||||
elif choice == "2":
|
||||
feedback = input(
|
||||
"\nPlease provide additional feedback on what you're looking for in candidates:\n"
|
||||
)
|
||||
self.state.scored_leads_feedback = feedback
|
||||
print("\nRe-running lead scoring with your feedback...")
|
||||
return "scored_leads_feedback"
|
||||
elif choice == "3":
|
||||
print("\nProceeding to write emails to all leads.")
|
||||
return "generate_emails"
|
||||
else:
|
||||
print("\nInvalid choice. Please try again.")
|
||||
return "human_in_the_loop"
|
||||
|
||||
@listen("generate_emails")
|
||||
async def write_and_save_emails(self):
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
print("Writing and saving emails for all leads.")
|
||||
|
||||
# Determine the top 3 candidates to proceed with
|
||||
top_candidate_ids = {
|
||||
candidate.id for candidate in self.state.hydrated_candidates[:3]
|
||||
}
|
||||
|
||||
tasks = []
|
||||
|
||||
# Create the directory 'email_responses' if it doesn't exist
|
||||
output_dir = Path(__file__).parent / "email_responses"
|
||||
print("output_dir:", output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async def write_email(candidate):
|
||||
# Check if the candidate is among the top 3
|
||||
proceed_with_candidate = candidate.id in top_candidate_ids
|
||||
|
||||
# Kick off the LeadResponseCrew for each candidate
|
||||
result = await (
|
||||
LeadResponseCrew()
|
||||
.crew()
|
||||
.kickoff_async(
|
||||
inputs={
|
||||
"candidate_id": candidate.id,
|
||||
"name": candidate.name,
|
||||
"bio": candidate.bio,
|
||||
"proceed_with_candidate": proceed_with_candidate,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# Sanitize the candidate's name to create a valid filename
|
||||
safe_name = re.sub(r"[^a-zA-Z0-9_\- ]", "", candidate.name)
|
||||
filename = f"{safe_name}.txt"
|
||||
print("Filename:", filename)
|
||||
|
||||
# Write the email content to a text file
|
||||
file_path = output_dir / filename
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(result.raw)
|
||||
|
||||
# Return a message indicating the email was saved
|
||||
return f"Email saved for {candidate.name} as {filename}"
|
||||
|
||||
# Create tasks for all candidates
|
||||
for candidate in self.state.hydrated_candidates:
|
||||
task = asyncio.create_task(write_email(candidate))
|
||||
tasks.append(task)
|
||||
|
||||
# Run all email-writing tasks concurrently and collect results
|
||||
email_results = await asyncio.gather(*tasks)
|
||||
|
||||
# After all emails have been generated and saved
|
||||
print("\nAll emails have been written and saved to 'email_responses' folder.")
|
||||
for message in email_results:
|
||||
print(message)
|
||||
|
||||
|
||||
async def run_flow():
|
||||
"""
|
||||
Run the flow.
|
||||
"""
|
||||
lead_score_flow = LeadScoreFlow()
|
||||
lead_score_flow.kickoff()
|
||||
|
||||
|
||||
async def plot_flow():
|
||||
"""
|
||||
Plot the flow.
|
||||
"""
|
||||
lead_score_flow = LeadScoreFlow()
|
||||
lead_score_flow.plot()
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_flow())
|
||||
|
||||
|
||||
def plot():
|
||||
asyncio.run(plot_flow())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
31
lead-score-flow/src/lead_score_flow/types.py
Normal file
31
lead-score-flow/src/lead_score_flow/types.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class JobDescription(BaseModel):
|
||||
title: str
|
||||
description: str
|
||||
skills: str
|
||||
|
||||
|
||||
class Candidate(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
email: str
|
||||
bio: str
|
||||
skills: str
|
||||
|
||||
|
||||
class CandidateScore(BaseModel):
|
||||
id: str
|
||||
score: int
|
||||
reason: str
|
||||
|
||||
|
||||
class ScoredCandidate(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
email: str
|
||||
bio: str
|
||||
skills: str
|
||||
score: int
|
||||
reason: str
|
||||
36
lead-score-flow/src/lead_score_flow/utils/candidateUtils.py
Normal file
36
lead-score-flow/src/lead_score_flow/utils/candidateUtils.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from typing import List
|
||||
|
||||
from lead_score_flow.types import Candidate, CandidateScore, ScoredCandidate
|
||||
|
||||
|
||||
def combine_candidates_with_scores(
|
||||
candidates: List[Candidate], candidate_scores: List[CandidateScore]
|
||||
) -> List[ScoredCandidate]:
|
||||
"""
|
||||
Combine the candidates with their scores using a dictionary for efficient lookups.
|
||||
"""
|
||||
print("COMBINING CANDIDATES WITH SCORES")
|
||||
print("SCORES:", candidate_scores)
|
||||
print("CANDIDATES:", candidates)
|
||||
# Create a dictionary to map score IDs to their corresponding CandidateScore objects
|
||||
score_dict = {score.id: score for score in candidate_scores}
|
||||
print("SCORE DICT:", score_dict)
|
||||
|
||||
scored_candidates = []
|
||||
for candidate in candidates:
|
||||
score = score_dict.get(candidate.id)
|
||||
if score:
|
||||
scored_candidates.append(
|
||||
ScoredCandidate(
|
||||
id=candidate.id,
|
||||
name=candidate.name,
|
||||
email=candidate.email,
|
||||
bio=candidate.bio,
|
||||
skills=candidate.skills,
|
||||
score=score.score,
|
||||
reason=score.reason,
|
||||
)
|
||||
)
|
||||
|
||||
print("SCORED CANDIDATES:", scored_candidates)
|
||||
return scored_candidates
|
||||
3
meeting_assistant_flow/.gitignore
vendored
Normal file
3
meeting_assistant_flow/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.env
|
||||
__pycache__/
|
||||
new_tasks.csv
|
||||
149
meeting_assistant_flow/README.md
Normal file
149
meeting_assistant_flow/README.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Meeting Assistant Flow
|
||||
|
||||
Welcome to the Meeting Assistant Flow project, powered by [crewAI](https://crewai.com). This example demonstrates how you can leverage Flows from crewAI to automate the process of managing meetings, including scheduling, note-taking, and follow-up actions. By utilizing Flows, the process becomes much simpler and more efficient.
|
||||
|
||||
## Overview
|
||||
|
||||
This flow will guide you through the process of setting up an automated meeting assistant. Here's a brief overview of what will happen in this flow:
|
||||
|
||||
1. **Load Meeting Notes**: The flow starts by loading the meeting notes from a file named `meeting_notes.txt`.
|
||||
|
||||
2. **Generate Tasks from Meeting Transcript**: The `MeetingAssistantCrew` is kicked off to generate tasks from the meeting transcript.
|
||||
|
||||
3. **Add Tasks to Trello**: The generated tasks are added to a Trello board.
|
||||
|
||||
4. **Save New Tasks to CSV**: The new tasks are saved to a CSV file named `new_tasks.csv`.
|
||||
|
||||
5. **Send Slack Notification**: A Slack notification is sent to a specified channel, informing about the new tasks added to Trello.
|
||||
|
||||
By following this flow, you can efficiently automate the process of managing meetings, leveraging the power of multiple AI agents to handle different aspects of the meeting 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 `TRELLO_API_KEY`, `TRELLO_TOKEN`, `TRELLO_BOARD_ID`, and `TRELLO_LIST_ID` into the `.env` file**
|
||||
**Add your `SLACK_TOKEN` and `SLACK_CHANNEL_ID` into the `.env` file**
|
||||
|
||||
To customize the behavior of the meeting assistant flow, you can update the agents and tasks defined in the `MeetingSchedulerCrew`, `NoteTakingCrew`, and `FollowUpCrew`. If you want to adjust the flow itself, you will need to modify the flow in `main.py`.
|
||||
|
||||
- **Agents and Tasks**: Modify `src/meeting_assistant_flow/config/agents.yaml` to define your agents and `src/meeting_assistant_flow/config/tasks.yaml` to define your tasks. This is where you can customize how meetings are scheduled, notes are taken, and follow-up actions are managed.
|
||||
|
||||
- **Flow Adjustments**: Modify `src/meeting_assistant_flow/main.py` to adjust the flow. This is where you can change how the flow orchestrates the different crews and tasks.
|
||||
|
||||
### Setting Up Trello
|
||||
|
||||
To enable the meeting assistant flow to interact with Trello, follow these steps to set up your Trello API credentials:
|
||||
|
||||
1. **Generate Trello API Key**:
|
||||
|
||||
- Visit the [Trello API Key page](https://trello.com/power-ups/admin/new) and log in with your Trello account.
|
||||
- Click on the "Create a Power-Up" button.
|
||||
- Fill in the required details for your Power-Up and click "Create".
|
||||
- Once created, you will see your API key. Copy this key and add it to your `.env` file as `TRELLO_API_KEY`.
|
||||
|
||||
2. **Generate Trello Token**:
|
||||
|
||||
- Visit the [Trello Power Up page](https://developer.atlassian.com/cloud/trello/) to learn how to create a Power-Up and generate your token.
|
||||
- Scroll down to the "OAuth" section and click on the "Token" link.
|
||||
- Authorize the application to access your Trello account.
|
||||
- You will be provided with a token. Copy this token and add it to your `.env` file as `TRELLO_TOKEN`.
|
||||
|
||||
3. **Find Trello Board ID**:
|
||||
|
||||
- Open Trello and navigate to the board you want to use.
|
||||
- The board ID is part of the URL. For example, in `https://trello.com/b/BOARD_ID/board-name`, `BOARD_ID` is your board ID.
|
||||
- Copy this ID and add it to your `.env` file as `TRELLO_BOARD_ID`.
|
||||
|
||||
4. **Find Trello List ID**:
|
||||
|
||||
- On your Trello board, click on the list where you want to add tasks.
|
||||
- Click on the three dots (menu) on the top right of the list and select "Copy Link".
|
||||
- The list ID is part of the URL. For example, in `https://trello.com/c/BOARD_ID/LIST_ID/card-name`, `LIST_ID` is your list ID.
|
||||
- Copy this ID and add it to your `.env` file as `TRELLO_LIST_ID`.
|
||||
|
||||
5. **Set Up Environment Variables**:
|
||||
- Add the following variables to your `.env` file:
|
||||
```plaintext
|
||||
TRELLO_API_KEY=your_trello_api_key
|
||||
TRELLO_TOKEN=your_trello_token
|
||||
TRELLO_BOARD_ID=your_trello_board_id
|
||||
TRELLO_LIST_ID=your_trello_list_id
|
||||
```
|
||||
|
||||
By following these steps, you will have set up your Trello API credentials correctly, allowing the meeting assistant flow to interact with your Trello board and lists.
|
||||
|
||||
### Setting Up Slack
|
||||
|
||||
To enable the meeting assistant flow to send notifications to Slack, follow these steps to set up your Slack API credentials:
|
||||
|
||||
1. **Create a Slack App**: Visit the [Slack API page](https://api.slack.com/apps) and create a new app.
|
||||
|
||||
2. **Generate Slack Token**: Under the "OAuth & Permissions" section, generate a token with the necessary permissions.
|
||||
|
||||
3. **Find Slack Channel ID**: To find your Slack channel ID, open Slack, go to the channel, and click on the channel name. The channel ID will be in the URL.
|
||||
|
||||
4. **Invite Slack Bot to Channel**: Invite the Slack bot to the channel by typing `/invite @your-bot-name` in the channel.
|
||||
|
||||
5. **Set Up Environment Variables**: Add the following variables to your `.env` file:
|
||||
- `SLACK_TOKEN`
|
||||
- `SLACK_CHANNEL_ID`
|
||||
|
||||
## 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 meeting_assistant_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 load meeting notes, then generate tasks from the transcript, add tasks to Trello, save tasks to a CSV file, and send a Slack notification.
|
||||
|
||||
## Understanding Your Flow
|
||||
|
||||
The meeting_assistant_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. **Load Meeting Notes**: This step loads the meeting notes from a file named `meeting_notes.txt`.
|
||||
|
||||
2. **Generate Tasks from Meeting Transcript**: The `MeetingAssistantCrew` is kicked off to generate tasks from the meeting transcript.
|
||||
|
||||
3. **Add Tasks to Trello**: The generated tasks are added to a Trello board.
|
||||
|
||||
4. **Save New Tasks to CSV**: The new tasks are saved to a CSV file named `new_tasks.csv`.
|
||||
|
||||
5. **Send Slack Notification**: A Slack notification is sent to a specified channel, informing about the new tasks added to Trello.
|
||||
|
||||
By understanding the flow structure, you can see how multiple crews are orchestrated to work together, each handling a specific part of the meeting management process. This modular approach allows for efficient and scalable meeting automation.
|
||||
|
||||
## Support
|
||||
|
||||
For support, questions, or feedback regarding the Meeting Assistant 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.
|
||||
82
meeting_assistant_flow/meeting_notes.txt
Normal file
82
meeting_assistant_flow/meeting_notes.txt
Normal file
@@ -0,0 +1,82 @@
|
||||
Alex: Hey team, good morning! Thanks for making time for this meeting. We’ve got a pretty important task ahead of us—integrating Stripe for our subscription model. We need to get this right to make sure our users have a smooth experience. How’s everyone doing today?
|
||||
|
||||
Jordan: Morning, Alex! I’m good, thanks. Excited to dive into this. Integrating Stripe sounds like a big job, though. What’s our current approach?
|
||||
|
||||
Alex: So, our main objective is to set up Stripe to manage our subscription model. We’re looking at three different tiers: Basic, Pro, and Enterprise. We need to set up these tiers in Stripe, integrate them into our app, and ensure everything works seamlessly.
|
||||
|
||||
Taylor: Sounds like a plan. What’s the exact breakdown for each subscription tier?
|
||||
|
||||
Alex: Here’s the pricing we’re going with:
|
||||
|
||||
Basic: $29 per month. This includes basic access to our AI model selection tool and a limited feature set.
|
||||
Pro: $49 per month. This tier offers additional features and priority support.
|
||||
Enterprise: $99 per month. This includes all features, custom support, and integration options.
|
||||
We’ll need to set these up in Stripe and ensure they’re reflected correctly in the app, both in terms of functionality and user experience.
|
||||
|
||||
Jordan: Got it. I’ll start on the frontend. For the checkout process, we’ll use Stripe Elements, right?
|
||||
|
||||
Alex: Yes, Stripe Elements is the way to go. It’s customizable and secure. Make sure the checkout form matches our app’s design. We also need to handle potential errors gracefully, such as invalid card details or insufficient funds.
|
||||
|
||||
Jordan: Okay, I’ll get started on that. I’ll also need to update our pricing page to reflect the new subscription tiers. For the checkout form, are there any specific design elements or features you want to highlight?
|
||||
|
||||
Alex: The design should be clean and straightforward. Users should easily understand what they’re getting with each plan. We need a clear comparison of features for each tier, and the checkout button should be prominent. Let’s also include a tooltip or help icon that explains what happens if they choose a different plan later on.
|
||||
|
||||
Jordan: Got it. I’ll make sure to include those details. What about the user dashboard? What functionalities do we need there?
|
||||
|
||||
Alex: On the user dashboard, we need to provide:
|
||||
|
||||
Current plan details and billing info.
|
||||
Options to upgrade, downgrade, or cancel their subscription.
|
||||
Access to invoices and payment history.
|
||||
A way to update payment methods.
|
||||
The dashboard should be intuitive and reflect the current subscription status in real-time. It should also notify users of any changes, such as successful plan upgrades or payment failures.
|
||||
|
||||
Taylor: On the backend side, I’ll focus on integrating Stripe’s API to handle all subscription-related tasks. This includes creating and managing subscriptions, processing payments, and dealing with webhooks for different events.
|
||||
|
||||
Alex: Exactly. We need to ensure the backend handles:
|
||||
|
||||
Creating subscriptions: When a user selects a plan and completes the payment.
|
||||
Processing payments: Handling successful and failed payments.
|
||||
Managing webhooks: For events like invoice payment success or failure, and subscription cancellations.
|
||||
Taylor: Okay, so we’ll need to handle several specific webhook events. Can we list those out?
|
||||
|
||||
Alex: Sure. We need to listen for:
|
||||
|
||||
invoice.payment_succeeded: To confirm successful payments and activate or continue the user’s subscription.
|
||||
invoice.payment_failed: To notify users of failed payments and prompt them to update their payment information. We should also consider sending an email notification here.
|
||||
customer.subscription.deleted: To manage cases where users cancel their subscriptions, including updating their access rights and possibly offering them a chance to re-subscribe.
|
||||
Taylor: Got it. I’ll set up webhook handlers for these events and ensure they update our system correctly. We’ll also need to test these handlers thoroughly. I’m thinking of creating a few test scenarios to simulate different events, like payment failures and cancellations.
|
||||
|
||||
Jordan: Once Taylor has the backend set up, I’ll integrate the frontend with the API. We need to make sure that any changes to the subscription are reflected in real-time on the dashboard.
|
||||
|
||||
Alex: Exactly. We should also handle any errors that might come up during the integration process and provide clear notifications to users. For instance, if a payment fails, we should clearly inform the user and provide instructions on how to fix it.
|
||||
|
||||
Taylor: I’ll prepare a detailed test plan and share it with you both. We should cover manual tests as well as automated tests. This will include testing the whole subscription flow from sign-up to billing to managing the subscription.
|
||||
|
||||
Jordan: That makes sense. I’ll start working on the frontend components and prepare them for integration. I’ll also make sure the Stripe Elements form is correctly styled and integrated. And I’ll handle the user notifications for different events, like subscription confirmations and payment errors.
|
||||
|
||||
Alex: Excellent. Let’s aim to get the initial integration done within the next two weeks. After that, we’ll need a week for testing. During the testing phase, we should ensure all edge cases are covered and everything is functioning as expected.
|
||||
|
||||
Jordan: Sounds good to me. I’ll update you on my progress and coordinate with Taylor to ensure everything on the frontend matches up with the backend.
|
||||
|
||||
Taylor: Same here. I’ll get started on the backend tasks and keep you updated on the webhook setup and API requirements.
|
||||
|
||||
Alex: Perfect. We’ll also need to plan for user documentation and support. Once everything is live, we should have clear instructions available for users on how to manage their subscriptions, access invoices, and handle any issues.
|
||||
|
||||
Jordan: Definitely. We’ll need a help section or FAQ that covers common issues and questions about subscriptions.
|
||||
|
||||
Taylor: Agreed. I’ll also make sure to document the API endpoints and webhook event handlers, so it’s easier to maintain and troubleshoot in the future.
|
||||
|
||||
Alex: Great. Let’s set up our next follow-up meeting for next week to review our progress and address any issues that come up. If either of you run into any blockers or need help, don’t hesitate to reach out.
|
||||
|
||||
Jordan: Will do. I’ll start on the frontend tasks today and keep you posted.
|
||||
|
||||
Taylor: I’ll begin with the backend setup and webhook integration. Looking forward to seeing how everything comes together.
|
||||
|
||||
Alex: Awesome. Thanks, everyone. Let’s make sure we get this right and provide a smooth experience for our users. Have a great day!
|
||||
|
||||
Jordan: You too, Alex. Talk soon!
|
||||
|
||||
Taylor: Talk soon. Thanks, everyone!
|
||||
|
||||
Alex: Talk soon. Bye for now!
|
||||
20
meeting_assistant_flow/pyproject.toml
Normal file
20
meeting_assistant_flow/pyproject.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[tool.poetry]
|
||||
name = "meeting_assistant_flow"
|
||||
version = "0.1.0"
|
||||
description = "meeting_assistant_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 = "*"
|
||||
slack-sdk = "^3.33.1"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
meeting_assistant_flow = "meeting_assistant_flow.main:main"
|
||||
run_flow = "meeting_assistant_flow.main:main"
|
||||
plot_flow = "meeting_assistant_flow.main:plot"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -0,0 +1,12 @@
|
||||
meeting_analyzer:
|
||||
role: >
|
||||
Meeting Transcript Analysis Agent
|
||||
goal: >
|
||||
Analyze the provided meeting transcript and extract important, actionable tasks or issues.
|
||||
The goal is to break down the meeting content into well-structured,
|
||||
detailed issues that can be easily understood and uploaded to Trello.
|
||||
|
||||
Here is the meeting transcript for your reference:\n\n {transcript}
|
||||
backstory: >
|
||||
You are an expert in analyzing meeting transcripts and summarizing the discussions into actionable tasks.
|
||||
Your ability to identify important issues helps ensure teams can follow up and address key points effectively.
|
||||
@@ -0,0 +1,13 @@
|
||||
analyze_meeting:
|
||||
description: >
|
||||
Analyze the provided meeting transcript and generate a set of detailed,
|
||||
well-organized issues based on the discussion.
|
||||
Focus on breaking down the transcript into manageable tasks or issues,
|
||||
making sure to document each issue thoroughly with steps to reproduce, acceptance criteria,
|
||||
and any other relevant details.
|
||||
|
||||
Here is the meeting transcript for your reference:\n\n {transcript}
|
||||
expected_output: >
|
||||
A JSON list of issues with titles and bodies, containing clear instructions,
|
||||
steps to reproduce, and acceptance criteria where applicable.
|
||||
agent: meeting_analyzer
|
||||
@@ -0,0 +1,40 @@
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.project import CrewBase, agent, crew, task
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from meeting_assistant_flow.types import (
|
||||
MeetingTaskList,
|
||||
)
|
||||
|
||||
|
||||
@CrewBase
|
||||
class MeetingAssistantCrew:
|
||||
"""Meeting Assistant Crew"""
|
||||
|
||||
agents_config = "config/agents.yaml"
|
||||
tasks_config = "config/tasks.yaml"
|
||||
llm = ChatOpenAI(model="gpt-4")
|
||||
|
||||
@agent
|
||||
def meeting_analyzer(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config["meeting_analyzer"],
|
||||
llm=self.llm,
|
||||
)
|
||||
|
||||
@task
|
||||
def analyze_meeting(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["analyze_meeting"],
|
||||
output_pydantic=MeetingTaskList,
|
||||
)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the Meeting Issue Generation Crew"""
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
95
meeting_assistant_flow/src/meeting_assistant_flow/main.py
Normal file
95
meeting_assistant_flow/src/meeting_assistant_flow/main.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
import asyncio
|
||||
import csv
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
from pydantic import BaseModel
|
||||
|
||||
from meeting_assistant_flow.crews.meeting_assistant_crew.meeting_assistant_crew import (
|
||||
MeetingAssistantCrew,
|
||||
)
|
||||
from meeting_assistant_flow.types import MeetingTask
|
||||
from meeting_assistant_flow.utils.slack_helper import send_message_to_channel
|
||||
from meeting_assistant_flow.utils.trello_helper import save_tasks_to_trello
|
||||
|
||||
|
||||
class MeetingState(BaseModel):
|
||||
transcript: str = "Meeting transcript goes here"
|
||||
tasks: List[MeetingTask] = []
|
||||
|
||||
|
||||
class MeetingFlow(Flow[MeetingState]):
|
||||
initial_state = MeetingState
|
||||
|
||||
@start()
|
||||
def load_meeting_notes(self):
|
||||
print("Loading Meeting Notes")
|
||||
print("Current working directory:", os.getcwd())
|
||||
|
||||
with open("meeting_notes.txt", "r") as file:
|
||||
self.state.transcript = file.read()
|
||||
|
||||
@listen(load_meeting_notes)
|
||||
def generate_tasks_from_meeting_transcript(self):
|
||||
print("Kickoff the Meeting Assistant Crew")
|
||||
output = (
|
||||
MeetingAssistantCrew()
|
||||
.crew()
|
||||
.kickoff(inputs={"transcript": self.state.transcript})
|
||||
)
|
||||
|
||||
tasks = output["tasks"]
|
||||
print("TASKS:", tasks)
|
||||
self.state.tasks = tasks
|
||||
|
||||
@listen(generate_tasks_from_meeting_transcript)
|
||||
def add_tasks_to_trello(self):
|
||||
print("Adding Tasks to Trello")
|
||||
save_tasks_to_trello(self.state.tasks)
|
||||
|
||||
@listen(generate_tasks_from_meeting_transcript)
|
||||
def save_new_tasks_to_csv(self):
|
||||
print("Saving New Tasks to CSV")
|
||||
with open("new_tasks.csv", "w", newline="") as file:
|
||||
writer = csv.writer(file)
|
||||
# Write the header row
|
||||
writer.writerow(["Name", "Description"])
|
||||
# Write the task data
|
||||
for task in self.state.tasks:
|
||||
writer.writerow([task.name, task.description])
|
||||
|
||||
@listen(generate_tasks_from_meeting_transcript)
|
||||
def send_slack_notification(self):
|
||||
print("Sending Slack Notification")
|
||||
message = f"{len(self.state.tasks)} New tasks have been added to Trello!"
|
||||
send_message_to_channel(message)
|
||||
|
||||
|
||||
async def run_flow():
|
||||
"""
|
||||
Run the flow.
|
||||
"""
|
||||
meeting_flow = MeetingFlow()
|
||||
meeting_flow.kickoff()
|
||||
|
||||
|
||||
async def plot_flow():
|
||||
"""
|
||||
Plot the flow.
|
||||
"""
|
||||
meeting_flow = MeetingFlow()
|
||||
meeting_flow.plot()
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_flow())
|
||||
|
||||
|
||||
def plot():
|
||||
asyncio.run(plot_flow())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
meeting_assistant_flow/src/meeting_assistant_flow/types.py
Normal file
10
meeting_assistant_flow/src/meeting_assistant_flow/types.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MeetingTask(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
|
||||
|
||||
class MeetingTaskList(BaseModel):
|
||||
tasks: list[MeetingTask]
|
||||
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from slack_sdk import WebClient
|
||||
from slack_sdk.errors import SlackApiError
|
||||
|
||||
# Load environment variables from a .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def send_message_to_channel(text: str):
|
||||
# Get your Slack token and channel ID from the environment variables
|
||||
SLACK_TOKEN = os.getenv("SLACK_TOKEN")
|
||||
SLACK_CHANNEL_ID = os.getenv("SLACK_CHANNEL_ID")
|
||||
client = WebClient(token=SLACK_TOKEN)
|
||||
|
||||
try:
|
||||
# Send a message to the channel
|
||||
response = client.chat_postMessage(
|
||||
channel=SLACK_CHANNEL_ID,
|
||||
text=text,
|
||||
)
|
||||
return response
|
||||
except SlackApiError as e:
|
||||
print(f"Error sending message: {e.response['error']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
message = "Hello, world! This is a test message from the Slack Helper."
|
||||
response = send_message_to_channel(message)
|
||||
if response:
|
||||
print("Message sent successfully!")
|
||||
else:
|
||||
print("Failed to send message.")
|
||||
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from meeting_assistant_flow.types import MeetingTask
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Your Trello API credentials loaded from environment variables
|
||||
API_KEY = os.getenv("TRELLO_API_KEY")
|
||||
TOKEN = os.getenv("TRELLO_TOKEN")
|
||||
|
||||
# The ID of the Trello board and list where you want to add the cards
|
||||
BOARD_ID = os.getenv("TRELLO_BOARD_ID")
|
||||
LIST_ID = os.getenv("TRELLO_LIST_ID")
|
||||
|
||||
|
||||
def create_trello_card(task_title, task_description):
|
||||
"""
|
||||
Create a new card in Trello for the given task.
|
||||
|
||||
:param task_title: Title of the task (will be the title of the Trello card)
|
||||
:param task_description: Detailed description of the task (will be the body of the Trello card)
|
||||
:return: Response object from Trello API call
|
||||
"""
|
||||
url = "https://api.trello.com/1/cards"
|
||||
|
||||
query = {
|
||||
"key": API_KEY,
|
||||
"token": TOKEN,
|
||||
"idList": LIST_ID,
|
||||
"name": task_title,
|
||||
"desc": task_description,
|
||||
}
|
||||
|
||||
response = requests.post(url, params=query)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"Task '{task_title}' successfully created in Trello.")
|
||||
else:
|
||||
print(f"Failed to create task '{task_title}' in Trello.")
|
||||
print(response.text)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def save_tasks_to_trello(tasks: List[MeetingTask]):
|
||||
"""
|
||||
Save a list of tasks to Trello. Each task is a dictionary with 'title' and 'body'.
|
||||
|
||||
:param tasks: List of tasks, where each task is a dict with 'title' and 'body'
|
||||
"""
|
||||
for task in tasks:
|
||||
if task.name and task.description:
|
||||
create_trello_card(task.name, task.description)
|
||||
else:
|
||||
print("Task is missing a title or description. Skipping...")
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
tasks = [
|
||||
{
|
||||
"title": "Add Token Count Progress Indicator to Website",
|
||||
"body": "I received a suggestion from a colleague to enhance the token count exceeded feature on our website...",
|
||||
},
|
||||
{
|
||||
"title": "Improve Mobile Responsiveness for Dashboard",
|
||||
"body": "We need to improve the mobile layout of the dashboard for better usability. The sidebar should collapse automatically...",
|
||||
},
|
||||
]
|
||||
|
||||
save_tasks_to_trello(tasks)
|
||||
2
write_a_book_with_flows/.gitignore
vendored
Normal file
2
write_a_book_with_flows/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
__pycache__/
|
||||
1946
write_a_book_with_flows/Automating_Tasks_with_CrewAI.md
Normal file
1946
write_a_book_with_flows/Automating_Tasks_with_CrewAI.md
Normal file
File diff suppressed because it is too large
Load Diff
81
write_a_book_with_flows/README.md
Normal file
81
write_a_book_with_flows/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Write a Book Flow
|
||||
|
||||
Welcome to the Book Writing Flow, powered by [crewAI](https://crewai.com). This template is designed to help you set up a multi-agent AI system with ease, leveraging the powerful and flexible framework provided by crewAI. Our goal is to enable your agents to collaborate effectively on complex tasks, maximizing their collective intelligence and capabilities.
|
||||
|
||||
## Overview
|
||||
|
||||
This flow will guide you through the process of writing a book by leveraging multiple AI agents, each with specific roles. Here's a brief overview of what will happen in this flow:
|
||||
|
||||
1. **Generate Book Outline**: The flow starts by using the `OutlineCrew` to create a comprehensive outline for your book. This crew will search the internet, define the structure, and main topics of the book based on the provided goal and topic.
|
||||
|
||||
2. **Write Book Chapters**: Once the outline is ready, the flow will kick off a new crew, `WriteBookChapterCrew`, for each chapter outlined in the previous step. Each crew will be responsible for writing a specific chapter, ensuring that the content is detailed and coherent.
|
||||
|
||||
3. **Join and Save Chapters**: In the final step, the flow will combine all the chapters into a single markdown file, creating a complete book. This file will be saved in the root folder of your project.
|
||||
|
||||
By following this flow, you can efficiently produce a well-structured and comprehensive book, leveraging the power of multiple AI agents to handle different aspects of the writing process.
|
||||
|
||||
## 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**
|
||||
|
||||
To customize the behavior of the book writing flow, you can update the agents and tasks defined in the `OutlineCrew` and `WriteBookChapterCrew`. If you want to adjust the flow itself, you will need to modify the flow in `main.py`.
|
||||
|
||||
- **Agents and Tasks**: Modify `src/write_a_book_with_flows/config/agents.yaml` to define your agents and `src/write_a_book_with_flows/config/tasks.yaml` to define your tasks. This is where you can customize how the book outline is generated and how chapters are written.
|
||||
|
||||
- **Flow Adjustments**: Modify `src/write_a_book_with_flows/main.py` to adjust the flow. This is where you can change how the flow orchestrates the different crews and tasks.
|
||||
|
||||
## 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 write_a_book_with_flows Crew, 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 generate a book outline, then create and run a crew for each chapter, and finally join all the chapters into a single markdown file.
|
||||
|
||||
## Understanding Your Flow
|
||||
|
||||
The write_a_book_with_flows 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. **OutlineCrew**: This crew is responsible for generating the book outline. It defines the structure and main topics of the book based on the provided goal and topic.
|
||||
|
||||
2. **WriteBookChapterCrew**: For each chapter outlined by the `OutlineCrew`, a new `WriteBookChapterCrew` is created. Each of these crews is responsible for writing a specific chapter, ensuring detailed and coherent content.
|
||||
|
||||
3. **Join and Save**: After all chapters are written, the flow combines them into a single markdown file, creating a complete book.
|
||||
|
||||
By understanding the flow structure, you can see how multiple crews are orchestrated to work together, each handling a specific part of the book writing process. This modular approach allows for efficient and scalable book production.
|
||||
|
||||
## Support
|
||||
|
||||
For support, questions, or feedback regarding the {{crew_name}} Crew 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.
|
||||
19
write_a_book_with_flows/pyproject.toml
Normal file
19
write_a_book_with_flows/pyproject.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[tool.poetry]
|
||||
name = "write_a_book_with_flows"
|
||||
version = "0.1.0"
|
||||
description = "write_a_book_with_flows 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 = "*"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
write_a_book_with_flows = "write_a_book_with_flows.main:main"
|
||||
run_flow = "write_a_book_with_flows.main:main"
|
||||
plot_flow = "write_a_book_with_flows.main:plot"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -0,0 +1,20 @@
|
||||
researcher:
|
||||
role: >
|
||||
Research Agent
|
||||
goal: >
|
||||
Gather comprehensive information about {topic} that will be used to create an organized and well-structured book outline.
|
||||
Here is some additional information about the author's desired goal for the book:\n\n {goal}
|
||||
backstory: >
|
||||
You're a seasoned researcher, known for gathering the best sources and understanding the key elements of any topic.
|
||||
You aim to collect all relevant information so the book outline can be accurate and informative.
|
||||
|
||||
outliner:
|
||||
role: >
|
||||
Book Outlining Agent
|
||||
goal: >
|
||||
Based on the research, generate a book outline about the following topic: {topic}
|
||||
The generated outline should include all chapters in sequential order and provide a title and description for each chapter.
|
||||
Here is some additional information about the author's desired goal for the book:\n\n {goal}
|
||||
backstory: >
|
||||
You are a skilled organizer, great at turning scattered information into a structured format.
|
||||
Your goal is to create clear, concise chapter outlines with all key topics and subtopics covered.
|
||||
@@ -0,0 +1,22 @@
|
||||
research_topic:
|
||||
description: >
|
||||
Research the provided topic of {topic} to gather the most important information that will
|
||||
be useful in creating a book outline. Ensure you focus on high-quality, reliable sources.
|
||||
|
||||
Here is some additional information about the author's desired goal for the book:\n\n {goal}
|
||||
expected_output: >
|
||||
A set of key points and important information about {topic} that will be used to create the outline.
|
||||
agent: researcher
|
||||
|
||||
generate_outline:
|
||||
description: >
|
||||
Create a book outline with chapters in sequential order based on the research findings.
|
||||
Ensure that each chapter has a title and a brief description that highlights the topics and subtopics to be covered.
|
||||
It's important to note that each chapter is only going to be 3,000 words or less.
|
||||
Also, make sure that you do not duplicate any chapters or topics in the outline.
|
||||
|
||||
Here is some additional information about the author's desired goal for the book:\n\n {goal}
|
||||
|
||||
expected_output: >
|
||||
An outline of chapters, with titles and descriptions of what each chapter will contain.
|
||||
agent: outliner
|
||||
@@ -0,0 +1,56 @@
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.project import CrewBase, agent, crew, task
|
||||
from crewai_tools import SerperDevTool
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from write_a_book_with_flows.types import BookOutline
|
||||
|
||||
|
||||
@CrewBase
|
||||
class OutlineCrew:
|
||||
"""Book Outline Crew"""
|
||||
|
||||
agents_config = "config/agents.yaml"
|
||||
tasks_config = "config/tasks.yaml"
|
||||
# llm = ChatOpenAI(model="chatgpt-4o-latest")
|
||||
llm = ChatOpenAI(model="gpt-4o")
|
||||
|
||||
@agent
|
||||
def researcher(self) -> Agent:
|
||||
search_tool = SerperDevTool()
|
||||
return Agent(
|
||||
config=self.agents_config["researcher"],
|
||||
tools=[search_tool],
|
||||
llm=self.llm,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
@agent
|
||||
def outliner(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config["outliner"],
|
||||
llm=self.llm,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
@task
|
||||
def research_topic(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["research_topic"],
|
||||
)
|
||||
|
||||
@task
|
||||
def generate_outline(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["generate_outline"], output_pydantic=BookOutline
|
||||
)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the Book Outline Crew"""
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
researcher:
|
||||
role: >
|
||||
Research Agent
|
||||
goal: >
|
||||
Gather comprehensive information about {topic} and {chapter_title} that will be used to enhance the content of the chapter.
|
||||
Here is some additional information about the author's desired goal for the book and the chapter:\n\n {goal}
|
||||
Here is the outline description for the chapter:\n\n {chapter_description}
|
||||
backstory: >
|
||||
You are an experienced researcher skilled in finding the most relevant and up-to-date information on any given topic.
|
||||
Your job is to provide insightful data that supports and enriches the writing process for the chapter.
|
||||
|
||||
writer:
|
||||
role: >
|
||||
Chapter Writer
|
||||
goal: >
|
||||
Write a well-structured chapter for the book based on the provided chapter title, goal, and outline.
|
||||
The chapter should be written in markdown format and contain around 3,000 words.
|
||||
backstory: >
|
||||
You are an exceptional writer, known for producing engaging, well-researched, and informative content.
|
||||
You excel at transforming complex ideas into readable and well-organized chapters.
|
||||
@@ -0,0 +1,35 @@
|
||||
research_chapter:
|
||||
description: >
|
||||
Research the provided chapter topic, title, and outline to gather additional content that will be helpful in writing the chapter.
|
||||
Ensure you focus on reliable, high-quality sources of information.
|
||||
|
||||
Here is some additional information about the author's desired goal for the book and the chapter:\n\n {goal}
|
||||
Here is the outline description for the chapter:\n\n {chapter_description}
|
||||
|
||||
When researching, consider the following key points:
|
||||
- you need to gather enough information to write a 3,000-word chapter
|
||||
- The chapter you are researching needs to fit in well with the rest of the chapters in the book.
|
||||
|
||||
Here is the outline of the entire book:\n\n
|
||||
{book_outline}
|
||||
expected_output: >
|
||||
A set of additional insights and information that can be used in writing the chapter.
|
||||
agent: researcher
|
||||
|
||||
write_chapter:
|
||||
description: >
|
||||
Write a well-structured chapter based on the chapter title, goal, and outline description.
|
||||
Each chapter should be written in markdown and should contain around 3,000 words.
|
||||
|
||||
Here is the topic for the book: {topic}
|
||||
Here is the title of the chapter: {chapter_title}
|
||||
Here is the outline description for the chapter:\n\n {chapter_description}
|
||||
|
||||
Important notes:
|
||||
- The chapter you are writing needs to fit in well with the rest of the chapters in the book.
|
||||
|
||||
Here is the outline of the entire book:\n\n
|
||||
{book_outline}
|
||||
expected_output: >
|
||||
A markdown-formatted chapter of around 3,000 words that covers the provided chapter title and outline description.
|
||||
agent: writer
|
||||
@@ -0,0 +1,51 @@
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.project import CrewBase, agent, crew, task
|
||||
from crewai_tools import SerperDevTool
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from write_a_book_with_flows.types import Chapter
|
||||
|
||||
|
||||
@CrewBase
|
||||
class WriteBookChapterCrew:
|
||||
"""Write Book Chapter Crew"""
|
||||
|
||||
agents_config = "config/agents.yaml"
|
||||
tasks_config = "config/tasks.yaml"
|
||||
llm = ChatOpenAI(model="gpt-4o")
|
||||
|
||||
@agent
|
||||
def researcher(self) -> Agent:
|
||||
search_tool = SerperDevTool()
|
||||
return Agent(
|
||||
config=self.agents_config["researcher"],
|
||||
tools=[search_tool],
|
||||
llm=self.llm,
|
||||
)
|
||||
|
||||
@agent
|
||||
def writer(self) -> Agent:
|
||||
return Agent(
|
||||
config=self.agents_config["writer"],
|
||||
llm=self.llm,
|
||||
)
|
||||
|
||||
@task
|
||||
def research_chapter(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["research_chapter"],
|
||||
)
|
||||
|
||||
@task
|
||||
def write_chapter(self) -> Task:
|
||||
return Task(config=self.tasks_config["write_chapter"], output_pydantic=Chapter)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the Write Book Chapter Crew"""
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=True,
|
||||
)
|
||||
143
write_a_book_with_flows/src/write_a_book_with_flows/main.py
Normal file
143
write_a_book_with_flows/src/write_a_book_with_flows/main.py
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python
|
||||
import asyncio
|
||||
from typing import List
|
||||
|
||||
from crewai.flow.flow import Flow, listen, start
|
||||
from pydantic import BaseModel
|
||||
|
||||
from write_a_book_with_flows.crews.write_book_chapter_crew.write_book_chapter_crew import (
|
||||
WriteBookChapterCrew,
|
||||
)
|
||||
from write_a_book_with_flows.types import Chapter, ChapterOutline
|
||||
|
||||
from .crews.outline_book_crew.outline_crew import OutlineCrew
|
||||
|
||||
|
||||
class BookState(BaseModel):
|
||||
title: str = (
|
||||
"The Current State of AI in September 2024: Trends Across Industries and What's Next"
|
||||
)
|
||||
book: List[Chapter] = []
|
||||
book_outline: List[ChapterOutline] = []
|
||||
topic: str = (
|
||||
"Exploring the latest trends in AI across different industries as of September 2024"
|
||||
)
|
||||
goal: str = """
|
||||
The goal of this book is to provide a comprehensive overview of the current state of artificial intelligence in September 2024.
|
||||
It will delve into the latest trends impacting various industries, analyze significant advancements,
|
||||
and discuss potential future developments. The book aims to inform readers about cutting-edge AI technologies
|
||||
and prepare them for upcoming innovations in the field.
|
||||
"""
|
||||
|
||||
|
||||
class BookFlow(Flow[BookState]):
|
||||
initial_state = BookState
|
||||
|
||||
@start()
|
||||
def generate_book_outline(self):
|
||||
print("Kickoff the Book Outline Crew")
|
||||
output = (
|
||||
OutlineCrew()
|
||||
.crew()
|
||||
.kickoff(inputs={"topic": self.state.topic, "goal": self.state.goal})
|
||||
)
|
||||
|
||||
chapters = output["chapters"]
|
||||
print("Chapters:", chapters)
|
||||
|
||||
self.state.book_outline = chapters
|
||||
return chapters
|
||||
|
||||
@listen(generate_book_outline)
|
||||
async def write_chapters(self):
|
||||
print("Writing Book Chapters")
|
||||
tasks = []
|
||||
|
||||
async def write_single_chapter(chapter_outline):
|
||||
output = await (
|
||||
WriteBookChapterCrew()
|
||||
.crew()
|
||||
.kickoff_async(
|
||||
inputs={
|
||||
"goal": self.state.goal,
|
||||
"topic": self.state.topic,
|
||||
"chapter_title": chapter_outline.title,
|
||||
"chapter_description": chapter_outline.description,
|
||||
"book_outline": [
|
||||
chapter_outline.model_dump_json()
|
||||
for chapter_outline in self.state.book_outline
|
||||
],
|
||||
}
|
||||
)
|
||||
)
|
||||
title = output["title"]
|
||||
content = output["content"]
|
||||
chapter = Chapter(title=title, content=content)
|
||||
return chapter
|
||||
|
||||
for chapter_outline in self.state.book_outline:
|
||||
print(f"Writing Chapter: {chapter_outline.title}")
|
||||
print(f"Description: {chapter_outline.description}")
|
||||
# Schedule each chapter writing task
|
||||
task = asyncio.create_task(write_single_chapter(chapter_outline))
|
||||
tasks.append(task)
|
||||
|
||||
# Await all chapter writing tasks concurrently
|
||||
chapters = await asyncio.gather(*tasks)
|
||||
print("Newly generated chapters:", chapters)
|
||||
self.state.book.extend(chapters)
|
||||
|
||||
print("Book Chapters", self.state.book)
|
||||
|
||||
@listen(write_chapters)
|
||||
async def join_and_save_chapter(self):
|
||||
print("Joining and Saving Book Chapters")
|
||||
# Combine all chapters into a single markdown string
|
||||
book_content = ""
|
||||
|
||||
for chapter in self.state.book:
|
||||
# Add the chapter title as an H1 heading
|
||||
book_content += f"# {chapter.title}\n\n"
|
||||
# Add the chapter content
|
||||
book_content += f"{chapter.content}\n\n"
|
||||
|
||||
# The title of the book from self.state.title
|
||||
book_title = self.state.title
|
||||
|
||||
# Create the filename by replacing spaces with underscores and adding .md extension
|
||||
filename = f"./{book_title.replace(' ', '_')}.md"
|
||||
|
||||
# Save the combined content into the file
|
||||
with open(filename, "w", encoding="utf-8") as file:
|
||||
file.write(book_content)
|
||||
|
||||
print(f"Book saved as {filename}")
|
||||
return book_content
|
||||
|
||||
|
||||
async def run_flow():
|
||||
"""
|
||||
Run the flow.
|
||||
"""
|
||||
book_flow = BookFlow()
|
||||
book_flow.kickoff()
|
||||
|
||||
|
||||
async def plot_flow():
|
||||
"""
|
||||
Plot the flow.
|
||||
"""
|
||||
book_flow = BookFlow()
|
||||
book_flow.plot()
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_flow())
|
||||
|
||||
|
||||
def plot():
|
||||
asyncio.run(plot_flow())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
17
write_a_book_with_flows/src/write_a_book_with_flows/types.py
Normal file
17
write_a_book_with_flows/src/write_a_book_with_flows/types.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ChapterOutline(BaseModel):
|
||||
title: str
|
||||
description: str
|
||||
|
||||
|
||||
class BookOutline(BaseModel):
|
||||
chapters: List[ChapterOutline]
|
||||
|
||||
|
||||
class Chapter(BaseModel):
|
||||
title: str
|
||||
content: str
|
||||
Reference in New Issue
Block a user