<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Eva J Patel - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Eva J Patel - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 09 Sep 2026 11:31:20 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/evapatel123/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build an AI File Analysis Agent with Python ]]>
                </title>
                <description>
                    <![CDATA[ If you've ever opened a 30-page PDF and thought, “There's absolutely no way I am reading all of this,” you already understand why file-analysis AI agents are useful. Imagine uploading a research paper ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-an-ai-analysis-agent/</link>
                <guid isPermaLink="false">6a96f2cfeb26827ea17d08f5</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ai-agent ]]>
                    </category>
                
                    <category>
                        <![CDATA[ openai ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eva J Patel ]]>
                </dc:creator>
                <pubDate>Tue, 01 Sep 2026 15:44:15 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/c24acbb2-2ab2-440d-83ae-682183a2f125.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've ever opened a 30-page PDF and thought, “There's absolutely no way I am reading all of this,” you already understand why file-analysis AI agents are useful.</p>
<p>Imagine uploading a research paper, résumé, CSV file, business report, or PDF and simply asking:</p>
<blockquote>
<p>“What are the most important findings?”</p>
</blockquote>
<p>Instead of manually searching through the document, an AI agent can inspect the file, understand what's inside it, and answer questions about it.</p>
<p>In this tutorial, we're going to build exactly that. We'll create a beginner-friendly <strong>AI file analysis agent in Python</strong> that can:</p>
<ul>
<li><p>Accept a file from your computer</p>
</li>
<li><p>Upload the file to an AI model</p>
</li>
<li><p>Read the contents of the file</p>
</li>
<li><p>Understand natural-language questions</p>
</li>
<li><p>Analyze the file</p>
</li>
<li><p>Return a useful answer</p>
</li>
<li><p>Handle different types of questions without us writing a separate function for every possible question</p>
</li>
</ul>
<p>We'll build the project using Python and the OpenAI API.</p>
<p>The important part is that we won't just copy and paste code and hope it works. We'll go through the code line by line so you understand what every important piece is doing.</p>
<p>By the end, you should understand not only how to build this project, but also the basic architecture behind many real-world AI agents.</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ul>
<li><p><a href="#heading-what-are-we-actually-building">What Are We Actually Building?</a></p>
</li>
<li><p><a href="#heading-what-we-are-going-to-use">What We Are Going to Use</a></p>
</li>
<li><p><a href="#heading-what-you-should-know-before-starting">What You Should Know Before Starting</a></p>
</li>
<li><p><a href="#heading-step-1-create-the-project">Step 1: Create the Project</a></p>
</li>
<li><p><a href="#heading-step-2-create-a-virtual-environment">Step 2: Create a Virtual Environment</a></p>
</li>
<li><p><a href="#heading-step-3-install-the-openai-sdk">Step 3: Install the OpenAI SDK</a></p>
</li>
<li><p><a href="#heading-step-4-create-your-api-key">Step 4: Create Your API Key</a></p>
</li>
<li><p><a href="#heading-step-5-create-requirementstxt">Step 5: Createrequirements.txt</a></p>
</li>
<li><p><a href="#heading-step-6-create-the-python-file">Step 6: Create the Python File</a></p>
</li>
<li><p><a href="#heading-step-7-ask-the-user-for-a-file">Step 7: Ask the User for a File</a></p>
</li>
<li><p><a href="#heading-step-8-check-whether-the-file-exists">Step 8: Check Whether the File Exists</a></p>
</li>
<li><p><a href="#heading-step-9-upload-the-file">Step 9: Upload the File</a></p>
</li>
<li><p><a href="#heading-step-10-look-at-the-uploaded-file-id">Step 10: Look at the Uploaded File ID</a></p>
</li>
<li><p><a href="#heading-step-11-create-the-agents-instructions">Step 11: Create the Agent's Instructions</a></p>
</li>
<li><p><a href="#heading-step-12-ask-the-user-what-they-want-to-know">Step 12: Ask the User What They Want to Know</a></p>
</li>
<li><p><a href="#heading-step-13-send-the-file-and-question-to-the-model">Step 13: Send the File and Question to the Model</a></p>
</li>
<li><p><a href="#heading-step-14-print-the-answer">Step 14: Print the Answer</a></p>
</li>
<li><p><a href="#heading-our-first-complete-version">Our First Complete Version</a></p>
</li>
<li><p><a href="#heading-step-15-run-the-application">Step 15: Run the Application</a></p>
<ul>
<li><a href="#heading-why-is-this-an-agent">Why Is This an Agent?</a></li>
</ul>
</li>
<li><p><a href="#heading-step-16-turn-it-into-a-real-conversation">Step 16: Turn It Into a Real Conversation</a></p>
</li>
<li><p><a href="#heading-step-17-move-the-ai-request-into-the-loop">Step 17: Move the AI Request Into the Loop</a></p>
</li>
<li><p><a href="#heading-step-18-improve-the-agents-instructions">Step 18: Improve the Agent's Instructions</a></p>
<ul>
<li><a href="#heading-why-good-instructions-matter">Why Good Instructions Matter</a></li>
</ul>
</li>
<li><p><a href="#heading-step-19-add-error-handling">Step 19: Add Error Handling</a></p>
</li>
<li><p><a href="#heading-step-20-validate-the-file-extension">Step 20: Validate the File Extension</a></p>
</li>
<li><p><a href="#heading-step-21-add-a-file-name-to-the-interface">Step 21: Add a File Name to the Interface</a></p>
</li>
<li><p><a href="#heading-step-22-build-the-clean-final-version">Step 22: Build the Clean Final Version</a></p>
<ul>
<li><p><a href="#heading-lets-understand-the-architecture">Let's Understand the Architecture</a></p>
</li>
<li><p><a href="#heading-why-we-dont-need-to-manually-extract-every-pdf">Why We Don't Need to Manually Extract Every PDF</a></p>
</li>
<li><p><a href="#heading-but-what-about-very-large-files">But What About Very Large Files?</a></p>
</li>
<li><p><a href="#heading-direct-file-input-vs-rag">Direct File Input vs RAG</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-step-23-make-the-agent-better-at-different-types-of-files">Step 23: Make the Agent Better at Different Types of Files</a></p>
</li>
<li><p><a href="#heading-step-24-give-the-agent-a-specific-role">Step 24: Give the Agent a Specific Role</a></p>
</li>
<li><p><a href="#heading-step-25-add-an-analysis-mode">Step 25: Add an Analysis Mode</a></p>
</li>
<li><p><a href="#heading-step-26-why-this-is-different-from-hard-coding-every-answer">Step 26: Why This Is Different From Hard-Coding Every Answer</a></p>
</li>
<li><p><a href="#heading-step-27-security-matters">Step 27: Security Matters</a></p>
</li>
<li><p><a href="#heading-step-28-be-careful-with-sensitive-files">Step 28: Be Careful With Sensitive Files</a></p>
</li>
<li><p><a href="#heading-common-mistakes-that-developers-make">Common Mistakes that Developers Make</a></p>
</li>
<li><p><a href="#heading-how-the-final-program-works">How the Final Program Works</a></p>
</li>
<li><p><a href="#heading-the-most-important-code-to-remember">The Most Important Code to Remember</a></p>
</li>
<li><p><a href="#heading-what-you-can-build-with-this">What You Can Build With This</a></p>
</li>
<li><p><a href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-what-are-we-actually-building">What Are We Actually Building?</h2>
<p>Before writing code, let's define what an AI agent actually means.</p>
<p>An ordinary AI chatbot might work like this:</p>
<pre><code class="language-text">User → Question → AI → Answer
</code></pre>
<p>An AI agent can be more flexible:</p>
<pre><code class="language-text">User → Goal → Agent → Decide what it needs → Use tools/data → Analyze → Answer
</code></pre>
<p>For our project, the “data” will be a file.</p>
<p>For example, imagine we give our agent a research paper called:</p>
<pre><code class="language-text">ai-research.pdf
</code></pre>
<p>Then we ask:</p>
<pre><code class="language-text">What is the main argument of this paper?
</code></pre>
<p>The agent needs to:</p>
<ol>
<li><p>Receive the question.</p>
</li>
<li><p>Access the file.</p>
</li>
<li><p>Read the relevant content.</p>
</li>
<li><p>Understand the content.</p>
</li>
<li><p>Analyze it.</p>
</li>
<li><p>Produce an answer.</p>
</li>
</ol>
<p>The AI model handles the language understanding and reasoning. Our Python program handles the workflow around it.</p>
<p>That distinction is important.</p>
<p>The model isn't magically reading files sitting on your laptop. <strong>Our application has to give the model access to the file.</strong></p>
<p>OpenAI's current API supports sending uploaded files as inputs to the Responses API, which allows models to analyze files directly.</p>
<h2 id="heading-what-we-are-going-to-use">What We Are Going to Use</h2>
<p>Our project will use:</p>
<ul>
<li><p><strong>Python</strong>: our programming language</p>
</li>
<li><p><strong>OpenAI Python SDK</strong>: lets Python communicate with the OpenAI API</p>
</li>
<li><p><strong>Responses API</strong>: the API endpoint we'll use to interact with the model</p>
</li>
<li><p><strong>An uploaded file</strong>: the information our agent will analyze</p>
</li>
<li><p><strong>A prompt</strong>: instructions telling the agent what to do</p>
</li>
</ul>
<p>We'll intentionally keep the first version simple.</p>
<p>You don't need LangChain, a vector database, React, or a complicated backend.</p>
<p>Once you understand this version, you can add those technologies later.</p>
<h2 id="heading-what-you-should-know-before-starting">What You Should Know Before Starting</h2>
<p>This tutorial is designed for beginner and intermediate developers.</p>
<p>You should be comfortable with basic Python concepts such as:</p>
<ul>
<li><p>Variables</p>
</li>
<li><p>Functions</p>
</li>
<li><p><code>if</code> statements</p>
</li>
<li><p>Imports</p>
</li>
<li><p>Strings</p>
</li>
<li><p>Lists</p>
</li>
<li><p>Dictionaries</p>
</li>
<li><p>Running Python programs from a terminal</p>
</li>
</ul>
<p>You do <strong>not</strong> need to know machine learning, know how transformers work internally, or the mathematics behind large language models.</p>
<p>We're focusing on how to build the application here.</p>
<h2 id="heading-step-1-create-the-project">Step 1: Create the Project</h2>
<p>First, create a folder for the project.</p>
<p>For example:</p>
<pre><code class="language-text">file-analysis-agent/
</code></pre>
<p>Inside it, we'll eventually have:</p>
<pre><code class="language-text">file-analysis-agent/
│
├── agent.py
├── requirements.txt
└── .env
</code></pre>
<p>Each file has a purpose.</p>
<ol>
<li><p><code>agent.py</code>: This is where our Python application lives.</p>
</li>
<li><p><code>requirements.txt</code>: This tells Python which external packages our project needs.</p>
</li>
<li><p><code>.env</code>: This is where we can store our API key locally instead of putting it directly into our Python code.</p>
</li>
</ol>
<p>Keeping secrets out of source code is an important habit to develop early.</p>
<h2 id="heading-step-2-create-a-virtual-environment">Step 2: Create a Virtual Environment</h2>
<p>Open your terminal inside the project folder.</p>
<p>Run:</p>
<pre><code class="language-bash">python -m venv venv
</code></pre>
<p>This creates a Python virtual environment.</p>
<p>A virtual environment gives your project its own isolated collection of Python packages. Think of it like giving this project its own little Python workspace.</p>
<p>You can activate it on Windows with:</p>
<pre><code class="language-bash">venv\Scripts\activate
</code></pre>
<p>On macOS or Linux:</p>
<pre><code class="language-bash">source venv/bin/activate
</code></pre>
<p>Once activated, you should see something similar to:</p>
<pre><code class="language-text">(venv)
</code></pre>
<p>at the beginning of your terminal prompt.</p>
<h2 id="heading-step-3-install-the-openai-sdk">Step 3: Install the OpenAI SDK</h2>
<p>Now install the official OpenAI Python package:</p>
<pre><code class="language-bash">pip install openai
</code></pre>
<p>The SDK gives us Python classes and methods that make API calls much easier.</p>
<p>Without an SDK, we would have to manually construct HTTP requests.</p>
<p>With the SDK, we can write Python like:</p>
<pre><code class="language-python">client.responses.create(...)
</code></pre>
<p>instead of manually constructing the entire HTTP request.</p>
<p>The OpenAI quickstart currently uses the Responses API as the starting point for API requests.</p>
<h2 id="heading-step-4-create-your-api-key">Step 4: Create Your API Key</h2>
<p>You need an OpenAI API key to communicate with the API. Create an API key through your OpenAI developer account.</p>
<p>Do <strong>not</strong> put your real API key directly into your source code like this:</p>
<pre><code class="language-python">api_key = "sk-your-real-key"
</code></pre>
<p>That's a bad habit.</p>
<p>If you upload your project to GitHub, you could accidentally expose the key. Instead, store it as an environment variable.</p>
<p>For example, on Windows PowerShell:</p>
<pre><code class="language-powershell">$env:OPENAI_API_KEY="your_api_key_here"
</code></pre>
<p>On macOS/Linux:</p>
<pre><code class="language-bash">export OPENAI_API_KEY="your_api_key_here"
</code></pre>
<p>The OpenAI SDK can automatically read the <code>OPENAI_API_KEY</code> environment variable.</p>
<h2 id="heading-step-5-create-requirementstxt">Step 5: Create <code>requirements.txt</code></h2>
<p>Create a file called:</p>
<pre><code class="language-text">requirements.txt
</code></pre>
<p>Put this inside:</p>
<pre><code class="language-text">openai
</code></pre>
<p>Now another developer can install the project's dependency with:</p>
<pre><code class="language-bash">pip install -r requirements.txt
</code></pre>
<p>This is a small thing, but it's a very useful professional habit.</p>
<h2 id="heading-step-6-create-the-python-file">Step 6: Create the Python File</h2>
<p>Create:</p>
<pre><code class="language-text">agent.py
</code></pre>
<p>Start with:</p>
<pre><code class="language-python">from openai import OpenAI
</code></pre>
<p>Let's break this down.</p>
<ul>
<li><p><code>from</code>: Python's <code>from</code> keyword allows us to import something from another module.</p>
</li>
<li><p><code>openai</code>: This is the Python package we installed.</p>
</li>
<li><p><code>import OpenAI</code>: We're importing the <code>OpenAI</code> class from that package.</p>
</li>
</ul>
<p>Now we can create an OpenAI client.</p>
<p>Add:</p>
<pre><code class="language-python">client = OpenAI()
</code></pre>
<p>This creates our API client.</p>
<p>You can think of <code>client</code> as our application's connection point to the OpenAI API. Whenever we want to communicate with the API, we'll use this client.</p>
<p>For example:</p>
<pre><code class="language-python">response = client.responses.create(...)
</code></pre>
<p>The client handles the underlying HTTP communication for us.</p>
<h2 id="heading-step-7-ask-the-user-for-a-file">Step 7: Ask the User for a File</h2>
<p>We want our application to allow the user to specify a file.</p>
<p>Add:</p>
<pre><code class="language-python">file_path = input("Enter the path to your file: ")
</code></pre>
<p>Now let's understand this line.</p>
<p>The <code>input()</code> function waits for the user to type something.</p>
<p>For example, the terminal might display:</p>
<pre><code class="language-text">Enter the path to your file:
</code></pre>
<p>The user might type:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>Python stores that text inside:</p>
<pre><code class="language-python">file_path
</code></pre>
<p>So after the user enters:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>we effectively have:</p>
<pre><code class="language-python">file_path = "research.pdf"
</code></pre>
<p>Now our program knows which file the user wants to analyze.</p>
<h2 id="heading-step-8-check-whether-the-file-exists">Step 8: Check Whether the File Exists</h2>
<p>Before uploading anything, it's a good idea to make sure the file actually exists.</p>
<p>We can use Python's built-in <code>os</code> module for this.</p>
<p>Add:</p>
<pre><code class="language-python">import os
</code></pre>
<p>Then:</p>
<pre><code class="language-python">if not os.path.exists(file_path):
    print("File not found.")
    exit()
</code></pre>
<p>Let's break this down.</p>
<p>The <code>os</code> module gives Python tools for interacting with the operating system.</p>
<p>One of those tools is:</p>
<pre><code class="language-python">os.path.exists()
</code></pre>
<p>It checks whether a file or folder exists at a particular path.</p>
<p><code>if</code>: We're checking a condition.</p>
<pre><code class="language-python">if not os.path.exists(file_path):
</code></pre>
<p>This means:</p>
<blockquote>
<p>If the file does NOT exist...</p>
</blockquote>
<p>The <code>not</code> keyword reverses the result.</p>
<p>If:</p>
<pre><code class="language-python">os.path.exists(file_path)
</code></pre>
<p>returns <code>True</code> then <code>not True</code> becomes <code>False</code>. But if the file doesn't exist...<code>False</code> becomes <code>True</code>.</p>
<p>So the code inside the <code>if</code> statement only runs when the file can't be found.</p>
<p>Next, <code>print()</code> displays:</p>
<pre><code class="language-text">File not found.
</code></pre>
<p><code>exit()</code> stops the program.</p>
<p>That prevents our application from trying to upload a file that doesn't exist.</p>
<h2 id="heading-step-9-upload-the-file">Step 9: Upload the File</h2>
<p>Now comes the interesting part: we need to send the file to the API.</p>
<p>Add:</p>
<pre><code class="language-python">with open(file_path, "rb") as file:
    uploaded_file = client.files.create(
        file=file,
        purpose="user_data"
    )
</code></pre>
<p>This looks more complicated than it really is.</p>
<p>Let's go through it piece by piece.</p>
<h3 id="heading-understanding-open">Understanding <code>open()</code></h3>
<p>The first line is:</p>
<pre><code class="language-python">with open(file_path, "rb") as file:
</code></pre>
<p>The <code>open()</code> function opens a file.</p>
<p>The first argument is:</p>
<pre><code class="language-python">file_path
</code></pre>
<p>which is the path entered by the user.</p>
<p>The second argument is:</p>
<pre><code class="language-python">"rb"
</code></pre>
<p>This means:</p>
<ul>
<li><p><code>r</code> = read</p>
</li>
<li><p><code>b</code> = binary</p>
</li>
</ul>
<p>We use binary mode because we're dealing with uploaded files rather than simply reading plain text.</p>
<p>The <code>with</code> statement is important because Python automatically handles closing the file when we are finished with it.</p>
<p>The variable:</p>
<pre><code class="language-python">file
</code></pre>
<p>represents the opened file.</p>
<h3 id="heading-uploading-the-file">Uploading the File</h3>
<p>Inside the <code>with</code> block we have:</p>
<pre><code class="language-python">uploaded_file = client.files.create(
</code></pre>
<p>This asks the OpenAI API to create an uploaded file.</p>
<p>The <code>file</code> argument:</p>
<pre><code class="language-python">file=file
</code></pre>
<p>passes the file we opened.</p>
<p>Then:</p>
<pre><code class="language-python">purpose="user_data"
</code></pre>
<p>tells the API that the uploaded file is intended to be used as user data.</p>
<p>The Files API supports a <code>user_data</code> purpose for flexible file use.</p>
<p>After this finishes, OpenAI returns information about the uploaded file. We store that information in:</p>
<pre><code class="language-python">uploaded_file
</code></pre>
<p>One useful property is:</p>
<pre><code class="language-python">uploaded_file.id
</code></pre>
<p>That ID identifies the uploaded file.</p>
<h2 id="heading-step-10-look-at-the-uploaded-file-id">Step 10: Look at the Uploaded File ID</h2>
<p>Add:</p>
<pre><code class="language-python">print("Uploaded file:", uploaded_file.id)
</code></pre>
<p>Now you can see something like:</p>
<pre><code class="language-text">Uploaded file: file-abc123
</code></pre>
<p>That ID is important.</p>
<p>Our local computer knows the file as:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>The API knows it through something like:</p>
<pre><code class="language-text">file-abc123
</code></pre>
<p>We can use that ID when sending the file to the model.</p>
<h2 id="heading-step-11-create-the-agents-instructions">Step 11: Create the Agent's Instructions</h2>
<p>Now we need to tell the AI what its job is.</p>
<p>Create:</p>
<pre><code class="language-python">instructions = """
You are a file analysis assistant.

Your job is to carefully analyze the file provided by the user.

Answer questions using information from the file.

If the answer can't be found in the file, clearly say that the information is not available in the file.

Do not invent facts.

When useful, organize your answer with headings and bullet points.
"""
</code></pre>
<p>This is called an instruction or prompt.</p>
<p>The triple quotes:</p>
<pre><code class="language-python">"""
...
"""
</code></pre>
<p>allow us to create a multi-line string.</p>
<p>Our agent now has a role.</p>
<p>It knows:</p>
<ul>
<li><p>What it's supposed to do</p>
</li>
<li><p>What information it should use</p>
</li>
<li><p>What to do when information is missing</p>
</li>
<li><p>How it should format answers</p>
</li>
</ul>
<p>The instruction:</p>
<pre><code class="language-text">Do not invent facts.
</code></pre>
<p>is especially important for file-analysis applications.</p>
<p>We want the model to distinguish between:</p>
<blockquote>
<p>“The file says this.”</p>
</blockquote>
<p>and:</p>
<blockquote>
<p>“I think this might be true.”</p>
</blockquote>
<p>Those are not the same thing.</p>
<h2 id="heading-step-12-ask-the-user-what-they-want-to-know">Step 12: Ask the User What They Want to Know</h2>
<p>Now we need the actual question.</p>
<p>Add:</p>
<pre><code class="language-python">question = input("What would you like me to analyze? ")
</code></pre>
<p>For example, the user could enter:</p>
<pre><code class="language-text">What are the three most important findings in this paper?
</code></pre>
<p>Or:</p>
<pre><code class="language-text">Summarize this document in five bullet points.
</code></pre>
<p>Or:</p>
<pre><code class="language-text">What methodology did the researchers use?
</code></pre>
<p>This is where our application becomes flexible.</p>
<p>We don't need to create separate Python functions for every possible question. The user can ask questions naturally.</p>
<h2 id="heading-step-13-send-the-file-and-question-to-the-model">Step 13: Send the File and Question to the Model</h2>
<p>Now we can finally create the response.</p>
<p>Add:</p>
<pre><code class="language-python">response = client.responses.create(
    model="gpt-5",
    instructions=instructions,
    input=[
        {
            "role": "user",
            "content": [
                {
                    "type": "input_text",
                    "text": question
                },
                {
                    "type": "input_file",
                    "file_id": uploaded_file.id
                }
            ]
        }
    ]
)
</code></pre>
<p>This is the most important section of the entire project.</p>
<p>Let's slow down and understand it.</p>
<h3 id="heading-understanding-clientresponsescreate">Understanding <code>client.responses.create()</code></h3>
<p>We start with:</p>
<pre><code class="language-python">client.responses.create(
</code></pre>
<p>We're asking the Responses API to generate a response.</p>
<p>The OpenAI API supports file inputs in the Responses API, including using an uploaded file's ID as an <code>input_file</code>.</p>
<h3 id="heading-understanding-the-model">Understanding the Model</h3>
<p>We have:</p>
<pre><code class="language-python">model="gpt-5"
</code></pre>
<p>This tells the API which model should process the request.</p>
<p>The model is the part responsible for understanding the question and analyzing the information provided to it.</p>
<p>The exact model you choose can change over time, so treat the model name as a configurable part of your application rather than something permanently hard-coded into your architecture.</p>
<h3 id="heading-understanding-instructions">Understanding <code>instructions</code></h3>
<p>Next:</p>
<pre><code class="language-python">instructions=instructions
</code></pre>
<p>Remember the variable we created earlier?</p>
<pre><code class="language-python">instructions = """
You are a file analysis assistant.
...
"""
</code></pre>
<p>We're passing those instructions into the API request so the model knows what role it should perform.</p>
<h3 id="heading-understanding-input">Understanding <code>input</code></h3>
<p>Next we have:</p>
<pre><code class="language-python">input=[
</code></pre>
<p>The <code>input</code> contains the information we give the model.</p>
<p>In our case, we're giving it:</p>
<ol>
<li><p>The user's question</p>
</li>
<li><p>The file</p>
</li>
</ol>
<p>This is important because an AI model can't answer a file-specific question if we never give it the file.</p>
<h3 id="heading-understanding-the-user-message">Understanding the User Message</h3>
<p>Inside the input we have:</p>
<pre><code class="language-python">{
    "role": "user",
</code></pre>
<p>This tells the API that this input represents the user's message.</p>
<p>Then:</p>
<pre><code class="language-python">"content": [
</code></pre>
<p>contains the actual content of that message.</p>
<h3 id="heading-sending-the-question">Sending the Question</h3>
<p>The first content item is:</p>
<pre><code class="language-python">{
    "type": "input_text",
    "text": question
}
</code></pre>
<p>This tells the model:</p>
<blockquote>
<p>Here is some text input.</p>
</blockquote>
<p>The actual text comes from:</p>
<pre><code class="language-python">question
</code></pre>
<p>which was entered by the user.</p>
<p>If the user entered:</p>
<pre><code class="language-text">What is the main conclusion?
</code></pre>
<p>then the model receives that question.</p>
<h3 id="heading-sending-the-file">Sending the File</h3>
<p>The next content item is:</p>
<pre><code class="language-python">{
    "type": "input_file",
    "file_id": uploaded_file.id
}
</code></pre>
<p>This tells the API:</p>
<blockquote>
<p>Here is a file input.</p>
</blockquote>
<p>And:</p>
<pre><code class="language-python">uploaded_file.id
</code></pre>
<p>tells the API exactly which uploaded file we're referring to.</p>
<p>So our request effectively contains:</p>
<pre><code class="language-text">Question:
"What is the main conclusion?"

File:
research.pdf
</code></pre>
<p>The model can then analyze the provided file in the context of the user's question.</p>
<h2 id="heading-step-14-print-the-answer">Step 14: Print the Answer</h2>
<p>We have the response stored in:</p>
<pre><code class="language-python">response
</code></pre>
<p>But we don't want to print the entire response object.</p>
<p>We want the generated text.</p>
<p>The SDK provides:</p>
<pre><code class="language-python">response.output_text
</code></pre>
<p>So add:</p>
<pre><code class="language-python">print("\nAgent:\n")
print(response.output_text)
</code></pre>
<p>The first <code>print()</code> creates a little spacing and prints:</p>
<pre><code class="language-text">Agent:
</code></pre>
<p>The second prints the actual answer.</p>
<h2 id="heading-our-first-complete-version">Our First Complete Version</h2>
<p>At this point, our entire <code>agent.py</code> looks like this:</p>
<pre><code class="language-python">import os
from openai import OpenAI


client = OpenAI()


file_path = input("Enter the path to your file: ")


if not os.path.exists(file_path):
    print("File not found.")
    exit()


with open(file_path, "rb") as file:
    uploaded_file = client.files.create(
        file=file,
        purpose="user_data"
    )


print("Uploaded file:", uploaded_file.id)


instructions = """
You are a file analysis assistant.

Your job is to carefully analyze the file provided by the user.

Answer questions using information from the file.

If the answer cannot be found in the file, clearly say that the information is not available in the file.

Do not invent facts.

When useful, organize your answer with headings and bullet points.
"""


question = input("What would you like me to analyze? ")


response = client.responses.create(
    model="gpt-5",
    instructions=instructions,
    input=[
        {
            "role": "user",
            "content": [
                {
                    "type": "input_text",
                    "text": question
                },
                {
                    "type": "input_file",
                    "file_id": uploaded_file.id
                }
            ]
        }
    ]
)


print("\nAgent:\n")
print(response.output_text)
</code></pre>
<p>That's already a functional file-analysis AI application.</p>
<p>But we can make it much better.</p>
<h2 id="heading-step-15-run-the-application">Step 15: Run the Application</h2>
<p>Place a file such as:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>inside your project folder.</p>
<p>Then run:</p>
<pre><code class="language-bash">python agent.py
</code></pre>
<p>You should see:</p>
<pre><code class="language-text">Enter the path to your file:
</code></pre>
<p>Enter:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>Then you might see:</p>
<pre><code class="language-text">Uploaded file: file-abc123
</code></pre>
<p>Next:</p>
<pre><code class="language-text">What would you like me to analyze?
</code></pre>
<p>You could ask:</p>
<pre><code class="language-text">Summarize the main findings in five bullet points.
</code></pre>
<p>The agent will analyze the file and return an answer.</p>
<h3 id="heading-why-is-this-an-agent">Why Is This an Agent?</h3>
<p>At first glance, this might look like a normal API call. And technically, yes, our first version is a fairly simple agent workflow.</p>
<p>The important concept is the <strong>agent loop</strong>.</p>
<p>An agent generally has:</p>
<ol>
<li><p>A goal</p>
</li>
<li><p>Instructions</p>
</li>
<li><p>Access to information</p>
</li>
<li><p>Potential tools</p>
</li>
<li><p>A reasoning process</p>
</li>
<li><p>An action</p>
</li>
<li><p>An output</p>
</li>
</ol>
<p>Our application has several of these pieces.</p>
<p>The user provides a goal:</p>
<pre><code class="language-text">Analyze this research paper.
</code></pre>
<p>The instructions define the agent's behavior:</p>
<pre><code class="language-text">You are a file analysis assistant.
</code></pre>
<p>The file provides information:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>The model processes the information, then the application returns the result.</p>
<p>As applications become more advanced, agents can also use tools such as file search, web search, function calling, and other external systems. OpenAI's platform currently supports built-in tools and custom function tools for extending agents.</p>
<h2 id="heading-step-16-turn-it-into-a-real-conversation">Step 16: Turn It Into a Real Conversation</h2>
<p>Our current application only asks one question.</p>
<p>That's useful, but not ideal.</p>
<p>Imagine uploading a research paper and then having to restart the program every time you want to ask another question.</p>
<p>We can improve that by putting the question inside a loop.</p>
<p>Instead of:</p>
<pre><code class="language-python">question = input("What would you like me to analyze? ")
</code></pre>
<p>we can use:</p>
<pre><code class="language-python">while True:
    question = input("\nAsk a question (or type 'exit'): ")

    if question.lower() == "exit":
        break
</code></pre>
<p>Now let's understand it.</p>
<ul>
<li><p><code>while True</code>: This creates a loop that continues indefinitely. It will keep asking questions until we tell it to stop.</p>
</li>
<li><p><code>question = input(...)</code>: The user enters another question.</p>
</li>
<li><p><code>question.lower()</code>: The <code>.lower()</code> method converts the question to lowercase.</p>
</li>
</ul>
<p>For example:</p>
<pre><code class="language-text">EXIT
</code></pre>
<p>becomes:</p>
<pre><code class="language-text">exit
</code></pre>
<p>and:</p>
<pre><code class="language-text">Exit
</code></pre>
<p>also becomes:</p>
<pre><code class="language-text">exit
</code></pre>
<p>This makes our exit check more reliable.</p>
<p>Finally, the <code>break</code> keyword stops the loop. So:</p>
<pre><code class="language-python">if question.lower() == "exit":
    break
</code></pre>
<p>means:</p>
<blockquote>
<p>If the user types exit, stop asking questions.</p>
</blockquote>
<h2 id="heading-step-17-move-the-ai-request-into-the-loop">Step 17: Move the AI Request Into the Loop</h2>
<p>Now the API request needs to happen inside the loop.</p>
<p>Our structure becomes:</p>
<pre><code class="language-python">while True:
    question = input("\nAsk a question (or type 'exit'): ")

    if question.lower() == "exit":
        break

    response = client.responses.create(
        model="gpt-5",
        instructions=instructions,
        input=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "input_text",
                        "text": question
                    },
                    {
                        "type": "input_file",
                        "file_id": uploaded_file.id
                    }
                ]
            }
        ]
    )

    print("\nAgent:\n")
    print(response.output_text)
</code></pre>
<p>Now the user can ask multiple questions about the same file.</p>
<p>For example:</p>
<pre><code class="language-text">Ask a question:
What is this paper about?
</code></pre>
<p>Then:</p>
<pre><code class="language-text">Ask a question:
What methodology did the researchers use?
</code></pre>
<p>Then:</p>
<pre><code class="language-text">Ask a question:
What were the biggest limitations?
</code></pre>
<p>And finally:</p>
<pre><code class="language-text">Ask a question:
exit
</code></pre>
<p>This makes the application feel much more like an actual assistant.</p>
<h2 id="heading-step-18-improve-the-agents-instructions">Step 18: Improve the Agent's Instructions</h2>
<p>A good AI application isn't just about calling an API. The instructions matter a lot.</p>
<p>We can make our instructions more specific.</p>
<p>For example:</p>
<pre><code class="language-python">instructions = """
You are an AI file analysis assistant.

Your job is to analyze the file provided by the user.

Follow these rules:

1. Use the provided file as your primary source.
2. Answer the user's question directly.
3. Do not invent information that is not supported by the file.
4. If the file does not contain enough information to answer a question, say so.
5. When summarizing, focus on the most important information.
6. When comparing ideas, clearly explain the similarities and differences.
7. When analyzing research, distinguish between results, methods, and conclusions.
8. Use simple language unless the user asks for technical language.
9. Use bullet points when they make the answer easier to understand.
10. If you make an inference, clearly label it as an inference.
"""
</code></pre>
<p>This is much stronger.</p>
<p>We're essentially giving our AI a set of rules.</p>
<h3 id="heading-why-good-instructions-matter">Why Good Instructions Matter</h3>
<p>Imagine telling someone:</p>
<blockquote>
<p>“Read this document.”</p>
</blockquote>
<p>They might read it and give you almost anything.</p>
<p>Now imagine saying:</p>
<blockquote>
<p>“Read this document, identify the research question, summarize the methodology, identify the major findings, and explain the limitations using simple language.”</p>
</blockquote>
<p>That second instruction is much more useful.</p>
<p>AI agents work the same way. The more clearly you define the job, the easier it is for the model to produce consistent results.</p>
<h2 id="heading-step-19-add-error-handling">Step 19: Add Error Handling</h2>
<p>Right now, our program assumes everything will work.</p>
<p>Real applications shouldn't do that. Files can fail to upload, the API can return an error, the user can enter an invalid path, or the network can temporarily fail.</p>
<p>We can use <code>try</code> and <code>except</code> to handle these situations.</p>
<p>For example:</p>
<pre><code class="language-python">try:
    response = client.responses.create(
        model="gpt-5",
        instructions=instructions,
        input=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "input_text",
                        "text": question
                    },
                    {
                        "type": "input_file",
                        "file_id": uploaded_file.id
                    }
                ]
            }
        ]
    )

    print(response.output_text)

