2.2 Building Rule-Based Chatbots
After exploring the theoretical challenges of symbolicism, in this section we will intuitively experience how rule-based systems work through a specific programming practice. We will attempt to reproduce ELIZA, an extremely influential early chatbot in the history of artificial intelligence.
2.2.1 ELIZA's Design Philosophy
ELIZA was a computer program released in 1966 by MIT computer scientist Joseph Weizenbaum<sup>[6]</sup>, one of the famous early attempts in the field of natural language processing. ELIZA was not a single program but a framework that could execute different "scripts." Among them, the most widely known and successful script was "DOCTOR," which imitated a Rogerian non-directive psychotherapist.
ELIZA's working method was extremely clever: it never directly answered questions or provided information but identified keywords in user input, then applied a set of preset transformation rules to convert user statements into open-ended questions. For example, when a user said "I am sad about my boyfriend," ELIZA might identify the keyword "I am sad about..." and apply a rule to generate the response: "Why are you sad about your boyfriend?"
Weizenbaum's design philosophy was not to create an agent that could truly "understand" human emotions; on the contrary, he wanted to prove that through some simple sentence transformation techniques, machines could create an illusion of "intelligence" and "empathy" without understanding the conversation content at all. However, to his surprise, many people who interacted with ELIZA (including his secretary) developed emotional dependence on it, deeply believing it could understand them.
The practical goal of this section is to reproduce ELIZA's core mechanism to deeply understand the advantages and fundamental limitations of this rule-driven approach.
2.2.2 Pattern Matching and Text Substitution
ELIZA's algorithm flow is based on Pattern Matching and Text Substitution, which can be clearly decomposed into the following four steps:
- Keyword Identification and Ranking: The rule base sets a priority for each keyword (such as
mother,dreamed,depressed). When input contains multiple keywords, the program selects the rule corresponding to the keyword with the highest priority for processing. - Decomposition Rules: After finding a keyword, the program uses decomposition rules with wildcards (
*) to capture the rest of the sentence.- Rule Example:
* my * - User Input:
"My mother is afraid of me" - Capture Result:
["", "mother is afraid of me"]
- Rule Example:
- Reassembly Rules: The program selects one from a set of reassembly rules associated with the decomposition rule to generate a response (usually randomly selected to increase diversity), and optionally uses the content captured in the previous step.
- Rule Example:
"Tell me more about your family." - Generated Output:
"Tell me more about your family."
- Rule Example:
- Pronoun Conversion: Before reassembly, the program performs simple pronoun conversion (such as
I→you,my→your) to maintain conversation coherence.
The entire workflow can be represented by a simple pseudocode idea:
FUNCTION generate_response(user_input):
// 1. Split user input into words
words = SPLIT(user_input)
// 2. Find the highest priority keyword rule
best_rule = FIND_BEST_RULE(words)
IF best_rule is NULL:
RETURN a_generic_response() // For example: "Please go on."
// 3. Use rule to decompose user input
decomposed_parts = DECOMPOSE(user_input, best_rule.decomposition_pattern)
IF decomposition_failed:
RETURN a_generic_response()
// 4. Perform pronoun conversion on decomposed parts
transformed_parts = TRANSFORM_PRONOUNS(decomposed_parts)
// 5. Use reassembly rules to generate response
response = REASSEMBLE(transformed_parts, best_rule.reassembly_patterns)
RETURN responseThrough this mechanism, ELIZA successfully simplified the complex natural language understanding problem into an operable, rule-based pattern matching game.
2.2.3 Implementation of Core Logic
Now, we will transform the technical principles described in the previous section into a simple, runnable Python function. The following code implements a mini version of ELIZA, which contains a small set of rules but is sufficient to demonstrate its core working mechanism.
import re
import random
# Define rule base: pattern (regular expression) -> response template list
rules = {
r'I need (.*)': [
"Why do you need {0}?",
"Would it really help you to get {0}?",
"Are you sure you need {0}?"
],
r'Why don\'t you (.*)\?': [
"Do you really think I don't {0}?",
"Perhaps eventually I will {0}.",
"Do you really want me to {0}?"
],
r'Why can\'t I (.*)\?': [
"Do you think you should be able to {0}?",
"If you could {0}, what would you do?",
"I don't know -- why can't you {0}?"
],
r'I am (.*)': [
"Did you come to me because you are {0}?",
"How long have you been {0}?",
"How do you feel about being {0}?"
],
r'.* mother .*': [
"Tell me more about your mother.",
"What was your relationship with your mother like?",
"How do you feel about your mother?"
],
r'.* father .*': [
"Tell me more about your father.",
"How did your father make you feel?",
"What has your father taught you?"
],
r'.*': [
"Please tell me more.",
"Let's change focus a bit... Tell me about your family.",
"Can you elaborate on that?"
]
}
# Define pronoun conversion rules
pronoun_swap = {
"i": "you", "you": "i", "me": "you", "my": "your",
"am": "are", "are": "am", "was": "were", "i'd": "you would",
"i've": "you have", "i'll": "you will", "yours": "mine",
"mine": "yours"
}
def swap_pronouns(phrase):
"""
Perform first/second person conversion on pronouns in input phrase
"""
words = phrase.lower().split()
swapped_words = [pronoun_swap.get(word, word) for word in words]
return " ".join(swapped_words)
def respond(user_input):
"""
Generate response based on rule base
"""
for pattern, responses in rules.items():
match = re.search(pattern, user_input, re.IGNORECASE)
if match:
# Capture matched part
captured_group = match.group(1) if match.groups() else ''
# Perform pronoun conversion
swapped_group = swap_pronouns(captured_group)
# Randomly select one from templates and format
response = random.choice(responses).format(swapped_group)
return response
# If no specific rule is matched, use the last wildcard rule
return random.choice(rules[r'.*'])
# Main chat loop
if __name__ == '__main__':
print("Therapist: Hello! How can I help you today?")
while True:
user_input = input("You: ")
if user_input.lower() in ["quit", "exit", "bye"]:
print("Therapist: Goodbye. It was nice talking to you.")
break
response = respond(user_input)
print(f"Therapist: {response}")
>>>
Therapist: Hello! How can I help you today?
You: I am feeling sad today.
Therapist: How long have you been feeling sad today?
You: I need some help with my project.
Therapist: Are you sure you need some help with your project?
You: My mother is not happy with my work.
Therapist: Tell me more about your mother.
You: quit
Therapist: Goodbye. It was nice talking to you.Through the above programming practice, we can intuitively summarize the fundamental limitations of rule-driven systems, which are direct confirmations of the theoretical challenges of symbolicism discussed in Section 2.1.4:
- Lack of Semantic Understanding: The system does not understand word meanings. For example, when faced with the input "I am not happy," it will still mechanically match the
I am (.*)rule and generate a semantically incorrect response because it cannot understand the role of the negation word "not." - No Contextual Memory: The system is stateless, with each response based only on the current single sentence input, unable to conduct coherent multi-turn conversations.
- Rule Scalability Problem: Attempting to add more rules leads to explosive growth in the rule base size, and conflict management and priority handling between rules become extremely complex, ultimately making the system difficult to maintain.
However, despite these obvious defects, ELIZA produced the famous "ELIZA effect" at the time, with many users believing it could understand them. This illusion of intelligence mainly stemmed from its clever conversation strategies (such as playing a passive questioner, using open-ended templates) and humans' innate emotional projection psychology.
ELIZA's practice clearly revealed the core contradiction of the symbolicism approach: the system's seemingly intelligent performance depends entirely on rules pre-coded by designers. However, facing the infinite possibilities of real-world language, this exhaustive method is destined to be unscalable. The system has no true understanding, only executing symbol operations, which is the root of its brittleness.