Build an AI Content Engine in Python
Research any topic with Firecrawl and generate learning content with GPT-5.4-mini.
Introduction
30 Second Summary
You know that feeling when you want to learn something new but dread spending hours searching for the right resources? Imagine having a personal study-guide maker that researches any topic and hands you a summary, quiz questions, and flashcards in seconds.
In this project, you will build a command-line AI Content Engine in Python that researches any topic from the live web and generates structured learning materials using OpenAI's GPT-5.4-mini model.
What You'll Build
A Python script that uses Firecrawl to search and scrape live web content about any topic, then sends that research to GPT-5.4-mini to generate three types of learning content saved to files on your computer.
By the end of this project, you'll have:
- A working Python project with environment variables for secure API key storage.
- A research function that scrapes live web content using Firecrawl's search and scrape methods.
- A content generation pipeline that produces structured summaries, quizzes, and flashcards using GPT-5.4-mini.
- Secret Mission: Add a content style parameter that lets you generate content in different tones (formal academic, casual explainer, or quiz-show host).
Are there any prerequisites?
You need Python installed on your Mac, an OpenAI API key, and a Firecrawl API key. Firecrawl offers a free tier with 1,000 credits per month, so signing up costs nothing.
Step 1 walks you through setting everything up, so don't worry if you haven't done this yet.
Set Up Your AI Development Environment
Before you can build anything, you need a workspace. Every Python project starts with a dedicated folder, an isolated environment for its packages, and the right libraries installed.
In this step, you will create the project folder, set up a virtual environment to keep your packages separate from the rest of your system, and install the three libraries that power the Content Engine.
In this step, get ready to:
- Create the project folder and a Python virtual environment.
- Install the required packages and configure your API keys.
- Verify everything works with a test script.
Create your project folder and virtual environment
A virtual environment is a self-contained copy of Python that lives inside your project folder. Any packages you install go into this environment and nowhere else, so different projects never interfere with each other.
- Press Cmd+Space to open your search bar.
- Type Terminal and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir content-engine
cd content-engine
- Confirm the folder was created by running:
pwd
You should see a path ending in /Desktop/content-engine.
- Create a virtual environment by running this command:
python3 -m venv venv
What does this command do?
This tells Python to use its built-in venv module to create a virtual environment in a folder called venv. The command finishes silently with no output, which means it worked.
- Activate the virtual environment by running:
source venv/bin/activate
Your terminal prompt now shows (venv) at the beginning. This confirms you are working inside the virtual environment.
- Verify Python is working by running:
python3 --version
You should see a version number like Python 3.9.x or higher.
Not seeing (venv) in your prompt?
Make sure you ran source venv/bin/activate from inside the content-engine folder. If you see an error about the file not existing, run ls and check that a venv folder is listed.
Help me activate my Python virtual environment.
Install packages and configure your API keys
Your project needs three packages: one to talk to OpenAI, one to scrape the web with Firecrawl, and one to load environment variables from a file.
- Install all three packages by running this command:
pip install openai firecrawl-py python-dotenv
What are these packages?
- openai lets you call OpenAI models (like GPT-5.4-mini) from Python.
- firecrawl-py lets you search the web and scrape pages into clean markdown using Firecrawl.
- python-dotenv loads variables from a .env file into your environment so you never hardcode secrets.
You should see output ending with a line that starts with Successfully installed followed by the package names.
Seeing a permissions error or network timeout?
Make sure your prompt shows (venv) before running pip install. If the virtual environment is not active, pip may try to install system-wide, which requires elevated permissions.
Help me fix my pip install error.
Next, you will create the two project files. Your API keys need to live in a special .env file so they stay out of your code.
- Switch back to VS Code.
- Click File in the top menu bar.
- Click Open Folder... (or press Cmd+O).
- Navigate to Desktop, select the content-engine folder, and click Open.
The Explorer sidebar on the left now shows your content-engine folder with the venv directory inside it.
- Hover over the CONTENT-ENGINE heading in the Explorer sidebar and click the New File icon (a page with a plus sign).
- Type .env as the filename and press Enter.
- Paste the following content into the .env file, replacing each placeholder with your actual API key:
OPENAI_API_KEY=your-openai-api-key-here
FIRECRAWL_API_KEY=your-firecrawl-api-key-here
Why a .env file?
Storing keys in a .env file keeps them separate from your code. The python-dotenv library reads this file at runtime so your script can access the keys without hardcoding them.
Replace your-openai-api-key-here with the OpenAI key you already have. Replace your-firecrawl-api-key-here with the Firecrawl key you obtained from firecrawl.dev.
- Save .env by pressing Cmd+S.
- Click the New File icon again in the Explorer sidebar.
- Type engine.py as the filename and press Enter.
- Leave engine.py empty for now and save it with Cmd+S.
Your Explorer sidebar should now show three items: .env, engine.py, and the venv folder.
Test that your libraries import correctly
Before building anything, you want to confirm that all three packages installed properly and can be imported without errors.
- Open the integrated terminal in VS Code by pressing Ctrl+` (the backtick key, to the left of the 1 key).
Does VS Code detect the virtual environment?
VS Code often detects the venv folder and asks if you want to use it as your Python interpreter. Click Yes if you see that prompt. If you do not see (venv) at the start of your terminal prompt, activate the environment manually by running source venv/bin/activate in the VS Code terminal.
- Create a test file by clicking the New File icon in the Explorer and naming it test.py.
- Paste the following code into test.py:
# Quick test to confirm all libraries are installed
from dotenv import load_dotenv
from firecrawl import Firecrawl
from openai import OpenAI
print("All libraries imported successfully!")
What does this code do?
Each from ... import ... line pulls in one of the libraries you just installed. If any import fails, Python throws an error immediately. The final print statement only runs if all three imports succeed.
- Save test.py with Cmd+S.
- Run the test by entering this command in the VS Code terminal:
python3 test.py
You should see the message All libraries imported successfully! printed in the terminal.
Seeing a ModuleNotFoundError?
This means the packages were installed outside your virtual environment, or the virtual environment is not active. Check that your terminal prompt starts with (venv). If it does not, run source venv/bin/activate and try the pip install command again.
Help me fix my ModuleNotFoundError.
- Once the test passes, you can delete test.py by right-clicking it in the Explorer and selecting Delete. It was only needed for verification.
✔️ Awesome, I've got everything!
Great. Double check that your .env file is saved with your real API keys replacing the placeholders.
ⓧ I'd like to double check the full code
Here is what your .env file should look like (with your actual keys in place of the placeholders):
OPENAI_API_KEY=your-openai-api-key-here
FIRECRAWL_API_KEY=your-firecrawl-api-key-here
Your engine.py file should exist but be empty. You will start writing code in it in the next step.
Your workspace is ready. Next up, you will write the research function that uses Firecrawl to pull live content from the web.
Build the Research Function with Firecrawl
Your environment is ready and your API keys are loaded. Now it's time to make your engine actually do something.
In this step, you'll build the research layer of your pipeline. Your engine will take any topic, search the live web for relevant pages using Firecrawl, then scrape the best result to get real content. This is the "input" and "research" phase of the Input > Research > Generate > Save pattern.
In this step, get ready to:
- Import your libraries and load environment variables.
- Write a function that searches the web and scrapes page content.
- Test the function with a real topic and verify it returns live web content.
Set up your imports and load environment variables
Every Python script starts by declaring what tools it needs. Your engine uses five libraries that handle different jobs in the pipeline.
- Switch back to engine.py in VS Code and add the following imports and setup code:
import os
import json
from dotenv import load_dotenv
from firecrawl import Firecrawl
from openai import OpenAI
# Load API keys from .env file into environment variables
load_dotenv()
What does this code do?
- os lets you read environment variables (your API keys).
- json gives you tools for working with structured data (used later in the project).
- load_dotenv() reads your .env file and makes those keys available as environment variables.
- Firecrawl and OpenAI are the API clients you'll use to search the web and generate content.
- Save engine.py.
Why import OpenAI now?
You won't use the OpenAI client until the next step. Declaring all imports at the top of the file is a Python convention that makes it easy to see at a glance what tools a script depends on.
Write the research_topic function
A function is a reusable block of code that takes an input and produces an output. Your research_topic function takes a topic string, searches the web for it, and returns a dictionary containing the source URL and scraped content.
- Add the following function below load_dotenv() in engine.py:
def research_topic(topic):
"""Research a topic using Firecrawl to get live web content."""
# Initialize the Firecrawl client with your API key
firecrawl = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY"))
# Search the web for pages about this topic
results = firecrawl.search(topic, limit=3)
# Grab the URL of the top search result
top_result = results.data[0]
source_url = top_result.url
# Scrape the full page content as markdown
doc = firecrawl.scrape(source_url, formats=["markdown"])
# Package everything into a dictionary to return
research_data = {
"topic": topic,
"source_url": source_url,
"content": doc.markdown
}
return research_data
What does this function do?
This function follows a two-step discover-then-extract pattern:
- firecrawl.search(topic, limit=3) asks Firecrawl to find up to 3 web pages related to your topic.
- results.data[0] grabs the first (most relevant) result and pulls out its URL.
- firecrawl.scrape(source_url, formats=["markdown"]) fetches the full page content and converts it to clean markdown text.
- The function returns a dictionary with three keys: the topic, the source URL, and the scraped content. Dictionaries are Python's way of grouping related data under named keys.
- Save engine.py.
Seeing a red underline under Firecrawl?
VS Code may show a warning if it hasn't recognized the installed packages yet. As long as your virtual environment is active in the terminal (you see (venv) in your prompt), the code will run correctly.
Help me fix import warnings in VS Code for my Firecrawl project.
Test the function with a sample topic
Before building the rest of the pipeline, confirm that your research function can actually reach the web and pull back real content.
- Add these temporary test lines at the bottom of engine.py, below the function:
# Temporary test - remove after verifying
research = research_topic("machine learning")
print(research["source_url"])
print(research["content"][:500])
What does this test do?
- research_topic("machine learning") calls your function with a sample topic.
- print(research["source_url"]) shows which web page Firecrawl found.
- research["content"][:500] prints just the first 500 characters of the scraped content so you can verify it's real text without flooding your terminal.
- Save engine.py.
- Run the script by entering this command in your terminal:
python3 engine.py
- You should see a URL printed on the first line, followed by a block of text content from that page.
Seeing an authentication or API key error?
- Double-check that your .env file has FIRECRAWL_API_KEY set to a valid key with no extra spaces or quotes around the value.
- Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt.
- Confirm you have free credits remaining on your Firecrawl account at firecrawl.dev.
Help me debug my Firecrawl API key error in Python.
- Now that you've confirmed the function works, remove the three temporary test lines from the bottom of engine.py.
- Save engine.py.
✔️ Awesome, I've got everything!
Great. Double check you've saved engine.py after removing the test lines.
I'd like to double check the full code
Here's what engine.py should look like at the end of this step:
import os
import json
from dotenv import load_dotenv
from firecrawl import Firecrawl
from openai import OpenAI
load_dotenv()
def research_topic(topic):
"""Research a topic using Firecrawl to get live web content."""
firecrawl = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY"))
results = firecrawl.search(topic, limit=3)
top_result = results.data[0]
source_url = top_result.url
doc = firecrawl.scrape(source_url, formats=["markdown"])
research_data = {
"topic": topic,
"source_url": source_url,
"content": doc.markdown
}
return research_data
Your engine can now reach out to the live web and pull back real content on any topic. Next up, you'll send that research to GPT-5.4-mini and turn it into structured learning materials.
Build the Content Generation Function with GPT-5.4-mini
Your research function can now pull live content from the web. The next piece of the puzzle is turning that raw research into something useful.
In this step, you will build a function that sends your research to OpenAI's gpt-5.4-mini model and gets back structured learning content. The function constructs a detailed prompt, makes an API call, and parses the response into separate sections.
In this step, get ready to:
- Write a content generation function with a detailed system prompt.
- Make an API call to GPT-5.4-mini and extract the response.
- Parse the generated text into a structured dictionary and test the full flow.
Write the function definition and system prompt
The generate_content function needs to tell GPT-5.4-mini exactly what format to produce. You will do this using an f-string that embeds the topic name directly into a multi-line prompt.
- In engine.py, scroll down to the blank space below the research_topic function.
- Add the generate_content function definition and system prompt by copying this code below research_topic:
def generate_content(topic, research):
"""Generate structured learning content using GPT-5.4-mini."""
client = OpenAI()
system_prompt = f"""You are an expert educator. Based on the research provided,
generate learning content about "{topic}" in the following exact format:
## Summary
Write a clear 3-paragraph summary of the topic.
## Quiz
Write 3 quiz questions with answers in this format:
Q1: [question]
A1: [answer]
Q2: [question]
A2: [answer]
Q3: [question]
A3: [answer]
## Flashcards
Write 5 flashcards in this format:
Front: [term or question]
Back: [definition or answer]
"""
What does this code do?
This chunk sets up the function and builds the instruction for the AI model.
- def generate_content(topic, research) defines a function that accepts a topic string and the research dictionary from the previous step.
- client = OpenAI() creates an OpenAI client. The SDK automatically reads your OPENAI_API_KEY from the environment, so you do not need to pass it explicitly.
- The system_prompt is an f-string (f"""...""") that injects the topic variable directly into the text. It tells the model the exact section headings and formats to use in its output.
What is an f-string?
An f-string is a Python string prefixed with f that lets you embed variables inside curly braces. For example, f"Hello {name}" inserts the value of name directly into the string.
Triple-quoted f-strings (f"""...""") let you write multi-line text without needing escape characters for newlines.
Build the user message and make the API call
The system prompt told GPT-5.4-mini what format to use. Now you need to give it the actual research content to work with, then send both messages to the API.
- Still inside the generate_content function, add the following code directly below the system_prompt variable (after the closing """):
user_message = f"""Here is the research content about "{topic}" from {research["source_url"]}:
{research["content"][:3000]}
Generate the learning content now."""
response = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)
generated_text = response.choices[0].message.content
What does this code do?
This chunk builds the user message and sends everything to the OpenAI API.
- user_message is another f-string that embeds the source URL and the first 3,000 characters of the scraped content. The [:3000] slice keeps the request small so it stays fast and cheap.
- client.chat.completions.create() is the API call. It takes a model name and a messages list. Each message is a dictionary with "role" and "content" keys.
- response.choices[0].message.content drills into the response object to pull out the actual text the model generated.
Why a list of dictionaries?
The OpenAI Chat Completions API expects messages as a list of dictionaries. Each dictionary has a "role" (who is speaking) and "content" (what they say). This is how APIs communicate structured data back and forth.
Dictionaries are the backbone of API communication in Python. You will see this pattern every time you work with web services.
Parse the response and test the function
The model returns one big block of text with headings like ## Summary, ## Quiz, and ## Flashcards. You need to split that text into separate pieces so you can save each section to its own file later.
- Still inside the generate_content function, add this code directly below the generated_text line:
content = {
"summary": generated_text.split("## Quiz")[0].replace("## Summary", "").strip(),
"quiz": generated_text.split("## Quiz")[1].split("## Flashcards")[0].strip(),
"flashcards": generated_text.split("## Flashcards")[1].strip()
}
return content
What does this code do?
This chunk parses the raw text into a clean dictionary using string methods.
- .split("## Quiz") cuts the text at the ## Quiz heading. Index [0] gives you everything before it (the summary). Index [1] gives you everything after it.
- .replace("## Summary", "") removes the heading text so you are left with just the body content.
- .strip() removes any extra whitespace at the start and end of each section.
- The return content statement sends the dictionary back to whatever code called this function.
- Save engine.py.
- To test the full flow, temporarily add these lines at the very bottom of engine.py (outside the function, with no indentation):
# Temporary test - remove after verifying
topic = "retrieval augmented generation"
research = research_topic(topic)
print("Research done! Generating content...")
content = generate_content(topic, research)
print(content["summary"][:200])
- Run the script to test by running:
python3 engine.py
You should see a short paragraph of AI-generated summary text printed to your terminal. This confirms both your research function and your content generation function are working together.
Seeing an AuthenticationError?
This means your OpenAI API key is invalid or missing. Double check that your .env file has OPENAI_API_KEY set to a valid key from your OpenAI account.
Make sure there are no extra spaces or quotes around the key value in .env.
Help me debug my OpenAI AuthenticationError in engine.py.
- Once you see the summary output, delete the temporary test lines you just added (everything below the return content line).
- Save engine.py again.
✔️ Awesome, I've got everything!
Great. Double check you have saved engine.py without the temporary test lines at the bottom.
ⓧ I'd like to double check the full code
Here is the complete engine.py at the end of this step:
import os
import json
from dotenv import load_dotenv
from firecrawl import Firecrawl
from openai import OpenAI
load_dotenv()
def research_topic(topic):
"""Research a topic using Firecrawl to get live web content."""
firecrawl = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY"))
results = firecrawl.search(topic, limit=3)
top_result = results.data[0]
source_url = top_result.url
doc = firecrawl.scrape(source_url, formats=["markdown"])
research_data = {
"topic": topic,
"source_url": source_url,
"content": doc.markdown
}
return research_data
def generate_content(topic, research):
"""Generate structured learning content using GPT-5.4-mini."""
client = OpenAI()
system_prompt = f"""You are an expert educator. Based on the research provided,
generate learning content about "{topic}" in the following exact format:
## Summary
Write a clear 3-paragraph summary of the topic.
## Quiz
Write 3 quiz questions with answers in this format:
Q1: [question]
A1: [answer]
Q2: [question]
A2: [answer]
Q3: [question]
A3: [answer]
## Flashcards
Write 5 flashcards in this format:
Front: [term or question]
Back: [definition or answer]
"""
user_message = f"""Here is the research content about "{topic}" from {research["source_url"]}:
{research["content"][:3000]}
Generate the learning content now."""
response = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)
generated_text = response.choices[0].message.content
content = {
"summary": generated_text.split("## Quiz")[0].replace("## Summary", "").strip(),
"quiz": generated_text.split("## Quiz")[1].split("## Flashcards")[0].strip(),
"flashcards": generated_text.split("## Flashcards")[1].strip()
}
return content
Your content engine can now research a topic and generate structured learning materials from it. Next up, you will build the save function and wire the whole pipeline together into a single command.
Build the Save Function and Run the Full Pipeline
Your engine can now research topics from the web and generate structured learning content from that research.
There is one piece missing. The generated content only exists in memory. If you close your terminal, it disappears. In this step, you will write a function that saves everything to files you can actually open and read. Then you will wire all three functions together into a single pipeline that runs end-to-end.
In this step, get ready to:
- Write a save_content function that creates folders and writes markdown files.
- Build the main orchestration block that calls each function in sequence.
- Run the full pipeline and verify it produces real output files.
Write the save function
Your engine generates a dictionary with three keys: summary, quiz, and flashcards. Each key holds a string of content. The save function will take that dictionary, create a dedicated folder for the topic, and write each piece of content to its own .md file.
- In engine.py, scroll to the bottom of the file (below the generate_content function) and add the save_content function by copying this code:
def save_content(topic, content):
"""Save generated content to markdown files in a topic folder."""
# Build a folder path like "output/retrieval-augmented-generation"
folder_name = f"output/{topic.lower().replace(' ', '-')}"
# Create the folder (and any parent folders) if it doesn't exist yet
os.makedirs(folder_name, exist_ok=True)
# Write the summary to its own file
summary_path = f"{folder_name}/summary.md"
with open(summary_path, "w") as file:
file.write(f"# {topic} - Summary\n\n")
file.write(content["summary"])
# Write the quiz to its own file
quiz_path = f"{folder_name}/quiz.md"
with open(quiz_path, "w") as file:
file.write(f"# {topic} - Quiz\n\n")
file.write(content["quiz"])
# Write the flashcards to their own file
flashcards_path = f"{folder_name}/flashcards.md"
with open(flashcards_path, "w") as file:
file.write(f"# {topic} - Flashcards\n\n")
file.write(content["flashcards"])
print(f"Content saved to {folder_name}/")
return folder_name
What does this code do?
This function takes your topic string and content dictionary, then writes everything to disk.
- folder_name uses an f-string with .lower() and .replace(' ', '-') to turn a topic like "Retrieval Augmented Generation" into a URL-friendly path like output/retrieval-augmented-generation.
- os.makedirs creates the folder and any parent folders that don't exist yet. The exist_ok=True parameter means it won't crash if the folder already exists from a previous run.
- with open(path, "w") as file: opens a file for writing. Python automatically closes the file when the with block ends.
- file.write() writes each content string into its file, with a markdown heading at the top.
- Save engine.py.
Why separate files instead of one big file?
Each content type serves a different purpose. A summary is for reading, a quiz is for testing yourself, and flashcards are for review. Saving them separately means you can open just the one you need without scrolling past the others.
Write the main orchestration block
Functions are like tools sitting on a shelf. They don't do anything until you call them. The main orchestration block is where you actually pick up each tool and use it in sequence.
- Below the save_content function, add the main pipeline code:
topic = "retrieval augmented generation"
print(f"Researching: {topic}")
research = research_topic(topic)
print(f"Found source: {research['source_url']}")
print(f"Generating content...")
content = generate_content(topic, research)
print(f"Content generated!")
print(f"Saving files...")
save_content(topic, content)
print("Done! Check your output folder.")
What does this code do?
This is the pipeline that ties your three functions together. Each line flows into the next.
- topic is a plain string variable. This is the input to your entire engine.
- research_topic(topic) calls your first function, which searches the web and returns a dictionary stored in research.
- generate_content(topic, research) passes both the topic and the research dictionary into your second function. The result is a new dictionary stored in content.
- save_content(topic, content) takes the topic and content dictionary and writes everything to files on disk.
- The print() statements show progress so you know which stage the engine is at while it runs.
- Save engine.py.
What is a pipeline?
A pipeline is a pattern where the output of one function becomes the input of the next. Your engine follows the pattern: Input > Research > Generate > Save. Each step transforms data and passes it forward.
✔️ Awesome, I've got everything!
Great. Double check you have saved engine.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete engine.py file at this point:
import os
import json
from dotenv import load_dotenv
from firecrawl import Firecrawl
from openai import OpenAI
load_dotenv()
def research_topic(topic):
"""Research a topic using Firecrawl to get live web content."""
firecrawl = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY"))
results = firecrawl.search(topic, limit=3)
top_result = results.data[0]
source_url = top_result.url
doc = firecrawl.scrape(source_url, formats=["markdown"])
research_data = {
"topic": topic,
"source_url": source_url,
"content": doc.markdown
}
return research_data
def generate_content(topic, research):
"""Generate structured learning content using GPT-5.4-mini."""
client = OpenAI()
system_prompt = f"""You are an expert educator. Based on the research provided,
generate learning content about "{topic}" in the following exact format:
## Summary
Write a clear 3-paragraph summary of the topic.
## Quiz
Write 3 quiz questions with answers in this format:
Q1: [question]
A1: [answer]
Q2: [question]
A2: [answer]
Q3: [question]
A3: [answer]
## Flashcards
Write 5 flashcards in this format:
Front: [term or question]
Back: [definition or answer]
"""
user_message = f"""Here is the research content about "{topic}" from {research["source_url"]}:
{research["content"][:3000]}
Generate the learning content now."""
response = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)
generated_text = response.choices[0].message.content
content = {
"summary": generated_text.split("## Quiz")[0].replace("## Summary", "").strip(),
"quiz": generated_text.split("## Quiz")[1].split("## Flashcards")[0].strip(),
"flashcards": generated_text.split("## Flashcards")[1].strip()
}
return content
def save_content(topic, content):
"""Save generated content to markdown files in a topic folder."""
folder_name = f"output/{topic.lower().replace(' ', '-')}"
os.makedirs(folder_name, exist_ok=True)
summary_path = f"{folder_name}/summary.md"
with open(summary_path, "w") as file:
file.write(f"# {topic} - Summary\n\n")
file.write(content["summary"])
quiz_path = f"{folder_name}/quiz.md"
with open(quiz_path, "w") as file:
file.write(f"# {topic} - Quiz\n\n")
file.write(content["quiz"])
flashcards_path = f"{folder_name}/flashcards.md"
with open(flashcards_path, "w") as file:
file.write(f"# {topic} - Flashcards\n\n")
file.write(content["flashcards"])
print(f"Content saved to {folder_name}/")
return folder_name
topic = "retrieval augmented generation"
print(f"Researching: {topic}")
research = research_topic(topic)
print(f"Found source: {research['source_url']}")
print(f"Generating content...")
content = generate_content(topic, research)
print(f"Content generated!")
print(f"Saving files...")
save_content(topic, content)
print("Done! Check your output folder.")
Run the complete engine
Everything is wired up. Time to run the full pipeline end-to-end and watch it research, generate, and save.
- Run your engine by entering this command in the terminal:
python3 engine.py
This will take 10-20 seconds. You should see output like this:
Researching: retrieval augmented generation
Found source: https://some-url.com/article
Generating content...
Content generated!
Saving files...
Content saved to output/retrieval-augmented-generation/
Done! Check your output folder.
- Verify the output files exist by running:
ls output/retrieval-augmented-generation/
You should see three files: summary.md, quiz.md, and flashcards.md.
- Open one of the files to see the generated content:
cat output/retrieval-augmented-generation/summary.md
You should see a markdown heading followed by a multi-paragraph summary about retrieval augmented generation, written by GPT-5.4-mini based on live web research.
Seeing an error when you run the script?
- If you see ModuleNotFoundError: make sure your virtual environment is still activated. You should see (venv) at the start of your terminal prompt.
- If you see an authentication error from OpenAI or Firecrawl: double-check that your .env file has the correct API keys with no extra spaces.
- If the script hangs for more than 60 seconds: your internet connection may be slow. Firecrawl needs to reach external websites to scrape content.
Help me debug my Content Engine script
Your AI Content Engine is fully operational. You have built a complete pipeline that takes a topic, researches it from the live web, generates structured learning materials with GPT-5.4-mini, and saves everything to organized files on your machine. Next up, you will extend this engine with error handling and a style parameter in the Secret Mission.
Secret mission
Add a Content Style Parameter
Your engine generates content in one voice. What if you could switch between a formal academic tone, a casual explainer, or an energetic quiz-show host? In this secret mission, you'll add a style parameter that transforms the entire personality of your generated content.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Both the OpenAI and Firecrawl APIs are pay-per-use, meaning nothing continues to charge after you stop running the script.
Decide whether to keep your project files for future use or delete them entirely.
Resources you used:
- content-engine project folder on Desktop (includes virtual environment, source code, .env, and generated output files).
Keep everything running
No action needed. Choose this if you want to keep your AI Content Engine ready to run again with new topics.
- Your project files stay on your Mac at no cost.
- Your API keys in .env are ready for future projects.
- The generated output files in output/ are yours to use however you like.
Pause - I'll come back to this later
Nothing to pause. Both APIs are pay-per-use with no running processes or background services.
- Your virtual environment is inactive unless you explicitly activate it.
- No servers, containers, or cloud resources are running.
- When you come back, navigate into content-engine and activate the virtual environment again to pick up where you left off.
Delete - I don't want to use this again
Remove all project files to start fresh. Your API keys remain valid for future projects even after deleting these files.
- Delete the entire content-engine folder by running these commands in Terminal:
cd ~/Desktop
rm -rf content-engine
This removes everything: your source code, virtual environment, .env file, installed packages, and all generated output files.
That's a Wrap!
That's a Wrap!
Nice work! You just built a working AI Content Engine that researches topics from the live web and generates structured learning materials using GPT-5.4-mini and Firecrawl.
You've learned how to:
- Build a complete AI application pipeline that follows the Input > Research > Generate > Save pattern, integrating two real APIs from a single Python script.
- Use every core Python fundamental in context: variables, strings, f-strings, dictionaries, lists, functions, file I/O, imports, and environment variables.
- Understand how dictionaries are the backbone of API communication, structuring both the request bodies you send and the responses you receive.
- Secret Mission: Add parameterized content styles with conditional prompt selection and error handling using try/except blocks.
Ready to quiz yourself?