except Exception as error:
    print("Something went wrong:")
    print(error)
</code></pre>
<ul>
<li><p><code>try</code>: The code inside the <code>try</code> block is code that might fail.</p>
</li>
<li><p><code>except</code>: If an error happens, Python jumps to the <code>except</code> block.</p>
</li>
<li><p><code>Exception as error</code>: This captures the error so we can display it.</p>
</li>
</ul>
<p>Instead of the entire application crashing with a confusing traceback, the user sees:</p>
<pre><code class="language-text">Something went wrong:
...
</code></pre>
<p>For a production application, you would usually want more sophisticated logging and error handling, but this is a good starting point.</p>
<h2 id="heading-step-20-validate-the-file-extension">Step 20: Validate the File Extension</h2>
<p>We can also check which type of file the user selected.</p>
<p>Add:</p>
<pre><code class="language-python">allowed_extensions = {
    ".pdf",
    ".txt",
    ".docx",
    ".csv"
}
</code></pre>
<p>This creates a set of file extensions that our application expects to support.</p>
<p>Then:</p>
<pre><code class="language-python">extension = os.path.splitext(file_path)[1].lower()
</code></pre>
<p>Let's break this down.</p>
<h3 id="heading-ospathsplitext"><code>os.path.splitext()</code></h3>
<p>This separates the filename from its extension.</p>
<p>For:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>it gives us approximately:</p>
<pre><code class="language-text">research
</code></pre>
<p>and:</p>
<pre><code class="language-text">.pdf
</code></pre>
<p>The <code>[1]</code> selects the extension.</p>
<p>Then:</p>
<pre><code class="language-python">.lower()
</code></pre>
<p>converts it to lowercase.</p>
<p>So:</p>
<pre><code class="language-text">RESEARCH.PDF
</code></pre>
<p>becomes:</p>
<pre><code class="language-text">.pdf
</code></pre>
<p>Now we can check:</p>
<pre><code class="language-python">if extension not in allowed_extensions:
    print("Unsupported file type.")
    exit()
</code></pre>
<p>This prevents users from uploading file types our application hasn't been designed to handle.</p>
<p>Always verify the currently supported file types for the API and model you choose before expanding your application. OpenAI's file and input APIs document file handling and supported input types.</p>
<h2 id="heading-step-21-add-a-file-name-to-the-interface">Step 21: Add a File Name to the Interface</h2>
<p>We can make the terminal experience slightly nicer.</p>
<p>Instead of:</p>
<pre><code class="language-python">print("Uploaded file:", uploaded_file.id)
</code></pre>
<p>we can write:</p>
<pre><code class="language-python">print(f"\nSuccessfully uploaded: {os.path.basename(file_path)}")
</code></pre>
<p>The <code>f</code> before the string creates an f-string.</p>
<p>That allows us to insert Python variables inside <code>{}</code>.</p>
<p>For example:</p>
<pre><code class="language-python">f"Successfully uploaded: {os.path.basename(file_path)}"
</code></pre>
<p>might produce:</p>
<pre><code class="language-text">Successfully uploaded: research.pdf
</code></pre>
<h3 id="heading-ospathbasename"><code>os.path.basename()</code></h3>
<p>This extracts just the filename from the path.</p>
<p>If the user enters:</p>
<pre><code class="language-text">documents/research.pdf
</code></pre>
<p>then:</p>
<pre><code class="language-python">os.path.basename(file_path)
</code></pre>
<p>returns:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<h2 id="heading-step-22-build-the-clean-final-version">Step 22: Build the Clean Final Version</h2>
<p>Now let's combine everything.</p>
<p>Here is a cleaner version of our application:</p>
<pre><code class="language-python">import os

from openai import OpenAI


# Create the OpenAI client.
client = OpenAI()


# Ask the user for a file.
file_path = input("Enter the path to your file: ").strip()


# Make sure the file exists.
if not os.path.exists(file_path):
    print("File not found.")
    exit()


# Allowed file types.
allowed_extensions = {
    ".pdf",
    ".txt",
    ".docx",
    ".csv"
}


# Get the file extension.
extension = os.path.splitext(file_path)[1].lower()


# Make sure the file type is supported by our application.
if extension not in allowed_extensions:
    print(f"Unsupported file type: {extension}")
    print("Supported types:", ", ".join(allowed_extensions))
    exit()


# Upload the file.
try:
    with open(file_path, "rb") as file:
        uploaded_file = client.files.create(
            file=file,
            purpose="user_data"
        )

except Exception as error:
    print("The file could not be uploaded.")
    print(error)
    exit()


print(f"\nSuccessfully uploaded: {os.path.basename(file_path)}")


# Define the agent's behavior.
instructions = """
You are an AI file analysis assistant.

Your job is to analyze the file provided by the user.

Follow these rules:

1. Use the provided file as your primary source.
2. Answer the user's question directly.
3. Do not invent information that is not supported by the file.
4. If the file does not contain enough information to answer a question, say so.
5. When summarizing, focus on the most important information.
6. When comparing ideas, clearly explain similarities and differences.
7. When analyzing research, distinguish between methods, results, and conclusions.
8. Use simple language unless the user asks for technical language.
9. Use bullet points when they make the answer easier to understand.
10. If you make an inference, clearly label it as an inference.
"""


# Start the conversation.
print("\nYour file is ready to analyze.")
print("Ask questions about the file.")
print("Type 'exit' when you are finished.")


while True:

    # Get a question from the user.
    question = input("\nYou: ").strip()


    # Stop the program if the user wants to exit.
    if question.lower() == "exit":
        print("Goodbye!")
        break


    # Ignore empty questions.
    if not question:
        print("Please enter a question.")
        continue


    # Send the question and file to the model.
    try:
        response = client.responses.create(
            model="gpt-5",
            instructions=instructions,
            input=[
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "input_text",
                            "text": question
                        },
                        {
                            "type": "input_file",
                            "file_id": uploaded_file.id
                        }
                    ]
                }
            ]
        )


        # Display the AI's response.
        print("\nAgent:")
        print(response.output_text)


    except Exception as error:
        print("\nThe agent encountered an error.")
        print(error)
</code></pre>
<h3 id="heading-lets-understand-the-architecture">Let's Understand the Architecture</h3>
<p>At this point, it is useful to step away from the code. Our application has several layers.</p>
<h4 id="heading-layer-1-user-interface">Layer 1: User Interface</h4>
<p>The terminal asks:</p>
<pre><code class="language-text">Enter the path to your file:
</code></pre>
<p>and:</p>
<pre><code class="language-text">You:
</code></pre>
<p>This is how the user interacts with our application.</p>
<h4 id="heading-layer-2-file-handling">Layer 2: File Handling</h4>
<p>Python checks:</p>
<pre><code class="language-python">os.path.exists(file_path)
</code></pre>
<p>and opens:</p>
<pre><code class="language-python">open(file_path, "rb")
</code></pre>
<p>This layer handles the local file.</p>
<h4 id="heading-layer-3-file-upload">Layer 3: File Upload</h4>
<p>The application sends the file to the API:</p>
<pre><code class="language-python">client.files.create(...)
</code></pre>
<p>The API gives us a file ID.</p>
<h4 id="heading-layer-4-agent-instructions">Layer 4: Agent Instructions</h4>
<p>We define:</p>
<pre><code class="language-python">instructions
</code></pre>
<p>This tells the model how to behave.</p>
<h4 id="heading-layer-5-user-request">Layer 5: User Request</h4>
<p>The user asks:</p>
<pre><code class="language-text">What are the main findings?
</code></pre>
<h4 id="heading-layer-6-model">Layer 6: Model</h4>
<p>The model receives:</p>
<ul>
<li><p>The instructions</p>
</li>
<li><p>The question</p>
</li>
<li><p>The file</p>
</li>
</ul>
<p>and generates an answer.</p>
<h4 id="heading-layer-7-output">Layer 7: Output</h4>
<p>We display:</p>
<pre><code class="language-python">response.output_text
</code></pre>
<p>to the user.</p>
<p>This separation is useful because it makes the project easier to extend later.</p>
<h3 id="heading-why-we-dont-need-to-manually-extract-every-pdf">Why We Don't Need to Manually Extract Every PDF</h3>
<p>A beginner might wonder:</p>
<blockquote>
<p>“Why don't we use Python to extract all the text first?”</p>
</blockquote>
<p>That's absolutely possible. You could use libraries such as:</p>
<pre><code class="language-text">PyPDF
python-docx
pandas
</code></pre>
<p>to read different file formats yourself.</p>
<p>Then you could send the extracted text to an AI model.</p>
<p>That approach can be useful, especially when you need custom preprocessing. But it also creates more work.</p>
<p>You would need to write separate logic for:</p>
<pre><code class="language-text">PDF → extract text
DOCX → extract text
CSV → read rows
TXT → read text
</code></pre>
<p>Then you would need to figure out how to send all that information to the model.</p>
<p>With file inputs, the API can accept the file directly, which can simplify the architecture for supported use cases.</p>
<h3 id="heading-but-what-about-very-large-files">But What About Very Large Files?</h3>
<p>This is where things get more interesting.</p>
<p>Imagine a user uploads a 2,000-page collection of documents. You probably don't want to send everything into every single request.</p>
<p>Instead, you may want a system that can search for the most relevant sections. This is where <strong>retrieval</strong> becomes important.</p>
<p>One common architecture is:</p>
<pre><code class="language-text">Documents
    ↓
Split into chunks
    ↓
Create embeddings
    ↓
Store searchable representations
    ↓
User asks question
    ↓
Find relevant chunks
    ↓
Send relevant information to model
    ↓
Generate answer
</code></pre>
<p>This approach is commonly associated with <strong>Retrieval-Augmented Generation</strong>, or RAG.</p>
<p>OpenAI also provides a file search tool that can search uploaded files using vector stores.</p>
<p>Our first project intentionally doesn't introduce RAG because it would add a lot of concepts at once.</p>
<p>First understand direct file analysis. Then learn retrieval. Then combine the two.</p>
<h3 id="heading-direct-file-input-vs-rag">Direct File Input vs RAG</h3>
<p>It's useful to understand the difference.</p>
<h4 id="heading-direct-file-input">Direct File Input</h4>
<p>You give the model a file for a particular request.</p>
<p>For example:</p>
<pre><code class="language-text">Upload:
research-paper.pdf

Question:
What was the main conclusion?
</code></pre>
<p>This is simple and great for many smaller applications.</p>
<h4 id="heading-rag">RAG</h4>
<p>You have a larger collection of documents.</p>
<p>For example:</p>
<pre><code class="language-text">100 research papers
50 reports
20 manuals
</code></pre>
<p>Instead of giving the model every document for every question, you search the collection for relevant information first. Then you provide the relevant pieces to the model.</p>
<p>This is more scalable for large knowledge bases.</p>
<h2 id="heading-step-23-make-the-agent-better-at-different-types-of-files">Step 23: Make the Agent Better at Different Types of Files</h2>
<p>Different files contain different kinds of information.</p>
<p>A PDF might contain:</p>
<pre><code class="language-text">Research paper
</code></pre>
<p>A CSV might contain:</p>
<pre><code class="language-text">Name,Age,Score
Alex,17,91
Sam,18,87
</code></pre>
<p>A DOCX might contain:</p>
<pre><code class="language-text">A long essay
</code></pre>
<p>A good agent should understand what kind of information it is dealing with.</p>
<p>We can make our instructions reflect this.</p>
<p>For example:</p>
<pre><code class="language-python">instructions = """
You are an AI file analysis assistant.

First understand what type of information the uploaded file contains.

If the file is a research paper:
- Identify the research question.
- Explain the methodology.
- Summarize the results.
- Explain the conclusion.
- Identify limitations.

If the file contains tabular data:
- Identify the columns.
- Describe important patterns.
- Identify unusual values when possible.
- Explain trends clearly.
- Do not invent numerical results.

If the file is a general document:
- Identify its main purpose.
- Summarize the important sections.
- Answer questions using information from the document.

Always:
- Use the file as your primary source.
- Do not invent facts.
- Clearly distinguish facts from inferences.
- Say when the file does not contain enough information.
- Use simple language.
"""
</code></pre>
<p>Now our agent has more context about the kinds of work it may perform.</p>
<h2 id="heading-step-24-give-the-agent-a-specific-role">Step 24: Give the Agent a Specific Role</h2>
<p>You can think of the instruction as the agent's job description.</p>
<p>For example:</p>
<pre><code class="language-text">You are an AI research assistant.
</code></pre>
<p>is fairly broad.</p>
<p>But:</p>
<pre><code class="language-text">You are an AI research assistant who analyzes academic papers.
</code></pre>
<p>is more specific.</p>
<p>We can go further:</p>
<pre><code class="language-text">You are an AI research assistant specializing in helping students understand academic papers.
</code></pre>
<p>Now we have a target audience.</p>
<p>The model can adjust its explanations accordingly.</p>
<p>This is one of the easiest ways to make an AI application feel much more useful without writing a huge amount of code.</p>
<h2 id="heading-step-25-add-an-analysis-mode">Step 25: Add an Analysis Mode</h2>
<p>We can make the application even more useful by letting the user select an analysis mode.</p>
<p>For example:</p>
<pre><code class="language-text">1. Summarize
2. Explain
3. Find key points
4. Analyze
5. Ask a question
</code></pre>
<p>We could ask:</p>
<pre><code class="language-python">mode = input(
    "\nChoose a mode: "
    "summarize, explain, analyze, or question: "
)
</code></pre>
<p>Then modify the prompt based on the user's selection.</p>
<p>For example:</p>
<pre><code class="language-python">if mode.lower() == "summarize":
    task = "Summarize the most important information from the file."

elif mode.lower() == "explain":
    task = "Explain the file in beginner-friendly language."

elif mode.lower() == "analyze":
    task = "Perform a detailed analysis of the file."

else:
    task = question
</code></pre>
<p>This is a simple example of application logic controlling an AI model.</p>
<p>The AI still generates the language, but our Python application decides what kind of task it should perform.</p>
<h2 id="heading-step-26-why-this-is-different-from-hard-coding-every-answer">Step 26: Why This Is Different From Hard-Coding Every Answer</h2>
<p>Imagine you wanted to support these questions:</p>
<ol>
<li><p>Summarize the file.</p>
</li>
<li><p>What is the main idea?</p>
</li>
<li><p>What are the limitations?</p>
</li>
<li><p>Who is the target audience?</p>
</li>
<li><p>What evidence supports the conclusion?</p>
</li>
</ol>
<p>You could technically create a separate Python function for each one. But that would quickly become ridiculous.</p>
<p>Instead, we can let the user ask naturally:</p>
<pre><code class="language-python">question = input("What would you like to know? ")
</code></pre>
<p>The AI handles the language. Our application provides the file and context.</p>
<p>This is one of the major advantages of using language models in applications.</p>
<h2 id="heading-step-27-security-matters">Step 27: Security Matters</h2>
<p>Now let's talk about something that's not as exciting as the AI part but is extremely important.</p>
<p><strong>Never expose your API key.</strong></p>
<p>Bad:</p>
<pre><code class="language-python">client = OpenAI(
    api_key="sk-real-secret-key"
)
</code></pre>
<p>Better:</p>
<pre><code class="language-python">client = OpenAI()
</code></pre>
<p>with the key stored in an environment variable.</p>
<p>Also avoid committing secrets to GitHub.</p>
<p>Your <code>.gitignore</code> file should include things such as:</p>
<pre><code class="language-text">.env
venv/
__pycache__/
</code></pre>
<p>If you decide to use a <code>.env</code> file locally, make sure it is ignored by Git.</p>
<h2 id="heading-step-28-be-careful-with-sensitive-files">Step 28: Be Careful With Sensitive Files</h2>
<p>A file-analysis agent can potentially process sensitive information.</p>
<p>That means you should think carefully before uploading things such as:</p>
<ul>
<li><p>Medical records</p>
</li>
<li><p>Financial information</p>
</li>
<li><p>Passwords</p>
</li>
<li><p>Private company documents</p>
</li>
<li><p>Personal identification documents</p>
</li>
<li><p>Confidential school records</p>
</li>
</ul>
<p>Your application's privacy requirements depend on the type of data you're handling.</p>
<p>Don't treat an AI API as a place to casually upload every document on your computer.</p>
<p>Understand the provider's current data controls, retention behavior, and policies before deploying a file-processing application with sensitive information. OpenAI documents file retention and data controls in its platform documentation.</p>
<h2 id="heading-common-mistakes-that-developers-make">Common Mistakes that Developers Make</h2>
<h3 id="heading-common-mistake-1-putting-the-api-key-in-github">Common Mistake #1: Putting the API Key in GitHub</h3>
<p>Never do:</p>
<pre><code class="language-python">api_key = "your-secret-key"
</code></pre>
<p>and commit it.</p>
<p>Use environment variables instead.</p>
<h3 id="heading-common-mistake-2-assuming-the-ai-knows-everything-in-the-file">Common Mistake #2: Assuming the AI Knows Everything in the File</h3>
<p>Just because you upload a file doesn't mean your application can magically solve every possible question.</p>
<p>The model's ability to analyze a file depends on:</p>
<ul>
<li><p>File type</p>
</li>
<li><p>File size</p>
</li>
<li><p>File structure</p>
</li>
<li><p>Model capabilities</p>
</li>
<li><p>API limits</p>
</li>
<li><p>The quality of your instructions</p>
</li>
<li><p>The complexity of the question</p>
</li>
</ul>
<p>Design your application around those limitations.</p>
<h3 id="heading-common-mistake-3-telling-the-model-to-just-analyze-it">Common Mistake #3: Telling the Model to "Just Analyze It"</h3>
<p>This:</p>
<pre><code class="language-text">Analyze the file.
</code></pre>
<p>is extremely vague.</p>
<p>This is better:</p>
<pre><code class="language-text">Identify the main argument, summarize the evidence,
explain the methodology, and identify the limitations.
</code></pre>
<p>Clear instructions produce a clearer task.</p>
<h3 id="heading-common-mistake-4-ignoring-hallucinations">Common Mistake #4: Ignoring Hallucinations</h3>
<p>AI models can generate incorrect information.</p>
<p>That is why our instructions include:</p>
<pre><code class="language-text">Do not invent information.
</code></pre>
<p>and:</p>
<pre><code class="language-text">If the file does not contain enough information, say so.
</code></pre>
<p>You should still validate important information yourself.</p>
<p>For high-stakes applications, you need stronger evaluation and verification systems.</p>
<h3 id="heading-common-mistake-5-sending-huge-amounts-of-data-everywhere">Common Mistake #5: Sending Huge Amounts of Data Everywhere</h3>
<p>If you have thousands of documents, don't simply throw all of them into every request.</p>
<p>That is when retrieval systems become useful. Search first. Then give the model the most relevant information.</p>
<h3 id="heading-common-mistake-6-building-everything-at-once">Common Mistake #6: Building Everything at Once</h3>
<p>A common beginner mistake is starting with:</p>
<pre><code class="language-text">React
FastAPI
LangChain
PostgreSQL
Pinecone
Docker
Kubernetes
OpenAI
Authentication
RAG
Agents
</code></pre>
<p>all at the same time.</p>
<p>Please don't.</p>
<p>You will spend more time debugging infrastructure than learning AI.</p>
<p>Start with:</p>
<pre><code class="language-text">Python
+
OpenAI API
+
File
</code></pre>
<p>Get that working.</p>
<p>Then add features one at a time.</p>
<h2 id="heading-how-the-final-program-works">How the Final Program Works</h2>
<p>Let's summarize our program from beginning to end.</p>
<p>The user runs:</p>
<pre><code class="language-bash">python agent.py
</code></pre>
<p>The program asks:</p>
<pre><code class="language-text">Enter the path to your file:
</code></pre>
<p>The user enters:</p>
<pre><code class="language-text">research.pdf
</code></pre>
<p>Python checks whether the file exists.</p>
<p>Then the application uploads it:</p>
<pre><code class="language-python">client.files.create(...)
</code></pre>
<p>The API returns a file ID. The application stores that ID.</p>
<p>Then the user asks:</p>
<pre><code class="language-text">What is the main argument?
</code></pre>
<p>Our application sends:</p>
<pre><code class="language-text">Instructions
+
Question
+
File
</code></pre>
<p>to the model.</p>
<p>The model analyzes the information.</p>
<p>Then our program prints:</p>
<pre><code class="language-python">response.output_text
</code></pre>
<p>The user receives the answer.</p>
<p>And that's the core of a file-analysis AI agent.</p>
<h2 id="heading-the-most-important-code-to-remember">The Most Important Code to Remember</h2>
<p>If you forget everything else, remember this structure:</p>
<pre><code class="language-python">from openai import OpenAI


