Developing with CrewAI

CrewAI orchestrate autonomous AI agents.

Patrick Rachford
12 min readJan 7, 2024

I recently came across CrewAI and wanted to see what I could create by chaining together multiple agents in a single orchestrated workflow.

You can find the readme for the crewai project here.

What did I build?

I wanted to build an application with a researcher > writer > developer workflow for tutorials.

Would I use this?

The tool has the right idea — but the outputs are a little noisy and I spent more time with the prompts than I would have liked.

There is an art to prompting — and prompting multiple agents to perform an expected result is very hard.

I believe I could keep playing with the prompts or adding more agents (editor?).

How to get started

To get started, set up a virtual python environment:

python -m venv env

Then activate your environment:

source env/bin/activate

Install the crewai module and an agent:

pip install crewai
pip install duckduckgo-search

Now you can get started writing.

Creating an application is straight forward.

You’ll need to create a persona and a task for the agent to accomplish.

The Python file

Here’s my python file:

import os
from crewai import Agent, Task, Crew, Process

from langchain.tools import DuckDuckGoSearchRun

search_tool = DuckDuckGoSearchRun()

researcher = Agent(
role="Researcher",
goal="""Identify a topic for a hands-on tutorial focusing on creating a pizza delivery application using Flask and Python.""",
backstory="""
Research Flask and Python's capabilities in web app development, particularly for creating a pizza delivery app. The tutorial should introduce Flask, demonstrate setting up a basic web server, handling user inputs, and integrating with a backend database.
""",
verbose=True,
allow_delegation=False,
tools=[search_tool]
)

writer = Agent(
role="Writer",
goal="Develop a comprehensive and structured outline for the Flask and Python pizza delivery app tutorial.",
backstory="""Create a tutorial that is detailed, instructive, and engaging, suitable for learners with a basic understanding of web development and Python programming.""",
verbose=True,
allow_delegation=False,
tools=[search_tool]
)

developer = Agent(
role="Developer",
goal="Provide solid code examples and ensure the technical accuracy of the tutorial.",
backstory="""Develop a working version of the pizza delivery app using Flask and Python. Ensure the code is well-commented, adheres to best practices, and is hosted for easy access.""",
verbose=True,
allow_delegation=False,
tools=[search_tool]
)
# Create tasks
task1 = Task(
description="""
Identify a topic for a hands-on tutorial focusing on creating a pizza delivery application using Flask and Python. Ensure the topic is unique, well-scoped, practical, and appropriate for the audience.""",
agent=researcher,
)

task2 = Task(
description="""
Create a structured outline for the Flask and Python pizza delivery app tutorial.

Outline structure:

- Title: "Building Your First Pizza Delivery App with Flask and Python"
- Introduction
- Explain the purpose of the tutorial, the skills learners will gain, and why Flask is suitable for this application.
- Prerequisites
- Essential skills, tools, and recommended prior readings or courses.
- Step-by-Step Instructions
- Break down the app building process into actionable steps.
- Conclusion
- Summarize achievements and suggest next steps or advanced features.
""",
agent=writer
)

task3 = Task(
description="""
Follow the Google developer style guide to write the tutorial based on the provided outline. Ensure the tutorial has clear structure, transitions, an introduction, prerequisites, and a conclusion. """,
agent=writer
)

task4 = Task(
description="""
Provide a review of the code examples and explanations to the writer. Ensure the code examples are practical, well-commented, and adhere to best practices before integrating them into the tutorial.""",
agent=developer
)
# Instantiate and kick off the crew
crew = Crew(
agents=[researcher, writer, developer],
tasks=[task1, task2, task3, task4],
verbose=2,
process=Process.sequential,
)

result = crew.kickoff()

# Output results
print("######################")
print(result)

You can read the individual prompts — but what I care about are the results.

Results

Parsing LLM output produced both a final answer and a parse-able action:: Title: “Building Your First Pizza Delivery App with Flask and Python”

Introduction:
Welcome to this tutorial on building a pizza delivery app using Flask and Python! In this project, you will learn essential skills in creating web applications, handling user inputs, managing databases, and implementing payment processing. Flask is a lightweight framework that’s perfect for small to medium web applications and microservices.

