Build a Simple Chatbot with Python

Published on: July 10, 2026
Reading time: 5 minutes
Chatbot simples em Python

A simple Python chatbot is an excellent beginner project because it combines input, strings, conditions, loops, functions, dictionaries, files, and testing in one practical application. The bot in this guide runs in the terminal and responds through explicit rules. It does not require a web service or a machine-learning model.

Rule-based chatbots are useful when the possible questions are limited and predictable, such as a help menu, study assistant, product information tool, or frequently asked questions interface. They also provide a clear foundation for understanding more advanced conversational systems.

How a rule-based chatbot works

The program follows a repeated process:

  1. Read a message from the user.
  2. Normalize the text.
  3. Identify a matching intent or keyword.
  4. Select a suitable reply.
  5. Display the reply.
  6. Continue until the user asks to leave.

This is an algorithm made of input, processing, output, and repetition. The concepts are covered in the programming logic guide and the article explaining algorithms in programming.

Create the project

Create a folder and a file named chatbot.py. A virtual environment is optional for this first version because it uses only the standard library, but an environment becomes useful when external packages are added. See the Python venv guide for setup instructions.

Build the smallest conversation loop

print("Bot: Hello! Type 'bye' to finish.")

while True:
    message = input("You: ")

    if message.lower() == "bye":
        print("Bot: Goodbye!")
        break

    print("Bot: I am still learning.")

The while True loop continues until break runs. The broader Python loops guide explains loop conditions, break, and continue.

Normalize user input

Users may type different capitalization and extra spaces. Normalize the text before matching it:

def normalize(text: str) -> str:
    return " ".join(text.strip().lower().split())

This function removes leading and trailing spaces, converts text to lowercase, and replaces repeated whitespace with single spaces.

print(normalize("  HELLO   BOT  "))  # hello bot

String operations are fundamental to conversational programs. Our Python slicing guide and related string material provide additional manipulation patterns.

Add exact response rules

def get_response(message: str) -> str:
    normalized = normalize(message)

    if normalized in {"hello", "hi", "hey"}:
        return "Hello! How can I help?"
    if normalized == "how are you":
        return "I am running normally. Thanks for asking!"
    if normalized in {"bye", "goodbye", "exit"}:
        return "Goodbye!"

    return "I did not understand that message."

A set is useful for groups of equivalent phrases because membership checks are clear and efficient. Learn more in the Python sets guide.

Connect the function to the loop

EXIT_MESSAGES = {"bye", "goodbye", "exit"}


def main() -> None:
    print("Bot: Hello! Type 'bye' to finish.")

    while True:
        message = input("You: ")
        normalized = normalize(message)
        response = get_response(message)
        print(f"Bot: {response}")

        if normalized in EXIT_MESSAGES:
            break


if __name__ == "__main__":
    main()

The if __name__ == "__main__" guard lets another file import the functions without immediately starting an interactive conversation.

Match keywords instead of full sentences

Exact matching becomes restrictive when users write longer phrases. Add keyword-based rules:

def get_response(message: str) -> str:
    normalized = normalize(message)

    if normalized in {"hello", "hi", "hey"}:
        return "Hello! How can I help?"
    if "hours" in normalized or "open" in normalized:
        return "We are available Monday through Friday, 9 AM to 6 PM."
    if "price" in normalized or "cost" in normalized:
        return "Tell me which product you want to know about."
    if "course" in normalized:
        return "We offer beginner and intermediate programming courses."
    if normalized in EXIT_MESSAGES:
        return "Goodbye!"

    return "I did not understand. Try asking about hours, prices, or courses."

Keyword matching is easy to understand but can produce false matches. The word “open” may refer to opening a file rather than business hours. As rules grow, organize them by intent and use more precise conditions.

Represent intents with dictionaries

INTENTS = {
    "greeting": {
        "keywords": {"hello", "hi", "hey"},
        "responses": [
            "Hello! How can I help?",
            "Hi! What would you like to know?",
        ],
    },
    "hours": {
        "keywords": {"hours", "open", "closing"},
        "responses": [
            "We are available Monday through Friday, 9 AM to 6 PM."
        ],
    },
    "pricing": {
        "keywords": {"price", "cost", "pricing"},
        "responses": [
            "Tell me which product or plan you want to check."
        ],
    },
}