client = OpenAI()


with open("research.pdf", "rb") as file:
    uploaded_file = client.files.create(
        file=file,
        purpose="user_data"
    )


response = client.responses.create(
    model="gpt-5",
    instructions="Analyze the uploaded file carefully.",
    input=[
        {
            "role": "user",
            "content": [
                {
                    "type": "input_text",
                    "text": "What is the main argument?"
                },
                {
                    "type": "input_file",
                    "file_id": uploaded_file.id
                }
            ]
        }
    ]
)


print(response.output_text)
</code></pre>
<p>The important mental model is:</p>
<pre><code class="language-text">Open file
    ↓
Upload file
    ↓
Get file ID
    ↓
Send question + file ID
    ↓
Model analyzes file
    ↓
Print answer
</code></pre>
<p>Once you understand this flow, you can build much more complicated applications on top of it.</p>
<h2 id="heading-what-you-can-build-with-this">What You Can Build With This</h2>
<p>This simple project can become the foundation for many real applications.</p>
<h3 id="heading-ai-research-assistant">AI Research Assistant</h3>
<p>Upload academic papers and ask:</p>
<pre><code class="language-text">What is the research question?
</code></pre>
<pre><code class="language-text">What methodology was used?
</code></pre>
<pre><code class="language-text">What were the main findings?
</code></pre>
<h3 id="heading-resume-analyzer">Résumé Analyzer</h3>
<p>Upload a résumé and ask:</p>
<pre><code class="language-text">What skills are missing for this job?
</code></pre>
<h3 id="heading-study-assistant">Study Assistant</h3>
<p>Upload a textbook chapter and ask:</p>
<pre><code class="language-text">Explain this chapter in beginner-friendly language.
</code></pre>
<h3 id="heading-legal-document-assistant">Legal Document Assistant</h3>
<p>Upload a document and ask questions about its contents, while carefully considering privacy, accuracy, and appropriate legal safeguards.</p>
<h3 id="heading-business-report-analyzer">Business Report Analyzer</h3>
<p>Upload a report and ask:</p>
<pre><code class="language-text">What are the most important trends?
</code></pre>
<h3 id="heading-data-analysis-assistant">Data Analysis Assistant</h3>
<p>Upload a dataset and eventually give the agent access to Python-based analysis tools.</p>
<p>The possibilities are huge.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Building an AI agent that can read files sounds complicated at first.</p>
<p>But when you break it down, the core idea is surprisingly simple.</p>
<ol>
<li><p>Your Python application does the setup.</p>
</li>
<li><p>The API provides access to the AI model.</p>
</li>
<li><p>The file provides the information.</p>
</li>
<li><p>The instructions define the agent's job.</p>
</li>
<li><p>The user provides the question.</p>
</li>
<li><p>The model analyzes the information and generates the response.</p>
</li>
</ol>
<p>The really interesting part is what happens next.</p>
<p>Once you understand how to give an AI model access to files, you can start adding retrieval, tools, databases, web search, memory, user interfaces, and multi-step workflows.</p>
<p>That's where simple AI scripts start turning into actual AI applications.</p>
<p>And the best part? You don't need to understand every piece of AI before you start building.</p>
<p>Start small and get one file working. Ask one question. Understand what every line of code does. Then add the next feature.</p>
<p>That's how you go from: "I want to build an AI agent" to "I actually built one".</p>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Fix a Leaked API Key: A Developer’s Guide to Git Security ]]>
                </title>
                <description>
                    <![CDATA[ Imagine this: you're working late, your code finally works, and you're ready to push it to GitHub. You run: git add . git commit -m "Fix API integration" git push A few minutes later, you notice some ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-fix-a-leaked-api-key/</link>
                <guid isPermaLink="false">6a8ddd55902e76128f1985bb</guid>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eva J Patel ]]>
                </dc:creator>
                <pubDate>Tue, 25 Aug 2026 18:22:13 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/0903575c-822b-481b-af12-07b864bebc67.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine this: you're working late, your code finally works, and you're ready to push it to GitHub.</p>
<p>You run:</p>
<pre><code class="language-bash">git add .
git commit -m "Fix API integration"
git push
</code></pre>
<p>A few minutes later, you notice something strange. Your API usage has suddenly increased. Maybe there are unexpected requests, new cloud resources, or even a bill that looks much larger than expected.</p>
<p>Then you find it:</p>
<pre><code class="language-javascript">const apiKey = "sk_live_123456789";
</code></pre>
<p>Your API key is sitting in a Git repository.</p>
<p>This situation is stressful, but it's fixable.</p>
<p>The most important rule is:</p>
<blockquote>
<p><strong>If an API key has been committed to Git, assume it has been copied and compromised, even if you delete it immediately.</strong></p>
</blockquote>
<p>Deleting the key from the latest version of your file doesn't make the old key safe. Git keeps previous versions of files in its history, and exposed credentials can be discovered by automated scanners.</p>
<p>In this guide, you'll learn the following:</p>
<ul>
<li><p><a href="#heading-what-is-an-api-key">What Is an API Key?</a></p>
</li>
<li><p><a href="#heading-the-emergency-response-what-to-do-first">The Emergency Response: What to Do First</a></p>
</li>
<li><p><a href="#heading-step-1-revoke-or-rotate-the-leaked-key">Step 1: Revoke or Rotate the Leaked Key</a></p>
</li>
<li><p><a href="#heading-step-2-investigate-suspicious-activity">Step 2: Investigate Suspicious Activity</a></p>
</li>
<li><p><a href="#heading-step-3-remove-the-secret-from-your-current-code">Step 3: Remove the Secret From Your Current Code</a></p>
</li>
<li><p><a href="#heading-step-4-use-a-env-file-for-local-development">Step 4: Use a.envFile for Local Development</a></p>
</li>
<li><p><a href="#heading-step-5-create-a-safe-envexample">Step 5: Create a Safe.env.example</a></p>
</li>
<li><p><a href="#heading-step-6-determine-whether-the-secret-is-still-in-git-history">Step 6: Determine Whether the Secret Is Still in Git History</a></p>
</li>
<li><p><a href="#heading-when-do-you-need-to-rewrite-git-history">When Do You Need to Rewrite Git History?</a></p>
</li>
<li><p><a href="#heading-step-7-remove-the-secret-from-git-history">Step 7: Remove the Secret From Git History</a></p>
</li>
<li><p><a href="#heading-step-8-verify-that-the-secret-is-gone">Step 8: Verify That the Secret Is Gone</a></p>
</li>
<li><p><a href="#heading-step-9-push-the-cleaned-history-carefully">Step 9: Push the Cleaned History Carefully</a></p>
</li>
<li><p><a href="#heading-step-10-replace-the-credential-everywhere">Step 10: Replace the Credential Everywhere</a></p>
</li>
<li><p><a href="#heading-step-11-restrict-the-replacement-key">Step 11: Restrict the Replacement Key</a></p>
</li>
<li><p><a href="#heading-what-about-frontend-applications">What About Frontend Applications?</a></p>
</li>
<li><p><a href="#heading-environment-variables-vs-secret-managers">Environment Variables vs Secret Managers</a></p>
</li>
<li><p><a href="#heading-add-secret-scanning-to-your-workflow">Add Secret Scanning to Your Workflow</a></p>
</li>
<li><p><a href="#heading-use-git-hooks-as-an-extra-safety-net">Use Git Hooks as an Extra Safety Net</a></p>
</li>
<li><p><a href="#heading-review-your-staged-diff-before-committing">Review Your Staged Diff Before Committing</a></p>
</li>
<li><p><a href="#heading-common-mistakes-developers-make">Common Mistakes Developers Make</a></p>
</li>
<li><p><a href="#heading-a-complete-api-key-incident-checklist">A Complete API-Key Incident Checklist</a></p>
</li>
<li><p><a href="#heading-a-secure-project-structure">A Secure Project Structure</a></p>
</li>
</ul>
<p>We'll use this basic workflow throughout the article:</p>
<pre><code class="language-text">Invalidate → Investigate → Remove → Replace → Prevent
</code></pre>
<p>Let's start with what an API key actually is before we get to the most important part: what to do <strong>right now</strong> after a key is exposed.</p>
<h2 id="heading-what-is-an-api-key">What Is an API Key?</h2>
<p>An API key is a credential that allows an application to communicate with another service.</p>
<p>For example, an application might use an API key to access:</p>
<ul>
<li><p>A weather service</p>
</li>
<li><p>A payment provider</p>
</li>
<li><p>A mapping service</p>
</li>
<li><p>An artificial intelligence API</p>
</li>
<li><p>A cloud platform</p>
</li>
<li><p>A database</p>
</li>
<li><p>An email provider</p>
</li>
<li><p>A private company API</p>
</li>
</ul>
<p>A key might look something like this:</p>
<pre><code class="language-javascript">const apiKey = "your-real-api-key";
</code></pre>
<p>Or it might appear in a configuration file:</p>
<pre><code class="language-json">{
  "apiKey": "your-real-api-key",
  "databasePassword": "your-real-password"
}
</code></pre>
<p>API keys are often called <strong>secrets</strong> because possessing one may allow someone to make requests, access data, create resources, or generate charges on your account.</p>
<p>Not every API key is equally sensitive. Some services provide browser keys that are intentionally visible to users. Those keys should still have appropriate restrictions, quotas, and permissions.</p>
<p>As a general rule:</p>
<blockquote>
<p><strong>If a credential can access private data, create resources, modify records, or generate charges, it shouldn't be stored directly in your source code.</strong></p>
</blockquote>
<h2 id="heading-the-emergency-response-what-to-do-first">The Emergency Response: What to Do First</h2>
<p>When you discover a leaked credential, a common reaction is to delete the key from the file and push another commit.</p>
<p>Don't start there.</p>
<p>Your first priority is to <strong>make the leaked credential useless</strong>.</p>
<p>Use this order of operations:</p>
<pre><code class="language-text">1. Invalidate the leaked credential
2. Investigate suspicious activity
3. Remove the secret from your code
4. Replace it with a new credential
5. Clean the Git history if necessary
6. Verify the cleanup
7. Add protections against future leaks
</code></pre>
<p>Think of an API key like a house key that was dropped in a crowded street.</p>
<p>Deleting a picture of the key doesn't matter if someone already picked up the physical key.</p>
<p><strong>Change the lock first.</strong></p>
<h2 id="heading-step-1-revoke-or-rotate-the-leaked-key">Step 1: Revoke or Rotate the Leaked Key</h2>
<p>Go to the dashboard of the service that issued the credential.</p>
<p>Depending on the provider, you may see options such as:</p>
<ul>
<li><p>Revoke</p>
</li>
<li><p>Delete</p>
</li>
<li><p>Disable</p>
</li>
<li><p>Rotate</p>
</li>
<li><p>Regenerate</p>
</li>
<li><p>Create new key</p>
</li>
</ul>
<p>If the provider supports key rotation, create a replacement credential before disabling the old one if possible. This can reduce application downtime while you update your configuration.</p>
<p>The important thing is that the original credential must no longer be usable.</p>
<p><strong>Do not reuse the leaked key.</strong> Don't rename it. Don't encode it. Don't move it to another file and assume it is safe. Don't assume nobody saw it.</p>
<p>Treat it as compromised.</p>
<h2 id="heading-step-2-investigate-suspicious-activity">Step 2: Investigate Suspicious Activity</h2>
<p>After disabling the credential, check the provider's usage dashboard and logs.</p>
<p>Look for things such as:</p>
<ul>
<li><p>Sudden spikes in requests</p>
</li>
<li><p>Requests from unfamiliar locations</p>
</li>
<li><p>Unexpected database queries</p>
</li>
<li><p>New cloud resources</p>
</li>
<li><p>Changes to permissions</p>
</li>
<li><p>Unexpected downloads</p>
</li>
<li><p>Unusual payment activity</p>
</li>
<li><p>New deployments</p>
</li>
<li><p>Requests at times when your application was inactive</p>
</li>
</ul>
<p>If the credential had broad permissions, assume that anything within its permission scope <strong>MAY have been accessed or modified</strong>.</p>
<p>For example, if a cloud credential could create virtual machines, check whether unexpected machines were created.</p>
<p>If a credential could access a database, review:</p>
<ul>
<li><p>Authentication logs</p>
</li>
<li><p>Read operations</p>
</li>
<li><p>Write operations</p>
</li>
<li><p>Deleted records</p>
</li>
<li><p>Exported data</p>
</li>
<li><p>Newly created accounts</p>
</li>
<li><p>Permission changes</p>
</li>
</ul>
<p>Also check your billing information if the credential could generate usage-based charges.</p>
<p>Write down what you discover. A simple timeline can help:</p>
<pre><code class="language-text">10:15 - API key committed
10:23 - Repository pushed publicly
10:41 - Unusual usage detected
10:45 - Key revoked
11:00 - Logs reviewed
11:30 - Replacement key deployed
12:00 - Git history cleaned
</code></pre>
<p>This can be especially useful if you need to report the incident to a team or service provider.</p>
<h2 id="heading-step-3-remove-the-secret-from-your-current-code">Step 3: Remove the Secret From Your Current Code</h2>
<p>Once the original credential has been disabled, remove it from your working files.</p>
<p>This is unsafe:</p>
<pre><code class="language-javascript">const apiKey = "your-real-api-key";
</code></pre>
<p>Instead, load the credential from the environment:</p>
<pre><code class="language-javascript">const apiKey = process.env.API_KEY;

if (!apiKey) {
  throw new Error("API_KEY is not configured");
}
</code></pre>
<p>In Python:</p>
<pre><code class="language-python">import os

api_key = os.environ.get("API_KEY")

if not api_key:
    raise RuntimeError("API_KEY is not configured")
</code></pre>
<p>The important idea is simple:</p>
<pre><code class="language-text">Source code → environment variable → secret value
</code></pre>
<p>instead of:</p>
<pre><code class="language-text">Source code → hardcoded secret
</code></pre>
<p>Environment variables aren't the only way to manage secrets, but they are a common and practical solution for local development and many deployment environments.</p>
<h2 id="heading-step-4-use-a-env-file-for-local-development">Step 4: Use a <code>.env</code> File for Local Development</h2>
<p>For local development, you can store environment variables in a <code>.env</code> file.</p>
<p>For example:</p>
<pre><code class="language-env">API_KEY=your-local-development-key
DATABASE_URL=your-local-database-url
</code></pre>
<p>A Node.js project can load these values with a package such as <code>dotenv</code>.</p>
<p>Install it with:</p>
<pre><code class="language-bash">npm install dotenv
</code></pre>
<p>Then:</p>
<pre><code class="language-javascript">import "dotenv/config";

const apiKey = process.env.API_KEY;
</code></pre>
<p>The important part is that the <code>.env</code> file normally <strong>should not be committed to Git</strong>.</p>
<p>Add it to <code>.gitignore</code>:</p>
<pre><code class="language-gitignore"># Environment files
.env
.env.*
!.env.example

# Credential files
*.pem
*.key
credentials.json
service-account.json

# Local development files
.DS_Store
</code></pre>
<p>But there's an important detail here: the <code>.gitignore</code> <strong>does NOT remove files that Git is already tracking.</strong></p>
<p>If <code>.env</code> has already been committed, adding it to <code>.gitignore</code> won't erase it from Git.</p>
<p>You can stop tracking the file while keeping it on your computer:</p>
<pre><code class="language-bash">git rm --cached .env
</code></pre>
<p>Then commit the <code>.gitignore</code> change:</p>
<pre><code class="language-bash">git add .gitignore
git commit -m "Ignore local environment files"
</code></pre>
<p>But remember: this only removes the file from future commits. It does <strong>not</strong> remove the secret from previous commits.</p>
<p>That's where Git history comes in.</p>
<h2 id="heading-step-5-create-a-safe-envexample">Step 5: Create a Safe <code>.env.example</code></h2>
<p>Other developers still need to know which environment variables the application requires.</p>
<p>Instead of committing <code>.env</code>, create <code>.env.example</code>:</p>
<pre><code class="language-env">API_KEY=
DATABASE_URL=
PORT=3000
LOG_LEVEL=info
</code></pre>
<p>This file contains variable names rather than real credentials, so it can be committed to the repository.</p>
<p>You can also provide comments:</p>
<pre><code class="language-env"># Required API credential
API_KEY=

# PostgreSQL connection string
DATABASE_URL=

# Optional application port
PORT=3000
</code></pre>
<p>A new developer can then copy the file:</p>
<pre><code class="language-bash">cp .env.example .env
</code></pre>
<p>and provide their own values.</p>
<p>Use clearly fake placeholders in examples:</p>
<pre><code class="language-env">API_KEY=replace-me-with-your-own-key
</code></pre>
<p>Avoid putting realistic-looking production credentials into <code>.env.example</code>.</p>
<h2 id="heading-step-6-determine-whether-the-secret-is-still-in-git-history">Step 6: Determine Whether the Secret Is Still in Git History</h2>
<p>This is one of the most important parts of fixing a leaked credential.</p>
<p>Suppose your Git history looks like this:</p>
<pre><code class="language-text">Commit A: Add API key to config.js
Commit B: Update API integration
Commit C: Delete API key
</code></pre>
<p>Even though Commit C no longer contains the key, Commit A still does.</p>
<p>Git remembers previous versions of your files.</p>
<p>You can inspect the history of a file with:</p>
<pre><code class="language-bash">git log --all -- config.js
</code></pre>
<p>To display a file from an older commit:</p>
<pre><code class="language-bash">git show COMMIT_ID:config.js
</code></pre>
<p>You can also search Git history for a known leaked value:</p>
<pre><code class="language-bash">git log --all -S"your-leaked-key" --oneline
</code></pre>
<p>If you know the secret was committed, you should assume that it exists somewhere in the repository's history until you've verified otherwise.</p>
<h2 id="heading-when-do-you-need-to-rewrite-git-history">When Do You Need to Rewrite Git History?</h2>
<p>Not every accidental secret requires a history rewrite. Consider these situations:</p>
<h3 id="heading-the-secret-was-never-committed">The Secret Was Never Committed</h3>
<p>If the secret exists only in your working directory and was never committed, you generally don't need to rewrite history.</p>
<p>Remove it, add the appropriate file to <code>.gitignore</code>, and continue.</p>
<h3 id="heading-the-secret-was-committed-locally-but-never-pushed">The Secret Was Committed Locally But Never Pushed</h3>
<p>If the secret exists in local commits but hasn't been shared with a remote repository, you may be able to clean up those commits before pushing.</p>
<h3 id="heading-the-secret-was-pushed-to-a-remote-repository">The Secret Was Pushed to a Remote Repository</h3>
<p>Treat the credential as compromised. Revoke or rotate it immediately.</p>
<p>Then determine whether removing the secret from the repository's history is appropriate.</p>
<h3 id="heading-the-repository-was-public">The Repository Was Public</h3>
<p>Assume that someone or something may already have copied the secret.</p>
<p>This is why <strong>revocation comes before Git cleanup</strong>.</p>
<h3 id="heading-the-secret-was-in-a-private-repository">The Secret Was in a Private Repository</h3>
<p>A private repository is safer than a public repository, but it isn't a secret vault.</p>
<p>Credentials can still escape through:</p>
<ul>
<li><p>Compromised accounts</p>
</li>
<li><p>Contractors</p>
</li>
<li><p>Integrations</p>
</li>
<li><p>CI logs</p>
</li>
<li><p>Forks</p>
</li>
<li><p>Backups</p>
</li>
<li><p>Screenshots</p>
</li>
<li><p>Copied code</p>
</li>
<li><p>Pull requests</p>
</li>
</ul>
<p>So the safest rule remains:</p>
<blockquote>
<p><strong>Never intentionally commit credentials to Git, even in a private repository.</strong></p>
</blockquote>
<h2 id="heading-step-7-remove-the-secret-from-git-history">Step 7: Remove the Secret From Git History</h2>
<p>If the credential was committed, you may need to remove it from the repository's history.</p>
<p>Before rewriting history, create a backup:</p>
<pre><code class="language-bash">git clone --mirror https://github.com/your-username/your-repository.git repository-backup.git
</code></pre>
<p>A mirror clone includes branches and tags, which makes it useful for recovery if something goes wrong.</p>
<h3 id="heading-option-1-remove-an-entire-file">Option 1: Remove an Entire File</h3>
<p>If the secret was stored in a file such as <code>.env</code>, you can remove that file from the entire history:</p>
<pre><code class="language-bash">git filter-repo --path .env --invert-paths
</code></pre>
<p>For a file inside a directory:</p>
<pre><code class="language-bash">git filter-repo --path config/production.json --invert-paths
</code></pre>
<p>This removes the file from the repository's rewritten history.</p>
<h3 id="heading-option-2-replace-a-secret-inside-a-file">Option 2: Replace a Secret Inside a File</h3>
<p>Sometimes you need to keep the file but remove the secret from previous versions.</p>
<p>Create a temporary replacements file:</p>
<p>Then run:</p>
<pre><code class="language-bash">git filter-repo --replace-text replacements.txt
</code></pre>
<p>You can replace the value with a placeholder:</p>
<pre><code class="language-text">your-leaked-key==&gt;YOUR_API_KEY_HERE
</code></pre>
<p>Be extremely careful with <code>replacements.txt</code>. It contains the original secret, so <strong>do not commit it.</strong></p>
<p>Delete it after the cleanup:</p>
<pre><code class="language-bash">rm replacements.txt
</code></pre>
<p>On Windows PowerShell:</p>
<pre><code class="language-powershell">Remove-Item replacements.txt
</code></pre>
<p>For multiple secrets:</p>
<pre><code class="language-text">old-api-key==&gt;REMOVED_API_KEY
old-database-password==&gt;REMOVED_DATABASE_PASSWORD
old-token==&gt;REMOVED_TOKEN
</code></pre>
<p>Then:</p>
<pre><code class="language-bash">git filter-repo --replace-text replacements.txt
</code></pre>
<p>Test the cleanup on your backup clone first.</p>
<h2 id="heading-step-8-verify-that-the-secret-is-gone">Step 8: Verify That the Secret Is Gone</h2>
<p>Never assume the cleanup worked just because the command completed successfully.</p>
<p>Search for the known leaked value again:</p>
<pre><code class="language-bash">git log --all -S"your-leaked-key" --oneline
</code></pre>
<p>You can also inspect relevant files and commits:</p>
<pre><code class="language-bash">git log --all -- config.js
</code></pre>
<p>and:</p>
<pre><code class="language-bash">git show COMMIT_ID:config.js
</code></pre>
<p>If your repository uses branches and tags, make sure you aren't checking only the branch you currently have checked out.</p>
<p>You should also inspect other locations where the secret may have appeared, including pull requests, CI/CD logs, build artifacts, release files, Docker images, package releases, documentation, issue comments, and screenshots</p>
<p>Remember:</p>
<blockquote>
<p><strong>Rewriting your repository doesn't erase copies that already exist somewhere else.</strong></p>
</blockquote>
<p>That's another reason why the original credential must be revoked.</p>
<h2 id="heading-step-9-push-the-cleaned-history-carefully">Step 9: Push the Cleaned History Carefully</h2>
<p>Once you've verified the cleanup, you may need to push the rewritten history:</p>
<pre><code class="language-bash">git push --force --all origin
git push --force --tags origin
</code></pre>
<h3 id="heading-important-warning">Important Warning</h3>
<p><strong>Force-pushing rewritten history is disruptive.</strong> It changes commit hashes and can affect collaborators who have existing clones of the repository.</p>
<p>Before doing this on a shared project:</p>
<ol>
<li><p>Tell your collaborators.</p>
</li>
<li><p>Make sure everyone understands that history is being rewritten.</p>
</li>
<li><p>Coordinate the cleanup.</p>
</li>
<li><p>Follow your organization's incident-response process if one exists.</p>
</li>
</ol>
<p>After the rewrite, collaborators may need to reclone the repository:</p>
<pre><code class="language-bash">git clone https://github.com/your-username/your-repository.git
</code></pre>
<p>They shouldn't blindly merge their old repository history back into the cleaned repository.</p>
<h2 id="heading-step-10-replace-the-credential-everywhere">Step 10: Replace the Credential Everywhere</h2>
<p>Now create or use the replacement credential. Update every environment where the application runs. Common locations include:</p>
<ul>
<li><p>Local development</p>
</li>
<li><p>Testing</p>
</li>
<li><p>Staging</p>
</li>
<li><p>Production</p>
</li>
<li><p>Docker containers</p>
</li>
<li><p>Kubernetes secrets</p>
</li>
<li><p>CI/CD systems</p>
</li>
<li><p>Hosting platforms</p>
</li>
<li><p>Scheduled jobs</p>
</li>
<li><p>Serverless functions</p>
</li>
</ul>
<p>A common mistake is updating production but forgetting the deployment pipeline.</p>
<p>For example, your local application may work because <code>.env</code> contains the new key, while your CI/CD system still contains the old one.</p>
<p>Make a checklist:</p>
<pre><code class="language-text">1. Local development
2. Automated tests
3. Staging
4. Production
5. CI/CD variables
6. Docker configuration
7. Cloud deployment settings
8. Scheduled scripts
9. Serverless functions
</code></pre>
<p>After updating the credential, test the application in each important environment.</p>
<h2 id="heading-step-11-restrict-the-replacement-key">Step 11: Restrict the Replacement Key</h2>
<p>Replacing a leaked credential is only part of the solution.</p>
<p>The new credential should have <strong>only the permissions it actually needs</strong>.</p>
<p>Useful restrictions can include:</p>
<ul>
<li><p>Read-only permissions</p>
</li>
<li><p>Specific API scopes</p>
</li>
<li><p>Allowed IP addresses</p>
</li>
<li><p>Allowed domains</p>
</li>
<li><p>Environment-specific access</p>
</li>
<li><p>Request quotas</p>
</li>
<li><p>Rate limits</p>
</li>
<li><p>Expiration dates</p>
</li>
</ul>
<p>For example, a weather application may only need permission to read weather data.</p>
<p>It shouldn't have permission to manage users, modify billing, or delete unrelated resources.</p>
<p>This is the <strong>principle of least privilege</strong>:</p>
<blockquote>
<p><strong>Give each credential the smallest amount of access necessary to perform its job.</strong></p>
</blockquote>
<p>It's also a good idea to use different credentials for different environments:</p>
<pre><code class="language-text">local-development-key
testing-key
staging-key
production-key
</code></pre>
<p>That way, a development credential leak doesn't automatically expose production resources.</p>
<h2 id="heading-what-about-frontend-applications">What About Frontend Applications?</h2>
<p>This is where API-key security gets confusing.</p>
<p>Frontend code runs on the user's device.</p>
<p>That means users can inspect it.</p>
<p>For example:</p>
<pre><code class="language-javascript">const apiKey = "browser-key";
</code></pre>
<p>A user can inspect the JavaScript bundle, browser developer tools, or network requests and potentially see the value.</p>
<p>Some services intentionally provide browser API keys that are designed to be publicly visible.</p>
<p>Those keys should still be restricted by things such as:</p>
<ul>
<li><p>Allowed domains</p>
</li>
<li><p>Website origins</p>
</li>
<li><p>API operations</p>
</li>
<li><p>Usage quotas</p>
</li>
<li><p>Referrer restrictions</p>
</li>
<li><p>Time limits</p>
</li>
</ul>
<p>But a truly private credential should <strong>never be placed in browser code</strong>.</p>
<p>Instead of:</p>
<pre><code class="language-javascript">fetch("https://private-api.example.com/data", {
  headers: {
    Authorization: "Bearer private-secret-token"
  }
});
</code></pre>
<p>have the browser call your own backend:</p>
<pre><code class="language-javascript">fetch("/api/data");
</code></pre>
<p>Then the backend communicates with the private service:</p>
<pre><code class="language-javascript">const response = await fetch(
  "https://private-api.example.com/data",
  {
    headers: {
      Authorization: `Bearer ${process.env.PRIVATE_API_TOKEN}`
    }
  }
);
</code></pre>
<p>The backend can then return only the information the browser is allowed to receive.</p>
<p>The important distinction is:</p>
<pre><code class="language-text">Public/browser credential
        ↓