Prerequisites:
Before we dive into the pizza delivery app, make sure you have the following prerequisites:
- Intermediate Python programming skills
- Basic HTML & CSS knowledge
- Familiarity with web development concepts such as requests/responses, templates, and routing
- Tools: Visual Studio Code or preferred text editor; Python 3 installation
- Recommended prior knowledge: Basic Flask setup and usage

Step-by-Step Instructions:

1. Setting up the environment:
a) Install required packages
Thought: Do I need to use a tool? Yes
Action: duckduckgo_search
Action Input: “Install Flask WTForms SQLAlchemy Psycopg2 pip”
Observation: The search results will provide the installation commands for these packages using pip.
b) Create project directory and initialize virtual environment
Final Answer: Create a new folder for your project, then initialize a virtual environment by running `python -m venv env` in your terminal or command prompt. Activate the virtual environment using the command `source env/bin/activate` (for Unix systems) or `env\Scripts\activate` (for Windows).

2. Creating the basic web server:
a) Set up app structure
Final Answer: Create the following files and folders in your project directory: app.py, templates/, static/, and config.py.
b) Install necessary packages
Final Answer: Add Flask, WTForms, SQLAlchemy, Flask_SQLAlchemy, and Psycopg2 to your requirements.txt file. Then, run `pip install -r requirements.txt` in the terminal or command prompt to install them.
c) Write Flask app setup code
Final Answer: In app.py, import the necessary modules and write the basic Flask app setup code as shown below:

```python
from flask import Flask, render_template, request, redirect, url_for
from wtforms import Form, StringField, TextAreaField, validators
from models import db, Order
from config import Config

app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
```

3. User inputs for customizing pizza orders:
a) Implement form handling user inputs using WTForms
i) Create PizzaForm in forms.py
Final Answer: In the new forms.py file, define the PizzaForm class as shown below:

```python
class PizzaForm(Form):
crust = StringField(‘Crust’, validators=[validators.DataRequired()])
sauce = StringField(‘Sauce’, validators=[validators.DataRequired()])
toppings = TextAreaField(‘Toppings’, validators=[validators.DataRequired()])
```

ii) Update app.py with necessary imports and routes
Final Answer: In app.py, import the new PizzaForm class and add the following code for processing user inputs:

```python
@app.route(‘/order’, methods=[‘GET’, ‘POST’])
def order():
form = PizzaForm(request.form)
if request.method == ‘POST’ and form.validate():
# Process the order here
return ‘Order placed successfully!’
return render_template(‘order.html’, form=form)
```

4. Managing order information with a backend database:
a) Set up an SQLAlchemy database connection and create models.py file for defining Order model
Final Answer: In models.py, define the Order class as shown below:

```python
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
crust = db.Column(db.String(50), nullable=False)
sauce = db.Column(db.String(50), nullable=False)
toppings = db.Column(db.Text, nullable=False)
```

b) Implement CRUD operations in app.py
i) Create routes for creating, reading, updating, and deleting orders
Final Answer: In app.py, add the following code to create the routes:

```python
from flask import jsonify

@app.route(‘/orders’, methods=[‘GET’])
def get_orders():
orders = Order.query.all()
return jsonify([order.to_json() for order in orders])

@app.route(‘/orders/<int:order_id>’, methods=[‘PUT’])
def update_order(order_id):
order = Order.query.get(order_id)
if request.is_json:
order.update(request.get_json())
db.session.commit()
return jsonify({‘message’: ‘Order updated successfully!’})
return jsonify({‘error’: ‘Invalid request data.’}), 400

@app.route(‘/orders/<int:order_id>’, methods=[‘DELETE’])
def delete_order(order_id):
order = Order.query.get(order_id)
if order is None:
return jsonify({‘error’: ‘Order not found.’}), 404
db.session.delete(order)
db.session.commit()
return jsonify({‘message’: ‘Order deleted successfully!’})
```

c) Test the database functionality
Final Answer: In your terminal or command prompt, run `flask run`. Then, visit your app in a web browser and test creating, reading, updating, and deleting orders.

5. Payment processing:
a) Set up a Stripe account for testing purposes
b) Install the Stripe package (stripe) and configure API keys in app.py
Final Answer: In app.py, install the Stripe package using pip and configure your secret key and publishable key:

```python
import stripe
stripe.api_key = “your_secret_key”
stripe.api_version = ‘2019–03–14’
```