Dictionaries group related values under meaningful keys. The Python collections comparison explains when to use dictionaries, lists, sets, and tuples.

Detect an intent

def detect_intent(message: str) -> str | None:
    words = set(normalize(message).split())

    for intent_name, intent_data in INTENTS.items():
        if words & intent_data["keywords"]:
            return intent_name

    return None

The set intersection operator & returns words present in both sets. If the intersection is not empty, at least one keyword matched.

Select varied replies

import random


def get_response(message: str) -> str:
    normalized = normalize(message)

    if normalized in EXIT_MESSAGES:
        return "Goodbye!"

    intent = detect_intent(message)
    if intent is None:
        return "I did not understand. Try asking about hours or prices."

    responses = INTENTS[intent]["responses"]
    return random.choice(responses)

Random alternatives make repeated conversations less mechanical. Do not use randomness for critical information such as prices, legal notices, or account status unless every alternative is equivalent.

Load intents from JSON

Separating content from code allows non-programmers to edit responses. Create intents.json:

{
  "greeting": {
    "keywords": ["hello", "hi", "hey"],
    "responses": [
      "Hello! How can I help?",
      "Hi! What would you like to know?"
    ]
  },
  "hours": {
    "keywords": ["hours", "open", "closing"],
    "responses": [
      "We are available Monday through Friday, 9 AM to 6 PM."
    ]
  }
}

Load and validate it:

import json
from pathlib import Path


def load_intents(path: str | Path) -> dict:
    file_path = Path(path)

    with file_path.open("r", encoding="utf-8") as file:
        data = json.load(file)

    if not isinstance(data, dict):
        raise ValueError("intent data must be a JSON object")

    for name, intent in data.items():
        if "keywords" not in intent or "responses" not in intent:
            raise ValueError(f"invalid intent: {name}")
        intent["keywords"] = set(intent["keywords"])

    return data

The official Python JSON documentation describes encoding and decoding. The pathlib guide explains file paths and directory handling.

Create a complete version

import json
import random
from pathlib import Path

EXIT_MESSAGES = {"bye", "goodbye", "exit"}


def normalize(text: str) -> str:
    return " ".join(text.strip().lower().split())


def load_intents(path: str | Path) -> dict:
    with Path(path).open("r", encoding="utf-8") as file:
        data = json.load(file)

    for name, intent in data.items():
        if not intent.get("keywords") or not intent.get("responses"):
            raise ValueError(f"intent {name!r} is incomplete")
        intent["keywords"] = set(intent["keywords"])

    return data


def detect_intent(message: str, intents: dict) -> str | None:
    words = set(normalize(message).split())

    for intent_name, intent_data in intents.items():
        if words & intent_data["keywords"]:
            return intent_name

    return None


def get_response(message: str, intents: dict) -> str:
    normalized = normalize(message)

    if normalized in EXIT_MESSAGES:
        return "Goodbye!"

    intent = detect_intent(message, intents)
    if intent is None:
        return "I did not understand. Please try another question."

    return random.choice(intents[intent]["responses"])


def main() -> None:
    intents = load_intents("intents.json")
    print("Bot: Hello! Type 'bye' to finish.")

    while True:
        message = input("You: ")
        response = get_response(message, intents)
        print(f"Bot: {response}")

        if normalize(message) in EXIT_MESSAGES:
            break


if __name__ == "__main__":
    main()

Handle file and JSON errors

A friendly command-line program can report expected configuration problems:

def main() -> None:
    try:
        intents = load_intents("intents.json")
    except FileNotFoundError:
        print("Bot configuration file was not found.")
        return
    except (json.JSONDecodeError, ValueError) as error:
        print(f"Invalid bot configuration: {error}")
        return

    # Start the conversation here.

Catch specific exceptions and preserve enough information to correct the problem. Our try and except guide explains this practice.

Remember a user’s name

Add a small state dictionary:

def main() -> None:
    intents = load_intents("intents.json")
    state = {"name": None}

    while True:
        message = input("You: ")
        normalized = normalize(message)

        if normalized.startswith("my name is "):
            state["name"] = message.strip()[11:].strip().title()
            print(f"Bot: Nice to meet you, {state['name']}!")
            continue

        response = get_response(message, intents)
        if state["name"] and normalized in {"hello", "hi", "hey"}:
            response = f"Hello, {state['name']}!"

        print(f"Bot: {response}")

        if normalized in EXIT_MESSAGES:
            break