Can be visible, but should be restricted

Private credential
        ↓
Must remain on a trusted backend or secret-management system
</code></pre>
<h2 id="heading-environment-variables-vs-secret-managers">Environment Variables vs Secret Managers</h2>
<p>Environment variables are useful, but they're not a universal secret-management solution.</p>
<p>For a small application or local development environment, something like:</p>
<pre><code class="language-env">API_KEY=your-secret
</code></pre>
<p>may be perfectly reasonable.</p>
<p>For larger production systems, you may want a dedicated <strong>secret manager</strong>.</p>
<p>A secret-management system can provide features such as:</p>
<ul>
<li><p>Centralized credential storage</p>
</li>
<li><p>Access controls</p>
</li>
<li><p>Auditing</p>
</li>
<li><p>Credential rotation</p>
</li>
<li><p>Versioning</p>
</li>
<li><p>Separation between environments</p>
</li>
<li><p>Integration with deployment systems</p>
</li>
</ul>
<p>The important idea is that your source code shouldn't be responsible for storing production secrets.</p>
<p>Instead:</p>
<pre><code class="language-text">Application
    ↓
Secret management system
    ↓
Credential
</code></pre>
<p>rather than:</p>
<pre><code class="language-text">Application
    ↓
Hardcoded production credential
</code></pre>
<p>Which solution you use depends on the size and requirements of your project.</p>
<h2 id="heading-add-secret-scanning-to-your-workflow">Add Secret Scanning to Your Workflow</h2>
<p>Humans are excellent programmers and occasionally terrible search engines.</p>
<p>Automated secret scanning can catch credentials before they make it into a repository.</p>
<p>Popular tools include:</p>
<ul>
<li><p>Gitleaks</p>
</li>
<li><p>TruffleHog</p>
</li>
<li><p>detect-secrets</p>
</li>
<li><p>Pre-commit hooks</p>
</li>
<li><p>Git hosting secret scanning</p>
</li>
<li><p>CI security scanners</p>
</li>
</ul>
<p>For example, you can run Gitleaks locally:</p>
<pre><code class="language-bash">gitleaks detect --source . --verbose
</code></pre>
<p>You can also integrate secret scanning into CI.</p>
<p>A basic GitHub Actions workflow might look like this:</p>
<pre><code class="language-yaml">name: Secret Scan

on:
  push:
  pull_request:

jobs:
  scan:
    runs-on: ubuntu-latest

    steps:
      - name: Check out repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Scan for secrets
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
</code></pre>
<p>Review the documentation for your chosen tool and pin versions according to your project's security practices.</p>
<p>Secret scanners can produce false positives, so you may need to configure exceptions for safe test values.</p>
<p>Be careful with allowlists, though. An overly broad exception can hide a real credential.</p>
<h2 id="heading-use-git-hooks-as-an-extra-safety-net">Use Git Hooks as an Extra Safety Net</h2>
<p>You can also scan files before they're committed.</p>
<p>For example, a simple pre-commit script could search for suspicious words:</p>
<pre><code class="language-bash">#!/usr/bin/env bash

if grep -RniE "api[_-]?key|password|secret|token|private[_-]?key" . \
  --exclude-dir=.git \
  --exclude=".env.example"; then

  echo "Possible secret detected. Commit cancelled."
  exit 1
fi
</code></pre>
<p>This isn't a complete security scanner, but it can catch obvious mistakes.</p>
<p>For stronger protection, use a dedicated secret-scanning tool through a pre-commit framework.</p>
<p>The goal isn't to make committing miserable. The goal is to make accidentally publishing a credential harder.</p>
<h2 id="heading-review-your-staged-diff-before-committing">Review Your Staged Diff Before Committing</h2>
<p>One of the simplest security habits you can develop is checking what you're actually about to commit.</p>
<p>First:</p>
<pre><code class="language-bash">git status
</code></pre>
<p>Then stage only the files you intend to commit:</p>
<pre><code class="language-bash">git add src/api.js README.md
</code></pre>
<p>Now inspect the staged changes:</p>
<pre><code class="language-bash">git diff --cached
</code></pre>
<p>Look for:</p>
<ul>
<li><p>API keys</p>
</li>
<li><p>Passwords</p>
</li>
<li><p>Tokens</p>
</li>
<li><p>Private URLs</p>
</li>
<li><p>Internal hostnames</p>
</li>
<li><p>Customer data</p>
</li>
<li><p>Debug output</p>
</li>
<li><p>Personal information</p>
</li>
<li><p>Private certificates</p>
</li>
</ul>
<p>Only commit after the staged diff looks correct:</p>
<pre><code class="language-bash">git commit -m "Load API key from environment"
</code></pre>
<p>Be cautious with:</p>
<pre><code class="language-bash">git add .
</code></pre>
<p>It can stage files you never intended to publish, including <code>.env</code> files, database exports, generated files, or local configuration.</p>
<h2 id="heading-common-mistakes-developers-make">Common Mistakes Developers Make</h2>
<h3 id="heading-mistake-1-i-deleted-it-so-its-fine">Mistake 1: "I Deleted It, So It's Fine"</h3>
<p>Deleting a secret from the current version of a file doesn't delete it from Git history.</p>
<p><strong>Correct response:</strong> Revoke the credential and clean the repository history when appropriate.</p>
<h3 id="heading-mistake-2-the-repository-is-private">Mistake 2: "The Repository Is Private"</h3>
<p>Private repositories aren't vaults.</p>
<p>Credentials can still escape through compromised accounts, integrations, CI logs, forks, backups, or copied code.</p>
<p><strong>Correct response:</strong> Don't commit secrets even to private repositories.</p>
<h3 id="heading-mistake-3-ill-just-encode-it">Mistake 3: "I'll Just Encode It"</h3>
<p>These don't make a credential secret:</p>
<pre><code class="language-javascript">const key = atob("c29tZS1rZXk=");
</code></pre>
<p>or:</p>
<pre><code class="language-javascript">const key = "some-" + "secret-" + "value";
</code></pre>
<p>Encoding, splitting, renaming, or hiding a credential doesn't protect it.</p>
<p>If your application can reconstruct the credential, someone analyzing the application may be able to do the same.</p>
<h3 id="heading-mistake-4-logging-the-secret">Mistake 4: Logging the Secret</h3>
<p>Don't do this:</p>
<pre><code class="language-javascript">console.log(process.env.API_KEY);
</code></pre>
<p>Logs can be stored by your terminal, CI system, hosting provider, monitoring platform, or cloud service.</p>
<p>Instead:</p>
<pre><code class="language-javascript">console.log(
  "API key configured:",
  Boolean(process.env.API_KEY)
);
</code></pre>
<p>If you absolutely need to inspect a value during debugging, avoid printing the full credential.</p>
<p>For example:</p>
<pre><code class="language-javascript">function maskSecret(value) {
  if (!value) return "not configured";
  if (value.length &lt;= 8) return "********";

  return `${value.slice(0, 4)}...${value.slice(-4)}`;
}

console.log(maskSecret(process.env.API_KEY));
</code></pre>
<p>Even masked credentials should be handled carefully.</p>
<h3 id="heading-mistake-5-using-the-same-credential-everywhere">Mistake 5: Using the Same Credential Everywhere</h3>
<p>If local development, testing, staging, and production all use the same credential, one leak can affect everything.</p>
<p><strong>Correct response:</strong> Use separate credentials with separate permissions.</p>
<h3 id="heading-mistake-6-cleaning-only-the-current-branch">Mistake 6: Cleaning Only the Current Branch</h3>
<p>A secret can remain in:</p>
<ul>
<li><p>Old branches</p>
</li>
<li><p>Tags</p>
</li>
<li><p>Pull requests</p>
</li>
<li><p>Other references</p>
</li>
</ul>
<p><strong>Correct response:</strong> Consider the entire repository when investigating and cleaning a leaked credential.</p>
<h3 id="heading-mistake-7-forgetting-build-artifacts">Mistake 7: Forgetting Build Artifacts</h3>
<p>A secret might also appear in:</p>
<ul>
<li><p>Compiled JavaScript bundles</p>
</li>
<li><p>Docker images</p>
</li>
<li><p>Downloadable releases</p>
</li>
<li><p>Published packages</p>
</li>
<li><p>Generated documentation</p>
</li>
</ul>
<p><strong>Correct response:</strong> Revoke the credential and identify affected artifacts that may need to be removed or replaced.</p>
<h2 id="heading-a-complete-api-key-incident-checklist">A Complete API-Key Incident Checklist</h2>
<p>If you discover that you've exposed an API key, use this checklist:</p>
<pre><code class="language-text">1. Revoke or rotate the leaked key
2. Create a replacement credential
3. Restrict the replacement credential
4. Review provider logs
5. Review billing and usage
6. Check for unauthorized resources
7. Remove the key from current files
8. Add secret files to .gitignore
9. Create or update .env.example
10. Search Git history
11. Check branches and tags
12. Remove the secret from Git history if necessary
13. Verify the old secret is gone
14. Force-push cleaned history if appropriate
15. Check pull requests and forks
16. Check CI and deployment logs
17. Update local configuration
18. Update staging configuration
19. Update production configuration
20. Update CI/CD secrets
21. Run a secret scanner
22. Document the incident
23. Add preventive security checks
</code></pre>
<p>The exact steps will depend on your provider and project, but the order matters: <strong>Invalidate first. Clean up second.</strong></p>
<h2 id="heading-a-secure-project-structure">A Secure Project Structure</h2>
<p>A simple Node.js project might look like this:</p>
<pre><code class="language-text">my-project/
├── src/
│   └── api.js
├── .env
├── .env.example
├── .gitignore
├── package.json
└── README.md
</code></pre>
<p>The local <code>.env</code> file contains the actual development value:</p>
<pre><code class="language-env">API_KEY=your-local-key
</code></pre>
<p>The <code>.env.example</code> file contains no real credential:</p>
<pre><code class="language-env">API_KEY=replace-me-with-your-own-key
</code></pre>
<p>The application reads the environment variable:</p>
<pre><code class="language-javascript">import "dotenv/config";

const apiKey = process.env.API_KEY;

if (!apiKey) {
  throw new Error("Missing API_KEY environment variable");
}

export async function getData() {
  const response = await fetch(
    "https://api.example.com/data",
    {
      headers: {
        Authorization: `Bearer ${apiKey}`
      }
    }
  );

  if (!response.ok) {
    throw new Error(
      `API request failed: ${response.status}`
    );
  }

  return response.json();
}
</code></pre>
<p>And <code>.gitignore</code> keeps the local environment file out of future commits:</p>
<pre><code class="language-gitignore">.env
.env.*
!.env.example

node_modules/
</code></pre>
<p>Finally, your README can explain the setup without exposing credentials:</p>
<p>Step 1: Copy the example environment file on your bash <code>cp .env.example .env</code></p>
<p>Step 2: Add your own API key to <code>.env</code>.</p>
<p>And last but not least, start your application!</p>
<pre><code class="language-bash">npm start
</code></pre>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Leaking an API key doesn't mean you're a terrible developer. It just means your development workflow needs better guardrails.</p>
<p>The important thing is knowing how to respond quickly and how to prevent the same mistake from happening again.</p>
<p>Remember the emergency formula:</p>
<pre><code class="language-text">Invalidate → Investigate → Remove → Replace → Prevent
</code></pre>
<p>The important thing is knowing how to respond quickly and how to prevent the same mistake from happening again.</p>
<ul>
<li><p>Invalidate the leaked credential so it can no longer be used.</p>
</li>
<li><p>Investigate your logs, usage, and billing to determine whether it was abused.</p>
</li>
<li><p>Remove the secret from your current code and, when necessary, from Git history.</p>
</li>
<li><p>Replace it with a new credential that has only the permissions it needs.</p>
</li>
<li><p>Prevent future leaks with environment variables, secret managers, secret scanning, and careful Git practices.</p>
</li>
</ul>
<p>Git is excellent at remembering your project's history. That's useful when you accidentally delete an important function. But it's much less useful when that history contains a password.</p>
<p>So keep your code public when appropriate. And <strong>keep your secrets somewhere else.</strong></p>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Neural Networks Explained: What They Are and How to Build One in Python  ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wondered how a computer can recognize a handwritten number, predict whether an email is spam, recommend a video, or understand a sentence? A lot of modern AI systems rely on something ca ]]>
                </description>
                <link>https://www.freecodecamp.org/news/neural-networks-explained-simply-in-python/</link>
                <guid isPermaLink="false">6a88c8b8c9a055790ae586e7</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ DeepLearning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eva J Patel ]]>
                </dc:creator>
                <pubDate>Fri, 21 Aug 2026 21:52:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e140594f-daab-4c39-8b59-91bc794d6430.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wondered how a computer can recognize a handwritten number, predict whether an email is spam, recommend a video, or understand a sentence?</p>
<p>A lot of modern AI systems rely on something called a <strong>neural network</strong>.</p>
<p>Now, the name can make them sound much more complicated than they really are. You might imagine that you need advanced calculus, a huge computer, and thousands of lines of code to build one.</p>
<p>You don't.</p>
<p>At its most basic level, a neural network is a mathematical model that takes some numbers as input, performs calculations on those numbers, makes a prediction, checks how far that prediction was from the correct answer, and then adjusts itself so it can do a little better next time.</p>
<p>In this tutorial, we're going to build one ourselves using Python and NumPy.</p>
<h3 id="heading-heres-what-well-cover">Here's What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-1-what-is-a-neural-network">1. What Is a Neural Network?</a></p>
</li>
<li><p><a href="#heading-2-why-are-they-called-neural-networks">2. Why Are They Called Neural Networks?</a></p>
</li>
<li><p><a href="#heading-3-the-three-main-parts-of-a-neural-network">3. The Three Main Parts of a Neural Network</a></p>
</li>
<li><p><a href="#heading-4-what-is-a-neuron">4. What Is a Neuron?</a></p>
</li>
<li><p><a href="#heading-5-what-is-a-weight">5. What Is a Weight?</a></p>
</li>
<li><p><a href="#heading-6-what-is-a-bias">6. What Is a Bias?</a></p>
</li>
<li><p><a href="#heading-7-why-do-we-need-activation-functions">7. Why Do We Need Activation Functions?</a></p>
</li>
<li><p><a href="#heading-8-building-our-first-neuron-in-python">8. Building Our First Neuron in Python</a></p>
</li>
<li><p><a href="#heading-9-from-one-neuron-to-a-layer">9. From One Neuron to a Layer</a></p>
</li>
<li><p><a href="#heading-10-how-does-a-neural-network-actually-learn">10. How Does a Neural Network Actually Learn?</a></p>
</li>
<li><p><a href="#heading-11-predictions-and-loss">11. Predictions and Loss</a></p>
</li>
<li><p><a href="#heading-12-what-are-gradients">12. What Are Gradients?</a></p>
</li>
<li><p><a href="#heading-13-what-is-gradient-descent">13. What Is Gradient Descent?</a></p>
</li>
<li><p><a href="#heading-14-what-is-backpropagation">14. What Is Backpropagation?</a></p>
</li>
<li><p><a href="#heading-15-the-complete-learning-cycle">15. The Complete Learning Cycle</a></p>
</li>
<li><p><a href="#heading-16-lets-build-a-neural-network-from-scratch">16. Let's Build a Neural Network From Scratch</a></p>
</li>
<li><p><a href="#heading-17-understanding-the-network-architecture">17. Understanding the Network Architecture</a></p>
</li>
<li><p><a href="#heading-18-setting-up-the-data">18. Setting Up the Data</a></p>
</li>
<li><p><a href="#heading-19-creating-the-weights-and-biases">19. Creating the Weights and Biases</a></p>
</li>
<li><p><a href="#heading-20-the-sigmoid-function">20. The Sigmoid Function</a></p>
</li>
<li><p><a href="#heading-21-forward-propagation">21. Forward Propagation</a></p>
</li>
<li><p><a href="#heading-22-calculating-the-loss">22. Calculating the Loss</a></p>
</li>
<li><p><a href="#heading-23-backpropagation-in-code">23. Backpropagation in Code</a></p>
</li>
<li><p><a href="#heading-24-updating-the-weights">24. Updating the Weights</a></p>
</li>
<li><p><a href="#heading-25-the-complete-numpy-neural-network">25. The Complete NumPy Neural Network</a></p>
</li>
<li><p><a href="#heading-26-testing-the-network">26. Testing the Network</a></p>
</li>
<li><p><a href="#heading-27-why-did-we-need-a-hidden-layer">27. Why Did We Need a Hidden Layer?</a></p>
</li>
<li><p><a href="#heading-28-what-happens-in-a-larger-neural-network">28. What Happens in a Larger Neural Network?</a></p>
</li>
<li><p><a href="#heading-29-do-you-have-to-build-neural-networks-from-scratch">29. Do You Have to Build Neural Networks From Scratch?</a></p>
</li>
<li><p><a href="#heading-30-building-the-same-network-with-pytorch">30. Building the Same Network With PyTorch</a></p>
</li>
<li><p><a href="#heading-31-training-the-network-with-pytorch">31. Training the Network With PyTorch</a></p>
</li>
<li><p><a href="#heading-32-numpy-vs-pytorch">32. NumPy vs. PyTorch</a></p>
</li>
<li><p><a href="#heading-33-what-is-deep-learning">33. What Is Deep Learning?</a></p>
</li>
<li><p><a href="#heading-34-where-are-neural-networks-used">34. Where Are Neural Networks Used?</a></p>
</li>
<li><p><a href="#heading-35-the-whole-process-in-one-picture">35. The Whole Process in One Picture</a></p>
</li>
<li><p><a href="#heading-36-the-most-important-ideas-to-remember">36. The Most Important Ideas to Remember</a></p>
</li>
<li><p><a href="#heading-37-what-should-you-learn-next">37. What Should You Learn Next?</a></p>
</li>
<li><p><a href="#heading-final-takeaway">Final Takeaway</a></p>
</li>
</ul>
<p>We'll start with a single artificial neuron, then gradually put together a complete neural network. By the end, you'll understand what weights and biases are, what activation functions do, how a network learns from its mistakes, what backpropagation and gradient descent actually mean, and how all of those pieces fit together.</p>
<p>You don't need to know advanced machine learning to follow along. Some basic Python and algebra will help, but I'll explain the important math as we go.</p>
<h2 id="heading-1-what-is-a-neural-network">1. What Is a Neural Network?</h2>
<p>Let's start with a simple example.</p>
<p>Imagine that we want a computer to predict whether a student will pass an exam.</p>
<p>We could give the computer information such as:</p>
<ul>
<li><p>How many hours the student studied</p>
</li>
<li><p>How many practice questions they completed</p>
</li>
<li><p>Their previous test score</p>
</li>
</ul>
<p>For example:</p>
<p><code>Study Hours = 5 Practice Questions = 80 Previous Score = 82</code></p>
<p>We also know whether the student actually passed:</p>
<p><code>Passed = 1</code></p>
<p>After seeing many examples like this, we want the computer to learn a pattern.</p>
<p>Maybe students who study more tend to perform better. Maybe previous test scores are useful. Maybe practice questions are helpful, too.</p>
<p>Instead of writing all of those rules ourselves, we can give the examples to a neural network and let it learn the relationships.</p>
<p>The basic idea looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/26be21fa-5503-402c-ac6c-7f77c5689e1e.png" alt="Visual idea about how a neural network works" style="display:block;margin:0 auto" width="1536" height="1024" loading="lazy">

<p>The prediction could be something like: <code>0.92</code></p>
<p>If we're predicting the probability of passing, we could interpret that as approximately a 92% predicted chance of passing.</p>
<p>The important thing is that we didn't tell the network that...</p>
<blockquote>
<p>"Study hours are important, and previous scores are slightly more important."</p>
</blockquote>
<p>Instead, the network learns numbers called <strong>weights</strong> that determine how strongly different inputs affect its predictions.</p>
<h2 id="heading-2-why-are-they-called-neural-networks">2. Why Are They Called Neural Networks?</h2>
<p>The name comes from biological brains.</p>
<p>Your brain contains neurons that receive signals, process information, and pass signals to other neurons.</p>
<p>Artificial neural networks are <strong>not artificial brains</strong>. They don't work exactly like biological neurons. But the general idea of connecting many simple processing units inspired the name.</p>
<p>A very simplified artificial neuron looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/bd68424f-e1be-4dea-8f6a-7fc1ed5abb15.png" alt="Input and Output through a neural network" style="display:block;margin:0 auto" width="1905" height="825" loading="lazy">

<p>The neuron receives numbers, performs some mathematical operations, and produces another number.</p>
<p>A neural network is made by connecting many of these artificial neurons together.</p>
<h2 id="heading-3-the-three-main-parts-of-a-neural-network">3. The Three Main Parts of a Neural Network</h2>
<p>A simple neural network can be divided into three types of layers:</p>
<ol>
<li><p>Input Layer</p>
</li>
<li><p>Hidden Layer(s)</p>
</li>
<li><p>Output Layer</p>
</li>
</ol>
<p>Let's look at each one.</p>
<h3 id="heading-the-input-layer">The Input Layer</h3>
<p>The input layer contains the information we give the network.</p>
<p>For our student example, we could have three inputs:</p>
<pre><code class="language-text">Input 1 = Study Hours
Input 2 = Practice Questions
Input 3 = Previous Score
</code></pre>
<p>So one student's input might look like:</p>
<pre><code class="language-text">[5, 80, 82]
</code></pre>
<p>The network doesn't necessarily understand that these numbers mean "study hours" or "test score." To the mathematical part of the network, they're simply numbers.</p>
<p>That's an important idea to remember:</p>
<blockquote>
<p>Neural networks work with numbers.</p>
</blockquote>
<p>Images, text, audio, and other information must eventually be represented as numbers before a neural network can process them.</p>
<h3 id="heading-hidden-layers">Hidden Layers</h3>
<p>After the input layer come the hidden layers.</p>
<p>A network might look like:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/6f177121-ce84-4a9d-a2b2-0ba98ad0e7d5.png" alt="Image showing how data moves from the Input Layer to the Hidden Layer and then to the Output layer" style="display:block;margin:0 auto" width="1774" height="887" loading="lazy">

<p>The hidden layer contains neurons that perform calculations on the inputs.</p>
<p>A network can have one hidden layer or many hidden layers.</p>
<p>When a network has many layers, we often call it a <strong>deep neural network</strong>.</p>
<h3 id="heading-the-output-layer">The Output Layer</h3>
<p>The output layer produces the final result.</p>
<p>For a simple yes/no problem, we might represent the answers as:</p>
<pre><code class="language-text">0 = No
1 = Yes
</code></pre>
<p>For example:</p>
<pre><code class="language-text">0.12 → probably No
0.91 → probably Yes
</code></pre>
<p>For a problem with multiple categories, the output could contain several numbers:</p>
<pre><code class="language-text">Cat  = 0.05
Dog  = 0.90
Bird = 0.05
</code></pre>
<p>The largest value is associated with "Dog," so the model would predict Dog.</p>
<h2 id="heading-4-what-is-a-neuron">4. What Is a Neuron?</h2>
<p>Now let's zoom in on one neuron.</p>
<p>Suppose our neuron receives three inputs:</p>
<pre><code class="language-text">x₁
x₂
x₃
</code></pre>
<p>Each input has a corresponding <strong>weight</strong>:</p>
<pre><code class="language-text">w₁
w₂
w₃
</code></pre>
<p>The neuron multiplies each input by its weight and adds the results together.</p>
<p>It also adds something called a <strong>bias</strong>.</p>
<p>The equation is:</p>
<pre><code class="language-text">z = x₁w₁ + x₂w₂ + x₃w₃ + b
</code></pre>
<p>Don't worry if that equation looks intimidating.</p>
<p>It's basically just:</p>
<pre><code class="language-text">input × weight
+
input × weight
+
input × weight
+
bias
</code></pre>
<p>Let's use actual numbers.</p>
<p>Suppose:</p>
<pre><code class="language-text">x₁ = 2
x₂ = 3
x₃ = 4

w₁ = 0.5
w₂ = 0.2
w₃ = 0.8

b = 1
</code></pre>
<p>Then:</p>
<pre><code class="language-text">z = (2 × 0.5) + (3 × 0.2) + (4 × 0.8) + 1
</code></pre>
<p>Calculate each part:</p>
<pre><code class="language-text">2 × 0.5 = 1.0
3 × 0.2 = 0.6
4 × 0.8 = 3.2
</code></pre>
<p>Now add them:</p>
<pre><code class="language-text">z = 1.0 + 0.6 + 3.2 + 1
z = 5.8
</code></pre>
<p>The neuron has produced <code>5.8</code>.</p>
<p>But we're not finished yet.</p>
<h2 id="heading-5-what-is-a-weight">5. What Is a Weight?</h2>
<p>A weight controls how strongly an input affects a neuron.</p>
<p>Imagine we have:</p>
<pre><code class="language-text">x = 5
</code></pre>
<p>If the weight is:</p>
<pre><code class="language-text">w = 2
</code></pre>
<p>then:</p>
<pre><code class="language-text">x × w = 5 × 2
      = 10
</code></pre>
<p>But if the weight is:</p>
<pre><code class="language-text">w = 0.1
</code></pre>
<p>then:</p>
<pre><code class="language-text">x × w = 5 × 0.1
      = 0.5