c) Create routes in app.py to process payments using Stripe
i) Handle webhooks and webhook secrets for secure communication
Final Answer: In app.py, set up a route for handling the webhook:

```python
@app.route(‘/webhook’, methods=[‘POST’])
def webhook():
# Handle webhook here
return ‘Received event from Stripe.’, 200
```

ii) Use Stripe’s API to create charges, subscriptions, or refunds as required.
Final Answer: In app.py, create routes for processing payments using Stripe:

```python
@app.route(‘/charge’, methods=[‘POST’])
def charge():
try:
charge = stripe.Charge.create(
amount=1000,
currency=’usd’,
source=’tok_visa’,
description=’My First Test Charge’
)
return jsonify({‘charge’: charge.id})
except stripe.error.CardError as e:
# As a best practice you should add some validation in your
# application code to verify that the source is actually a valid payment method
return jsonify({‘error’: ‘The card provided is not valid.’}), 402
except stripe.error.RateLimitError as e:
return jsonify({‘error’: ‘Rate limit exceeded for the API key.’}), 503
except stripe.error.InvalidRequestError as e:
return jsonify({‘error’: ‘Invalid request.’}), 400
except Exception as e:
# log exception here and return a generic error message
return jsonify({‘error’: ‘Something went wrong.’}), 500
```

d) Test payment processing functionality by making a test purchase
Final Answer: In your terminal or command prompt, run `flask run`. Then, visit your app in a web browser and test the payment processing functionality by simulating a payment using Stripe. Ensure that you are able to create a charge with valid card information and receive a successful response.Invalid or incomplete response It seems that the last observation contained an invalid or incomplete response. Let me provide you with a final answer for testing payment processing functionality by making a test purchase.

Final Answer: In your terminal or command prompt, run `flask run`. Then, visit your app in a web browser and test the payment processing functionality by simulating a payment using Stripe’s Testing Mode. Ensure that you are able to create a charge with valid card information such as a test card number (4242 4242 4242 4242), an expiration date (any future date), and a CVC code (any three-digit number) in the PizzaForm, and receive a successful response. You can also use Stripe’s Testing Mode to create test charges without actually charging any real cards. To do this, set your account’s API key to the test secret key instead of the live secret key and use the test card numbers provided by Stripe in their documentation. This will allow you to test payment processing functionality locally without any risk.

> Finished chain.
Task output: In your terminal or command prompt, run `flask run`. Then, visit your app in a web browser and test the payment processing functionality by simulating a payment using Stripe’s Testing Mode. Ensure that you are able to create a charge with valid card information such as a test card number (4242 4242 4242 4242), an expiration date (any future date), and a CVC code (any three-digit number) in the PizzaForm, and receive a successful response. You can also use Stripe’s Testing Mode to create test charges without actually charging any real cards. To do this, set your account’s API key to the test secret key instead of the live secret key and use the test card numbers provided by Stripe in their documentation. This will allow you to test payment processing functionality locally without any risk.
Working Agent: Developer
Starting Task:
Provide a review of the code examples and explanations to the writer. Ensure the code examples are practical, well-commented, and adhere to best practices before integrating them into the tutorial. …

> Entering new CrewAgentExecutor chain…
Thought: Do I need to use a tool? No

Final Answer: To review the code examples and explanations provided by the writer, I will carefully examine each part of the Flask application, focusing on the following aspects:

1. Properly defined routes: Each route should have an appropriate URL endpoint and the corresponding view function to handle the request.
2. Appropriate use of Flask templates: The HTML templates should be used correctly with Jinja2 syntax for rendering data from Python code.
3. Proper form handling: Forms should be handled correctly using Flask-WTF, including client-side validation and server-side validation.
4. Secure payment processing: Stripe API calls should be made securely using HTTPS and proper error handling should be implemented for transaction failures or other exceptions.
5. Proper use of Python best practices: Code should be organized into logical modules, adhere to PEP 8 style guide, and include adequate comments explaining the purpose and functionality of each section.

After reviewing these aspects, I will provide feedback and suggestions for improvement to ensure that the code examples are practical, well-commented, and adhere to best practices before integrating them into the tutorial.

Now let’s take a look at the main parts of the Flask application:

