第二章 ELIZA.py可能出现的问题 · 详细分析
分析对象 :
code/chapter2/ELIZA.py(代码层)+ 第二章核心概念(概念层)实验方法 :以
importlib只读加载仓库中的ELIZA.py(不改动原文件 ),固定random.seed()保证可复现,逐条记录命中的规则 + 捕获内容 + 实际输出 。所有输出均为本机实测 ,不是推断。结论先行 :ELIZA 原实现的 8 个缺陷全部可修 (修复版见 §四,已实测通过);但第 9 个缺陷------"它根本不懂语义" ------在符号主义路线内不可修 。这正是本章要论证的历史必然性:不是工程师不够努力,而是方法论到达了天花板。
一、速览:可复现缺陷清单
| # | 缺陷 | 一句话现象 | 严重度 | 可修? |
|---|---|---|---|---|
| D1 | 无关键词优先级 | I am angry at my father 命中的是 I am 而不是 father 规则 |
高 | ✅ |
| D2 | re.search 子串抢答 |
You know what I am saying? 在第 14 个字符处被 I am 规则截胡 |
高 | ✅ |
| D3 | 规则硬依赖标点 | Why don't you listen to me(少个问号)→ 整条规则失效,掉到兜底 |
中 | ✅ |
| D4 | 反射与模板视角错位 | I need you → Why do you need i?(宾语位置应为 me) |
中 | ✅ |
| D5 | 捕获未剥标点 | I need help! → ...get help!?(双标点) |
中 | ✅ |
| D6 | 强制小写化 | I AM SAD → 捕获变 sad,大小写信息永久丢失 |
低 | ✅ |
| D7 | 死代码 | respond() 末尾的兜底 return 永远执行不到 |
低 | ✅ |
| D8 | 无状态 + 不可复现 | 不记任何上下文;random.choice 让同句每次输出不同 |
中 | ✅ |
| D9 | 无语义理解 | I am not happy → being not happy(不懂"not") |
根本 | ❌ 方法论问题 |
⚠️ D9 是重点,但不要误以为它"只是又一条 bug" :D1--D8 修完,你得到的仍是一个不能理解"not"、不能理解时态、不能理解语序 的系统。这 8 条是工程债,第 9 条是范式边界------第二章整章就是在解释:为什么这条边界必须靠"从数据中学习"来突破。
二、缺陷逐条分析
D1 · 优先级被隐式交给 dict 插入顺序
现场
输入: 'I am angry at my father'
实测: 命中 'I am (.*)' 捕获=('angry at my father',)
输出: 'How long have you been angry at your father?'
期望: 命中 '.* father .*' → 'Tell me more about your father.'
根因 :respond() 用 for pattern, responses in rules.items() 顺序扫描 ,首条命中即 return。Python 3.7+ 的 dict 保序 → 规则书写顺序 = 优先级 。而章节 §2.2.2 的算法第 1 步明确要求"关键词识别与排序(优先级) "------这一步在实现里被省略了。
更隐蔽的连带效应 :I am sad because my father left 命中的是 I am (.*),整个 because 从句被吞进捕获组:
捕获 = ('sad because my father left',)
输出 = 'Did you come to me because you are sad because your father left?' ← 双重 because
修复 :把规则改成显式带优先级的结构,按优先级降序扫描 ((优先级, 模式, 模板) 三元组),并把 family 类关键词排到 I am 之前:
python
RULES = [(10, r"\bsorry\b", [...]), (6, r"\bmother\b", [...]), (5, r"\bi am\b\s*(.+)", [...]), ...]
for priority, pattern, templates in sorted(RULES, key=lambda r: -r[0]):
对应教材概念 :符号系统的脆弱性(Brittleness) ------规则之间的相互作用没有统一仲裁机制,只能靠"摆放顺序"这种隐式约定,加规则时任何一次挪动都可能静默改变全局行为。
D2 · re.search 让规则在句子中间"抢答"
现场
输入: 'You know what I am saying?'
实测: re.search(r'I am (.*)', 输入) → span=(14, 26) 捕获='saying?'
输出: 'Did you come to me because you are saying??'
根因 :re.search = 句中任意位置 匹配(不是 re.match 的句首匹配,也不是全串匹配)。子串 I am 出现在句中也会激活规则。
一条输出里叠了三个缺陷:
| 现象 | 根因 |
|---|---|
| 句中截胡 | D2(re.search) |
being saying(语法错位) |
saying? 是现在分词,模板却是 you are {0};系统不认识词性 → D9 |
saying??(双问号) |
捕获未剥标点 → D5 |
修复 :加词边界 \b,并(若要做原版 ELIZA 的效果)限定匹配位置:
python
r"\bi am\b\s*(.+)" # \b 保证 "I am" 是独立词,不会切进别的词里
📌 注意:
\b只能解决"词内误切",解决不了"句子中间合法出现" ------You know what I am saying?里的I am本来就是独立词。要判断"这里该不该由 I am 规则接管",需要理解句子结构 → 又回到 D9。 这正是"工程修复"与"范式局限"的分界线。
D3 · 规则硬依赖标点
现场
'Why don\'t you listen to me?' → ✅ 命中 "Why don\\'t you (.*)\\?"
'Why don\'t you listen to me' → ❌ 掉到兜底 '.*' → 'Let\'s change focus a bit...'
根因 :模式里写死了 \?。用户少写一个问号、写成逗号、或者用中文问号 ?,整条规则静默失效。
修复 :把标点变成可选 ,并在捕获后统一剥离(见 D5 的 clean()):
python
r"\bwhy don'?t you\b\s*(.+?)\s*[?.!]?$"
对应教材概念 :"一遇到规则之外的微小变化或新情况,系统便可能完全失灵" ------这就是 2.1.4 里 Brittleness 的最小可复现实例。真实用户永远不会按你的正则写标点。
D4 · 代词反射与模板"视角错位"(you → i 塞进了宾语空位)
现场
python
swap_pronouns('you') = 'i'
'I need you' → 'Would it really help you to get i?' # ❌ 病句,且是小写 i
⚠️ 先说一个容易骂错的地方 (我一开始也写错了,查资料后修正):
原版 ELIZA 的反射表同样把 you 反射成 I ------所以"映射本身写错了"这个说法不成立 。原版之所以不出现病句,是因为它把这件工作分成了两层:
① 反射层:把关键词之后的片段做人称转换 "I hate you" → "YOU hate I"
② 模板层:重组规则里【硬编码】治疗师视角的代词
(MAD-SLIP 原版规则组形如 ((0 YOU 0 I 0) (DO YOU WISH TO 3 ME) ...),
其中的 ME 是写死在模板里的,3 处填入反射后的动词短语)
真正的根因 :这份迷你实现只有反射这一层,却把反射结果直接填进需要宾语的位置 ------"Why do you need {0}?" 的空位是宾语,填入第一人称主语形式 i 就必然出病句。两层被压成了一层,视角错位就暴露了。
修法有两种(v2 用的是第 ① 种):
| 方案 | 做法 | 结果 |
|---|---|---|
| ① 改反射表 | "you" → "me"(本 v2 采用) |
I need you → Why do you need me? ✅ 直接得到语法正确的英语 |
| ② 改模板 | 保持 you → I,把模板写成治疗师视角("DO YOU WISH TO 3 ME" 式) |
更忠实原版 ELIZA,但每条模板都要人工斟酌视角 |
连带问题 :swap_pronouns 第一行 phrase.lower().split() 把整句强制小写 → 即使用对了映射,也会输出小写 i;且 split() 按空格分词,标点会粘在词尾(help!),导致查表失败。
修复:case-preserving + 拆分"前缀标点 / 词干 / 后缀标点"再查表:
python
PRONOUN_SWAP = {"i": "you", "you": "me", "me": "you", "my": "your", ...}
def swap_pronouns(phrase):
out = []
for w in phrase.split():
prefix = w[:len(w) - len(w.lstrip(".,!?;:\"'"))]
suffix = w[len(w.rstrip(".,!?;:\"'")):]
core = w[len(prefix):len(w) - len(suffix)]
mapped = PRONOUN_SWAP.get(core.lower())
if mapped:
mapped = mapped.capitalize() if core[:1].isupper() else mapped
out.append(f"{prefix}{mapped}{suffix}")
else:
out.append(w)
return " ".join(out)
修复后实测:I need you → Why do you need me? ✅
D5 · 捕获内容不剥标点 → 输出双标点
现场
'I need help!' → 'Would it really help you to get help!?'
'You know what I am saying?' → '...you are saying??'
'I am feeling sad today.' → 'How do you feel about being feeling sad today.?'
根因:模板自带句号/问号,捕获内容又保留了原文标点,二者叠加。
修复:捕获后统一清洗:
python
def clean(text):
return text.strip().strip(".!?,;:").strip()
💡 顺带一提 :
'I am feeling sad today.'的输出还暴露了 D9 ------模板being {0}套上动名词短语feeling sad today得到being feeling sad,语法上说不通。程序在符号层面完全"正确",在语义层面完全错误------这就是"符号操作 ≠ 理解"。
D6 · 强制小写化丢失大小写
现场
'I AM SAD' → 捕获 'SAD' → swap_pronouns 小写化 → 'sad'
输出: 'Did you come to me because you are sad?'
影响 :专有名词(人名、地名)会被破坏------My mother loves Beijing → your mother loves beijing。
修复:同 D4 的 case-preserving 版本(只对词干小写做查表,输出保留原大小写)。
D7 · 函数末尾的兜底 return 是死代码
现场
python
re.search(r'.*', '') → True # 空串都能匹配
re.search(r'.*', 'xyz') → True
根因 :rules 的最后一条是 r'.*',它能匹配任何 输入(包括空串)。因此 for 循环必然 在最后一条上命中并 return,函数末尾的
python
return random.choice(rules[r'.*']) # ← 永远执行不到
永远不会被执行 。这也是为什么 '我很难过'、''、' ' 都能拿到回应------它们是被循环内的 .* 规则接住的,不是被这个兜底接住的。
修复 :删掉死代码;或把 r'.*' 从 rules 里移出,作为真正的 try/else 兜底,让"未命中任何规则"能被观测到(便于统计关键词覆盖率)。
📌 教学价值 :这条死代码恰好演示了"兜底规则的边界在哪"------
.*通配规则既是安全网,也让系统失去了"我不知道该说什么"的表达能力 。ELIZA 永远不会说"我不明白",它只会转移话题。LLM 的"我不知道"(拒答/不确定性表达)反而是它比规则系统更诚实的地方。
D8 · 无状态 + 输出不可复现
现场
python
# 同一句输入连跑三次,得到三种输出(random.choice 生效)
'I am feeling sad today.' →
'How do you feel about being feeling sad today.?'
'Did you come to me because you are feeling sad today.?'
'How do you feel about being feeling sad today.?'
两个子问题:
| 子问题 | 后果 | 修复 |
|---|---|---|
| 无状态:不保存任何历史 | 无法做多轮连贯对话;用户说"我叫 Tom",下一句它就忘了 | 加 self.memory(见 §四) |
不可复现 :random.choice 无种子 |
无法写断言、无法复现 bug、无法做回归测试 | 加 seed 参数(测试时固定) |
对应教材概念 :"系统是无状态的(Stateless),每次回应仅基于当前单句输入" (章节 §2.2.3 原文)。这也是为什么第一章要用 prompt_history 维护对话历史------记忆不是可选功能,是序贯环境的基本要求。
D9 · 无语义理解(不可修,方法论天花板)
现场
| 输入 | 输出 | 它"看不到"的东西 |
|---|---|---|
I am not happy |
How long have you been not happy? |
否定 :not 只是普通词 |
I am feeling sad today. |
How do you feel about being feeling sad today.? |
词性/时态 :feeling 是动名词 |
You know what I am saying? |
...you are saying?? |
从句结构 :what I am saying 是从句 |
That is what I am thinking |
How do you feel about being thinking? |
同上 |
我很难过 |
Let's change focus a bit... |
语言与语义:只认英文关键词 |
为什么修不掉 :这四条各自都能"打补丁"(加否定规则、加分词表、加从句识别......),但每打一个补丁,就新增一批需要维护的规则,并引入新的规则冲突 (补丁之间互相抢答,见 D1)。这就是组合爆炸(§五有数学说明)。
🔑 这一条是本章的灵魂 :ELIZA 并不是"做得不够好",而是这条路本身的上限就到这里 。魏泽鲍姆本人在 1976 年出版的《计算机能力与人类理性》中就对 AI 应用提出了强烈的伦理质疑------他是最早反思"机器看似理解实为操纵"的人之一,而 ELIZA 正是他用来证明这一点的实验装置。
三、概念层"可能出现的问题"(高频误区辨析)
这一节对应"你以为你懂了,其实记反了"的十个点。
| # | 常见误区 ❌ | 正确理解 ✅ |
|---|---|---|
| C1 | "心智社会里的智能体 = 第一章说的智能体" | 完全是两个概念 。心智社会的"智能体"是无心的简单心智过程 (如 GRASP 只负责抓握);第一章的智能体是能感知-决策-行动的完整实体 。明斯基的"智能体"更接近函数/模块(→ 现代叫 agent 的滥用源头就在这) |
| C2 | PSSH 的充分性与必要性"说的是一回事" | 二者互为逆命题 :充分性 = "符号系统 → 智能";必要性 = "智能 → 符号系统"。反驳前者用工程困境(知识获取瓶颈等),反驳后者用大脑/联结主义 |
| C3 | 正向链 = 从目标出发(因为"正"听起来像"直奔结果") | 反了 。正向链从已知事实 出发(数据驱动 );反向链从假设目标 出发(目标驱动 )。MYCIN 用的是反向链(先假设致病菌,再找证据) |
| C4 | 框架问题 = 规则太多导致组合爆炸 | 不是。框架问题是"动作发生后,如何高效判断哪些事物没变" (不变量的表示难题)。规则膨胀属于知识获取瓶颈 / 扩展性问题 |
| C5 | "专家系统失败是因为规则写错了" | 失败源于方法论 :专家的内隐知识无法显式化成 IF-THEN (知识获取瓶颈),且真实世界充满例外(脆弱性)。规则写得再对,也覆盖不了开放世界 |
| C6 | Cyc 是深度学习项目 | Cyc 是符号主义 的常识知识库工程(Lenat),手工编码常识 数十年,成果有限------它是"符号主义撞墙"的证据,不是联结主义的 |
| C7 | 联结主义 = 深度学习 | 联结主义 1980s 就重新兴起了(PDP、反向传播);深度学习是它在算力+数据到位后的爆发形态(约 2012 年后)。中间隔了两次寒冬与三十年 |
| C8 | RL 就是"有奖励的监督学习" | 本质不同:① 监督学习的数据预先标注、i.i.d. ;RL 的数据靠自己与环境交互产生 ,且决策之间强相关 ;② RL 优化累积回报 (可牺牲眼前),监督学习只拟合单步标签 ;③ RL 有探索-利用权衡,监督学习没有 |
| C9 | LLM 的推理能力是设计出来的 | 是涌现能力 :规模跨过阈值后才出现、未被直接训练的能力(ICL、CoT)。⚠️ 注意学术争议 :有研究认为部分"涌现"是评测指标不连续 造成的假象(Schaeffer et al., 2023, Are Emergent Abilities of LLMs a Mirage? )------考试时按教材口径答,但心里要留这个问号 |
| C10 | 神经符号 = 神经网络 + 知识图谱拼在一起 | 现代用法更精确:神经网络提供直觉与表示(系统 1),符号中间步骤(Thought / 计划 / 工具调用 JSON)提供可操作的推理接口(系统 2)。第一章的 Thought-Action-Observation 就是那个"符号接口" |
| C11 | ELIZA 效应 = 通过了图灵测试 | 图灵测试是判定方法 (人类能否分辨);ELIZA 效应是心理现象 ------人明知 对方可能是程序,仍产生情感投射与信任。它说明人类的判断标准很容易被对话策略欺骗 |
| C12 | 预训练解决了框架问题 | 只解决了知识获取瓶颈 (常识被压进权重)。框架问题(状态不变量的表示)并没有被消解------今天的智能体仍要为"记忆里什么该保留、什么该更新"设计策略(→ 图 2.10 的 Memory Update) |
四、动手题:ELIZA v2 完整实现(修复 D1--D8,已实测通过)
对应习题 3。实测环境 :本机 Python 3,脚本以
importlib回放固定对话,random.seed固定,输出可复现。
4.1 v1 → v2 实测对照
| 输入 | ELIZA 原版 | ELIZA v2 | 修了哪条 |
|---|---|---|---|
My name is Tom. |
Let's change focus a bit... |
Nice to meet you, Tom. |
D8(记忆) |
I am 30 years old. |
How long have you been 30 years old.? |
At 30, what matters most to you? |
D5/D1 |
I am not happy |
How long have you been not happy? |
Why do you think you are not happy? |
D9 的局部补丁 |
I need you |
Would it really help you to get i? |
Why do you need me? |
D4 ✅ |
I am angry at my father |
How long have you been angry at your father? |
Tell me more about your father. |
D1 ✅ |
You know what I am saying? |
How long have you been saying?? |
Tom, how long have you been saying? |
D2/D5 ✅ |
I need help! |
Would it really help you to get help!? |
Tom, would it really help you to get help? |
D5 ✅ |
My mother is not happy with my work. |
What was your relationship with your mother like? |
Tom, tell me more about your mother. |
D8(名字注入) |
Why don't you listen to me |
Let's change focus a bit... |
Tom, can you elaborate on that? |
D3(不再因缺问号全盘失效) |
4.2 完整代码(单文件可运行,无需任何 API Key)
python
# -*- coding: utf-8 -*-
"""
ELIZA v2 ------ 针对原实现四处缺陷的修复版(习题 3 的动手题)
修:① 关键词优先级 ② 词边界+去标点 ③ 代词转换 ④ 否定处理 ⑤ 上下文记忆
"""
import re
import random
# ---- 规则表:(优先级, 模式, 响应模板)。优先级越大越优先,模仿原版 ELIZA 的 keyword rank ----
# 模式统一用单捕获组捕获「关键内容」,无捕获组的规则自己负责措辞
RULES = [
(10, r"\bsorry\b", ["Please don't apologize.", "Apologies are not necessary."]),
( 8, r"\bmy name is\s+([A-Za-z]+)", ["Nice to meet you, {0}.", "Hello {0}, how are you feeling today?"]),
( 7, r"\bi (?:am|'m)\s+(\d+)\s*(?:years old)?\b",
["How does being {0} feel?", "At {0}, what matters most to you?"]),
( 6, r"\bmother\b", ["Tell me more about your mother.",
"How do you feel about your mother?"]),
( 6, r"\bfather\b", ["Tell me more about your father.",
"How did your father make you feel?"]),
( 5, r"\bi need\b\s*(.+)", ["Why do you need {0}?", "Would it really help you to get {0}?"]),
( 5, r"\bi am\b\s*(.+)", ["How long have you been {0}?", "How do you feel about being {0}?"]),
( 1, r".*", ["Please tell me more.", "Can you elaborate on that?"]),
]
# 否定感知:命中 "not/no/never/n't" 时改用这组模板,避免"being not happy"这类病句
NEG_RULES = [
( 5, r"\bi (?:am|'m)\s+not\s+(.+)", ["Why do you think you are not {0}?",
"What makes you say you are not {0}?"]),
( 5, r"\bi (?:do not|don't)\s+(.+)", ["Tell me why you don't {0}."]),
]
# ---- 代词转换:case-preserving;并把 you→me / me→you,使 "I need you" → "Why do you need me?" ----
PRONOUN_SWAP = {
"i": "you", "you": "me", "me": "you", "my": "your", "your": "my",
"am": "are", "are": "am", "was": "were", "were": "was",
"mine": "yours", "yours": "mine", "i'm": "you're", "you're": "i'm",
}
def swap_pronouns(phrase: str) -> str:
"""保留首字母大小写,且不再把整句粗暴小写化"""
out = []
for w in phrase.split():
# 把词切成「前缀标点 / 词干 / 后缀标点」,只对词干做替换,标点原样保留
prefix = w[:len(w) - len(w.lstrip(".,!?;:\"'"))]
suffix = w[len(w.rstrip(".,!?;:\"'")):]
core = w[len(prefix):len(w) - len(suffix)]
mapped = PRONOUN_SWAP.get(core.lower())
if mapped:
mapped = mapped.capitalize() if core[:1].isupper() else mapped
out.append(f"{prefix}{mapped}{suffix}")
else:
out.append(w)
return " ".join(out)
def clean(text: str) -> str:
"""去掉捕获文本首尾的标点与空白 ------ 原版 'saying?' → 'being saying??' 的根因之一"""
return text.strip().strip(".!?,;:").strip()
class Eliza:
def __init__(self, seed=None):
if seed is not None:
random.seed(seed)
self.memory = {} # 上下文记忆:name / age
def _remember(self, user_input: str):
m = re.search(r"\bmy name is\s+([A-Za-z]+)", user_input, re.IGNORECASE)
if m:
self.memory["name"] = m.group(1).capitalize()
m = re.search(r"\b(?:i am|i'm)\s+(\d+)\s*(?:years old)?\b", user_input, re.IGNORECASE)
if m:
self.memory["age"] = m.group(1)
def respond(self, user_input: str) -> str:
if not user_input.strip():
return "Please tell me what's on your mind."
self._remember(user_input)
# ① 先扫否定规则(优先级最高),② 再按优先级降序扫主规则表
for priority, pattern, templates in sorted(NEG_RULES + RULES, key=lambda r: -r[0]):
m = re.search(pattern, user_input, re.IGNORECASE)
if not m:
continue
captured = clean(m.group(1)) if m.groups() else ""
reply = random.choice(templates).format(swap_pronouns(captured)) if m.groups() \
else random.choice(templates)
# ⑤ 记忆注入:已知姓名时,把名字自然地带进回复
name = self.memory.get("name")
if name and random.random() < 0.6:
reply = f"{name}, {reply[0].lower() + reply[1:]}"
return reply
return "Please tell me more."
def chat(script=None, seed=20260916):
"""script=None 走真实交互;传入列表走脚本化回放(便于测试)"""
bot = Eliza(seed=seed)
print("Therapist: Hello! How can I help you today?")
lines = script if script is not None else _stdin_lines()
for line in lines:
print(f"You: {line}")
if line.lower() in ["quit", "exit", "bye"]:
print("Therapist: Goodbye. It was nice talking to you.")
break
print(f"Therapist: {bot.respond(line)}")
def _stdin_lines():
"""交互输入;EOF(Ctrl+Z/Ctrl+D)时优雅退出而不是抛 EOFError"""
while True:
try:
yield input("You: ")
except EOFError:
return
if __name__ == "__main__":
chat()
4.3 实测对话回放(含记忆注入)
Therapist: Hello! How can I help you today?
You: My name is Tom.
Therapist: Nice to meet you, Tom.
You: I am 30 years old.
Therapist: How does being 30 feel?
You: My mother is not happy with my work.
Therapist: Tom, tell me more about your mother.
You: I need you
Therapist: Why do you need me?
You: I am angry at my father
Therapist: How did your father make you feel?
You: bye
Therapist: Goodbye. It was nice talking to you.
4.4 但请务必记住:v2 依然是符号主义
| 它修好了 | 它没修好(也不该指望) |
|---|---|
| 优先级、词边界、标点、代词、记忆、否定模板 | 它依然不懂 "为什么母亲不高兴"、"not happy" 到底什么情绪、我很难过 是什么意思 |
| 靠的是手工补规则 | 每加一条规则,就多一份冲突与维护成本(§五) |
| 能通过脚本化回放做回归测试 | 换个领域(法律咨询、订机票)就要整库重写 |
🔑 与第三章的接口 :ChatGPT 在同样输入上的表现差异,不是"规则写得更好",而是"知识来源不同" ------它从万亿语料里学到的分布式语义表示 ,天然携带否定、时态、语序、常识。这就是"从符号到联结"的那一步跨越。
五、「组合爆炸」的数学说明(习题 3.4)
问题:为什么基于规则的方法在开放域对话中必然遇到组合爆炸、难以扩展维护?
(1)覆盖率视角------线性投入 vs 指数空间
设词表规模 V、句子长度上限 n,则可能的句子数为 V^n。取保守值 V = 10^4(常用英文词量级)、n = 5:
句子空间 ≈ (10^4)^5 = 10^20
而规则库规模 R 的增长受知识工程师人力 线性约束(每人每天写 k 条 → R ≈ k·t,t 为工时):
覆盖率 ≈ R / V^n = k·t / 10^20 → t → ∞ 时仍 → 0
投入线性增长、需求指数增长 → 覆盖率恒趋于 0。这就是"无论多努力都追不上"的数学形态。
(2)组合视角------乘积式爆炸
更贴近工程的估计:要覆盖真实对话,规则数约等于各维度的笛卡尔积:
R ≈ |意图| × |句式| × |语境| × |说话人角色| × |情绪| × ...
即使每个维度只取 100 种,R ≈ 100^3 = 10^6 条规则 ------ 这已远超任何团队的手工维护能力(MYCIN 作为单一垂直领域 的专家系统,也只做了 600 条规则,而它处理的是结构化的医学问答,不是开放域闲聊)。
(3)交互成本------规则不是独立的
规则之间存在优先级冲突、捕获组互斥、顺序敏感 (实测见 D1:I am 抢走了 father 的关键词)。若必须两两检查一致性:
冲突检测复杂度 = O(R²) R = 10^6 → 10^12 次比较
规则库越大,加一条新规则的风险越高------这与软件工程里"全局状态的耦合"是同一种病。
(4)与学习范式的对照
| 符号主义 | 联结主义/预训练 | |
|---|---|---|
| 能力来源 | 工程师逐条写 | 从数据中自动拟合 |
| 投入单位 | 每条规则(人的时间) | 每个训练步(算力) |
| 覆盖率增长 | 线性 / 指数需求 | 随参数与数据规模超线性提升 |
| 加新领域的边际成本 | ≈ 重写整个规则库 | 微调/提示词即可迁移 |
| 失败的形态 | 静默失效 / 脆弱 | 幻觉 / 不精确 |
🔑 结论 :组合爆炸不是"工程没做好",而是**"知识必须由人显式写出"这一前提的必然代价**。第二章全部的历史,本质上就是人类逐步放弃这个前提的过程------先交出"感知"(联结主义),再交出"决策"(RL),最后交出"知识"(预训练)。
六、课外补充:本章之后发生了什么(时间线 + 两个协议)
以下不属于教材正文 ,是我为建立全局坐标补的,请自行核对后再引用。
| 年份 | 事件 | 属于哪条思潮 |
|---|---|---|
| 1950 | 图灵发表《计算机器与智能》(图灵测试) | 哲学前提 |
| 1956 | 达特茅斯会议,AI 学科诞生 | ------ |
| 1966 | ELIZA(魏泽鲍姆) | 符号主义(规则对话) |
| 1968--70 | SHRDLU(威诺格拉德) | 符号主义(集成闭环) |
| 1969 | 框架问题(McCarthy & Hayes) | 符号主义的困境 |
| 1970s | MYCIN(斯坦福) | 符号主义(专家系统巅峰) |
| 1976 | 物理符号系统假说(Newell & Simon) | 符号主义理论基石 |
| 1984 | Cyc 常识知识库启动 | 符号主义(常识的攻坚战) |
| 1986 | 《心智社会》 (明斯基)+ PDP/反向传播 | 分布式智能 + 联结主义复兴 |
| 1987--93 | 第二次 AI 寒冬(LISP 机市场崩溃) | ------ |
| 1992 | TD-Gammon(Tesauro,RL + 神经网络) | 行为主义/RL |
| 1997 | 深蓝击败卡斯帕罗夫 | 符号主义(搜索 + 评估) |
| 2012 | AlexNet 引爆深度学习 | 联结主义 |
| 2016 | AlphaGo 击败李世石 | RL + 深度学习 |
| 2017 | Transformer 论文《Attention Is All You Need》 | 联结主义(架构基础) |
| 2018 | BERT / GPT-1;预训练-微调范式确立 | 预训练 |
| 2020 | GPT-3(上下文学习能力显现) | 预训练 + 涌现能力 |
| 2022 | ChatGPT (RLHF 对齐);ReAct 论文(推理 + 行动交替) | LLM 智能体元年 |
| 2024-11 | MCP (Model Context Protocol,Anthropic 开源)------模型 ↔ 工具的纵向标准;同月 Letta 发布 AI Agent 技术栈图(即图 2.12) | 智能体基础设施 |
| 2025-03 | ACP(Agent Communication Protocol,IBM) | 智能体间协议 |
| 2025-04 | A2A(Agent2Agent Protocol,Google 发布)------智能体 ↔ 智能体的横向协作标准 | 智能体间协议 |
| 2025-06 | Google 将 A2A 捐赠给 Linux Foundation(Microsoft、AWS、Salesforce、SAP 等 100+ 公司支持) | 协议标准化 |
| 2025-08 | IBM 的 ACP 并入 A2A,统一为智能体间协议 | 协议收敛 |
| 2025-12 | Anthropic 将 MCP 捐赠给 Linux Foundation 下新成立的 Agentic AI Foundation(AAIF)(与 Block、OpenAI 共同发起) | 协议治理中立化 |
一句话记住这两个协议的分工 :MCP 管"纵向"(智能体 ↔ 工具/数据),A2A 管"横向"(智能体 ↔ 智能体) ------它们都是本章 §2.3.3"智能体的社会性(通信语言、交互协议)"那个 1986 年构想的工程化落地。
Sources:
- Donating the Model Context Protocol and establishing the Agentic AI Foundation --- Anthropic
- Big tech takes steps to build open standards for agentic AI --- CIO Dive
- Anthropic says MCP will stay 'open, neutral, and community-driven' after donating project to Linux Foundation --- ITPro
- 关于 D4 的"原版 ELIZA 反射表":
you → I的反射表与 MAD-SLIP 规则组形式(模板内硬编码ME,如(DO YOU WISH TO 3 ME))见 ELIZA --- Wikiwand、Eliza / Doctor(Hayden 实现的 pre-/post-substitution 结构)、ELIZA Deep Dive(含reflection_of表与现代 Python 复现)
七、快速诊断速查表
7.1 代码层:症状 → 真因 → 定位方法
| 症状 | 最可能的真因 | 定位方法 |
|---|---|---|
输出里有 being not XXX / not happy |
D9(无否定处理) | 打印命中规则 → 确认命中的是 I am (.*) |
输出里出现小写 i / help!? / saying?? |
D4 代词表 + D5 标点 | 单独调 swap_pronouns('you') 看返回值 |
| 明明有关键词却没命中(如"父亲"被"我是"抢走) | D1 优先级靠 dict 顺序 | 打印命中的 pattern 与 span |
某句话永远掉到 Please tell me more. |
D3 标点不匹配 / 关键词不在表内 | 用 re.search 手工逐个模式试 |
| 同一句每次输出不同,无法复现 | D8 random.choice 无种子 |
加 random.seed() 或注入固定 seed |
感觉最后那句兜底 return 从没生效过 |
D7 死代码 | re.search(r'.*', '') → True |
7.2 概念层:快速自检
text
□ 我能说清"充分性"与"必要性"分别被谁反驳吗?
□ 我说得出 MYCIN 用的是正向链还是反向链吗?(反向链 = 目标驱动)
□ 我知道"框架问题"问的不是"规则太多"吗?(问的是"什么没变")
□ 我能分清"知识获取瓶颈"(知识哪来)与"常识问题"(知识多广)吗?
□ 我明白明斯基的"智能体"和第一章的"智能体"不是一个东西吗?
□ 我能说出 RL 的"奖励"与监督学习的"标签"的本质差别吗?
□ 我记得"涌现能力"指 ICL 与 CoT,且它是有学术争议的吗?
□ 我能说出预训练解决的是"知识获取瓶颈",但**没有**解决框架问题吗?
7.3 总结
ELIZA.py用 40 行代码,让你亲手复现了符号主义的全部成就与全部局限。它能命中关键词、能转换人称、能随机应变------但它永远不知道你在说什么 。
这不是这个程序的失败,这是"手工编码知识"这条路的失败------而第二章剩下的全部内容,就是人类如何一步步放弃它。