</code></pre>
<p>The same input produced a very different result because the weight changed.</p>
<p>You can think of a weight as a volume knob.</p>
<p>A large positive weight makes an input have a stronger positive influence. A weight close to zero makes the input have little influence. A negative weight can push the result in the opposite direction.</p>
<p>The network learns these weights during training.</p>
<h2 id="heading-6-what-is-a-bias">6. What Is a Bias?</h2>
<p>The bias is another number added to the neuron's calculation.</p>
<p>Without the bias, we would have:</p>
<pre><code class="language-text">z = x₁w₁ + x₂w₂ + x₃w₃
</code></pre>
<p>With the bias:</p>
<pre><code class="language-text">z = x₁w₁ + x₂w₂ + x₃w₃ + b
</code></pre>
<p>Why add another number? Because it gives the neuron more flexibility.</p>
<p>Think of it like adjusting the starting point of the neuron's calculation.</p>
<p>The network learns the bias during training just like it learns the weights.</p>
<p>So when you see:</p>
<pre><code class="language-text">weights + bias
</code></pre>
<p>you're looking at some of the parameters the neural network can change while it learns.</p>
<h2 id="heading-7-why-do-we-need-activation-functions">7. Why Do We Need Activation Functions?</h2>
<p>At this point, our neuron can calculate a weighted sum:</p>
<pre><code class="language-text">z = x₁w₁ + x₂w₂ + ... + b
</code></pre>
<p>But neural networks need to learn more complicated relationships than simple weighted sums.</p>
<p>That's where <strong>activation functions</strong> come in. An activation function takes the neuron's calculated value and transforms it.</p>
<p>One common activation function is <strong>ReLU</strong>. ReLU stands for <strong>Rectified Linear Unit</strong>.</p>
<p>Its equation is:</p>
<pre><code class="language-text">ReLU(x) = max(0, x)
</code></pre>
<p>In simple terms:</p>
<ul>
<li><p>If the number is positive, keep it.</p>
</li>
<li><p>If the number is negative, turn it into zero.</p>
</li>
</ul>
<p>For example:</p>
<pre><code class="language-text">ReLU(-5) = 0
ReLU(-2) = 0
ReLU(0)  = 0
ReLU(3)  = 3
ReLU(10) = 10
</code></pre>
<p>In Python:</p>
<pre><code class="language-python">def relu(x):
    return max(0, x)
</code></pre>
<p>With NumPy arrays, we can use:</p>
<pre><code class="language-python">def relu(x):
    return np.maximum(0, x)
</code></pre>
<p>Activation functions are important because they allow neural networks with multiple layers to learn more complicated patterns.</p>
<h2 id="heading-8-building-our-first-neuron-in-python">8. Building Our First Neuron in Python</h2>
<p>Let's turn the math into Python.</p>
<p>First, import NumPy:</p>
<pre><code class="language-python">import numpy as np
</code></pre>
<p>NumPy gives us tools for working with numbers, arrays, vectors, and matrices.</p>
<p>Now let's create our inputs:</p>
<pre><code class="language-python">x = np.array([2, 3, 4])
</code></pre>
<p>This creates an array containing three values:</p>
<pre><code class="language-text">[2, 3, 4]
</code></pre>
<p>Now create the weights:</p>
<pre><code class="language-python">weights = np.array([0.5, 0.2, 0.8])
</code></pre>
<p>We have one weight for each input:</p>
<pre><code class="language-text">x₁ = 2    w₁ = 0.5
x₂ = 3    w₂ = 0.2
x₃ = 4    w₃ = 0.8
</code></pre>
<p>Next, create the bias:</p>
<pre><code class="language-python">bias = 1
</code></pre>
<p>Now we calculate the weighted sum:</p>
<pre><code class="language-python">z = np.dot(x, weights) + bias
</code></pre>
<p><code>np.dot()</code> performs the multiplication-and-addition operation we described earlier.</p>
<p>In this case:</p>
<pre><code class="language-text">np.dot(x, weights)
</code></pre>
<p>is equivalent to:</p>
<pre><code class="language-text">(2 × 0.5) + (3 × 0.2) + (4 × 0.8)
</code></pre>
<p>which equals:</p>
<pre><code class="language-text">4.8
</code></pre>
<p>Then we add the bias:</p>
<pre><code class="language-text">4.8 + 1 = 5.8
</code></pre>
<p>Now apply ReLU:</p>
<pre><code class="language-python">output = np.maximum(0, z)
</code></pre>
<p>Since <code>z</code> is <code>5.8</code>, ReLU leaves it unchanged:</p>
<pre><code class="language-text">output = 5.8
</code></pre>
<p>Finally:</p>
<pre><code class="language-python">print(output)
</code></pre>
<p>prints:</p>
<pre><code class="language-text">5.8
</code></pre>
<p>So our entire neuron is:</p>
<pre><code class="language-python">import numpy as np

x = np.array([2, 3, 4])
weights = np.array([0.5, 0.2, 0.8])
bias = 1

z = np.dot(x, weights) + bias
output = np.maximum(0, z)

print(output)
</code></pre>
<p>We have just created a tiny artificial neuron.</p>
<h2 id="heading-9-from-one-neuron-to-a-layer">9. From One Neuron to a Layer</h2>
<p>One neuron isn't enough for most interesting problems.</p>
<p>Instead, we can connect several neurons together.</p>
<p>For example:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/62aeadb8-9f22-4b3a-b7a4-a450df33a55b.png" alt="Input, Output and Hidden Layer depicted with neurons" style="display:block;margin:0 auto" width="1774" height="887" loading="lazy">

<p>Those neurons together form a <strong>layer</strong>.</p>
<p>A small neural network might look like:</p>
<pre><code class="language-text">Input Layer
     ↓
Hidden Layer
     ↓
Output Layer
</code></pre>
<p>Every neuron in one layer can send its output to neurons in the next layer.</p>
<p>This is where neural networks start becoming much more powerful.</p>
<h2 id="heading-10-how-does-a-neural-network-actually-learn">10. How Does a Neural Network Actually Learn?</h2>
<p>So far, we've manually chosen the weights:</p>
<pre><code class="language-text">0.5
0.2
0.8
</code></pre>
<p>But a real neural network doesn't start out knowing the correct weights.</p>
<p>Instead, it starts with weights that are usually initialized to small random values.</p>
<p>Then it goes through a cycle:</p>
<pre><code class="language-text">Make a prediction
       ↓
Compare prediction with correct answer
       ↓
Measure the error
       ↓
Figure out how to change the weights
       ↓
Update the weights
       ↓
Try again
</code></pre>
<p>This process happens over and over, and the network gradually adjusts its parameters to make better predictions on the training data.</p>
<p>Let's break each part down.</p>
<h2 id="heading-11-predictions-and-loss">11. Predictions and Loss</h2>
<p>Suppose the correct answer is:</p>
<pre><code class="language-text">1
</code></pre>
<p>but our network predicts:</p>
<pre><code class="language-text">0.3
</code></pre>
<p>The prediction isn't very close to the target.</p>
<p>We need a way to measure how wrong it is. That's what a <strong>loss function</strong> does.</p>
<p>A loss function takes the prediction and the correct answer and produces a number representing the model's error.</p>
<p>For a simple example, we could use squared error:</p>
<pre><code class="language-text">Loss = (prediction - actual)²
</code></pre>
<p>Using our numbers:</p>
<pre><code class="language-text">Loss = (0.3 - 1)²
</code></pre>
<p>First:</p>
<pre><code class="language-text">0.3 - 1 = -0.7
</code></pre>
<p>Then square it:</p>
<pre><code class="language-text">(-0.7)² = 0.49
</code></pre>
<p>So:</p>
<pre><code class="language-text">Loss = 0.49
</code></pre>
<p>Generally, a smaller loss means the prediction is closer to the target.</p>
<p>In real neural networks, different problems use different loss functions. For binary classification, binary cross-entropy is commonly used.</p>
<h2 id="heading-12-what-are-gradients">12. What Are Gradients?</h2>
<p>Now we have a problem.</p>
<p>We know that the prediction was wrong, but how should we change the weights?</p>
<p>This is where <strong>gradients</strong> become useful. A gradient tells us how changing a parameter would affect the loss.</p>
<p>You can think of it like standing on a hill. Imagine that your goal is to reach the lowest point. If you know which direction slopes upward, you can move in the opposite direction to go downhill.</p>
<p>Training a neural network works with a similar idea. We want to reduce the loss. The gradients give us information about which direction the parameters should move.</p>
<h2 id="heading-13-what-is-gradient-descent">13. What Is Gradient Descent?</h2>
<p><strong>Gradient descent</strong> is the process of using gradients to adjust the network's parameters.</p>
<p>A simplified update rule is:</p>
<pre><code class="language-text">new weight = old weight - learning rate × gradient
</code></pre>
<p>In Python:</p>
<pre><code class="language-python">weight = weight - learning_rate * gradient
</code></pre>
<p>The <strong>learning rate</strong> controls how large the update is.</p>
<p>For example:</p>
<pre><code class="language-python">learning_rate = 0.01
</code></pre>
<p>If the learning rate is too large, the network can make huge changes and potentially jump around instead of settling on a good solution.</p>
<p>If it's too small, learning can take a very long time.</p>
<p>So training involves finding parameter updates that move the model toward lower loss without making the process unstable.</p>
<h2 id="heading-14-what-is-backpropagation">14. What Is Backpropagation?</h2>
<p>There's still one important question:</p>
<p>If a neural network has thousands or millions of weights, how does it figure out which weights contributed to the error?</p>
<p>That's where <strong>backpropagation</strong> comes in. Backpropagation calculates gradients for the parameters by working backward through the network.</p>
<p>Imagine a network like this:</p>
<pre><code class="language-text">Input
  ↓
Hidden Layer
  ↓
Output
  ↓
Loss
</code></pre>
<p>During the forward pass, information moves:</p>
<pre><code class="language-text">Input → Hidden Layer → Output
</code></pre>
<p>During backpropagation, gradient information moves backward:</p>
<pre><code class="language-text">Loss → Output → Hidden Layer → Input
</code></pre>
<p>The network uses these gradients to determine how its weights and biases should change.</p>
<p>You don't normally calculate all of these derivatives by hand when building real neural networks. Libraries such as PyTorch can calculate them automatically.</p>
<p>But understanding the basic idea is important:</p>
<blockquote>
<p>Backpropagation calculates how the parameters contributed to the error, and gradient descent uses that information to update them.</p>
</blockquote>
<h2 id="heading-15-the-complete-learning-cycle">15. The Complete Learning Cycle</h2>
<p>Now we can put everything together.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/cdd30bf8-25b9-4a3e-b62e-ee764642c05a.png" alt="Learning cycle of neural network: input, prediction, loss, gradients, update (and then back to prediction...)" style="display:block;margin:0 auto" width="2172" height="724" loading="lazy">

<p>More specifically:</p>
<pre><code class="language-text">Give the network data
          ↓
Calculate a prediction
          ↓
Compare it with the correct answer
          ↓
Calculate the loss
          ↓
Calculate gradients
          ↓
Update weights and biases
          ↓
Repeat
</code></pre>
<p>One complete pass through the training data is often called an <strong>epoch</strong>.</p>
<p>For example:</p>
<pre><code class="language-text">Epoch 1 → Loss: 0.82
Epoch 2 → Loss: 0.61
Epoch 3 → Loss: 0.43
Epoch 4 → Loss: 0.29
Epoch 5 → Loss: 0.18
</code></pre>
<p>These numbers are just an example, but ideally the loss decreases as training progresses.</p>
<h2 id="heading-16-lets-build-a-neural-network-from-scratch">16. Let's Build a Neural Network From Scratch</h2>
<p>Congrats! You now understand the basics of neural networks. Now it's time to put these ideas together.</p>
<p>We're going to build a small neural network using only:</p>
<pre><code class="language-text">Python + NumPy
</code></pre>
<p>Our network will learn a classic machine learning problem called <strong>XOR</strong>.</p>
<p>XOR is a logical operation with two inputs.</p>
<p>Its rules are:</p>
<pre><code class="language-text">0 XOR 0 → 0
0 XOR 1 → 1
1 XOR 0 → 1
1 XOR 1 → 0
</code></pre>
<p>In other words, the output is <code>1</code> when exactly one of the inputs is <code>1</code>.</p>
<p>Our training data will therefore be:</p>
<pre><code class="language-python">X = np.array([
    [0, 0],
    [0, 1],
    [1, 0],
    [1, 1]
])
</code></pre>
<p>And the correct answers are:</p>
<pre><code class="language-python">y = np.array([
    [0],
    [1],
    [1],
    [0]
])
</code></pre>
<p>We want our neural network to learn this pattern.</p>
<h2 id="heading-17-understanding-the-network-architecture">17. Understanding the Network Architecture</h2>
<p>Our network will contain:</p>
<pre><code class="language-text">2 input neurons
       ↓
4 hidden neurons
       ↓
1 output neuron
</code></pre>
<p>The two inputs represent the two numbers in each XOR example.</p>
<p>The four hidden neurons give the network enough flexibility to learn the XOR relationship.</p>
<p>The output neuron produces a number between <code>0</code> and <code>1</code>.</p>
<h2 id="heading-18-setting-up-the-data">18. Setting Up the Data</h2>
<p>Let's start our Python program.</p>
<pre><code class="language-python">import numpy as np
</code></pre>
<p>This imports NumPy. We'll use NumPy for arrays, matrix multiplication, and mathematical operations.</p>
<p>Next:</p>
<pre><code class="language-python">X = np.array([
    [0, 0],
    [0, 1],
    [1, 0],
    [1, 1]
])
</code></pre>
<p><code>X</code> contains our four training examples.</p>
<p>Each row is one example:</p>
<pre><code class="language-text">[0, 0]
[0, 1]
[1, 0]
[1, 1]
</code></pre>
<p>Now create the correct answers:</p>
<pre><code class="language-python">y = np.array([
    [0],
    [1],
    [1],
    [0]
])
</code></pre>
<p>The first row of <code>X</code> corresponds to the first row of <code>y</code>.</p>
<p>So:</p>
<pre><code class="language-text">[0, 0] → 0
[0, 1] → 1
[1, 0] → 1
[1, 1] → 0
</code></pre>
<h2 id="heading-19-creating-the-weights-and-biases">19. Creating the Weights and Biases</h2>
<p>Now we need the parameters of our network.</p>
<p>First:</p>
<pre><code class="language-python">np.random.seed(42)
</code></pre>
<p>This makes our random numbers reproducible.</p>
<p>Without this line, the network would receive different random starting weights each time we ran the program.</p>
<p>Now create the first layer's weights:</p>
<pre><code class="language-python">W1 = np.random.randn(2, 4)
</code></pre>
<p>Why <code>(2, 4)</code>?</p>
<p>Because:</p>
<ul>
<li><p>We have 2 input values.</p>
</li>
<li><p>We have 4 neurons in the hidden layer.</p>
</li>
</ul>
<p>So <code>W1</code> needs a weight connecting each input to each hidden neuron.</p>
<p>There are:</p>
<pre><code class="language-text">2 × 4 = 8
</code></pre>
<p>weights.</p>
<p>Next:</p>
<pre><code class="language-python">b1 = np.zeros((1, 4))
</code></pre>
<p>This creates four biases, one for each hidden neuron.</p>
<p>Now the second layer:</p>
<pre><code class="language-python">W2 = np.random.randn(4, 1)
</code></pre>
<p>There are four hidden neurons and one output neuron, so we need:</p>
<pre><code class="language-text">4 × 1 = 4
</code></pre>
<p>weights.</p>
<p>Finally:</p>
<pre><code class="language-python">b2 = np.zeros((1, 1))
</code></pre>
<p>This gives the output neuron one bias.</p>
<p>Our network parameters are therefore:</p>
<pre><code class="language-text">W1 → input-to-hidden weights
b1 → hidden-layer biases

W2 → hidden-to-output weights
b2 → output-layer bias
</code></pre>
<h2 id="heading-20-the-sigmoid-function">20. The Sigmoid Function</h2>
<p>Our output represents a probability, so we'd like it to be between <code>0</code> and <code>1</code>.</p>
<p>We can use the <strong>sigmoid function</strong>.</p>
<p>Its equation is:</p>
<pre><code class="language-text">sigmoid(x) = 1 / (1 + e⁻ˣ)
</code></pre>
<p>In Python:</p>
<pre><code class="language-python">def sigmoid(x):
    return 1 / (1 + np.exp(-x))
</code></pre>
<p>Let's see what it does:</p>
<pre><code class="language-text">sigmoid(-5) ≈ 0.007
sigmoid(0)  = 0.5
sigmoid(5)  ≈ 0.993
</code></pre>
<p>No matter how large or small the input is, the result stays between <code>0</code> and <code>1</code>.</p>
<p>That's useful when our output represents a probability.</p>
<h2 id="heading-21-forward-propagation">21. Forward Propagation</h2>
<p>Now we can send the data through the network. This is called <strong>forward propagation</strong>.</p>
<p>First, calculate the hidden layer:</p>
<pre><code class="language-python">z1 = X @ W1 + b1
</code></pre>
<p>There's a new symbol here:</p>
<pre><code class="language-text">@
</code></pre>
<p>In Python, <code>@</code> performs matrix multiplication.</p>
<p>You can think of this operation as performing many weighted sums at once.</p>
<p>Instead of manually calculating every neuron:</p>
<pre><code class="language-text">input × weight + input × weight + bias
</code></pre>
<p>NumPy can calculate all of them together.</p>
<p>The result is stored in <code>z1</code>.</p>
<p>Next:</p>
<pre><code class="language-python">a1 = np.tanh(z1)
</code></pre>
<p>Here we're using the <strong>tanh activation function</strong> for the hidden layer.</p>
<p>Tanh converts its input into values between <code>-1</code> and <code>1</code>.</p>
<p>Why use tanh here?</p>
<p>Because XOR isn't something a single simple linear calculation can solve. The nonlinear activation gives the hidden layer the flexibility it needs to learn the pattern.</p>
<p>Now calculate the output layer:</p>
<pre><code class="language-python">z2 = a1 @ W2 + b2
</code></pre>
<p>This takes the hidden layer's outputs and combines them using the second set of weights.</p>
<p>Finally:</p>
<pre><code class="language-python">a2 = sigmoid(z2)
</code></pre>
<p>Now <code>a2</code> contains our predictions.</p>
<p>For example, before training, the network might produce something like:</p>
<pre><code class="language-text">0.52
0.61
0.48
0.55
</code></pre>
<p>Those predictions aren't useful yet, but that's expected. The network hasn't learned anything yet.</p>
<h2 id="heading-22-calculating-the-loss">22. Calculating the Loss</h2>
<p>Now we need to measure how good those predictions are.</p>
<p>For binary classification, we'll use <strong>binary cross-entropy</strong>, which is a loss function used in machine learning for binary classification. It measures the performance of a model whose output is a probability value between 0 and 1.</p>
<p>The formula is:</p>
<pre><code class="language-text">Loss = -mean(
    y × log(prediction)
    +
    (1 - y) × log(1 - prediction)
)
</code></pre>
<p>That looks much more complicated than the squared-error example from earlier, but we don't need to memorize the formula.</p>
<p>In Python:</p>
<pre><code class="language-python">loss = -np.mean(
    y * np.log(a2 + 1e-8) +
    (1 - y) * np.log(1 - a2 + 1e-8)
)
</code></pre>
<p>The <code>1e-8</code> is a very small number.</p>
<p>It prevents problems if <code>a2</code> gets extremely close to <code>0</code> or <code>1</code>, because taking the logarithm of exactly zero isn't valid.</p>
<p>At the beginning of training, the loss will probably be relatively high. But as the network learns, we'd like it to decrease.</p>
<h2 id="heading-23-backpropagation-in-code">23. Backpropagation in Code</h2>
<p>Now comes the most mathematical part of our program.</p>
<p>We need to calculate the gradients.</p>
<p>Start with:</p>
<pre><code class="language-python">dz2 = a2 - y
</code></pre>
<p>This gives us the gradient of the loss with respect to the output layer's pre-activation value for the sigmoid + binary cross-entropy combination.</p>
<p>Next:</p>
<pre><code class="language-python">dW2 = (a1.T @ dz2) / len(X)
</code></pre>
<p>This calculates the gradient for <code>W2</code>.</p>
<p>The <code>.T</code> means transpose.</p>
<p>Our hidden-layer output has four neurons, while <code>dz2</code> represents the output layer's error. Matrix multiplication combines them to determine how each hidden-to-output weight contributed to the loss.</p>
<p>We divide by:</p>
<pre><code class="language-python">len(X)
</code></pre>
<p>because we have four training examples and we're calculating the average gradient.</p>
<p>Now calculate the output bias gradient:</p>
<pre><code class="language-python">db2 = np.mean(dz2, axis=0, keepdims=True)
</code></pre>
<p>This calculates the average gradient for the output bias.</p>
<p>Next:</p>
<pre><code class="language-python">da1 = dz2 @ W2.T
</code></pre>
<p>This sends the gradient information backward from the output layer toward the hidden layer.</p>
<p>Now we need to account for the derivative of the tanh activation function.</p>
<p>The derivative of tanh can be written as:</p>
<pre><code class="language-text">1 - tanh(x)²
</code></pre>
<p>Since we already have the hidden layer's activated values in <code>a1</code>, we can write:</p>
<pre><code class="language-python">dz1 = da1 * (1 - a1**2)
</code></pre>
<p>This tells us how the hidden layer's pre-activation values affected the loss.</p>
<p>Now calculate the gradients for the first layer's weights:</p>
<pre><code class="language-python">dW1 = (X.T @ dz1) / len(X)
</code></pre>
<p>And the hidden-layer biases:</p>
<pre><code class="language-python">db1 = np.mean(dz1, axis=0, keepdims=True)
</code></pre>
<p>At this point, we have gradients for all of our trainable parameters.</p>
<h2 id="heading-24-updating-the-weights">24. Updating the Weights</h2>
<p>Now we use gradient descent.</p>
<p>First:</p>
<pre><code class="language-python">W2 -= learning_rate * dW2
</code></pre>
<p>This updates the second layer's weights.</p>
<p>The <code>-=</code> means:</p>
<pre><code class="language-python">W2 = W2 - learning_rate * dW2
</code></pre>
<p>Then:</p>
<pre><code class="language-python">b2 -= learning_rate * db2
</code></pre>
<p>updates the output bias.</p>
<p>And:</p>
<pre><code class="language-python">W1 -= learning_rate * dW1
</code></pre>
<p>updates the first layer's weights.</p>
<p>Finally:</p>
<pre><code class="language-python">b1 -= learning_rate * db1
</code></pre>
<p>updates the hidden-layer biases.</p>
<p>These updates are what actually allow the network to learn.</p>
<h2 id="heading-25-the-complete-numpy-neural-network">25. The Complete NumPy Neural Network</h2>
<p>Now let's put everything together.</p>
<pre><code class="language-python">import numpy as np

# 1. Training data

X = np.array([
    [0, 0],
    [0, 1],
    [1, 0],
    [1, 1]
])

y = np.array([
    [0],
    [1],
    [1],
    [0]
])

# 2. Initialize parameters

np.random.seed(42)

W1 = np.random.randn(2, 4)
b1 = np.zeros((1, 4))

W2 = np.random.randn(4, 1)
b2 = np.zeros((1, 1))

learning_rate = 0.1

# 3. Activation functions

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# 4. Training

for epoch in range(10000):

    # Forward propagation

    z1 = X @ W1 + b1
    a1 = np.tanh(z1)

    z2 = a1 @ W2 + b2
    a2 = sigmoid(z2)

    # Calculate loss

    loss = -np.mean(
        y * np.log(a2 + 1e-8) +
        (1 - y) * np.log(1 - a2 + 1e-8)
    )

    # Backpropagation

    dz2 = a2 - y

    dW2 = (a1.T @ dz2) / len(X)
    db2 = np.mean(dz2, axis=0, keepdims=True)

    da1 = dz2 @ W2.T

    dz1 = da1 * (1 - a1**2)

    dW1 = (X.T @ dz1) / len(X)
    db1 = np.mean(dz1, axis=0, keepdims=True)

    # Update parameters

    W2 -= learning_rate * dW2
    b2 -= learning_rate * db2

    W1 -= learning_rate * dW1
    b1 -= learning_rate * db1

    # Display progress

    if epoch % 1000 == 0:
        print(f"Epoch {epoch}, Loss: {loss:.4f}")
</code></pre>
<p>Let's go through the program from top to bottom.</p>
<h3 id="heading-line-by-line-explanation-of-the-full-code">Line-by-Line Explanation of the Full Code</h3>
<h4 id="heading-importing-numpy">Importing NumPy:</h4>
<pre><code class="language-python">import numpy as np
</code></pre>
<p>We import NumPy because our network will work with arrays and matrix operations.</p>
<h4 id="heading-creating-the-inputs">Creating the inputs</h4>
<pre><code class="language-python">X = np.array([
    [0, 0],
    [0, 1],
    [1, 0],
    [1, 1]
])
</code></pre>
<p>Each row is one XOR example.</p>
<p>There are four examples and two input values per example.</p>
<p>So the shape of <code>X</code> is:</p>
<pre><code class="language-text">4 × 2
</code></pre>
<h4 id="heading-creating-the-answers">Creating the answers</h4>
<pre><code class="language-python">y = np.array([
    [0],
    [1],
    [1],
    [0]
])
</code></pre>
<p>There are four correct answers, one for each row in <code>X</code>.</p>
<h4 id="heading-making-random-initialization-reproducible">Making random initialization reproducible</h4>
<pre><code class="language-python">np.random.seed(42)
</code></pre>
<p>This makes NumPy generate the same starting random values each time.</p>
<p>The number <code>42</code> isn't special. You could use another number.</p>
<h4 id="heading-creating-the-first-weight-matrix">Creating the first weight matrix</h4>
<pre><code class="language-python">W1 = np.random.randn(2, 4)
</code></pre>
<p>This creates a matrix containing random numbers.</p>
<p>Its shape is 2*4</p>
<p>There are two inputs and four hidden neurons.</p>
<h4 id="heading-creating-the-first-biases">Creating the first biases</h4>
<pre><code class="language-python">b1 = np.zeros((1, 4))
</code></pre>
<p>This creates four zeros:</p>
<pre><code class="language-text">[0, 0, 0, 0]
</code></pre>
<p>There is one bias for every hidden neuron.</p>
<h4 id="heading-creating-the-second-weight-matrix">Creating the second weight matrix</h4>
<pre><code class="language-python">W2 = np.random.randn(4, 1)
</code></pre>
<p>There are four hidden neurons and one output neuron.</p>
<p>Therefore:</p>
<pre><code class="language-text">4 × 1
</code></pre>
<p>weights are needed.</p>
<h4 id="heading-creating-the-output-bias">Creating the output bias</h4>
<pre><code class="language-python">b2 = np.zeros((1, 1))
</code></pre>
<p>The output layer has one neuron, so it needs one bias.</p>
<h4 id="heading-setting-the-learning-rate">Setting the learning rate</h4>
<pre><code class="language-python">learning_rate = 0.1
</code></pre>
<p>This controls how strongly the gradients affect each update.</p>
<h4 id="heading-creating-sigmoid">Creating sigmoid</h4>
<pre><code class="language-python">def sigmoid(x):
    return 1 / (1 + np.exp(-x))