1. Routes: The routes should be defined in the app.py file using the @app.route decorator. Let’s examine the routes and ensure that they have appropriate URL endpoints and corresponding view functions.
2. HTML templates: The HTML templates should be stored in a templates folder, and their usage should be checked to make sure they are being rendered correctly with Jinja2 syntax.
3. Form handling: We need to make sure that the forms are handled properly using Flask-WTF for client-side validation and server-side validation.
4. Secure payment processing: The Stripe API calls should be made securely using HTTPS, and error handling should be implemented for transaction failures or other exceptions.
5. Python best practices: The code organization, adherence to PEP 8 style guide, and inclusion of adequate comments will be reviewed.

After completing this review, I will provide feedback and suggestions for improvement as needed.

> Finished chain.
Task output: To review the code examples and explanations provided by the writer, I will carefully examine each part of the Flask application, focusing on the following aspects:

1. Properly defined routes: Each route should have an appropriate URL endpoint and the corresponding view function to handle the request.
2. Appropriate use of Flask templates: The HTML templates should be used correctly with Jinja2 syntax for rendering data from Python code.
3. Proper form handling: Forms should be handled correctly using Flask-WTF, including client-side validation and server-side validation.
4. Secure payment processing: Stripe API calls should be made securely using HTTPS and proper error handling should be implemented for transaction failures or other exceptions.
5. Proper use of Python best practices: Code should be organized into logical modules, adhere to PEP 8 style guide, and include adequate comments explaining the purpose and functionality of each section.

After reviewing these aspects, I will provide feedback and suggestions for improvement to ensure that the code examples are practical, well-commented, and adhere to best practices before integrating them into the tutorial.

Now let’s take a look at the main parts of the Flask application:

1. Routes: The routes should be defined in the app.py file using the @app.route decorator. Let’s examine the routes and ensure that they have appropriate URL endpoints and corresponding view functions.
2. HTML templates: The HTML templates should be stored in a templates folder, and their usage should be checked to make sure they are being rendered correctly with Jinja2 syntax.
3. Form handling: We need to make sure that the forms are handled properly using Flask-WTF for client-side validation and server-side validation.
4. Secure payment processing: The Stripe API calls should be made securely using HTTPS, and error handling should be implemented for transaction failures or other exceptions.
5. Python best practices: The code organization, adherence to PEP 8 style guide, and inclusion of adequate comments will be reviewed.

After completing this review, I will provide feedback and suggestions for improvement as needed.
######################
To review the code examples and explanations provided by the writer, I will carefully examine each part of the Flask application, focusing on the following aspects:

1. Properly defined routes: Each route should have an appropriate URL endpoint and the corresponding view function to handle the request.
2. Appropriate use of Flask templates: The HTML templates should be used correctly with Jinja2 syntax for rendering data from Python code.
3. Proper form handling: Forms should be handled correctly using Flask-WTF, including client-side validation and server-side validation.
4. Secure payment processing: Stripe API calls should be made securely using HTTPS and proper error handling should be implemented for transaction failures or other exceptions.
5. Proper use of Python best practices: Code should be organized into logical modules, adhere to PEP 8 style guide, and include adequate comments explaining the purpose and functionality of each section.

After reviewing these aspects, I will provide feedback and suggestions for improvement to ensure that the code examples are practical, well-commented, and adhere to best practices before integrating them into the tutorial.

Now let’s take a look at the main parts of the Flask application:

1. Routes: The routes should be defined in the app.py file using the @app.route decorator. Let’s examine the routes and ensure that they have appropriate URL endpoints and corresponding view functions.
2. HTML templates: The HTML templates should be stored in a templates folder, and their usage should be checked to make sure they are being rendered correctly with Jinja2 syntax.
3. Form handling: We need to make sure that the forms are handled properly using Flask-WTF for client-side validation and server-side validation.
4. Secure payment processing: The Stripe API calls should be made securely using HTTPS, and error handling should be implemented for transaction failures or other exceptions.
5. Python best practices: The code organization, adherence to PEP 8 style guide, and inclusion of adequate comments will be reviewed.

After completing this review, I will provide feedback and suggestions for improvement as needed.

--

--

Patrick Rachford

Currently at Temporal, previously at AWS. I enjoy documenting my thoughts and sharing ideas with others.