从Wordle求解器看AI Agent:信息熵、状态与工具调用

📅 2026/8/27 8:44:31
从Wordle求解器看AI Agent:信息熵、状态与工具调用
不知道你有没有过这种感受一个看起来简单到不能再小的游戏规则一旦你试图让程序或 AI 自己“学会”怎么玩难点会突然冒出一大堆。Wordle 就是这样一个典型例子——五个字母、六次机会、绿黄灰三种反馈听起来连小学生都能秒懂但如果你真的动手写一个求解器很快就会发现这不是“查字典”这么简单。我第一次写 Wordle 求解器时第一版代码只用了一句“把不符合反馈的单词全部过滤掉”结果在第二批猜测时就经常出现候选词列表直接归零的情况。后来我才意识到问题出在重复字母、反馈状态的建模以及最关键的“猜哪个词能带来最多信息”这几个环节上。也正是从那个时候开始我意识到这一类“小到不能再小的 AI 挑战”反而是理解 AI Agent 开发、工具调用、状态机和搜索策略的最佳训练场。这篇文章就用 “Wordle but small AI mini challenges” 这个项目标题展开带你把一个完整的小型 AI 猜词系统从零写出来。我们会实现三版方案第一版是纯规则过滤的基准求解器第二版引入信息熵概念让 AI 每次猜词都尽可能获得更多信息第三版把求解器封装成一个可被大模型调用的工具模拟 Agent 调用工具解决问题的完整链路。读完你不仅能跑通一个 Wordle 求解器还能把这一套“环境建模 工具封装 Agent 循环”的思路迁移到其他 AI 小项目里。1. 这篇文章真正要解决的问题先说判断Wordle 类 AI 项目真正的难点从来不是“猜词”而是“状态建模”和“决策策略”。很多初学者一上来就想着调用大模型直接生成答案这是典型的思路误区。大模型确实能猜词但它不会告诉你为什么猜这个词也不会在连续几轮错误之后自动修正策略——这恰恰是 AI 工程里最需要解决的部分。“Wordle but small AI mini challenges”这个项目最值得做的原因有三点。第一它把复杂的 AI 问题压缩到一个极小的规模里。没有海量数据、没有复杂网络、没有训练环节核心就是一个单词表加一套反馈规则但你依然要设计算法、处理边界情况、验证效果。这种“小而完整”的项目特别适合理解 AI 推理的基础逻辑。第二它天然适合讲清楚“工具调用”的价值。大模型直接猜词容易产生幻觉但如果把求解器封装成工具让大模型每一次决策都调用工具获取精确反馈结果就会稳定很多。这是当前 AI Agent 开发里最重要的工程模式之一。第三它有明确的评价标准。你不需要主观判断 AI 好不好只需要看它在六次机会内能不能猜中、平均用了几次以及候选词列表的收敛速度。这种可量化的反馈对开发调试极其友好。什么样的读者最应该读这篇文章如果你正在学 AI Agent 开发想找一个不用烧钱、不需要大量数据、又足够完整的练手项目或者你已经在做 AI 编程但总是对“状态管理”“工具调用”“提示词设计”这三个概念理解得不够落地又或者你只是对 Wordle 感兴趣想看看程序怎么玩这个游戏——这篇文章都适合你。2. 基础概念与核心原理2.1 Wordle 的规则本质Wordle 的规则用一句话就能说清猜一个五字母单词每次猜测后系统对每个位置给出绿、黄、灰三种反馈。绿色代表该字母在正确位置黄色代表该字母存在于答案中但位置不对灰色代表该字母完全不在答案中。但这句话里藏着一个特别容易搞错的细节重复字母的反馈规则。比如答案单词是“crane”你猜了“cocoa”第一个 c 是绿色第二个 c 会是什么颜色答案是灰色。因为答案里只有一个 c已经被第一个位置用掉了。这类逻辑不处理好的话求解器的候选词过滤马上就会出错。2.2 状态反馈的数据结构写程序时我们用一个长度为 5 的列表来表示一次猜测的反馈每个元素取G绿色、Y黄色、W灰色之一。例如# 反馈示例猜词 crane答案是 slate # 结果为 [W, G, Y, W, Y] # 解释 # 第 0 位 c 不在答案中 - W # 第 1 位 r 在答案中且位置正确 - G # 第 2 位 a 在答案中但位置不对 - Y # 第 3 位 n 不在答案中 - W # 第 4 位 e 在答案中但位置不对 - Y这个数组就是整个求解器唯一依赖的外部反馈。所有候选词的筛选、评分、信息熵计算都围绕这个 5 位数组展开。2.3 几类求解策略的对比策略核心思想优点缺点暴力枚举把所有五字母词都猜一遍实现最简单六次机会内基本猜不中排除过滤每次根据反馈筛掉不可能的词逻辑直观有时候候选词会快速耗尽词频排序优先猜常见词效果尚可依赖词频数据且首猜词不一定最优信息熵选择期望信息量最大的词平均猜测次数少需要额外计算理解门槛稍高LLM 直接生成让大模型输出猜测看起来“智能”易幻觉、不可控、不透明其中“排除过滤”是基础“信息熵”是优化“LLM 工具调用”是向 Agent 工程迁移的关键。下面会把这三层都写出来。3. 环境准备与前置条件这个项目对运行环境的要求非常低核心逻辑只用 Python 标准库就能写完不需要装任何第三方包。Python 3.9 或更高版本推荐 3.10一个能跑 Python 脚本的命令行环境如果后面要接大模型准备一个可用的模型服务 API Key可选一个包含五字母英文单词的词典文件可以自己收集下面会给出最小示例环境验证很简单在终端执行python --version只要输出是 3.9 以上就可以继续。在开始之前先想清楚我们不需要一个包含几十万单词的完整词典甚至不需要真实的 Wordle 官方答案列表。为了演示求解逻辑用一个 8 到 10 个词的微型词表就够跑通流程。后面要追求更强效果时再换成覆盖更全的词典即可。这一步会避免很多人“先找全量词典再写代码”的启动困难。建议的项目结构如下wordle-ai-mini/ ├── wordle.py # 核心规则与求解器 ├── agent_demo.py # Agent 调用工具演示 ├── dictionary.txt # 五字母单词表 └── README.md # 项目说明4. 核心流程拆解整个 AI 猜词系统按下面这条链路运行这也是所有 Agent 类项目的通用骨架初始化候选词列表即“所有可能的答案”。AI 生成一个猜测词。系统根据答案计算反馈绿黄灰数组。AI 根据反馈过滤掉不可能的候选词。检查是否猜中猜中则结束否则回到第 2 步。这里要特别标注一个最容易出错的地方第 2 步“AI 生成猜测词”的颗粒度。如果 AI 直接生成一个单词每次猜词都是独立决策前面几轮积累的反馈信息完全靠模型自己“记得”很容易出现逻辑混乱。更稳妥的做法是让 AI 只做“选择”也就是从候选词列表里选一个词而不是凭空生成。这个区别非常关键我们可以把它叫做“填空式决策”与“开放式生成”的区别。在 Agent 工程里前者意味着你给模型一个明确的、受限的动作空间后者意味着让模型自由发挥。对于状态空间很小的任务受限动作空间的效果通常远好于开放式生成。从算法层面看第 4 步的过滤逻辑应该是这样的如果某位置反馈是 G那么候选词该位置必须等于猜词的该字母。如果某位置反馈是 Y那么候选词该位置不能等于猜词的该字母但候选词中必须至少包含一个该字母。如果某位置反馈是 W那么候选词中不能再包含该字母除非某个 G 或 Y 位置已经消耗了同字母的额度。最后一条是最容易写错的一定要结合 2.1 节的重复字母规则去实现。5. 完整示例与代码实现5.1 模块一评估函数评估函数负责根据“答案”和“猜测”计算出反馈数组。这是整个项目的地基后续所有逻辑都依赖它的正确性。# 文件路径wordle.py from collections import Counter def evaluate_guess(answer: str, guess: str) - list[str]: 根据 Wordle 规则计算反馈。 参数 answer: 真实答案五字母字符串 guess: 本次猜测五字母字符串 返回 长度为 5 的反馈列表元素为 G / Y / W if len(answer) ! len(guess): raise ValueError(answer 和 guess 长度必须一致且应为 5) n len(answer) result [W] * n # 统计答案中每个字母的剩余次数 remaining Counter(answer) # 第一遍标记绿色并扣除该字母的剩余次数 for i in range(n): if guess[i] answer[i]: result[i] G remaining[guess[i]] - 1 # 第二遍标记黄色或灰色 for i in range(n): if result[i] G: continue ch guess[i] if ch in remaining and remaining[ch] 0: result[i] Y remaining[ch] - 1 else: result[i] W return result这段代码的核心是“先绿后黄”的两遍扫描。如果只做一遍循环遇到重复字母时很容易给出错误的反馈。先扣除绿色占用的次数再判断黄色就能防止把同一个字母额度重复使用。5.2 模块二排除法求解器有了评估函数接下来实现候选词过滤和基础求解流程。# 文件路径wordle.py from typing import Iterable def filter_candidates( candidates: list[str], guess: str, feedback: list[str], ) - list[str]: 根据一次猜测的反馈过滤候选词列表。 参数 candidates: 当前候选词列表 guess: 本次猜测 feedback: evaluate_guess 返回的反馈数组 返回 仍有可能成为答案的词列表 result [] for word in candidates: if matches_feedback(word, guess, feedback): result.append(word) return result def matches_feedback(word: str, guess: str, feedback: list[str]) - bool: 核心判断某个候选词 word 是否与当前反馈保持一致。 判断方式是假设 word 就是答案重新计算一次反馈 如果计算出的反馈和真实反馈完全一致就保留该词。 return evaluate_guess(word, guess) feedback这里用了一个非常聪明的 trick不直接写一堆条件判断而是“假设某个词就是答案评估一次反馈如果和真实反馈一致就认定它仍然是候选词”。为什么这个写法更好因为evaluate_guess本身已经处理好了重复字母逻辑过滤逻辑不用重复实现第二遍。这种“用评估函数反向验证”的思路在测试驱动开发里非常实用也减少了条件遗漏的风险。接下来是基础求解器# 文件路径wordle.py def solve_by_elimination( dictionary: list[str], answer: str, max_attempts: int 6, verbose: bool True, ) - int | None: 使用排除法求解 Wordle。 返回 猜中时的次数1 到 max_attempts如果超次未中返回 None candidates list(dictionary) # 第一猜默认选第一个词实际项目中可以换成语料里最常见的词 guess candidates[0] for attempt in range(1, max_attempts 1): feedback evaluate_guess(answer, guess) if verbose: print(f第 {attempt} 次猜测{guess}反馈{.join(feedback)}) if feedback [G] * 5: if verbose: print(f猜中了答案就是 {answer}) return attempt candidates filter_candidates(candidates, guess, feedback) if not candidates: if verbose: print(候选词列表已为空请检查词典或反馈逻辑) return None guess candidates[0] if verbose: print(f{max_attempts} 次内未猜中最终候选词{candidates}) return None这个排除法能跑通完整流程但它有个明显短板每次只是“选候选词里的第一个词”并没有考虑“哪个词能给下一轮带来最多信息”。在候选词数量很多时选择哪一个词会显著影响收敛速度这就是下一小节信息熵出场的原因。5.3 模块三信息熵优化版本信息熵的核心思想是对于当前候选词列表如果我选择某个词作为猜测那么每个可能的反馈结果会把我当前的候选词分成不同数量的子集。反馈结果越“均匀”说明猜这个词之后的不确定性越小也就是信息量越大。我们选择能让“期望信息量”最大的词。计算公式是import math def calculate_entropy(candidates: list[str], guess: str) - float: 计算猜测 guess 对当前候选词列表的期望信息熵。 # 统计每个反馈模式对应的候选词数量 pattern_count {} for word in candidates: fb tuple(evaluate_guess(word, guess)) pattern_count[fb] pattern_count.get(fb, 0) 1 total len(candidates) entropy 0.0 for count in pattern_count.values(): p count / total entropy - p * math.log2(p) return entropy def best_guess_by_entropy(candidates: list[str], full_dictionary: list[str]) - str: 从全量词典中选择一个能带来最大信息熵的猜测词。 注意猜测词不一定要在候选词列表里因为好的猜测可能来自 已经确定不可能的单词它能更快地缩小答案范围。 best_word candidates[0] best_score -1.0 # 如果候选词很少只在候选词里选减少计算量 search_space candidates if len(candidates) 100 else full_dictionary for word in search_space: score calculate_entropy(candidates, word) if score best_score: best_score score best_word word return best_word信息熵版本的求解器只需要把solve_by_elimination中的guess candidates[0]改成guess best_guess_by_entropy(candidates, dictionary)为什么要从全量词典里选词而不是只从候选词里选举个例子如果当前候选词只有两个且都是“____t”结尾那么猜测一个包含更多不同字母的词比如“crane”可能得到非常分散的反馈帮助你瞬间锁定答案。这个“crane”很可能已经不在候选词列表里但它的信息价值很高。这个细节正是很多 Wordle 求解器效果不佳的原因他们只看候选词不利用外部单词来获得更多信号。5.4 模块四Agent 工具调用模拟现在进入第三个层次把求解能力封装成工具模拟 Agent 调用工具的完整链路。在真实的 AI Agent 工程中大模型通常是“决策者”工具是“执行者”。决策者根据当前状态决定调用哪个工具执行者返回精确结果决策者再根据结果做下一步决定。这个过程可以用一个最小化的循环来演示。# 文件路径agent_demo.py 模拟 Agent 调用工具解决 Wordle 任务。 这里不依赖真实大模型而是用一个小型决策逻辑模拟模型行为。 如果你要接入真实大模型可以把 choose_tool 替换为 LLM 调用。 from wordle import ( evaluate_guess, filter_candidates, best_guess_by_entropy, ) # 微型词典用于演示 DICTIONARY [ crane, slate, crisp, train, plane, brave, grape, dream, ] # 模拟每个工具的描述 TOOLS { get_feedback: 根据答案和猜测词返回绿黄灰反馈, filter_candidates: 根据反馈过滤候选词列表, select_best_guess: 从候选词中选择信息量最大的猜测词, } def run_agent_demo(answer: str, max_attempts: int 6) - int | None: 以 Agent 循环的方式求解 Wordle。 每个循环内Agent 会依次调用工具更新自己的状态。 candidates list(DICTIONARY) guess best_guess_by_entropy(candidates, DICTIONARY) print(fAgent 可用工具{list(TOOLS.keys())}) print(f任务开始真正的答案是{answer}仅演示用) print(- * 40) for attempt in range(1, max_attempts 1): print(f\n[Agent 状态] 当前第 {attempt} 次尝试) print(f[Agent 决策] 调用 select_best_guess 工具选择猜测词{guess}) # 调用工具 1获取反馈 feedback evaluate_guess(answer, guess) print(f[工具返回] get_feedback 执行完毕反馈{.join(feedback)}) if feedback [G] * 5: print(f[结果] 第 {attempt} 次猜中答案{answer}) return attempt # 调用工具 2过滤候选词 before_count len(candidates) candidates filter_candidates(candidates, guess, feedback) after_count len(candidates) print(f[工具返回] filter_candidates 执行完毕候选词数量{before_count} - {after_count}) if not candidates: print([错误] 候选词列表为空说明词典或反馈逻辑有问题) return None # 调用工具 3选择下一个猜测 guess best_guess_by_entropy(candidates, DICTIONARY) print(f[工具返回] select_best_guess 执行完毕下一猜测词{guess}) print(f[结果] {max_attempts} 次内未猜中) return None if __name__ __main__: run_agent_demo(answercrane)这段代码里的“Agent”其实是一个固定决策序列选择猜测、获取反馈、过滤候选、再选择猜测。但它展示了一个非常重要的工程模式——我们把每个能力拆成了独立的工具函数任何一个环节都可以替换成真实的大模型调用而不用重写整条链路。如果在真实的 Agent 项目里接大模型逻辑会是Agent 收到“当前候选词有 8 个”这一状态由大模型决定“调用 select_best_guess”工具执行完返回一个词大模型再决定“调用 get_feedback”如此循环。大模型只负责决策不负责计算反馈或统计词频这样能大幅降低幻觉风险也更容易排查错误。6. 运行结果与效果验证写完代码后先在终端跑一下排除法版本python -c from wordle import solve_by_elimination; solve_by_elimination([crane, slate, crisp, train, plane, brave, grape, dream], answercrane)预期输出类似第 1 次猜测crane反馈GGGGG 猜中了答案就是 crane因为是微型词典且第一个词就是答案所以一次猜中。为了验证过滤逻辑可以故意让答案和首猜不同比如python -c from wordle import solve_by_elimination; solve_by_elimination([crane, slate, crisp, train, plane, brave, grape, dream], answerslate)这时会看到系统经过多轮猜测逐步缩小范围最终猜中。如果候选词列表为空第一个要怀疑的就是evaluate_guess里重复字母的处理。再跑一下 Agent 演示python agent_demo.py预期会看到 Agent 每轮打印“选择猜测词”“获取反馈”“过滤候选词”的过程最后输出第几次猜中。这里建议重点观察候选词数量的变化从第一轮到最后一轮数量应该逐步下降直到收敛到 1。如果某轮候选词数量不降反升说明反馈逻辑或词典清单存在重复单词。判断成功的标准有三个能在 6 次内猜中答案。每轮候选词数量呈下降趋势。没有任何一轮反馈与答案状态矛盾。运行失败时按以下顺序排查先单独测evaluate_guess的已知用例再测filter_candidates最后才看整个求解循环。不要从头到尾看一遍代码找 bug那样效率很低。7. 常见问题与排查思路问题现象可能原因排查方式解决方案第一轮候选词就归零词典里有非五字母词或重复单词检查词典数据清洗词典确保词长均为 5重复字母的判断错误评估函数没有处理“先绿后黄”用evaluate_guess(crane, cocoa)这类用例测试改成两遍扫描先扣绿色再判黄色排除法猜了很多次仍不中每次选第一个候选词信息量太差打印每轮候选词数量换用信息熵版本best_guess_by_entropy接入大模型后AI 一直猜不在词典里的词模型在“生成”而不是“选择”检查提示词是否限制了动作空间提示词要求模型只能从给定候选词列表中选一个作为输出且必须调用工具LLM 返回格式不稳定无法解析模型自由发挥不遵守 JSON 格式查看原始响应内容使用结构化输出约束在代码里做格式校验解析失败时返回重试API 调用次数过多消耗 credits 太快Agent 循环里每次决策都调用大模型打印每次工具调用的 token 和费用对简单的状态更新改用本地函数大模型只负责关键决策第 4 和第 5 个问题在真实 Agent 开发里尤其常见。很多从算法转向 Agent 开发的人会高估大模型的能力让它自由生成答案结果遇到幻觉和格式不稳定。好的工程做法是把动作空间缩到最小让大模型只做“选择”而不是“创造”。8. 最佳实践与工程建议8.1 从“小词典跑通”开始再换全量词典这是我在做小型 AI 项目时最推荐的方式。先用 10 个词跑通逻辑再换 1000 个词最后换全量词典。每一次换词表你都有可能发现新边界问题但定位 bug 的成本会低很多。如果一开始就上全量词典遇到问题时你甚至无法判断是词典问题还是逻辑问题。8.2 状态显式化不要依赖记忆在这个项目里候选词列表就是 Agent 的“记忆”。在真实的 Agent 工程中你应当把关键状态放到显式的变量、数据库或配置里而不是依赖大模型的上下文记忆。大模型对长上下文的注意力会衰减对状态描述的精确度也会下降。显式状态 工具更新是当前最稳定的 Agent 模式。8.3 工具函数要单一职责evaluate_guess只做评估、filter_candidates只做过滤、best_guess_by_entropy只做选择。这个设计也许看起来有点“多此一举”但当你把一个普通脚本改造成 Agent 工具时单一职责的价值会立刻体现出来——每个函数都能独立测试也都能独立被大模型调用。如果一开始就把所有逻辑写在一个大函数里后面很难拆分。8.4 日志是 Agent 调试的命脉Agent 项目比普通脚本难调试得多因为每一轮都有“决策”环节。建议把下面这些信息全部打出来当前轮次和状态调用的工具名工具返回结果状态更新后的变化候选词数量、剩余次数等run_agent_demo里的打印就是一个最小示例。生产级项目里应该换成结构化日志把这些信息写入文件或日志系统方便回放和定位。8.5 控制大模型的调用次数接真实大模型时每一次 API 调用都对应一次成本。Wordle 这个小问题六轮就能解决但如果你不加限制Agent 可能因为解析失败、重试、各种异常路径实际调用几十次。建议在 Agent 循环中设置全局最大调用次数并且在每次调用前都检查是否已触发上限。这个机制不是可选项是生产环境的必需项。8.6 安全边界与权限最小化虽然是演示项目但“Agent 调用工具”这个模式进入生产环境后工具本身可能涉及文件读取、数据库写入、第三方 API 等操作。原则是能不放权就不放权能只读就不要写能局部作用就不要全局作用。每一个工具函数都应该校验入参并明确它能做什么、不能做什么。在 Wordle 项目里体现为所有函数都只接受字符串和列表不访问文件系统不执行外部命令。9. 总结与后续学习方向“Wordle but small AI mini challenges”这个项目最有价值的地方不是把猜词游戏做得多完美而是通过一个足够小的场景把 AI 工程里的核心链路完整走了一遍规则建模、状态更新、策略选择、工具封装、Agent 循环。你得到的不是一个只能用于猜词的玩具而是一套可以迁移到其他任务的模板。下一步可以从三个方向继续深入。第一换一个更大的真实词典统计信息熵版本在全量词表上的平均猜测次数看看和排除法相差多少第二接入真实大模型把run_agent_demo里的固定决策序列替换成 LLM 调用比较“LLM 直接猜词”和“LLM 选择工具”两种模式的效果差异第三把同样的架构迁移到其他游戏或任务上比如数字猜谜、二十问、甚至简单的命令行工具编排。这个项目也顺带解释了热搜里常看到的几个概念AI Agent 开发到底在做什么AI 幻觉为什么可怕credits 在 AI 调用里为什么重要。说到底一个可靠的 Agent 不是让模型自由发挥而是给模型一个受控环境、一组清晰工具、一条可回滚的逻辑链路。Wordle 只是第一步但它是一块非常通透的敲门砖。建议收藏备用动手敲一遍代码收获会比只看文章大得多。