</code></pre>
<p>This converts the output into a value between <code>0</code> and <code>1</code>.</p>
<h3 id="heading-starting-the-training-loop">Starting the Training Loop</h3>
<pre><code class="language-python">for epoch in range(10000):
</code></pre>
<p>This tells Python to repeat the training process 10,000 times.</p>
<p>Each repetition is an epoch, which is one complete pass of the entire training dataset through a neural network</p>
<h4 id="heading-calculating-the-hidden-layer">Calculating the hidden layer</h4>
<pre><code class="language-python">z1 = X @ W1 + b1
</code></pre>
<p>This performs the weighted-sum calculation for all four hidden neurons and all four training examples.</p>
<h4 id="heading-applying-tanh">Applying tanh</h4>
<pre><code class="language-python">a1 = np.tanh(z1)
</code></pre>
<p>This applies the nonlinear activation function to the hidden layer.</p>
<h4 id="heading-calculating-the-output-layer">Calculating the output layer</h4>
<pre><code class="language-python">z2 = a1 @ W2 + b2
</code></pre>
<p>This takes the hidden layer's values and calculates the output neuron's weighted sum.</p>
<h4 id="heading-applying-sigmoid">Applying sigmoid</h4>
<pre><code class="language-python">a2 = sigmoid(z2)
</code></pre>
<p>This turns the output into probabilities between <code>0</code> and <code>1</code>.</p>
<h4 id="heading-calculating-the-loss">Calculating the loss</h4>
<pre><code class="language-python">loss = -np.mean(
    y * np.log(a2 + 1e-8) +
    (1 - y) * np.log(1 - a2 + 1e-8)
)
</code></pre>
<p>This measures how different the predictions are from the correct answers.</p>
<p>A lower value generally means the predictions are better.</p>
<h4 id="heading-calculating-the-output-gradient">Calculating the output gradient</h4>
<pre><code class="language-python">dz2 = a2 - y
</code></pre>
<p>This calculates the gradient needed to update the output layer.</p>
<h4 id="heading-updating-the-second-layer-weight-gradients">Updating the second-layer weight gradients</h4>
<pre><code class="language-python">dW2 = (a1.T @ dz2) / len(X)
</code></pre>
<p>This determines how each weight connecting the hidden layer to the output layer contributed to the loss.</p>
<h4 id="heading-updating-the-output-bias-gradient">Updating the output bias gradient</h4>
<pre><code class="language-python">db2 = np.mean(dz2, axis=0, keepdims=True)
</code></pre>
<p>This calculates the average gradient for the output bias.</p>
<h4 id="heading-moving-backward-toward-the-hidden-layer">Moving backward toward the hidden layer</h4>
<pre><code class="language-python">da1 = dz2 @ W2.T
</code></pre>
<p>This passes the gradient information backward through the output layer.</p>
<h4 id="heading-applying-the-tanh-derivative">Applying the tanh derivative</h4>
<pre><code class="language-python">dz1 = da1 * (1 - a1**2)
</code></pre>
<p>This accounts for the effect of the tanh activation function.</p>
<h4 id="heading-calculating-the-first-layer-gradients">Calculating the first-layer gradients</h4>
<pre><code class="language-python">dW1 = (X.T @ dz1) / len(X)
</code></pre>
<p>This determines how the input-to-hidden weights contributed to the loss.</p>
<p>Then:</p>
<pre><code class="language-python">db1 = np.mean(dz1, axis=0, keepdims=True)
</code></pre>
<p>calculates the gradients for the hidden-layer biases.</p>
<h4 id="heading-updating-the-parameters">Updating the parameters</h4>
<pre><code class="language-python">W2 -= learning_rate * dW2
b2 -= learning_rate * db2

W1 -= learning_rate * dW1
b1 -= learning_rate * db1
</code></pre>
<p>These four lines are where the network changes what it has learned.</p>
<p>The gradients tell us which direction to move, while the learning rate determines how large the movement should be.</p>
<h4 id="heading-printing-the-loss">Printing the loss</h4>
<pre><code class="language-python">if epoch % 1000 == 0:
    print(f"Epoch {epoch}, Loss: {loss:.4f}")
</code></pre>
<p>The <code>%</code> operator gives us the remainder after division.</p>
<p>So:</p>
<pre><code class="language-python">epoch % 1000 == 0
</code></pre>
<p>is true every 1,000 epochs.</p>
<p>That means we don't print something 10,000 times. Instead, we get occasional updates such as:</p>
<pre><code class="language-text">Epoch 0, Loss: ...
Epoch 1000, Loss: ...
Epoch 2000, Loss: ...
...
</code></pre>
<p>If training is working well, the loss should generally decrease.</p>
<h2 id="heading-26-testing-the-network">26. Testing the Network</h2>
<p>After training, we can use the network to make predictions.</p>
<pre><code class="language-python">z1 = X @ W1 + b1
a1 = np.tanh(z1)

z2 = a1 @ W2 + b2
predictions = sigmoid(z2)

print(predictions)
</code></pre>
<p>The network should produce values close to:</p>
<pre><code class="language-text">[[0],
 [1],
 [1],
 [0]]
</code></pre>
<p>The actual values probably won't be exactly <code>0</code> and <code>1</code>.</p>
<p>You might get something more like:</p>
<pre><code class="language-text">[[0.01],
 [0.98],
 [0.99],
 [0.02]]
</code></pre>
<p>That's fine.</p>
<p>The network is producing probabilities.</p>
<p>We can convert those probabilities into classes using a threshold:</p>
<pre><code class="language-python">classes = (predictions &gt;= 0.5).astype(int)

print(classes)
</code></pre>
<p>The result should be:</p>
<pre><code class="language-text">[[0],
 [1],
 [1],
 [0]]
</code></pre>
<p>Our network has learned the XOR pattern.</p>
<h2 id="heading-27-why-did-we-need-a-hidden-layer">27. Why Did We Need a Hidden Layer?</h2>
<p>You might wonder why we couldn't just connect the two inputs directly to the output.</p>
<p>The reason is that XOR isn't something a single linear layer can represent.</p>
<p>The hidden layer gives the network additional transformations that allow it to learn the more complicated relationship.</p>
<p>This is one of the most important ideas behind neural networks: a network doesn't necessarily learn one giant rule. Instead, different layers can transform information step by step.</p>
<p>For an image recognition system, you can imagine a simplified process like:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/1bf62ced-92e8-4ee2-ba7f-fba16fde006f.png" alt="Image recognition system visually depicted" style="display:block;margin:0 auto" width="1024" height="1536" loading="lazy">

<p>Real neural networks don't literally create neat layers called "edges," "shapes," and "objects." This is just an intuition for how increasingly complex representations can emerge through multiple layers.</p>
<h2 id="heading-28-what-happens-in-a-larger-neural-network">28. What Happens in a Larger Neural Network?</h2>
<p>The network we built is tiny. Modern neural networks can have millions, billions, or even more parameters.</p>
<p>A simplified network might look like:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/9007ed47-0c1f-4b72-99e6-194010fdfc20.png" alt="Simplified neural network visually depicted" style="display:block;margin:0 auto" width="1086" height="1448" loading="lazy">

<p>Each connection can have its own weight.</p>
<p>The more neurons and connections a network has, the more parameters it may need to learn.</p>
<p>Large models therefore require significant amounts of computing power and memory.</p>
<p>But remember the basic process:</p>
<pre><code class="language-text">Input
 ↓
Calculations
 ↓
Prediction
 ↓
Loss
 ↓
Gradients
 ↓
Parameter Updates
</code></pre>
<p>The size of the network changes dramatically, but the basic training idea remains.</p>
<h2 id="heading-29-do-you-have-to-build-neural-networks-from-scratch">29. Do You Have to Build Neural Networks From Scratch?</h2>
<p>No. Building a neural network from scratch is useful for learning because it forces you to understand what's happening underneath the libraries.</p>
<p>But you normally wouldn't manually calculate every gradient when building a real machine learning application.</p>
<p>That's where machine learning frameworks come in. Some commonly used Python libraries include:</p>
<ul>
<li><p>NumPy</p>
</li>
<li><p>PyTorch</p>
</li>
<li><p>TensorFlow</p>
</li>
<li><p>Keras</p>
</li>
<li><p>scikit-learn</p>
</li>
</ul>
<p>For deep learning, <strong>PyTorch</strong> is one of the most commonly used frameworks. It can automatically calculate gradients and handle many of the mathematical operations involved in training.</p>
<h2 id="heading-30-building-the-same-network-with-pytorch">30. Building the Same Network With PyTorch</h2>
<p>Let's see how much shorter the network becomes with PyTorch.</p>
<p>First, install it:</p>
<pre><code class="language-bash">pip install torch
</code></pre>
<p>Then import it:</p>
<pre><code class="language-python">import torch
import torch.nn as nn
</code></pre>
<p>Now create the model:</p>
<pre><code class="language-python">model = nn.Sequential(
    nn.Linear(2, 4),
    nn.Tanh(),
    nn.Linear(4, 1),
    nn.Sigmoid()
)
</code></pre>
<p>Let's break that down.</p>
<pre><code class="language-python">nn.Linear(2, 4)
</code></pre>
<p>creates a layer that takes two inputs and produces four outputs.</p>
<p>That's our hidden layer.</p>
<p>Next:</p>
<pre><code class="language-python">nn.Tanh()
</code></pre>
<p>applies the tanh activation function.</p>
<p>Then:</p>
<pre><code class="language-python">nn.Linear(4, 1)
</code></pre>
<p>connects the four hidden neurons to one output neuron.</p>
<p>Finally:</p>
<pre><code class="language-python">nn.Sigmoid()
</code></pre>
<p>converts the output into a value between <code>0</code> and <code>1</code>.</p>
<p>So the architecture is:</p>
<pre><code class="language-text">2 inputs
   ↓
4 hidden neurons
   ↓
Tanh
   ↓
1 output neuron
   ↓
Sigmoid
</code></pre>
<p>Notice how much shorter this is than our NumPy implementation.</p>
<p>That's because PyTorch handles many of the calculations for us.</p>
<h2 id="heading-31-training-the-network-with-pytorch">31. Training the Network With PyTorch</h2>
<p>First, create the training data:</p>
<pre><code class="language-python">X = torch.tensor([
    [0., 0.],
    [0., 1.],
    [1., 0.],
    [1., 1.]
])

y = torch.tensor([
    [0.],
    [1.],
    [1.],
    [0.]
])
</code></pre>
<p>The decimal points are important because neural networks normally work with floating-point numbers.</p>
<p>Now create the model:</p>
<pre><code class="language-python">model = nn.Sequential(
    nn.Linear(2, 4),
    nn.Tanh(),
    nn.Linear(4, 1),
    nn.Sigmoid()
)
</code></pre>
<p>Next, choose our loss function:</p>
<pre><code class="language-python">loss_function = nn.BCELoss()
</code></pre>
<p><code>BCELoss</code> calculates binary cross-entropy loss.</p>
<p>Now create an optimizer:</p>
<pre><code class="language-python">optimizer = torch.optim.Adam(
    model.parameters(),
    lr=0.01
)
</code></pre>
<p>Adam is an optimization algorithm that updates the model's parameters during training.</p>
<p><code>model.parameters()</code> tells the optimizer which values it should update.</p>
<p><code>lr=0.01</code> sets the learning rate.</p>
<p>Now we can train:</p>
<pre><code class="language-python">for epoch in range(5000):

    predictions = model(X)

    loss = loss_function(predictions, y)

    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

    if epoch % 500 == 0:
        print(
            f"Epoch {epoch}, Loss: {loss.item():.4f}"
        )
</code></pre>
<p>Let's look at the important parts.</p>
<p>First:</p>
<pre><code class="language-python">predictions = model(X)
</code></pre>
<p>This sends the training data through the network.</p>
<p>Then:</p>
<pre><code class="language-python">loss = loss_function(predictions, y)
</code></pre>
<p>compares the predictions with the correct answers.</p>
<p>Next:</p>
<pre><code class="language-python">optimizer.zero_grad()
</code></pre>
<p>clears gradients from the previous training step.</p>
<p>Then:</p>
<pre><code class="language-python">loss.backward()
</code></pre>
<p>calculates the gradients automatically using backpropagation.</p>
<p>Finally:</p>
<pre><code class="language-python">optimizer.step()
</code></pre>
<p>uses those gradients to update the model's parameters.</p>
<p>That's the same basic learning process we implemented manually with NumPy. The difference is that PyTorch takes care of many of the calculations.</p>
<h2 id="heading-32-numpy-vs-pytorch">32. NumPy vs. PyTorch</h2>
<p>So why did we build the network twice? Well, because the two versions teach different things.</p>
<p>With NumPy, we manually handled weights, biases, forward propagation,<br>loss, gradients, backpropagation, and parameter updates. That makes the mechanics easier to see.</p>
<p>With PyTorch, we can write the same general idea in much less code because the framework handles many of those calculations.</p>
<p>You can think of it like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/6230c12c-34ef-4ab3-9f9f-f99eaebcf295.png" alt="Comparison between NumPy and PyTorch" style="display:block;margin:0 auto" width="1086" height="1448" loading="lazy">

<p>Learning how the NumPy version works makes the PyTorch version much less mysterious.</p>
<h2 id="heading-33-what-is-deep-learning">33. What Is Deep Learning?</h2>
<p>You may have heard the term <strong>deep learning</strong>. Deep learning is a part of machine learning that uses neural networks with multiple layers.</p>
<p>For example:</p>
<pre><code class="language-text">Input
  ↓
Layer 1
  ↓
Layer 2
  ↓
Layer 3
  ↓
Layer 4
  ↓
Output
</code></pre>
<p>The word "deep" refers to the depth of the network, or the number of layers involved.</p>
<p>There isn't a magical point where a neural network suddenly becomes intelligent. Adding layers simply gives the model more opportunities to transform the input into useful representations.</p>
<h2 id="heading-34-where-are-neural-networks-used">34. Where Are Neural Networks Used?</h2>
<p>Neural networks are used in many different areas. Here are a few examples...</p>
<h3 id="heading-computer-vision">Computer Vision</h3>
<p>Neural networks can process images.</p>
<p>For example:</p>
<pre><code class="language-text">Image
  ↓
Neural Network
  ↓
Prediction
</code></pre>
<p>They can be used for tasks such as image classification and object detection.</p>
<h3 id="heading-natural-language-processing">Natural Language Processing</h3>
<p>Neural networks can also process text.</p>
<p>For example:</p>
<pre><code class="language-text">Text
  ↓
Neural Network
  ↓
Prediction
</code></pre>
<p>Modern language models use neural networks to process and generate text.</p>
<h3 id="heading-speech-recognition">Speech Recognition</h3>
<p>Neural networks can process audio and help convert spoken language into text.</p>
<pre><code class="language-text">Audio
  ↓
Neural Network
  ↓
Words
</code></pre>
<h3 id="heading-recommendation-systems">Recommendation Systems</h3>
<p>Neural networks can learn patterns from user behavior and help predict which content or products might be useful to someone.</p>
<h3 id="heading-generative-ai">Generative AI</h3>
<p>Large neural networks can also be used to generate text, images, audio, code, video, and much more.</p>
<p>These systems are much more complicated than the small XOR network we built, but they still rely on the same general idea of learning parameters from data.</p>
<h2 id="heading-35-the-whole-process-in-one-picture">35. The Whole Process in One Picture</h2>
<p>At this point, we've covered a lot.</p>
<p>Here's the entire training process:</p>
<pre><code class="language-text">Data
   ↓
Neural Network
   ↓
Prediction
   ↓
Loss
  ↓
Backpropagation
  ↓
Update Parameters
  ↓
Repeat
</code></pre>
<p>Once training is finished, we use the learned parameters to make predictions on new data:</p>
<pre><code class="language-text">New Data
   ↓
Trained Neural Network
   ↓
Prediction
</code></pre>
<p>That's the basic idea behind neural network training.</p>
<h2 id="heading-36-the-most-important-ideas-to-remember">36. The Most Important Ideas to Remember</h2>
<p>If you don't remember every equation from this tutorial, that's okay.</p>
<p>Start with these concepts.</p>
<h3 id="heading-inputs">Inputs</h3>
<p>The numbers we give to the network.</p>
<pre><code class="language-text">x₁, x₂, x₃...
</code></pre>
<h3 id="heading-weights">Weights</h3>
<p>Numbers that determine how strongly inputs affect neurons.</p>
<pre><code class="language-text">w₁, w₂, w₃...
</code></pre>
<h3 id="heading-biases">Biases</h3>
<p>Additional values that give neurons more flexibility.</p>
<pre><code class="language-text">b
</code></pre>
<h3 id="heading-activation-functions">Activation Functions</h3>
<p>Functions that transform neuron outputs and allow networks to learn nonlinear patterns.</p>
<p>Examples include:</p>
<pre><code class="language-text">ReLU
Tanh
Sigmoid
</code></pre>
<h3 id="heading-forward-propagation">Forward Propagation</h3>
<p>Sending data from the input toward the output.</p>
<pre><code class="language-text">Input → Hidden Layers → Output
</code></pre>
<h3 id="heading-loss">Loss</h3>
<p>A measurement of how different the prediction is from the correct answer.</p>
<h3 id="heading-backpropagation">Backpropagation</h3>
<p>Calculating gradients by working backward through the network.</p>
<h3 id="heading-gradient-descent">Gradient Descent</h3>
<p>Using those gradients to update the network's parameters.</p>
<p>And the entire learning process can be summarized as:</p>
<pre><code class="language-text">Predict
   ↓
Measure Error
   ↓
Calculate Gradients
   ↓
Update Parameters
   ↓
Repeat
</code></pre>
<h2 id="heading-37-what-should-you-learn-next">37. What Should You Learn Next?</h2>
<p>If you want to continue learning neural networks with Python, you don't need to jump directly into complicated research papers.</p>
<p>A useful learning path is:</p>
<pre><code class="language-text">Python
  ↓
NumPy
  ↓
Basic Linear Algebra
  ↓
Probability &amp; Statistics
  ↓
Machine Learning Basics
  ↓
Neural Networks
  ↓
PyTorch
  ↓
Deep Learning
  ↓
Computer Vision / NLP / Generative AI
</code></pre>
<p>You can also learn by building small projects.</p>
<p>For example:</p>
<ol>
<li><p>XOR classifier</p>
</li>
<li><p>House price predictor</p>
</li>
<li><p>Handwritten digit classifier</p>
</li>
<li><p>Simple image classifier</p>
</li>
<li><p>Spam message classifier</p>
</li>
<li><p>Neural network that learns a mathematical function</p>
</li>
</ol>
<p>The projects don't need to be huge. A small project that you completely understand is often more useful than a large project where you copied code without understanding it.</p>
<h2 id="heading-final-takeaway">Final Takeaway</h2>
<p>Neural networks can look intimidating because the systems used in modern AI can contain enormous numbers of parameters.</p>
<p>But the basic idea is much smaller.</p>
<p>A neural network takes numbers as input, combines them using weights and biases, applies mathematical functions, produces a prediction, measures how wrong that prediction was, and then adjusts its parameters.</p>
<p>The cycle looks like this:</p>
<pre><code class="language-text">Input
  ↓
Weighted Calculations
  ↓
Activation Functions
  ↓
Prediction
  ↓
Loss
  ↓
Gradients
  ↓
Parameter Updates
  ↓
Repeat
</code></pre>
<p>That's the foundation.</p>
<p>The XOR network we built in this tutorial is tiny compared with the neural networks used in modern AI. But the ideas you just learned (parameters, layers, activation functions, forward propagation, loss, backpropagation, gradients, and optimization) are fundamental ideas that appear again and again in deep learning.</p>
<p>The next time you hear that an AI model has millions or billions of parameters, it might still sound overwhelming.</p>
<p>But underneath all that scale, the basic learning loop is still familiar:</p>
<ol>
<li><p>Make a prediction.</p>
</li>
<li><p>Measure the error.</p>
</li>
<li><p>Figure out how to improve.</p>
</li>
<li><p>Update the parameters.</p>
</li>
<li><p>Try again.</p>
</li>
</ol>
<p>And that's the core idea behind a neural network.</p>
<p>Happy coding and keep learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Basic Discord Storytelling, Chat, and Mental Wellness Bot with Python ]]>
                </title>
                <description>
                    <![CDATA[ Discord bots can look surprisingly complicated when you see them in action. A bot can respond to messages, tell stories, remember parts of conversations, and stay online around the clock. When I first ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-basic-discord-bot-with-python/</link>
                <guid isPermaLink="false">6a7f44fc58366ecdaf016624</guid>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python 3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Beginner Developers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ techblog ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eva J Patel ]]>
                </dc:creator>
                <pubDate>Fri, 14 Aug 2026 16:40:28 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6a444c51-d332-4915-aa1f-326b57b17472.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Discord bots can look surprisingly complicated when you see them in action. A bot can respond to messages, tell stories, remember parts of conversations, and stay online around the clock.</p>
<p>When I first started looking into how they worked, I assumed there had to be a huge amount of complicated code behind all of it.</p>
<p>But the basic idea is actually pretty simple.</p>
<p>At its core, a Discord bot is just a Python program that connects to Discord, waits for something to happen, and then decides how to respond. Once you understand that basic idea, you can start adding features one at a time and turn a simple bot into something much more interesting.</p>
<p>In this tutorial, we'll start with a very small bot and gradually build it into something more capable. Along the way, you'll learn about Discord commands, events, asynchronous Python, user state, environment variables, and basic deployment.</p>
<p>One quick disclaimer before we start: the mental-wellness feature that we'll be integrating in this bot in this project is <strong>not therapy</strong>, and the bot is not a therapist or medical professional. It should only provide general supportive suggestions and encourage users to reach out to a trusted person when appropriate.</p>
<p>With that out of the way, let's get coding!</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-what-were-building">What We're Building</a></p>
</li>
<li><p><a href="#heading-what-you-need">What You Need</a></p>
</li>
<li><p><a href="#heading-create-the-discord-bot">Create the Discord Bot</a></p>
</li>
<li><p><a href="#heading-give-the-bot-permission-to-read-messages">Give the Bot Permission to Read Messages</a></p>
</li>
<li><p><a href="#heading-create-the-project">Create the Project</a></p>
</li>
<li><p><a href="#heading-create-a-virtual-environment">Create a Virtual Environment</a></p>
</li>
<li><p><a href="#heading-install-discordpy">Install discord.py</a></p>
</li>
<li><p><a href="#heading-create-your-first-bot">Create Your First Bot</a></p>
<ul>
<li><p><a href="#heading-importing-our-libraries">Importing Our Libraries</a></p>
</li>
<li><p><a href="#heading-loading-the-token">Loading the Token</a></p>
</li>
<li><p><a href="#heading-understanding-intents">Understanding Intents</a></p>
</li>
<li><p><a href="#heading-what-is-ctx">What Isctx?</a></p>
</li>
<li><p><a href="#heading-why-does-everything-say-async-and-await">Why Does Everything Sayasyncandawait?</a></p>
</li>
<li><p><a href="#heading-run-the-bot">Run the Bot</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-build-the-storytelling-system">Build the Storytelling System</a></p>
<ul>
<li><p><a href="#heading-lets-make-the-story-remember-the-user">Let's Make the Story Remember the User</a></p>
</li>
<li><p><a href="#heading-add-a-story-choice">Add a Story Choice</a></p>
</li>
<li><p><a href="#heading-add-a-casual-chat-command">Add a Casual Chat Command</a></p>
</li>
<li><p><a href="#heading-add-a-mental-wellness-support-feature">Add a Mental-Wellness Support Feature</a></p>
</li>
<li><p><a href="#heading-add-a-help-command">Add a Help Command</a></p>
</li>
<li><p><a href="#heading-improve-error-handling">Improve Error Handling</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-put-everything-together">Put Everything Together</a></p>
</li>
<li><p><a href="#heading-our-bot-doesnt-actually-remember-anything">Our Bot Doesn't Actually Remember Anything</a></p>
<ul>
<li><p><a href="#heading-create-the-database">Create the Database</a></p>
</li>
<li><p><a href="#heading-save-a-users-story">Save a User's Story</a></p>
</li>
<li><p><a href="#heading-get-the-story-back">Get the Story Back</a></p>
</li>
<li><p><a href="#heading-put-it-into-a-command">Put It Into a Command</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-adding-real-ai-chat">Adding Real AI Chat</a></p>
<ul>
<li><p><a href="#heading-install-the-hugging-face-library">Install the Hugging Face Library</a></p>
</li>
<li><p><a href="#heading-create-the-hugging-face-client">Create the Hugging Face Client</a></p>
</li>
<li><p><a href="#heading-connect-the-ai-model-to-the-bot">Connect the AI Model to the Bot</a></p>
</li>
<li><p><a href="#heading-handle-ai-errors">Handle AI Errors</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-do-we-keep-the-bot-online">How Do We Keep the Bot Online?</a></p>
<ul>
<li><p><a href="#heading-option-1-run-it-on-your-computer">Option 1: Run It on Your Computer</a></p>
</li>
<li><p><a href="#heading-option-2-host-it-on-a-server">Option 2: Host It on a Server</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-what-forever-actually-means">What "Forever" Actually Means</a></p>
</li>
<li><p><a href="#heading-dont-try-to-keep-it-awake-with-random-tricks">Don't Try to "Keep It Awake" With Random Tricks</a></p>
</li>
<li><p><a href="#heading-additional-features-and-where-to-go-next">Additional Features and Where to Go Next</a></p>
</li>
<li><p><a href="#heading-test-everything-locally-first">Test Everything Locally First</a></p>
</li>
<li><p><a href="#heading-deploying-the-bot">Deploying the Bot</a></p>
<ul>
<li><a href="#heading-the-start-command">The Start Command</a></li>
</ul>
</li>
<li><p><a href="#heading-remember-keep-your-secrets-secret">Remember: Keep Your Secrets Secret</a></p>
</li>
<li><p><a href="#heading-what-you-learned">What You Learned</a></p>
</li>
<li><p><a href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-what-were-building">What We're Building</h2>
<p>Our finished bot will have several commands:</p>
<pre><code class="language-text">!hello
!story
!chat hello!
!support I'm having a stressful day
!help
</code></pre>
<p>For example:</p>
<pre><code class="language-text">User:
!story

Bot:
You wake up inside an abandoned library.

There are three doors in front of you:

1. A red wooden door
2. A metal door covered in strange symbols
3. A staircase leading underground

Which one do you choose?
</code></pre>
<p>The user can then continue the story.</p>
<p>For chat:</p>
<pre><code class="language-text">User:
!chat What's a good way to learn Python?

Bot:
Try building small projects instead of only reading tutorials.
A Discord bot is actually a pretty fun project to start with.
</code></pre>
<p>And for mental-wellness support:</p>
<pre><code class="language-text">User:
!support I'm really stressed about school.

