
把 Wordle 改造成一组小型 AI 挑战是我最近在练手时觉得性价比很高的方向。Wordle 规则很简单六次机会猜一个五字母英文单词每次猜测会返回绿、黄、灰三种标记。绿代表位置和字母都对黄代表字母对但位置不对灰代表字母不在答案里。正因为规则清晰、反馈闭环短它非常适合用来测试 AI 在受限场景下的表现。这个 mini challenges 并不想做成一个完整游戏而是想回答一个问题让 AI 在信息不完整、反馈逐轮更新的条件下能不能稳定完成任务、控制猜测次数、遵守输出格式。如果你正在学 LLM 接口调用、提示词工程或者想找一个小型 agent 练手项目Wordle 这个场景比普通对话式 Demo 更有价值。1. 先说结论这类小挑战最值得练什么1.1 Wordle 为什么适合做 AI 小实验很多人在接触大模型之后最喜欢做的练习是让模型写文章、写代码、聊天但这些任务缺乏明确的对错标准很难判断模型到底有没有进步。Wordle 不一样。每一轮反馈都是确定的不是模型自己说自己猜得怎么样而是程序根据答案判定出来的。你可以把每一轮历史和反馈当作可观测数据去分析模型到底哪里出了问题。还有一个特点任务规模可控。五字母单词有限词表一轮最多六次猜测。跑一局通常只需要几次 API 调用耗时几十秒以内。这意味着你可以做大量对比实验比如同一个挑战集用不同模型跑同一模型用不同提示词跑最后用统计结果判断方案好坏。和动辄需要长链路 agent 任务相比它的调试成本极低。我一般会建议把第一版写成一个不依赖模型的规则脚本。这样做有两个作用一方面验证游戏逻辑和反馈函数本身没写错另一方面给 AI 方案提供一个基线分数。如果没有基线分数模型跑赢了或跑输了你都不知道该怎么评价。1.2 把挑战拆成三个难度等级这套 mini challenges 的梯度可以设计成基础接口调用、多轮自动闭环、策略优化。三个等级分别对应三个实际能力目标。第一级只测模型能不能根据规则输出合法猜测。你不需要写完整游戏只需要把游戏规则放在 system 提示词里给模型一个例子然后让模型输出一个五字母单词。这一步主要确认 API 调用、解析返回值、token 控制这些基本功。第二级把游戏闭环跑起来。程序负责根据答案生成反馈把历史反馈放进对话上下文让模型在每一轮看到之前的猜测和反馈后继续猜。这一步开始涉及状态管理你不能只把最新一轮反馈传给模型而是要把完整历史都带进去否则模型很容易迷失。第三级才是“挑战”真正成立的地方。给模型或脚本限制猜测次数统计一批挑战的成功率、平均步数、无效词率。你可以让模型自己说明理由也可以让模型从候选词表里选择。到这里它就从一个对话示例升级成可量化评估的小型 AI 任务了。2. 环境准备先跑通一条最小链路2.1 最小依赖和运行条件本地跑这套小挑战不需要高配置。CPU 就够不需要显卡因为推理大部分发生在模型服务端。你需要准备的是Python 3.9 以上环境、一个 API 客户端库、一份五字母单词表、一个 API key。我通常用 OpenAI 的 Python SDK 做示例如果你用的是其他兼容服务代码逻辑基本不变只是把客户端地址、密钥和模型名换成自己的。为了避免把密钥写死在代码里用环境变量读取export OPENAI_API_KEY你的key然后安装依赖pip install openai如果不想装 SDK用 requests 也能做只是要多处理一层 JSON 结构。学习阶段用 SDK 更省事。2.2 词表是很容易翻车的环节很多人觉得词表不重要实际上它是影响结果最大的因素之一。如果词表里混入专有名词、缩写、复数形式模型和脚本的候选判断都会被带偏。很多公开词表会包含低频词和生僻古词测试时会出现模型完全不认识的情况。我建议先做一次清洗。统一成小写字母过滤掉长度不是 5 的词再排除包含标点和数字的词。如果你用的是 Linux 系统自带词表可以直接筛grep -E ^[a-z]{5}$ /usr/share/dict/words | head -n 3000 wordlist.txt如果不是系统词表就从公开英文单词列表里下载然后按同样的规则清洗。清洗后固定成一份wordlist.txt每行一个词。到底用多大词表取决于目标。如果只是演示流程一千个常见词足够如果想更接近真实 Wordle 难度可以准备两三千个词。词表不是越大越好因为生僻词会增加模型输出非法词的概率也会让基线过滤算法的候选空间变大导致策略难收敛。2.3 接口调用前至少确认三件事调用 API 前我会先做三个最小验证。第一密钥能不能正常鉴权。第二选的模型能不能接受低温参数。第三返回内容能不能被稳定解析成单个词。一个容易忽略的坑是模型在收到“请输出一个单词”后仍然可能输出解释性文字比如“我猜是 crate”。如果你把整段返回当成猜测后面一定出问题。解决办法是把 max_tokens 设小比如 10并在 prompt 里强调“只输出单词不要输出其他内容”。如果还不行就加一道后处理用正则提取纯字母部分import re def clean_guess(text): match re.search(r\b([a-z]{5})\b, text.lower()) return match.group(1) if match else 这个函数放在所有调用的统一出口能避免后面很多环节被脏数据污染。3. 核心逻辑反馈函数必须能处理重复字母3.1 先写一个简单但严格的反馈函数Wordle 的反馈函数看起来简单很多人第一版会写成这样def feedback_simple(guess, answer): result [] for i, ch in enumerate(guess): if ch answer[i]: result.append(G) elif ch in answer: result.append(Y) else: result.append(B) return .join(result)这个实现对于大部分单词没问题但遇到重复字母会出错。举个例子答案是 apple猜测是 pspae简单逻辑会把多余且不存在的 p 标成黄色实际标准规则会优先保证每个字母的使用次数。为了让反馈准确需要用计数来控制from collections import Counter def feedback(guess, answer): n len(answer) status [B] * n remaining Counter(answer) # 第一轮标记绿色 for i, ch in enumerate(guess): if ch answer[i]: status[i] G remaining[ch] - 1 # 第二轮标记黄色 for i, ch in enumerate(guess): if status[i] B and remaining.get(ch, 0) 0: status[i] Y remaining[ch] - 1 return .join(status)这个版本先处理所有正确位置的字母把它们的数量从计数中扣掉然后再处理剩下的字母。这样重复字母就不会被误判成黄色或绿色。3.2 用边界样例验证反馈函数写完反馈函数后不要直接进入下一步先跑几个边界用例。我常用这几组answer apple, guess apple反馈应该是 GGGGG。answer apple, guess pspae看重复字母 p 是否只标一次。answer moody, guess mmmma看 m 是否只标一个绿色后续 m 不会标黄色。完全不相干的词比如 answer stone, guess ccccp应该全 B。为什么要花时间验证这里因为后续所有关于模型能力的结论都建立在反馈正确的基础上。如果反馈函数本身有误模型再怎么优化都会被错误信息带偏。大量 mini challenge 实验跑完发现结果异常最后定位到反馈函数少处理了重复字母这种情况很常见。4. 猜词策略规则基线和大模型的区别4.1 基线法每次把候选词过滤掉一批把 Wordle 当成搜索问题最稳定的解法其实是穷举过滤。每次根据反馈从候选词表中删掉不可能的词。比如绿色位置必须匹配黄色字母必须存在且不在当前位置灰色字母在答案里不出现。下面是一个简化版的候选过滤函数def filter_words(words, guess, fb): candidates [] for word in words: ok True for i, st in enumerate(fb): if st G and word[i] ! guess[i]: ok False break if st Y and (guess[i] not in word or word[i] guess[i]): ok False break if st B and guess[i] in word: ok False break if ok: candidates.append(word) return candidates这个版本对重复字母处理得还不够细但已经能说明问题。你可以继续优化比如优先选择能最大化信息增益的词而不是只选候选集合里第一个合法词。信息熵的做法在 Wordle 社区已经很成熟核心思想是选一个词让下一次候选集合的期望规模最小。我建议至少先把基线跑起来。原因很简单后续用 LLM 猜词时可以用基线作为对照。如果大模型在同样挑战集上的成功率、平均步数没有超过基线那说明模型只是在“看起来聪明”并没有真正利用反馈做推理。4.2 让大模型基于反馈猜词大模型猜词最直接的方式是把之前的猜测和反馈全部放进对话历史然后让模型输出下一个猜测。每次拿到新反馈后追加一条消息进入下一轮调用。import os from openai import OpenAI client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) def ask_next_guess(history, modelgpt-4o-mini): messages [ {role: system, content: You are playing Wordle. Output only a 5-letter English word, no explanation.} ] for guess, fb in history: messages.append({role: user, content: fGuess: {guess} - {fb}}) messages.append({role: user, content: Next guess:}) resp client.chat.completions.create( modelmodel, messagesmessages, temperature0.2, max_tokens10, ) return resp.choices[0].message.content.strip().lower()这里有几个关键点。第一history 是完整列表不是只给最新一轮。模型如果只看到当前反馈无法推导出之前哪些字母已经被排除。第二temperature 不要调太高。Wordle 是需要遵守约束的任务不是创意生成任务高随机性会让模型输出奇怪单词。第三max_tokens 要小避免输出多余内容。关于模型名例子里的 gpt-4o-mini 只是一个通用示例实际替换成你能访问的模型即可。模型选择会影响性能但不会改变整体流程。小模型能力弱一点更需要把提示词写清楚。4.3 如何判断 AI 是真的在推理还是在背词跑几局之后你会发现一个现象有些词模型一眼就能猜中有些词它会在五轮里反复围绕同一个字母组合打转。要判断模型是否在真正利用反馈可以看一组中间指标。一个是“无效词率”也就是模型输出了不在合法词表里的词。这个比例高说明模型输出控制能力不够。另一种叫法就是典型的大模型幻觉在具体任务里的表现。另一个是“反馈利用率”通过日志观察模型在获得黄色反馈后下一轮是否尝试把该字母放到其他位置。如果连续多轮都没有改变多半是历史上下文或提示词设计不足。还有一种判断方式对同一份挑战集把历史顺序打乱后再跑一遍。如果模型成绩明显变差说明它多少依赖上下文顺序推理如果成绩完全没变化那就要怀疑它是不是只靠词频在猜。不过这个实验比较繁琐一般做了几轮 baseline 对比之后再考虑即可。5. 批量跑 mini challenges别只看一局5.1 构造一个可复现的挑战集单独跑几局只能说明流程通没通不能说明方案好坏。为了评估 AI 能力我建议准备一个固定挑战集比如从词表里随机挑 50 个词作为答案设置随机种子保证每次复现。import random import json def load_words(path): with open(path, r, encodingutf-8) as f: return [w.strip().lower() for w in f if w.strip()] words load_words(wordlist.txt) random.seed(42) challenge_set random.sample(words, 50)用随机种子后无论跑多少次挑战集都一样。这样你改提示词、换模型、调参数之后结果可以直接对比不会因为题目不同而产生误差。5.2 记录和统计指标跑单个挑战时我会把结果存成一条记录包含目标词、是否成功、猜测历史、总猜测次数。跑完之后统一汇总。示例格式如下{ target: apple, ok: true, guesses: 4, history: [crate, apply, ample, apple] }统计时主要看四个指标成功率、成功局平均猜测次数、失败局数、无效词率。成功率决定方案能不能用平均次数决定效率无效词率能反映模型输出控制能力。只看成功率容易忽略无效词问题因为即使中途输出过非法词只要后续猜中看起来也是成功的但真实流程里非法词应该被直接判负或重试。把这些指标整理成表格指标数值成功局数42 / 50成功率84%成功局平均猜测次数3.8失败局数8无效词率12%表格本身只是结果更重要的是异常分析。我会特意检查失败局是集中在某些生僻词还是所有词都存在问题。如果是生僻词说明词表覆盖不好如果是随机失败说明模型推理稳定性不足。5.3 成本、重试和输出文件批量跑 50 个词每个词最多六次猜测总共最多 300 次调用。这个量级实际消耗不大但也不能完全忽略。如果每一轮都塞入完整历史并使用大尺寸模型token 消耗会随轮数增长。因此我会在每轮打印进度并记录当前累计调用次数。批量任务里一定会遇到临时失败比如限流、超时、返回空内容。我建议加一个简单重试机制同一轮最多重试两次重试之间等待几秒。如果还是失败把这一条标注为 api_error而不是直接当成猜词失败。发起调用的部分可以单独封装成函数统一处理重试和日志主循环会干净很多。输出文件要按时间命名避免覆盖之前的实验结果。比如results_20250612_1430.json。这样后面做多模型对比时才知道每个实验文件对应的模型和提示词版本。6. 实测中容易踩的坑和排查顺序6.1 模型一直猜同一个词最常见的问题是模型连续几轮都输出同一个词比如 crate、crate、crate。出现这种情况先不要怀疑模型傻了。第一步看 history 是否真的把每一轮反馈都传给了模型。如果你在循环里只保存了 guess没有保存 feedback模型看到的内容自然没变化。第二步看 temperature 是不是设置得过高或过低。过低时模型倾向于选择概率最高词即使它已经被反馈排除过高时会忽略约束。在 Wordle 这种场景我一般用 0.1 到 0.3。第三步看提示词里是不是要求模型输出解释性内容导致实际猜测内容被截断成同一个词。先抓一下模型返回的原始文本再判断是不是解析问题。6.2 反馈看起来没被用上有时候模型没有一直猜同一个词但明显在用词性联想而不是逻辑排除。比如答案给了 apple前一次猜 peach反馈里黄色字母已经说明 a、e 存在但位置不对模型下一步却猜 grape把 p 和 a 放在完全不符合反馈的位置。这种问题通常不是 API 调用的问题而是 prompt 设计不足。模型需要看到“当前可用字母”和“当前位置排除”的约束而不是只看到原始的 G/Y/B 字符串。你可以尝试把历史反馈翻译成更直白的约束比如“字母 a 不在第 1 位字母 e 在第 5 位字母 p 不在第 2 位”。实践里这种自然语言化的约束往往比生硬的反馈代码更有效。另外要确认反馈生成函数没有把 G 和 Y 解释反了。如果标记反了模型也会看起来完全没利用反馈。6.3 API 报错、token 超限和限流批量跑多了之后API 类报错会越来越多。常见的有鉴权失败、余额不足、请求频率超限、返回内容为空。我的排查顺序是先看报错文本再查 key 和服务配置再看调用频次最后看是否某个 prompt 太长导致 token 超限。如果是限流错误不要盲目增加并发先在循环里加 sleep 或指数退避。这套小挑战本身不需要高并发一次跑一个词就会稳很多。如果连续多次失败把发起调用单独写成函数在函数里做重试和日志。还有一类容易被忽略的问题是模型返回了完整句子比如输出“The answer is apple”你直接取值后得到的是整句话。解决办法不仅是 max_tokens 调小还要写一个抽取函数用正则提取句中唯一的五字母小写单词就像前面 2.3 里的 clean_guess 那样。7. 边界在哪里以及下一步值得做什么7.1 别让 LLM 去做穷举题Wordle 从本质上是个有限搜索问题词表只有几千个词时用脚本做信息熵选择会比大多数 LLM 更稳定。所以不要把“超过脚本基线”作为这组挑战的唯一目标。LLM 在省配置、灵活交互、自然语言理解方面有优势但纯计算和信息维护不是它的强项。这也意味着如果你的目标是做一个真正高胜率的 Wordle AI直接用传统算法写脚本最稳妥。把 LLM 引入这个场景更多是为了学习多轮状态管理、反馈利用、输出约束这些通用能力。搞清楚这个边界后续实验方向才不会跑偏。7.2 可以继续做的方向这套 mini challenges 后续可以扩展的方向很多。比如同一挑战集上对比不同模型观察模型规模对推理表现的影响也可以换成纯本地模型看看本地模型的输出控制能力和云端模型差多少还可以把每一轮候选列表交给模型让它从候选词里选模拟一个带工具调用的 agent。另一个值得做的方向是把它变成一个可视化学习页面前端展示每次猜测和反馈后端记录模型决策日志。这样不仅能提升实验效率也能让非技术背景的人理解 AI 在约束下做决策的过程。不管扩到哪一步我都建议先把当前这套基础流程跑顺。先单任务再批量先规则基线再模型方案。等结果稳定之后再去做多模型对比和 agent 化改造。这套小挑战真正的价值不是赛过大模型或者赛过脚本而是让你对 AI 应用开发的整个反馈闭环变得敏感起来。