This state exists only while the program runs. Durable memory requires a file or database and raises privacy questions about what should be stored and for how long.

Test the chatbot logic

Keep input/output outside the core functions so they can be tested:

def test_normalize_removes_extra_spaces():
    assert normalize("  HELLO   BOT ") == "hello bot"


def test_exit_message_returns_goodbye():
    assert get_response("bye", {}) == "Goodbye!"


def test_detect_intent_matches_keyword():
    intents = {
        "hours": {
            "keywords": {"hours", "open"},
            "responses": ["Open weekdays"],
        }
    }
    assert detect_intent("What are your hours?", intents) == "hours"

Use Pytest to automate these checks. For deterministic tests, avoid asserting one particular random response; instead, verify that the result belongs to the allowed response list.

Improve matching carefully

Possible extensions include:

  • remove punctuation before tokenization;
  • assign priority to specific intents;
  • match multiword phrases before individual words;
  • use regular expressions for structured values such as order numbers;
  • add a confidence score based on the number of matching keywords;
  • ask a clarification question when two intents tie.

Each improvement should have tests. A more complex matching rule is not useful if it creates unpredictable replies.

Turn the chatbot into another interface

The functions in this guide are independent of the terminal. The same response engine can later power:

  • a web page built with Flask or Django;
  • a desktop interface;
  • a REST API;
  • a Telegram bot;
  • a help widget inside another application.

Keep interface-specific code separate from intent detection and responses. This separation reduces duplication.

Limitations of a rule-based chatbot

A keyword bot does not truly understand language. It can miss synonyms, context, spelling variations, negation, and references to earlier messages. It also needs manual updates when topics change. These limitations are acceptable when the scope is narrow and responses must remain predictable.

For customer-facing use, clearly disclose that the user is interacting with an automated system and provide a path to human support when the bot cannot solve the issue.

Common beginner mistakes

  • One enormous chain of conditions: separate normalization, detection, and response selection into functions.
  • No fallback response: every unmatched message needs a safe answer.
  • Case-sensitive matching: normalize input first.
  • Using substring checks for very short words: token sets reduce accidental matches.
  • Mixing content with code: JSON or another structured format makes responses easier to maintain.
  • No exit rule: provide an obvious way to end the conversation.
  • No tests: a new intent can accidentally capture messages meant for another rule.

Conclusion

A simple Python chatbot brings together core programming skills in a project that produces immediate feedback. Start with a conversation loop and exact rules, then add normalization, intent dictionaries, random alternatives, JSON configuration, state, and tests. Keep the response engine separate from the interface so the same logic can later run in a web app, messaging bot, or desktop program.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Criptografia e segurança de dados em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Secure Password Generator in Python

    Build a secure password generator in Python with secrets, configurable character rules, a CLI, passphrases, validation, and practical tests.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Jogo da forca para iniciantes desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Hangman Game in Python

    Build a complete Hangman game in Python with random words, input validation, repeated-letter checks, lives, ASCII art, and replay support.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Quiz interativo no terminal desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Terminal Quiz Game in Python

    Build a terminal quiz game in Python with questions, input validation, scoring, shuffled answers, replay support, JSON loading, and tests.

    Ler mais

    Tempo de leitura: 4 minutos
    10/07/2026
    Jogo de adivinhação de números desenvolvido com Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Build a Number Guessing Game in Python

    Build a number guessing game in Python with random numbers, input validation, hints, limited attempts, replay support, and clean functions.

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Desenhos para iniciantes usando Turtle em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Python Turtle Drawing: Complete Beginner Guide

    Learn Python Turtle drawing from scratch: move the turtle, draw shapes, use colors, handle keys, and create a complete geometric

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026
    Criação de jogos para iniciantes usando Pygame em Python
    Projects
    Foto de perfil de Leandro Hirt da Academify

    Pygame for Beginners: Build Your First Game

    Learn Pygame from scratch: install it, create a window, handle events, move a player, detect collisions, add scoring, and build

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026