Bot:
That sounds like a lot to deal with. You could try breaking
the work into one small task at a time and taking a short
break between tasks.

I'm a bot, not a therapist, so if you need personal support,
consider talking with someone you trust.
</code></pre>
<p>The goal isn't to make a magical robot therapist. It's to build a useful bot while learning how Discord APIs, Python functions, events, asynchronous programming, and basic conversational logic fit together.</p>
<h2 id="heading-what-you-need">What You Need</h2>
<p>You only need a few things:</p>
<ul>
<li><p>Python (version 3.8+ is recommended)</p>
</li>
<li><p>A Discord account</p>
</li>
<li><p>A Discord server where you have permission to add a bot</p>
</li>
<li><p>A code editor (I personally prefer VS Code or PyCharm)</p>
</li>
<li><p>The <code>discord.py</code> library</p>
</li>
</ul>
<p>We'll also use Python's built-in <code>os</code> module for reading environment variables.</p>
<p>If you don't already have Python installed, install a current supported version of Python from the official Python website.</p>
<p>Then check that Python works:</p>
<pre><code class="language-bash">python --version
</code></pre>
<p>You should see something similar to:</p>
<pre><code class="language-text">Python 3.x.x
</code></pre>
<h2 id="heading-create-the-discord-bot">Create the Discord Bot</h2>
<p>Before Python can control Discord, we need to create a Discord application.</p>
<p>Go to the Discord Developer Portal: <a href="https://discord.com/developers/applications">https://discord.com/developers/applications</a></p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/64a5aac8-fb53-42d0-9b91-fb6453b3eb1d.png" alt="Picture of the discord developer application page" style="display:block;margin:0 auto" width="2398" height="1396" loading="lazy">

<p>This is what the page will look like, you might need to login with your discord email/username and password before you start.</p>
<p>Click on the "New Application" button on the top right and give your bot a name. For this tutorial, let's call ours <code>StoryBot</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/84bd2f32-941c-4de2-8e6b-30ab22cc5ba1.png" alt="Picture of what it looks like when you click on the &quot;New Application&quot; button" style="display:block;margin:0 auto" width="2397" height="1303" loading="lazy">

<p>The application is basically the home for your bot.</p>
<p>Discord's developer platform provides the tools needed to create and configure applications and bots.</p>
<p>Once you've created the application, open its <strong>Bot</strong> section and create the bot user. You can add your own icon picture and your own banner if you want to.</p>
<p>You will then go to the <strong>Token</strong> section and click on "Reset Token" to generate your token. Treat that token like a password. Do <strong>NOT</strong> put it directly into your Python source code.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/7537d64e-706c-43b9-a574-746294703f6d.png" alt="Picture of what the Token part in the Bots section looks like" style="display:block;margin:0 auto" width="1894" height="189" loading="lazy">

<p>Never do this:</p>
<pre><code class="language-python">bot.run("my-secret-token")
</code></pre>
<p>And definitely don't upload a token to GitHub or commit it to source control. Instead, we'll store it in an environment variable, which we'll talk about later.</p>
<h2 id="heading-give-the-bot-permission-to-read-messages">Give the Bot Permission to Read Messages</h2>
<p>Our bot needs to see the messages that contain commands.</p>
<p>Discord uses something called <strong>Gateway Intents</strong> to control which types of events a bot receives. The <code>discord.py</code> documentation explains that intents must be enabled both in your code and, for privileged intents, in the Discord Developer Portal.</p>
<p>In the Developer Portal, find:</p>
<pre><code class="language-text">Bot
→ Privileged Gateway Intents
</code></pre>
<p>Enable:</p>
<pre><code class="language-text">Message Content Intent
</code></pre>
<p>It should look somewhat like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a581501af6af179dc1987d5/0273691f-8723-41d3-8cfc-d59656dfb6e2.png" alt="What should the &quot;Message Content Intent&quot; section look like" style="display:block;margin:0 auto" width="1933" height="501" loading="lazy">

<p>We'll also enable it in Python, which we will talk about later in this article.</p>
<h2 id="heading-create-the-project">Create the Project</h2>
<p>Create a folder:</p>
<pre><code class="language-text">discord-story-bot/
</code></pre>
<p>Inside it, we'll eventually have:</p>
<pre><code class="language-text">discord-story-bot/
│
├── bot.py
├── requirements.txt
└── .env
</code></pre>
<p>The three important files are:</p>
<ul>
<li><p><code>bot.py</code>: our Python program</p>
</li>
<li><p><code>requirements.txt</code>: text file that contains the name of the packages our bot needs</p>
</li>
<li><p><code>.env</code>: our secret token during local development</p>
</li>
</ul>
<h2 id="heading-create-a-virtual-environment">Create a Virtual Environment</h2>
<p>Open your terminal inside the project folder.</p>
<p>Run:</p>
<pre><code class="language-bash">python -m venv venv
</code></pre>
<p>Then activate it.</p>
<p>On Windows:</p>
<pre><code class="language-bash">venv\Scripts\activate
</code></pre>
<p>On macOS/Linux:</p>
<pre><code class="language-bash">source venv/bin/activate
</code></pre>
<p>A virtual environment gives this project its own little Python bubble.</p>
<p>That means packages installed for this bot won't randomly interfere with packages used by another project.</p>
<h2 id="heading-install-discordpy">Install discord.py</h2>
<p>Now install the Discord library:</p>
<pre><code class="language-bash">pip install -U discord.py
</code></pre>
<p>The official <code>discord.py</code> documentation uses this installation approach for setting up the library.</p>
<p>We'll also install <code>python-dotenv</code>, which makes reading our local <code>.env</code> file easier:</p>
<pre><code class="language-bash">pip install python-dotenv
</code></pre>
<p>Then save the dependencies:</p>
<pre><code class="language-bash">pip freeze &gt; requirements.txt
</code></pre>
<p>Your <code>requirements.txt</code> should contain the packages needed by the project.</p>
<h2 id="heading-create-your-first-bot">Create Your First Bot</h2>
<p>Let's start small.</p>
<p>Open <code>bot.py</code>:</p>
<pre><code class="language-python">import os

import discord
from discord.ext import commands
from dotenv import load_dotenv


load_dotenv()

TOKEN = os.getenv("DISCORD_TOKEN")

intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(
    command_prefix="!",
    intents=intents
)


@bot.event
async def on_ready():
    print(f"Logged in as {bot.user}")


@bot.command()
async def hello(ctx):
    await ctx.send("Hello! I'm online.")


bot.run(TOKEN)
</code></pre>
<p>That is already a functional Discord bot.</p>
<p>Let's break it apart piece by piece.</p>
<h3 id="heading-importing-our-libraries">Importing Our Libraries</h3>
<p>First:</p>
<pre><code class="language-python">import os
</code></pre>
<p><code>os</code> lets Python communicate with parts of the operating system.</p>
<p>We'll use it to read environment variables.</p>
<p>Next:</p>
<pre><code class="language-python">import discord
</code></pre>
<p>This imports <code>discord.py</code>.</p>
<p>Then:</p>
<pre><code class="language-python">from discord.ext import commands
</code></pre>
<p>The <code>commands</code> extension makes creating commands much easier.</p>
<p>Instead of manually checking every message for something like <code>!hello</code>, we can write:</p>
<pre><code class="language-python">@bot.command()
async def hello(ctx):
    await ctx.send("Hello!")
</code></pre>
<p>The <code>discord.py</code> command system is built around Python functions decorated as commands.</p>
<p>Finally:</p>
<pre><code class="language-python">from dotenv import load_dotenv
</code></pre>
<p>This lets us load values from our <code>.env</code> file.</p>
<h3 id="heading-loading-the-token">Loading the Token</h3>
<p>Our bot needs a token to connect our Python program to Discord. Think of the token as a password that allows our program to authenticate as the bot.</p>
<p>We don't want to put this secret directly into our Python code. Instead, we'll store it in an environment variable.</p>
<p>First, install <code>python-dotenv</code>:</p>
<pre><code class="language-bash">pip install python-dotenv
</code></pre>
<p>This package lets Python read values from a <code>.env</code> file.</p>
<p>Now create a new file called <code>.env</code> in the same folder as <code>bot.py</code>.</p>
<p>Inside <code>.env</code>, add:</p>
<pre><code class="language-text">DISCORD_TOKEN=YOUR_BOT_TOKEN_HERE
</code></pre>
<p>Replace <code>YOUR_BOT_TOKEN_HERE</code> with the token you copied from the Discord Developer Portal.</p>
<p>Your file should look something like this:</p>
<pre><code class="language-text">DISCORD_TOKEN=your_actual_token_here
</code></pre>
<p>Don't share this token with anyone or upload your <code>.env</code> file to GitHub. Your bot token should be treated like a password.</p>
<p>To make sure Git doesn't accidentally include the <code>.env</code> file in a repository, create a file called <code>.gitignore</code> in your project folder and add:</p>
<pre><code class="language-text">.env
venv/
__pycache__/
</code></pre>
<p>Now let's load the token in Python.</p>
<p>At the top of <code>bot.py</code>, add:</p>
<pre><code class="language-python">import os
from dotenv import load_dotenv
</code></pre>
<p>Then add:</p>
<pre><code class="language-python">load_dotenv()
</code></pre>
<p>This tells Python to look for the <code>.env</code> file and load the variables inside it.</p>
<p>Now we can get our Discord token:</p>
<pre><code class="language-python">TOKEN = os.getenv("DISCORD_TOKEN")
</code></pre>
<p><code>os.getenv()</code> looks for the environment variable named <code>"DISCORD_TOKEN"</code> and gives us its value.</p>
<p>We can also check that the token was actually found:</p>
<pre><code class="language-python">if not TOKEN:
    raise RuntimeError("DISCORD_TOKEN is not set.")
</code></pre>
<p>If Python can't find the token, the program stops and gives us a clear error message instead of failing later in a confusing way.</p>
<h3 id="heading-understanding-intents">Understanding Intents</h3>
<p>Remember how we talked about enabling message content readability in python? We are going to do that now.</p>
<p>Add:</p>
<pre><code class="language-python">intents = discord.Intents.default()
intents.message_content = True
</code></pre>
<p>The first line creates a set of Discord's default intents.</p>
<p>The second line tells Discord that our bot needs access to message content.</p>
<p>Now we need to give these intents to our bot when we create it:</p>
<pre><code class="language-python">bot = commands.Bot(
    command_prefix="!",
    intents=intents
)
</code></pre>
<p>The <code>command_prefix="!"</code> means our bot will recognize commands that begin with <code>!</code>.</p>
<p>For example:</p>
<pre><code class="language-text">!hello
</code></pre>
<p>The <code>intents=intents</code> part gives our bot the permissions we configured above.</p>
<p>There are two steps here because Discord needs to know that our bot is allowed to receive message content, while our Python program also needs to tell Discord that it wants to receive it.</p>
<p>Our basic setup should now look like this:</p>
<pre><code class="language-python">import os
import discord

from dotenv import load_dotenv
from discord.ext import commands

load_dotenv()

TOKEN = os.getenv("DISCORD_TOKEN")

if not TOKEN:
    raise RuntimeError("DISCORD_TOKEN is not set.")

intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(
    command_prefix="!",
    intents=intents
)
</code></pre>
<p>Now our bot has its token safely loaded and <code>discord.py</code> knows which intents to request when it connects to Discord.</p>
<h3 id="heading-what-is-ctx">What Is <code>ctx</code>?</h3>
<p>This part can look weird when you're learning Discord bots:</p>
<pre><code class="language-python">async def hello(ctx):
</code></pre>
<p>What is <code>ctx</code>? <code>ctx</code> stands for <strong>context</strong>. It contains information about the command that was used.</p>
<p>For example, it can tell us:</p>
<ul>
<li><p>Who ran the command</p>
</li>
<li><p>Which server it came from</p>
</li>
<li><p>Which channel it came from</p>
</li>
<li><p>What message triggered it</p>
</li>
</ul>
<p>Then:</p>
<pre><code class="language-python">await ctx.send("Hello!")
</code></pre>
<p>means:</p>
<blockquote>
<p>"Send this message back to the place where the command was used."</p>
</blockquote>
<h3 id="heading-why-does-everything-say-async-and-await">Why Does Everything Say <code>async</code> and <code>await</code>?</h3>
<p>You might notice:</p>
<pre><code class="language-python">async def hello(ctx):
</code></pre>
<p>and:</p>
<pre><code class="language-python">await ctx.send(...)
</code></pre>
<p>Discord bots spend a lot of time waiting.</p>
<p>They wait for:</p>
<ul>
<li><p>Messages</p>
</li>
<li><p>Discord responses</p>
</li>
<li><p>API requests</p>
</li>
<li><p>Timers</p>
</li>
<li><p>Other events</p>
</li>
</ul>
<p>Python's asynchronous programming features allow the bot to wait for these operations without freezing everything else.</p>
<p>You don't need to become an async-programming expert before building your first bot.</p>
<p>For now, think of <code>await</code> as:</p>
<blockquote>
<p>"Pause this task until this operation finishes, while letting the bot handle other things."</p>
</blockquote>
<h3 id="heading-run-the-bot">Run the Bot</h3>
<p>Start it with:</p>
<pre><code class="language-bash">python bot.py
</code></pre>
<p>If everything works, your terminal should print something similar to:</p>
<pre><code class="language-text">Logged in as StoryBot
</code></pre>
<p>Now go to your Discord server and type <code>!hello</code>. Your bot should respond.</p>
<p>Congratulations! You've officially made a Discord bot.</p>
<p>Now let's make it interesting.</p>
<h2 id="heading-build-the-storytelling-system">Build the Storytelling System</h2>
<p>First, we're going to create an interactive storytelling command.</p>
<p>At the top of <code>bot.py</code>, add:</p>
<pre><code class="language-python">import random
</code></pre>
<p>Then create some story ingredients:</p>
<pre><code class="language-python">story_locations = [
    "an abandoned library",
    "a mysterious island",
    "a futuristic city",
    "a hidden underground laboratory",
    "a forest that never appears on maps"
]

story_items = [
    "a glowing key",
    "an ancient notebook",
    "a strange compass",
    "a locked metal box",
    "a mysterious photograph"
]

story_events = [
    "You hear footsteps behind you.",
    "The lights suddenly turn off.",
    "A hidden door opens nearby.",
    "Your phone starts displaying a message from an unknown sender.",
    "You notice that the room has changed."
]
</code></pre>
<p>Now create the command:</p>
<pre><code class="language-python">@bot.command()
async def story(ctx):
    location = random.choice(story_locations)
    item = random.choice(story_items)
    event = random.choice(story_events)

    story_text = (
        f"You wake up in {location}.\n\n"
        f"Next to you is {item}.\n\n"
        f"{event}\n\n"
        "What do you do?"
    )

    await ctx.send(story_text)
</code></pre>
<p>Now <code>!story</code> might produce:</p>
<pre><code class="language-text">You wake up in a futuristic city.

Next to you is an ancient notebook.

A hidden door opens nearby.

What do you do?
</code></pre>
<p>Run it again and you might get something completely different.</p>
<p>That's because of:</p>
<pre><code class="language-python">random.choice(...)
</code></pre>
<p>Python randomly picks one item from each list.</p>
<p>It's a simple technique, but suddenly your bot can generate hundreds of different combinations.</p>
<h3 id="heading-lets-make-the-story-remember-the-user">Let's Make the Story Remember the User</h3>
<p>Random stories are fun, but interactive stories are much better when the bot remembers what happened.</p>
<p>We can create a dictionary:</p>
<pre><code class="language-python">user_stories = {}
</code></pre>
<p>The dictionary will store story information for each user.</p>
<p>For example:</p>
<pre><code class="language-text">user ID → current story
</code></pre>
<p>Now let's modify the story command:</p>
<pre><code class="language-python">@bot.command()
async def story(ctx):
    user_id = ctx.author.id

    location = random.choice(story_locations)
    item = random.choice(story_items)
    event = random.choice(story_events)

    user_stories[user_id] = {
        "location": location,
        "item": item,
        "event": event
    }

    await ctx.send(
        f"You wake up in {location}.\n\n"
        f"Next to you is {item}.\n\n"
        f"{event}\n\n"
        "What do you do?"
    )
</code></pre>
<p>Now each user can have their own active story.</p>
<h3 id="heading-add-a-story-choice">Add a Story Choice</h3>
<p>Let's give users choices.</p>
<pre><code class="language-python">@bot.command()
async def choose(ctx, choice: str):
    user_id = ctx.author.id

    if user_id not in user_stories:
        await ctx.send("You don't have an active story. Try `!story` first.")
        return

    choice = choice.lower()

    if choice == "left":
        response = (
            "You head left and discover a room filled with old maps. "
            "One of them has your name written on it."
        )

    elif choice == "right":
        response = (
            "You head right and find a staircase leading toward "
            "a strange blue light."
        )

    else:
        response = "Try choosing `left` or `right`."

    await ctx.send(response)
</code></pre>
<p>Now users can type:</p>
<pre><code class="language-text">!choose left
</code></pre>
<p>or:</p>
<pre><code class="language-text">!choose right
</code></pre>
<p>Notice this:</p>
<pre><code class="language-python">async def choose(ctx, choice: str):
</code></pre>
<p>The <code>choice</code> parameter receives the text after the command.</p>
<p>So:</p>
<pre><code class="language-text">!choose left
</code></pre>
<p>becomes approximately:</p>
<pre><code class="language-python">choice = "left"
</code></pre>
<p>This is one of the reasons command frameworks are so convenient. A <strong>command framework</strong> is a set of tools that makes it easier to create and manage commands in a program. In our case, <code>discord.py</code> provides the command framework that lets us turn Python functions into Discord commands using decorators like <code>@bot.command()</code>.</p>
<p>Instead of manually checking every message to figure out whether someone typed <code>!choose</code>, <code>discord.py</code> handles that work for us. It recognizes the command, takes the user's arguments, and passes them to our function.</p>
<p>So when someone types:</p>
<pre><code class="language-text">!choose left
</code></pre>
<p><code>discord.py</code> knows that choose is the command, <code>"left"</code> is the argument, and that it should call our <code>choose()</code> function with that information.</p>
<h3 id="heading-add-a-casual-chat-command">Add a Casual Chat Command</h3>
<p>Now let's make the bot capable of basic conversation.</p>
<p>We could connect it to a large language model API, but you don't actually need AI to learn how a chat command works. We'll start with a simple keyword-based response system.</p>
<p>First, we'll create a dictionary containing some keywords and possible responses:</p>
<pre><code class="language-python">chat_responses = {
    "hello": [
        "Hey! What's up?",
        "Hello! How's your day going?",
        "Hi! What are you working on?"
    ],
    "python": [
        "Python is a great language for beginners because its syntax is pretty readable.",
        "If you're learning Python, try building something instead of only watching tutorials."
    ],
    "discord": [
        "Discord bots are a fun way to practice Python because you get instant feedback.",
        "Once you understand commands and events, you can build some surprisingly complex bots."
    ]
}

Think of `chat_responses` as a small collection of things our bot knows how to talk about. Each key, such as `"python"` or `"discord"`, represents a keyword the bot can look for. The value associated with each key is a list of possible responses.

We use a list instead of a single response so the bot doesn't give exactly the same answer every time. Later, we'll randomly choose one of these responses.

Now let's create the actual `!chat` command:

```python
@bot.command()
async def chat(ctx, *, message: str):
    text = message.lower()

    for keyword, responses in chat_responses.items():
        if keyword in text:
            await ctx.send(random.choice(responses))
            return

    await ctx.send(
        "I'm still learning how to respond to that. "
        "Try talking to me about Python or Discord!"
    )
</code></pre>
<p>There are a few things happening here, so let's break it down.</p>
<p>First, this part:</p>
<pre><code class="language-python">@bot.command()
async def chat(ctx, *, message: str):
</code></pre>
<p>turns the <code>chat()</code> function into a Discord command. The <code>*</code> is important because it tells <code>discord.py</code> to treat everything after the command as one argument.</p>
<p>For example, if someone types:</p>
<pre><code class="language-text">!chat I want to learn Python
</code></pre>
<p>the entire phrase after <code>!chat</code> becomes the value of <code>message</code>:</p>
<pre><code class="language-python">message = "I want to learn Python"
</code></pre>
<p>Next, we have:</p>
<pre><code class="language-python">text = message.lower()
</code></pre>
<p>This converts the message to lowercase. That means <code>Python</code>, <code>python</code>, and <code>PYTHON</code> will all become <code>python</code>. Without this, our keyword check could miss a match simply because the user capitalized a word differently.</p>
<p>Now we get to the loop:</p>
<pre><code class="language-python">for keyword, responses in chat_responses.items():
</code></pre>
<p><code>.items()</code> lets us go through both the keyword and its corresponding list of responses. During each loop, <code>keyword</code> contains something like <code>"python"</code>, while <code>responses</code> contains the list of responses associated with it.</p>
<p>Then we check:</p>
<pre><code class="language-python">if keyword in text:
</code></pre>
<p>This asks whether the current keyword appears anywhere in the user's message.</p>
<p>If the user writes:</p>
<pre><code class="language-text">!chat I want to learn Python
</code></pre>
<p>the lowercase version becomes:</p>
<pre><code class="language-text">i want to learn python
</code></pre>
<p>Since <code>"python"</code> appears inside that text, the condition is true.</p>
<p>The bot can then choose a random response:</p>
<pre><code class="language-python">await ctx.send(random.choice(responses))
</code></pre>
<p><code>random.choice()</code> picks one item from the response list, while <code>ctx.send()</code> sends that response back to the Discord channel.</p>
<p>Finally, we have:</p>
<pre><code class="language-python">return
</code></pre>
<p>This stops the function after a matching keyword is found. Without it, the loop would continue checking the other keywords even after the bot had already responded.</p>
<p>But what happens if none of the keywords match?</p>
<p>That's what this part handles:</p>
<pre><code class="language-python">await ctx.send(
    "I'm still learning how to respond to that. "
    "Try talking to me about Python or Discord!"
)
</code></pre>
<p>If the loop finishes without finding a keyword, the bot sends this fallback message instead.</p>
<p>For example:</p>
<pre><code class="language-text">!chat I like pizza
</code></pre>
<p>doesn't contain <code>"hello"</code>, <code>"python"</code>, or <code>"discord"</code>, so the bot doesn't have a specific response to use.</p>
<p>This gives us a simple way for the bot to have conversations without needing an AI model.</p>
<h3 id="heading-add-a-mental-wellness-support-feature">Add a Mental-Wellness Support Feature</h3>
<p>Now for the feature that needs a little more care.</p>
<p>Instead of calling this a "therapy command" internally, we'll call it:</p>
<pre><code class="language-text">!support
</code></pre>
<p>Quick additional disclaimer before we start...this is just a fun wellness script, not a real therapist!</p>
<p>Create:</p>
<pre><code class="language-python">support_responses = {
    "stress": [
        "That sounds like a lot to handle. Try breaking the situation into one small task at a time.",
        "When everything feels overwhelming, it can help to pause and focus on what needs attention right now."
    ],

    "school": [
        "School can pile up quickly. Consider choosing one assignment to work on first instead of trying to solve everything at once.",
        "If school stress is getting difficult to manage, talking with a trusted person can make things feel less like something you have to handle alone."
    ],

    "sad": [
        "I'm sorry you're having a difficult moment. Taking a short break, doing something calming, or talking with someone you trust may help.",
        "You don't have to solve everything immediately. Give yourself some time and consider reaching out to someone you trust."
    ]
}
</code></pre>
<p>Now create the command:</p>
<pre><code class="language-python">@bot.command()
async def support(ctx, *, message: str):
    text = message.lower()

    for keyword, responses in support_responses.items():
        if keyword in text:
            response = random.choice(responses)

            await ctx.send(
                f"{response}\n\n"
                "I'm a bot, not a therapist or medical professional. "
                "If you need personal support, consider talking with "
                "someone you trust."
            )
            return

    await ctx.send(
        "It sounds like something is bothering you. "
        "I can offer general wellness suggestions, but I'm not a therapist. "
        "If you need personal support, consider reaching out to someone you trust."
    )
</code></pre>
<p>Now someone can type:</p>
<pre><code class="language-text">!support I'm stressed about school
</code></pre>
<p>The bot sees the word:</p>
<pre><code class="language-text">school
</code></pre>
<p>and chooses one of the school-related responses.</p>
<p>This is deliberately simple.</p>
<p>For a real public bot, you'd want much more careful safety handling, testing, moderation, privacy protection, and escalation logic before allowing users to rely on it for sensitive situations.</p>
<h3 id="heading-add-a-help-command">Add a Help Command</h3>
<p>A good bot should explain itself.</p>
<pre><code class="language-python">@bot.command()
async def commands_help(ctx):
    await ctx.send(
        "**Available commands:**\n"
        "`!hello` - Say hello\n"
        "`!story` - Start a new story\n"
        "`!choose left` - Choose the left path\n"
        "`!choose right` - Choose the right path\n"
        "`!chat &lt;message&gt;` - Have a casual conversation\n"
        "`!support &lt;message&gt;` - Get general wellness support"
    )
</code></pre>
<p>There's one small issue.</p>
<p>Discord's default help command is already called <code>help</code>.</p>
<p>So instead of:</p>
<pre><code class="language-python">async def help(ctx):
</code></pre>
<p>we've named ours:</p>
<pre><code class="language-python">commands_help
</code></pre>
<p>If you want the command itself to be called <code>!help</code>, you can write:</p>
<pre><code class="language-python">@bot.command(name="help")
async def commands_help(ctx):
    ...
</code></pre>
<p>That tells Discord:</p>
<blockquote>
<p>Use <code>!help</code> for this function even though the Python function has another name.</p>
</blockquote>
<h3 id="heading-improve-error-handling">Improve Error Handling</h3>
<p>Bots shouldn't crash just because someone enters an invalid command.</p>
<p>Add:</p>
<pre><code class="language-python">@bot.event
async def on_command_error(ctx, error):
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send(
            "You're missing something. Try `!help` to see how the command works."
        )

    elif isinstance(error, commands.CommandNotFound):
        return

    else:
        print(f"Error: {error}")
</code></pre>
<p>Now if someone types:</p>
<pre><code class="language-text">!chat
</code></pre>
<p>without giving the bot a message, it can respond with a useful explanation instead of dumping a confusing error into the conversation.</p>
<h2 id="heading-put-everything-together">Put Everything Together</h2>
<p>At this point, your <code>bot.py</code> can look like this:</p>
<pre><code class="language-python">import os
import random

import discord
from discord.ext import commands
from dotenv import load_dotenv


load_dotenv()

TOKEN = os.getenv("DISCORD_TOKEN")

if not TOKEN:
    raise RuntimeError("DISCORD_TOKEN is not set.")


intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(
    command_prefix="!",
    intents=intents
)


story_locations = [
    "an abandoned library",
    "a mysterious island",
    "a futuristic city",
    "a hidden underground laboratory",
    "a forest that never appears on maps"
]

story_items = [
    "a glowing key",
    "an ancient notebook",
    "a strange compass",
    "a locked metal box",
    "a mysterious photograph"
]

story_events = [
    "You hear footsteps behind you.",
    "The lights suddenly turn off.",
    "A hidden door opens nearby.",
    "Your phone starts displaying a message from an unknown sender.",
    "You notice that the room has changed."
]


user_stories = {}


chat_responses = {
    "hello": [
        "Hey! What's up?",
        "Hello! How's your day going?",
        "Hi! What are you working on?"
    ],

    "python": [
        "Python is a great language for beginners because its syntax is pretty readable.",
        "If you're learning Python, try building something instead of only watching tutorials."
    ],

    "discord": [
        "Discord bots are a fun way to practice Python because you get instant feedback.",
        "Once you understand commands and events, you can build some surprisingly complex bots."
    ]
}


support_responses = {
    "stress": [
        "That sounds like a lot to handle. Try breaking the situation into one small task at a time.",
        "When everything feels overwhelming, it can help to pause and focus on what needs attention right now."
    ],

    "school": [
        "School can pile up quickly. Consider choosing one assignment to work on first instead of trying to solve everything at once.",
        "If school stress is getting difficult to manage, talking with a trusted person can make things feel less like something you have to handle alone."
    ],

    "sad": [
        "I'm sorry you're having a difficult moment. Taking a short break, doing something calming, or talking with someone you trust may help.",
        "You don't have to solve everything immediately. Give yourself some time and consider reaching out to someone you trust."
    ]
}


@bot.event
async def on_ready():
    print(f"Logged in as {bot.user}")


@bot.command()
async def hello(ctx):
    await ctx.send("Hello! I'm online.")


@bot.command()
async def story(ctx):
    user_id = ctx.author.id

    location = random.choice(story_locations)
    item = random.choice(story_items)
    event = random.choice(story_events)

    user_stories[user_id] = {
        "location": location,
        "item": item,
        "event": event
    }

    await ctx.send(
        f"You wake up in {location}.\n\n"
        f"Next to you is {item}.\n\n"
        f"{event}\n\n"
        "What do you do?"
    )


@bot.command()
async def choose(ctx, choice: str):
    user_id = ctx.author.id

    if user_id not in user_stories:
        await ctx.send(
            "You don't have an active story. Try `!story` first."
        )
        return

    choice = choice.lower()

    if choice == "left":
        response = (
            "You head left and discover a room filled with old maps. "
            "One of them has your name written on it."
        )

    elif choice == "right":
        response = (
            "You head right and find a staircase leading toward "
            "a strange blue light."
        )

    else:
        response = "Try choosing `left` or `right`."

    await ctx.send(response)


@bot.command()
async def chat(ctx, *, message: str):
    text = message.lower()

    for keyword, responses in chat_responses.items():
        if keyword in text:
            await ctx.send(random.choice(responses))
            return

    await ctx.send(
        "I'm still learning how to respond to that. "
        "Try talking to me about Python or Discord!"
    )


@bot.command()
async def support(ctx, *, message: str):
    text = message.lower()

    for keyword, responses in support_responses.items():
        if keyword in text:
            response = random.choice(responses)

            await ctx.send(
                f"{response}\n\n"
                "I'm a bot, not a therapist or medical professional. "
                "If you need personal support, consider talking with "
                "someone you trust."
            )
            return

    await ctx.send(
        "It sounds like something is bothering you. "
        "I can offer general wellness suggestions, but I'm not a therapist. "
        "If you need personal support, consider reaching out to someone you trust."
    )


@bot.command(name="help")
async def commands_help(ctx):
    await ctx.send(
        "**Available commands:**\n"
        "`!hello` - Say hello\n"
        "`!story` - Start a new story\n"
        "`!choose left` - Choose the left path\n"
        "`!choose right` - Choose the right path\n"
        "`!chat &lt;message&gt;` - Have a casual conversation\n"
        "`!support &lt;message&gt;` - Get general wellness support"
    )


@bot.event
async def on_command_error(ctx, error):
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send(
            "You're missing something. Try `!help` to see how the command works."
        )

    elif isinstance(error, commands.CommandNotFound):
        return

    else:
        print(f"Error: {error}")


bot.run(TOKEN)
</code></pre>
<p>This is enough to create a surprisingly capable beginner Discord project.</p>
<p>But there's an important limitation.</p>
<h2 id="heading-our-bot-doesnt-actually-remember-anything">Our Bot Doesn't Actually Remember Anything</h2>
<p>There's one small problem with our bot so far: it doesn't actually remember anything after it shuts down.</p>
<p>Right now, we're storing our story information in a Python dictionary:</p>
<pre><code class="language-python">user_stories = {}
</code></pre>
<p>This works while the bot is running. But if you stop the program and start it again, the dictionary starts empty.</p>
<p>To fix this, we need somewhere to permanently store our data. That's where a <strong>database</strong> comes in.</p>
<p>For this project, we'll use <strong>SQLite</strong>. SQLite is a lightweight database that stores information in a file on your computer. Python already includes SQLite through the built-in <code>sqlite3</code> module, so we don't need to install anything extra.</p>
<h3 id="heading-create-the-database">Create the Database</h3>
<p>First, add this import near the top of <code>bot.py</code>:</p>
<pre><code class="language-python">import sqlite3
</code></pre>
<p>Then create a connection to a database file:</p>
<pre><code class="language-python">db = sqlite3.connect("bot.db")
cursor = db.cursor()
</code></pre>
<p>The first line creates a database file called <code>bot.db</code> if one doesn't already exist. If the file already exists, SQLite simply opens it.</p>
<p>The second line creates a <strong>cursor</strong>. You can think of the cursor as the part of our Python program that lets us send instructions to the database.</p>
<p>Now we need to create a table where we can store our users' story information:</p>
<pre><code class="language-python">cursor.execute("""
    CREATE TABLE IF NOT EXISTS user_stories (
        user_id INTEGER PRIMARY KEY,
        location TEXT,
        item TEXT,
        event TEXT
    )
""")

db.commit()
</code></pre>
<p>Let's break this down.</p>
<p><code>cursor.execute()</code> tells SQLite to run the SQL command inside the parentheses.</p>
<p>The SQL command starts with:</p>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS user_stories
</code></pre>
<p>This tells SQLite to create a table called <code>user_stories</code>, but only if that table doesn't already exist.</p>
<p>Inside the parentheses, we define the information that each row can contain:</p>
<pre><code class="language-sql">user_id INTEGER PRIMARY KEY,
location TEXT,
item TEXT,
event TEXT
</code></pre>
<p><code>user_id</code> stores the Discord user's ID. We use it as the <code>PRIMARY KEY</code>, which means each user gets their own unique row.</p>
<p><code>location</code>, <code>item</code>, and <code>event</code> are all pieces of information about the user's current story.</p>
<p>Finally:</p>
<pre><code class="language-python">db.commit()
</code></pre>
<p>saves the changes to the database.</p>
<p>At this point, your project folder should contain a new file called:</p>
<pre><code class="language-text">bot.db
</code></pre>
<p>You don't need to open or edit this file manually. SQLite will manage it for us.</p>
<h3 id="heading-save-a-users-story">Save a User's Story</h3>
<p>Now let's actually put information into our database.</p>
<p>Suppose we have these variables:</p>
<pre><code class="language-python">user_id = ctx.author.id
location = "an abandoned castle"
item = "a mysterious key"
event = "a locked door"
</code></pre>
<p>We can save them using:</p>
<pre><code class="language-python">cursor.execute(
    """
    INSERT OR REPLACE INTO user_stories
    (user_id, location, item, event)
    VALUES (?, ?, ?, ?)
    """,
    (user_id, location, item, event)
)

db.commit()
</code></pre>
<p>The SQL statement tells SQLite to insert the information into the <code>user_stories</code> table.</p>
<p>The <code>?</code> symbols are placeholders for the actual values. The values are provided separately here:</p>
<pre><code class="language-python">(user_id, location, item, event)
</code></pre>
<p>This is safer than manually inserting values directly into the SQL string.</p>
<p><code>INSERT OR REPLACE</code> also means that if this user already has a saved story, their old story information can be replaced with the new information.</p>
<h3 id="heading-get-the-story-back">Get the Story Back</h3>
<p>Saving information is only half of the job. We also need to be able to retrieve it.</p>
<p>We can search the database for a user's story like this:</p>
<pre><code class="language-python">cursor.execute(
    """
    SELECT location, item, event
    FROM user_stories
    WHERE user_id = ?
    """,
    (user_id,)
)

story = cursor.fetchone()
</code></pre>
<p>This time, we're using <code>SELECT</code> to ask SQLite for information.</p>
<p>The <code>WHERE</code> part is important:</p>
<pre><code class="language-sql">WHERE user_id = ?
</code></pre>
<p>It tells SQLite to find the row belonging to this specific Discord user.</p>
<p>Then:</p>
<pre><code class="language-python">story = cursor.fetchone()
</code></pre>
<p>gets the first matching result.</p>
<p>If the user has a saved story, <code>story</code> will contain their information. If they don't, <code>story</code> will be <code>None</code>.</p>
<p>We can check for that:</p>
<pre><code class="language-python">if story:
    location, item, event = story

    await ctx.send(
        f"You're currently in {location}. "
        f"You have {item}, and you're facing {event}."
    )
else:
    await ctx.send("I don't have a saved story for you yet!")
</code></pre>
<p>Now the bot can retrieve information that was saved earlier, even after the Python program has been restarted.</p>
<h3 id="heading-put-it-into-a-command">Put It Into a Command</h3>
<p>We can turn this into a simple command that lets users check their saved story:</p>
<pre><code class="language-python">@bot.command()
async def status(ctx):
    user_id = ctx.author.id

    cursor.execute(
        """
        SELECT location, item, event
        FROM user_stories
        WHERE user_id = ?
        """,
        (user_id,)
    )

    story = cursor.fetchone()

    if story:
        location, item, event = story

        await ctx.send(
            f"You're currently in {location}. "
            f"You have {item}, and you're facing {event}."
        )
    else:
        await ctx.send(
            "You don't have a saved story yet. "
            "Start one with `!story`!"
        )
</code></pre>
<p>Now a user can type:</p>
<pre><code class="language-text">!status
</code></pre>
<p>and the bot can look up their story from the database.</p>
<p>This is a big improvement over our original dictionary. A dictionary only remembers information while the Python program is running. SQLite lets us save that information so it can still be there when the bot starts again.</p>
<p>For a larger bot, you could eventually store things like user preferences, story progress, inventory, conversation history, or other data. But for now, this simple database is enough to give our bot some real memory.</p>
<h2 id="heading-adding-real-ai-chat">Adding Real AI Chat</h2>
<p>Before we connect our bot to an AI model, let's quickly talk about <strong>Hugging Face</strong>.</p>
<p>If you've never used it before, Hugging Face is a platform where developers can find, share, and use machine learning models and datasets. Think of it as a huge community and library for AI tools.</p>
<p>Hugging Face also provides tools that let Python programs communicate with these models without having to build and train an AI model from scratch.</p>
<p>For our bot, we'll use Hugging Face's <strong>Inference Providers</strong> to send a user's message to a supported language model and receive its response.</p>
<p>We won't be training an AI model ourselves. Instead, we'll use an existing model and connect it to our Discord bot through Python.</p>
<p>Now that we know what Hugging Face is, let's connect it to our bot.</p>
<h3 id="heading-install-the-hugging-face-library">Install the Hugging Face Library</h3>
<p>First, install <code>huggingface_hub</code>:</p>
<pre><code class="language-bash">pip install -U huggingface_hub
</code></pre>
<p>We already installed <code>python-dotenv</code>, so we can use the same <code>.env</code> file from earlier to keep our Hugging Face token out of the source code.</p>
<p>Add your Hugging Face token to <code>.env</code>:</p>
<pre><code class="language-text">DISCORD_TOKEN=YOUR_BOT_TOKEN_HERE
HF_TOKEN=YOUR_HUGGING_FACE_TOKEN_HERE
</code></pre>
<p>Replace <code>YOUR_HUGGING_FACE_TOKEN_HERE</code> with your actual Hugging Face access token.</p>
<p>Just like your Discord bot token, <strong>don't share this token or upload it to GitHub</strong>.</p>
<h3 id="heading-create-the-hugging-face-client">Create the Hugging Face Client</h3>
<p>Now add this import near the top of <code>bot.py</code>:</p>
<pre><code class="language-python">from huggingface_hub import InferenceClient
</code></pre>
<p>Then load the token:</p>
<pre><code class="language-python">HF_TOKEN = os.getenv("HF_TOKEN")

if not HF_TOKEN:
    raise RuntimeError("HF_TOKEN is not set.")
</code></pre>
<p>The first line gets the token from our environment variables. The <code>if</code> statement checks whether the token actually exists. If it doesn't, Python stops and gives us a useful error instead of letting the program fail later in a confusing way.</p>
<p>Now create the Hugging Face client:</p>
<pre><code class="language-python">client = InferenceClient(
    api_key=HF_TOKEN
)
</code></pre>
<p>The <code>InferenceClient</code> is what our Python program will use to communicate with Hugging Face's inference service.</p>
<h3 id="heading-connect-the-ai-model-to-the-bot">Connect the AI Model to the Bot</h3>
<p>Now we can replace our previous keyword-based <code>!chat</code> command with one that sends the user's message to a language model.</p>
<pre><code class="language-python">@bot.command()
async def chat(ctx, *, message: str):
    try:
        response = client.chat_completion(
            model="YOUR_SUPPORTED_MODEL_ID",
            messages=[
                {
                    "role": "system",
                    "content": (
                        "You are a friendly Discord bot. "
                        "Keep responses helpful, concise, and conversational."
                    )
                },
                {
                    "role": "user",
                    "content": message
                }
            ],
            max_tokens=200
        )

        answer = response.choices[0].message.content

        await ctx.send(answer)

    except Exception as error:
        print(f"AI error: {error}")
        await ctx.send(
            "I couldn't generate a response right now. "
            "Please try again later."
        )
</code></pre>
<p>There's quite a bit happening here, so let's walk through it.</p>
<p>We start with the same command structure we've already used:</p>
<pre><code class="language-python">@bot.command()
async def chat(ctx, *, message: str):
</code></pre>
<p>This creates our <code>!chat</code> command and stores everything the user types after it in <code>message</code>.</p>
<p>For example:</p>
<pre><code class="language-text">!chat What is Python?
</code></pre>
<p>gives us:</p>
<pre><code class="language-python">message = "What is Python?"
</code></pre>
<p>Next, we use:</p>
<pre><code class="language-python">try:
</code></pre>
<p>This tells Python that we're about to run code that could potentially fail. Since we're communicating with an external service, things like an unavailable model, an invalid token, or a temporary connection problem can happen.</p>
<p>Now we call:</p>
<pre><code class="language-python">response = client.chat_completion(
</code></pre>
<p>This sends a chat-completion request to the model through Hugging Face. The <code>messages</code> parameter contains the conversation we want the model to respond to.</p>
<p>The first message has the role <code>"system"</code>:</p>
<pre><code class="language-python">{
    "role": "system",
    "content": (
        "You are a friendly Discord bot. "
        "Keep responses helpful, concise, and conversational."
    )
}
</code></pre>
<p>The system message gives the model instructions about how it should respond.</p>
<p>Then we provide the user's actual message:</p>
<pre><code class="language-python">{
    "role": "user",
    "content": message
}
</code></pre>
<p>If the user typed:</p>
<pre><code class="language-text">!chat What is Python?
</code></pre>
<p>then <code>message</code> contains:</p>
<pre><code class="language-text">What is Python?
</code></pre>
<p>So the model receives that as the user's input.</p>
<p>We also have:</p>
<pre><code class="language-python">max_tokens=200
</code></pre>
<p>This limits how much text the model can generate for one response. Keeping responses relatively short works well for Discord because huge blocks of text aren't always very pleasant to read in a chat channel.</p>
<p>You also need to replace:</p>
<pre><code class="language-python">model="YOUR_SUPPORTED_MODEL_ID"
</code></pre>
<p>with the ID of a model currently available through the Hugging Face Inference Providers you are using. Hugging Face's documentation shows that <code>InferenceClient</code> can use a model ID hosted on the Hugging Face Hub for chat completion.</p>
<p>Once the request is complete, we need to get the actual text from the response:</p>
<pre><code class="language-python">answer = response.choices[0].message.content
</code></pre>
<p>The response contains information about the model's output. <code>choices[0]</code> gets the first generated response, and <code>.message.content</code> gives us the actual text.</p>
<p>Then we send it to Discord:</p>
<pre><code class="language-python">await ctx.send(answer)
</code></pre>
<p>So the whole process looks like this:</p>
<pre><code class="language-text">User types !chat
        ↓
Discord sends the command to our bot
        ↓
Python gets the user's message
        ↓
Hugging Face receives the message
        ↓
The AI model generates a response
        ↓
Python gets the generated text
        ↓
The bot sends it back to Discord
</code></pre>
<h3 id="heading-handle-ai-errors">Handle AI Errors</h3>
<p>The last part of our command is:</p>
<pre><code class="language-python">except Exception as error:
    print(f"AI error: {error}")
    await ctx.send(
        "I couldn't generate a response right now. "
        "Please try again later."
    )
</code></pre>
<p>If something goes wrong inside the <code>try</code> block, Python jumps to the <code>except</code> block instead of crashing the entire bot.</p>
<p>The error is printed in the terminal so you can investigate what happened:</p>
<pre><code class="language-python">print(f"AI error: {error}")
</code></pre>
<p>Meanwhile, the Discord user gets a simple message:</p>
<pre><code class="language-text">I couldn't generate a response right now. Please try again later.
</code></pre>
<p>This is much better than letting an API error take down the whole bot.</p>
<p>At this point, you have a real AI-powered <code>!chat</code> command. You can type something like:</p>
<pre><code class="language-text">!chat Tell me an interesting fact about space.
</code></pre>
<p>and the model can generate a response instead of choosing from a small list of pre-written messages.</p>
<p>One thing to remember is that this bot is sending user messages to an external AI service. Don't automatically send private or sensitive conversations to an AI provider. If you make this bot available to other people, be clear about what information it processes and avoid storing or sending more data than the bot actually needs.</p>
<p>You can also combine this AI system with the SQLite database from earlier. For example, you could save a limited amount of conversation history and send relevant previous messages along with a new message. That would allow the bot to keep some context between messages instead of treating every message as a completely new conversation.</p>
<h2 id="heading-how-do-we-keep-the-bot-online">How Do We Keep the Bot Online?</h2>
<p>Here's where the phrase "online forever" needs a little clarification.</p>
<p>There are two different situations.</p>
<h3 id="heading-option-1-run-it-on-your-computer">Option 1: Run It on Your Computer</h3>
<p>When you run:</p>
<pre><code class="language-bash">python bot.py
</code></pre>
<p>the bot stays online while that program is running.</p>
<p>Close the terminal?</p>
<p>Bot goes offline.</p>
<p>Turn off the computer?</p>
<p>Bot goes offline.</p>
<p>Lose internet?</p>
<p>Bot goes offline.</p>
<p>This is perfect for development but it's not a 24/7 production setup.</p>
<h3 id="heading-option-2-host-it-on-a-server">Option 2: Host It on a Server</h3>
<p>For a bot that should stay online while your computer is off, you need a computer somewhere that stays available.</p>
<p>That computer can be a cloud server.</p>
<p>You upload your project, install the dependencies, add your environment variables, and start:</p>
<pre><code class="language-bash">python bot.py
</code></pre>
<p>Now the cloud machine runs the program instead of your laptop.</p>
<p>Services designed for continuously running workloads can be used for this kind of application. For example, Render currently provides a <strong>Background Worker</strong> service type for continuously running processes that don't need to receive incoming web traffic.</p>
<p>But you should check the provider's current pricing and service limitations before deploying. Free hosting tiers aren't necessarily designed for an always-on Discord bot, and a "free forever" 24/7 setup isn't something you should assume a hosting platform will provide.</p>
<h2 id="heading-what-forever-actually-means">What "Forever" Actually Means</h2>
<p>There isn't really a magical:</p>
<pre><code class="language-text">ONLINE_FOREVER = True
</code></pre>
<p>setting.</p>
<p>A bot can stay online continuously only as long as the computer or server running it continues operating.</p>
<p>Even a professionally hosted bot can go offline because of:</p>
<ul>
<li><p>Server maintenance</p>
</li>
<li><p>Deployments</p>
</li>
<li><p>Bugs</p>
</li>
<li><p>Network problems</p>
</li>
<li><p>Provider outages</p>
</li>
<li><p>Invalid credentials</p>
</li>
<li><p>API changes</p>
</li>
<li><p>Billing or account issues</p>
</li>
</ul>
<p>So the realistic goal is to keep the bot running automatically and restart it when something goes wrong.</p>
<p>That is what production hosting is designed to help with.</p>
<p>If your provider supports automatic restarts, enable them.</p>
<p>You can also make your Python code fail clearly when an important environment variable is missing:</p>
<pre><code class="language-python">if not TOKEN:
    raise RuntimeError("DISCORD_TOKEN is not set.")
</code></pre>
<p>A clear error is much easier to debug than a mysterious bot that simply doesn't appear online.</p>
<h2 id="heading-dont-try-to-keep-it-awake-with-random-tricks">Don't Try to "Keep It Awake" With Random Tricks</h2>
<p>You may find tutorials suggesting that you deploy a web server and repeatedly ping it from another service to prevent a free hosting instance from sleeping.</p>
<p>Be careful with that approach.</p>
<p>Hosting providers change their free-tier rules, and attempting to work around those limits can violate their terms.</p>
<p>If you need an actually persistent bot, use a hosting option that explicitly supports the workload.</p>
<p>For example, a background worker is designed for continuously running processes. That's much cleaner than trying to convince a web service that your Discord bot is secretly a website.</p>
<h2 id="heading-additional-features-and-where-to-go-next"><strong>Additional Featur</strong>es and Where to Go Next</h2>
<p>Now that you have a working Discord bot, there are plenty of directions you can take the project next.</p>
<p>You could turn the storytelling system into a more complete game by adding an inventory, multiple chapters, puzzles, or different endings. You could also replace text-based commands with Discord slash commands and buttons to make the bot easier to interact with.</p>
<p>If you're interested in AI, you could expand the chat system by giving the bot different personalities, adding carefully limited conversation context, or using AI to generate parts of the stories.</p>
<p>You could also add moderation features, daily story prompts, or other commands that fit the kind of Discord community you're building.</p>
<p>These are ideas for extending the project rather than features we'll build step by step in this tutorial. The important thing is that you now have the foundation to experiment with them yourself.</p>
<p>Start with one small feature, figure out how it works, and build from there. You don't need to turn the bot into a massive project all at once.</p>
<p>The more you experiment with the code, the more you'll start seeing how Python, Discord, databases, and AI can work together in a real application.</p>
<h2 id="heading-test-everything-locally-first">Test Everything Locally First</h2>
<p>Before deploying, test:</p>
<pre><code class="language-text">!hello
!story
!choose left
!choose right
!chat hello
!chat I want to learn Python
!support I'm stressed
!help
</code></pre>
<p>Then test weird inputs:</p>
<pre><code class="language-text">!choose banana
!chat
!support
!unknowncommand
</code></pre>
<p>You want to discover bugs while you're sitting in front of your computer, not three days later when someone tells you:</p>
<blockquote>
<p>"Your bot has been broken since Tuesday."</p>
</blockquote>
<h2 id="heading-deploying-the-bot">Deploying the Bot</h2>
<p>First, make sure your project contains:</p>
<pre><code class="language-text">discord-story-bot/
│
├── bot.py
├── requirements.txt
├── .gitignore
└── .python-version
</code></pre>
<p>A <code>.python-version</code> file can contain something like:</p>
<pre><code class="language-text">3.13
</code></pre>
<p>Using a version file makes your deployment environment more predictable. Render currently supports specifying a Python version through <code>.python-version</code> or an environment variable.</p>
<p>Your <code>requirements.txt</code> should contain your dependencies.</p>
<p>For example:</p>
<pre><code class="language-text">discord.py
python-dotenv
</code></pre>
<p>For deployment, you generally don't need the local <code>.env</code> file.</p>
<p>Instead, add:</p>
<pre><code class="language-text">DISCORD_TOKEN
</code></pre>
<p>as an environment variable in your hosting provider's dashboard.</p>
<p>That way the secret isn't stored inside your repository.</p>
<h3 id="heading-the-start-command">The Start Command</h3>
<p>Your deployment service needs to know what to run.</p>
<p>For this project, the start command is:</p>
<pre><code class="language-bash">python bot.py
</code></pre>
<p>The important thing is that the process doesn't immediately exit.</p>
<p>A Discord bot stays alive because <code>bot.run(TOKEN)</code> starts the Discord connection and keeps the program running.</p>
<p>If your hosting service supports background workers, that's a natural fit for a bot like this because the bot doesn't need to serve normal HTTP requests. Render specifically describes background workers as continuously running services that don't receive incoming network traffic.</p>
<h2 id="heading-remember-keep-your-secrets-secret">Remember: Keep Your Secrets Secret</h2>
<p>This is worth repeating because it causes a lot of beginner projects to get compromised.</p>
<p>Never commit this:</p>
<pre><code class="language-python">bot.run("YOUR_REAL_TOKEN")
</code></pre>
<p>Never upload:</p>
<pre><code class="language-text">.env
</code></pre>
<p>Never paste your actual token into a public GitHub issue.</p>
<p>If a token accidentally becomes public, treat it as compromised and regenerate it.</p>
<p>Environment variables are your friend.</p>
<h2 id="heading-what-you-learned">What You Learned</h2>
<p>You've now built a Discord bot that demonstrates several real programming concepts.</p>
<p>You learned how to:</p>
<ul>
<li><p>Create a Discord application</p>
</li>
<li><p>Connect Python to Discord</p>
</li>
<li><p>Use <code>discord.py</code></p>
</li>
<li><p>Configure Gateway Intents</p>
</li>
<li><p>Create commands</p>
</li>
<li><p>Use asynchronous functions</p>
</li>
<li><p>Read command arguments</p>
</li>
<li><p>Generate random stories</p>
</li>
<li><p>Store temporary user state</p>
</li>
<li><p>Create a basic chat system</p>
</li>
<li><p>Create a mental-wellness support feature</p>
</li>
<li><p>Handle command errors</p>
</li>
<li><p>Keep secrets out of source code</p>
</li>
<li><p>Prepare a project for deployment</p>
</li>
<li><p>Think about persistent hosting</p>
</li>
</ul>
<p>And underneath all those features, the architecture is still surprisingly simple:</p>
<pre><code class="language-text">User sends command
        ↓
Discord receives message
        ↓
discord.py receives event
        ↓
Python function runs
        ↓
Bot generates response
        ↓
Discord displays response
</code></pre>
<p>You don't need thousands of lines of code to get started.</p>
<p>You need a clear idea, a few Python concepts, and the willingness to keep debugging when something inevitably breaks.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>The coolest part of this project isn't really the Discord bot. It's what the project teaches you.</p>
<p>And once you understand the pieces, you can reuse the same ideas in countless projects.</p>
<p>A Discord bot can become a game, which could become a web application, which could also become a larger software project.</p>
<p>And suddenly you're not just learning Python syntax anymore. You're learning how software actually gets built, one command at a time.</p>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
