Learning Goals
3 min- Build keyword/regex intent matching.
- Map intents to scripted responses (with variety).
- Add a small "memory" (the user's name, last topic).
- Understand the limits — and why LLMs (next lessons) are different.
Warm-Up · ELIZA, 1966
5 minUser: I am feeling sad Bot: Why do you say you are feeling sad? User: my homework is hard Bot: Tell me more about your homework.
ELIZA, the first famous chatbot, used pure pattern-matching — reflecting your words back. No understanding at all, yet people felt heard. That's the power (and danger) of scripted bots.
A rule bot matches input to an intent, then returns a scripted response. It's reliable and free for narrow domains (FAQ, menus). It has zero understanding — anything outside its rules fails. That's the gap LLMs fill.
New Concept · Intents & Responses
14 minIntent matching with keywords
import re, random INTENTS = { "greeting": (["hello", "hi", "hey", "yo"], ["Hi there!", "Hello! How can I help?"]), "bye": (["bye", "goodbye", "see you"], ["Goodbye!", "See you later!"]), "hours": (["open", "hours", "time"], ["We're open 9am-6pm, Mon-Sat."]), "thanks": (["thanks", "thank you"], ["You're welcome!", "Anytime!"]), } def detect_intent(text): text = text.lower() for intent, (keywords, _) in INTENTS.items(): if any(re.search(rf"\b{k}\b", text) for k in keywords): return intent return None
Respond
def respond(text): intent = detect_intent(text) if intent: return random.choice(INTENTS[intent][1]) return "Sorry, I didn't understand. Try asking about our hours."
The chat loop
while True: user = input("You: ") if user.lower() in ("quit", "exit"): break print("Bot:", respond(user))
Regex for richer patterns
# capture a name: "my name is Aisyah" m = re.search(r"my name is (\w+)", text.lower()) if m: name = m.group(1).title() return f"Nice to meet you, {name}!"
The hard limit
Add a rule for every phrasing and you'll never finish — language has infinite variety. Rule bots break the moment a user phrases something unexpectedly. That brittleness is exactly why neural LLMs took over open-ended conversation.
Worked Example · A Café FAQ Bot with Memory
12 min# cafe_bot.py — rule-based FAQ bot with a little memory import re, random INTENTS = { "greeting": (["hello", "hi", "hey"], ["Hi! Welcome to Kopi Corner ☕", "Hello there!"]), "menu": (["menu", "drinks", "coffee", "food"], ["We serve kopi, teh tarik, toast and nasi lemak."]), "hours": (["open", "hours", "close", "time"], ["Open daily 7am-7pm."]), "location": (["where", "located", "address"], ["We're at 12 Jalan Mawar, KL."]), "thanks": (["thanks", "thank"], ["You're welcome! 😊"]), "bye": (["bye", "goodbye"], ["Jumpa lagi! 👋"]), } memory = {} def respond(text): low = text.lower() # memory: capture name m = re.search(r"my name is (\w+)", low) if m: memory["name"] = m.group(1).title() return f"Nice to meet you, {memory['name']}!" for intent, (keys, replies) in INTENTS.items(): if any(re.search(rf"\b{k}\b", low) for k in keys): reply = random.choice(replies) if "name" in memory and intent == "greeting": reply = f"{reply} Good to see you again, {memory['name']}!" return reply return "I'm just a simple bot — try asking about the menu, hours, or location." if __name__ == "__main__": print("Bot: Hi! Ask me about Kopi Corner. (type 'bye' to leave)") while True: user = input("You: ").strip() reply = respond(user) print("Bot:", reply) if "bye" in user.lower(): break
Sample chat
You: hi, my name is Aisyah Bot: Nice to meet you, Aisyah! You: what's on the menu? Bot: We serve kopi, teh tarik, toast and nasi lemak. You: when do you close? Bot: Open daily 7am-7pm. You: tell me a joke Bot: I'm just a simple bot — try asking about the menu, hours, or location.
Read the diff
Intents + scripted replies + a tiny memory dict = a usable FAQ bot, no API, no cost. But watch the last line: "tell me a joke" falls through to the fallback. A rule bot can only do what you scripted. Tomorrow we connect a real LLM that handles anything.
Try It Yourself
13 minAdd intents for "wifi password", "parking", and "halal". Test them.
Add a fallback that reflects the user's words: "I feel X" → "Why do you feel X?". Use regex capture.
Hint
m = re.search(r"i (?:feel|am) (.+)", low) if m: return f"Why do you feel {m.group(1)}?"
Replace keyword matching with a TF-IDF + nearest-example approach: store example phrases per intent, vectorise the user input, pick the closest intent by cosine similarity. More robust to phrasing.
Mini-Challenge · Quiz Bot
8 minBuild a bot that runs a 5-question quiz: it asks, matches the user's answer (allowing for case/whitespace/synonyms), tracks score, and gives feedback. Pure rules — no LLM.
Recap
3 minA rule bot detects an intent (keywords/regex/TF-IDF) and returns a scripted reply, optionally with a little memory. It's free, fast, and reliable for narrow domains — and brittle outside them. That brittleness is the whole reason LLMs exist. From the next lesson, we let a real language model handle open-ended conversation.
Vocabulary Card
- intent
- The user's goal behind a message (greeting, ask hours, etc.).
- rule-based
- Behaviour from hand-written patterns, not learned from data.
- fallback
- The default reply when no intent matches.
- brittleness
- Failing on inputs outside the scripted rules — the core limit of rule bots.
Homework
4 minBuild a rule-based bot for a domain you know (your school, a game, a hobby) with at least 6 intents, memory of the user's name, and an ELIZA-style fallback. Then write 3 example messages it FAILS on — and note what an LLM would need to handle them.
Adapt cafe_bot.py to your domain. The "failure cases" section is the point — it motivates why we move to LLMs next.