
在开发 AI 应用时很多人容易陷入“只会调接口”的误区真正能落地的项目往往需要把游戏规则、前后端交互和模型调用结合起来。Wordle 是一款规则简单但反馈逻辑并不简单的猜词游戏正好可以作为 AI 小挑战的载体。本文将带你从零搭建一个 Wordle 小游戏并加入三个独立的 AI 小挑战AI 猜词提示、AI 生成谜题、AI 游戏点评。整个项目基于 Python Flask OpenAI API 实现前端只使用一个简单的 HTML 页面适合想学习 Web 开发、AI 工程实践以及 Prompt 设计的新手开发者。这个项目不追求复杂架构重点是把一条完整的 AI 应用开发链路打通前端发起请求后端处理游戏逻辑再通过模型接口生成内容。你可以把它当成一个可以持续扩展的模板后续无论是接入更多模型还是增加多人对战、排行榜都能在这个基础上进行。如果你正准备做自己的第一个 AI 编程小项目这篇文章值得读完。1. 项目背景与概念1.1 Wordle 游戏规则回顾Wordle 最初是一个基于网页的单词猜谜游戏每天只有一个目标单词。玩家需要在有限次数内通过输入 5 字母单词来推断出目标词。每次提交猜测后系统会针对每一个字母给出三种反馈状态绿色表示字母和位置都正确黄色表示字母出现了但位置不对灰色表示字母没有出现在答案中。例如目标词是apple你猜crane系统会根据每个位置的字母返回对应颜色。这个过程看似简单但判断逻辑需要考虑重复字母的情况。如果目标词中只有一个p而猜测词中有两个p那么只有一个p能显示黄色或绿色另一个必须显示灰色。这个细节决定了 Wordle 的规则完整性也是我们在后端实现中最需要小心的部分。1.2 “small AI mini challenges”如何理解项目标题“Wordle but small AI mini challenges”可以理解为“以 Wordle 为底座的多个小型 AI 挑战集合”。这里的小挑战并不是让你直接调一个模型返回文本而是把 AI 能力拆分成几个任务每个任务都围绕 Wordle 场景展开。第一个挑战是 AI 猜词提示。玩家在进行了若干次猜测后可以让 AI 根据目前的猜测记录推荐一个更有可能猜中答案的单词。第二个挑战是 AI 生成谜题。在传统词表之外可以让 AI 现场生成一个 5 字母单词作为目标词让每一局游戏都充满随机性。第三个挑战是 AI 游戏点评。游戏结束后AI 根据玩家的猜测序列生成一段总结性的点评既有鼓励性又有趣味性。这样的设计让开发者可以分别练习 Prompt 编写、JSON 解析、异常处理等能力而不是一次性把“智能”堆在游戏入口。每一类小挑战都有清晰的输入输出边界后续换模型、换提示词都更方便。1.3 项目整体架构从技术架构上看项目分为三部分前端页面、后端 Flask 服务、AI 模型接口。前端只负责收集用户输入、展示反馈结果并调用后端提供的几个/api接口。后端承担 Wordle 规则判断、会话状态管理、AI 调用封装等核心职责。AI 服务通过 OpenAI 的 Chat Completions 接口完成但为了不把 API Key 暴露在浏览器中所有模型请求都在后端完成。这样分层带来的好处很明显前端可以随时替换成小程序、桌面应用或者命令行版本后端游戏逻辑与 AI 能力互相独立便于单元测试API Key 不会出现在浏览器网络请求中安全性更高。对于这个项目来说Flask 足够轻量不需要引入 Django 这样重量级的框架。2. 环境准备与项目结构2.1 运行环境与依赖本文示例使用 Python 3.10 及以上版本操作系统不限。由于项目是基于 Flask 的 Web 应用你需要安装以下依赖Flask 用于搭建 Web 服务OpenAI SDK 用于调用 Chat Completions 接口python-dotenv 用于读取环境变量文件。版本需要根据你的项目实际情况调整。本文示例以常见环境为例重点演示配置思路。建议你使用虚拟环境管理依赖避免全局 Python 环境出现冲突。在 Windows、Linux 或 macOS 下虚拟环境创建命令略有差异但使用方式基本一致。2.2 创建项目结构在开始写代码前先创建一个干净的目录结构。建议命名为wordle-ai-mini-challenges然后按照下面的结构创建文件和文件夹wordle-ai-mini-challenges/ ├── app.py ├── requirements.txt ├── .env.example ├── .env └── templates/ └── index.htmlapp.py是 Flask 应用入口包含游戏逻辑和所有 API 路由。requirements.txt用于记录依赖包。.env.example是环境变量模板.env是我们本地实际使用的环境变量文件。由于templates目录是 Flask 默认寻找模板的位置所以前端页面放在这个目录下。下面创建一个requirements.txt文件内容如下flask2.3,3.1 openai1.0,2.0 python-dotenv1.0使用以下命令安装依赖pip install -r requirements.txt如果你使用的是 conda也可以先创建虚拟环境再执行上面的命令。安装完成后建议继续阅读环境变量配置。2.3 配置环境变量项目用到两个关键环境变量OPENAI_API_KEY和FLASK_SECRET_KEY。OPENAI_API_KEY是你调用模型接口时需要的凭证不能直接写死在代码里更不能提交到 Git 仓库。FLASK_SECRET_KEY用于 Flask 的 session 加密是保障服务安全的基础配置。在项目根目录创建.env.example文件作为配置文件模板OPENAI_API_KEYsk-xxxx OPENAI_BASE_URLhttps://api.openai.com/v1 OPENAI_MODELgpt-4o-mini FLASK_SECRET_KEYrandom-secret-key然后复制为.env并填入自己的 API Keycp .env.example .envOPENAI_BASE_URL是可选项。如果你使用的是 OpenAI 兼容接口的其他服务可以修改这个地址来适配。OPENAI_MODEL建议根据自己的账号权限调整不同模型的能力和价格差别比较大。注意.env文件不要提交到版本库建议在.gitignore中加入它。3. 核心设计思路与 AI 接入3.1 猜词评判逻辑Wordle 的评判逻辑是项目的核心算法。标准规则要求先判断绿色再判断黄色最后剩下的才是灰色。这个顺序很重要因为只有先确定位置完全匹配的字母才能正确处理重复字母的分配问题。我们用一个没有重复字母的例子来理解目标词是stone猜测词是shine。s在第 0 位匹配标记为绿色h不在目标词中标记为灰色i不在目标词中灰色n在目标词中但位置不同标记为黄色e位置一样标记为绿色。最终结果是两个绿色、一个黄色、两个灰色。在实现时需要先建立一个目标词的字母计数表。第一轮遍历把所有字母和位置都匹配的位置标成绿色同时从计数表中扣掉对应字母。第二轮遍历如果某个猜测字母仍然在计数表中且数量大于 0就标记为黄色并扣减数量否则标记为灰色。这样就能正确处理重复字母。3.2 AI 服务封装为了让后端代码更简洁我们把所有模型调用统一封装成一个ai_chat函数。这个函数接收消息数组和温度参数内部调用 OpenAI Chat Completions 接口并返回模型回复的文本内容。如果请求出错函数不会让整个服务崩溃而是记录日志并返回None由调用方决定如何处理。为什么要单独封装因为在实际项目中模型接口可能因为网络、配额、超时等原因失败。把异常处理集中在一个函数内可以避免在每个路由里重复写try...except。同时以后如果要切换到其他兼容模型只需要修改这一处封装对上层业务几乎无感。3.3 Prompt 设计要点Prompt 是整个 AI 小挑战效果好坏的关键。对 AI 猜词提示这个任务我们在 Prompt 中明确告诉模型只输出一个单词不要解释。对 AI 生成谜题我们要求输出一个 5 字母英文单词并去掉标点和引号。对 AI 点评我们限制字数和风格让输出更稳定。在编写 Prompt 时建议把任务指令放在前面再把上下文数据传入。用 JSON 字符串来传递猜测记录可以让模型快速理解数据结构。同时把温度参数设置得低一些更适合需要确定结果的场景。记住Prompt 不是越复杂越好而是越明确越好。3.4 三个 AI 小挑战设计第一个小挑战是 AI 猜词提示。玩家的猜测记录会以数组形式发送到后端后端把这些记录放进 Prompt请求模型返回一个推荐单词。为了避免模型直接说出答案我们不把目标词告诉模型只告诉它玩家已经猜过哪些词。这样 AI 的建议更有推理意义。第二个小挑战是 AI 生成谜题。点击“新游戏AI 生成”按钮后后端请求模型生成一个 5 字母单词校验合法性后写入 session作为本局目标词。如果模型返回的单词长度不是 5或者包含非字母字符就自动回退到内置词表保证游戏可以继续。第三个小挑战是 AI 游戏点评。在玩家猜中或放弃后前端把整局猜测序列发送给后端模型根据猜测序列生成本局总结。这个挑战可以练习多状态输入 Prompt也能锻炼后端处理模型返回文本的能力。4. 完整实战从零实现 Wordle AI 小挑战4.1 编写 Flask 后端在项目根目录创建app.py以下是完整后端代码。代码中包含词表、猜词判断逻辑、AI 调用封装和三个 API 路由。import os import random import json import logging from flask import Flask, request, session, jsonify, render_template from dotenv import load_dotenv load_dotenv() app Flask(__name__) app.secret_key os.getenv(FLASK_SECRET_KEY, dev-only-secret) from openai import OpenAI _client_kwargs {api_key: os.getenv(OPENAI_API_KEY, not-configured)} if os.getenv(OPENAI_BASE_URL): _client_kwargs[base_url] os.getenv(OPENAI_BASE_URL) client OpenAI(**_client_kwargs) MODEL_NAME os.getenv(OPENAI_MODEL, gpt-4o-mini) logging.basicConfig(levellogging.INFO) WORDS [ apple, crane, stone, radio, shine, brand, cloud, train, plant, house, mouse, round, peace, dream, green, light, water, paper, table, chair, think, quick, brown, river, ] def evaluate_guess(target, guess): if len(target) ! len(guess): return None result [] target_chars list(target) status [None] * len(target) for i, (g, t) in enumerate(zip(guess, target)): if g t: status[i] correct target_chars[i] None for i, g in enumerate(guess): if status[i] correct: result.append({letter: g, status: correct}) elif g in target_chars: status[i] present target_chars[target_chars.index(g)] None result.append({letter: g, status: present}) else: status[i] absent result.append({letter: g, status: absent}) return result def ai_chat(messages, temperature0.7): try: response client.chat.completions.create( modelMODEL_NAME, messagesmessages, temperaturetemperature, ) return response.choices[0].message.content except Exception: app.logger.exception(AI service call failed) return None def clean_word(text): candidates text.strip().split() if not candidates: return word candidates[0].strip(\ .!?) return word.lower() app.route(/) def index(): return render_template(index.html) app.route(/api/new_game, methods[POST]) def new_game(): target random.choice(WORDS) session[target_word] target return jsonify({word_length: len(target), message: 新游戏已开始}) app.route(/api/ai_generate_word, methods[POST]) def ai_generate_word(): prompt 生成一个随机的 5 字母英文单词只输出这个单词不要引号不要解释。 reply ai_chat([{role: user, content: prompt}], temperature0.8) word clean_word(reply) if reply else if len(word) ! 5 or not word.isalpha(): word random.choice(WORDS) session[target_word] word return jsonify({word: word, word_length: len(word)}) app.route(/api/guess, methods[POST]) def guess(): data request.get_json() word data.get(word, ).strip().lower() target session.get(target_word) if not target: target random.choice(WORDS) session[target_word] target if len(word) ! len(target): return jsonify({error: f请输入长度为 {len(target)} 的单词}), 400 result evaluate_guess(target, word) win all(r[status] correct for r in result) if win: return jsonify({result: result, win: True, target_word: target}) return jsonify({result: result, win: False, target_word: None}) app.route(/api/ai_hint, methods[POST]) def ai_hint(): data request.get_json() guesses data.get(guesses, []) if not guesses: return jsonify({error: 至少要先猜一次再让 AI 给提示}), 400 prompt ( 你是 Wordle 游戏助手。玩家已有以下猜测记录 f{json.dumps(guesses, ensure_asciiFalse)}\n 请根据这些猜测推荐一个更有可能猜中答案的 5 字母英文单词。 只输出一个单词不要解释不要加引号。 ) reply ai_chat([ {role: system, content: 你是一个 Wordle 解题助手回答尽可能简短。}, {role: user, content: prompt} ]) if not reply: return jsonify({error: AI 服务暂时不可用请稍后重试}), 502 return jsonify({hint: clean_word(reply)}) app.route(/api/ai_review, methods[POST]) def ai_review(): data request.get_json() guesses data.get(guesses, []) win data.get(win, False) prompt ( f玩家玩 Wordle 的猜测序列{json.dumps(guesses, ensure_asciiFalse)}\n f最终是否猜中{是 if win else 否}\n 请用 40 字以内给出一句中文点评风格轻松友好不透露答案。 ) reply ai_chat([ {role: system, content: 你是 Wordle 游戏解说员文字活泼但不夸张。}, {role: user, content: prompt} ]) if not reply: return jsonify({error: AI 服务暂时不可用请稍后重试}), 502 return jsonify({review: reply.strip()}) if __name__ __main__: app.run(debugTrue)上面的代码里evaluate_guess是核心规则函数ai_chat是所有 AI 请求的通用入口。路由/api/new_game和/api/ai_generate_word都会设置 session 中的目标词前者使用内置词表后者由 AI 生成。这样做的好处是游戏的普通模式和 AI 生成模式完全解耦后续增加数据库存储也会很方便。4.2 编写前端页面在templates目录下创建index.html。为了便于直接复制运行这里把 CSS 和 JavaScript 都写在同一个 HTML 文件中。前端主要负责展示字母块颜色、保存猜测记录、调用后端接口并显示 AI 结果。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleWordle AI Mini Challenges/title style body { font-family: system-ui, sans-serif; max-width: 560px; margin: 48px auto; background: #1a1a2e; color: #eee; padding: 0 16px; } h1 { font-size: 28px; text-align: center; } .board { display: grid; gap: 8px; margin: 24px 0; } .row { display: flex; gap: 8px; } .tile { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; border-radius: 6px; background: #333; text-transform: uppercase; } .tile.correct { background: #538d4e; } .tile.present { background: #b59f3b; } .tile.absent { background: #3a3a3c; } .controls { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px 0; } input { padding: 10px; font-size: 18px; border: none; border-radius: 6px; width: 160px; } button { padding: 10px 14px; font-size: 15px; border: none; border-radius: 6px; background: #0f3460; color: #fff; cursor: pointer; } button:hover { opacity: 0.85; } .ai-output { margin-top: 20px; padding: 14px 16px; background: #16213e; border-radius: 8px; min-height: 24px; line-height: 1.6; white-space: pre-wrap; } .message { margin-top: 8px; font-size: 14px; opacity: 0.8; } /style /head body h1Wordle AI Mini Challenges/h1 div idboard classboard/div div classcontrols input idguessInput maxlength5 placeholder输入英文单词 button onclicksubmitGuess()提交猜测/button button onclickaiHint()AI 提示/button button onclickaiReview()AI 点评/button /div div classcontrols button onclicknewGame(false)新游戏内置词表/button button onclicknewGame(true)新游戏AI 生成/button /div div idmessage classmessage/div div idaiOutput classai-output/div script let guesses []; let lastWin false; function setMessage(text) { document.getElementById(message).textContent text; } function render() { const board document.getElementById(board); board.innerHTML ; for (const g of guesses) { const row document.createElement(div); row.className row; for (const item of g.result) { const tile document.createElement(div); tile.className tile item.status; tile.textContent item.letter; row.appendChild(tile); } board.appendChild(row); } } function clearInput() { document.getElementById(guessInput).value ; } async function api(url, body) { const res await fetch(url, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(body || {}) }); const data await res.json(); if (!res.ok) { throw new Error(data.error || 请求失败); } return data; } async function newGame(ai) { try { if (ai) { const data await api(/api/ai_generate_word); setMessage(已由 AI 生成新谜题长度 data.word_length); } else { const data await api(/api/new_game); setMessage(新游戏已开始长度 data.word_length); } guesses []; lastWin false; document.getElementById(aiOutput).textContent ; render(); clearInput(); } catch (e) { setMessage(e.message); } } async function submitGuess() { const input document.getElementById(guessInput); const word input.value.trim().toLowerCase(); if (!word) { setMessage(请输入单词); return; } try { const data await api(/api/guess, { word }); guesses.push({ word, result: data.result }); lastWin data.win; render(); if (data.win) { setMessage(恭喜猜中答案是 data.target_word); } else { setMessage(未猜中继续加油。); } clearInput(); } catch (e) { setMessage(e.message); } } async function aiHint() { if (guesses.length 0) { setMessage(至少先猜一次再让 AI 给提示); return; } try { const data await api(/api/ai_hint, { guesses: guesses.map(g g.word) }); document.getElementById(aiOutput).textContent AI 提示 data.hint; } catch (e) { document.getElementById(aiOutput).textContent e.message; } } async function aiReview() { if (guesses.length 0) { setMessage(先进行几轮猜测再生成点评); return; } try { const data await api(/api/ai_review, { guesses: guesses.map(g g.word), win: lastWin }); document.getElementById(aiOutput).textContent AI 点评 data.review; } catch (e) { document.getElementById(aiOutput).textContent e.message; } } // 页面加载时自动开始一个新游戏 newGame(false); /script /body /html前端使用原生 JavaScript 请求后端接口不依赖任何前端框架。render函数会把每次猜测的字母块按状态渲染成不同颜色。api函数封装了fetch统一处理 JSON 请求和错误信息。三个 AI 挑战按钮分别调用后端对应的接口然后把结果展示在aiOutput区域。4.3 运行与验证完成以上两个文件后在项目根目录执行启动命令python app.py如果一切正常可以看到类似下面的日志输出* Running on http://127.0.0.1:5000然后打开浏览器访问http://127.0.0.1:5000。建议先用“内置词表”模式测试基础功能输入一个 5 字母单词观察字母块颜色是否正确。再点击“AI 提示”确认 AI 返回了一个合法单词。如果你使用的是命令行工具也可以用 curl 测试后端接口例如启动服务后执行curl -X POST http://127.0.0.1:5000/api/new_game返回结果应包含word_length字段。继续执行curl -X POST http://127.0.0.1:5000/api/guess \ -H Content-Type: application/json \ -d {word:apple}后端会返回每个字母的状态。注意不同人的 session 不同目标词也可能不同因此反馈结果不一定是标准答案但长度反馈必须正确。4.4 预期效果正常运行时页面会展示一个空的 Wordle 棋盘。每猜一个单词棋盘会增加一行字母块显示对应颜色。点击“AI 提示”后AI 输出区域会显示一个推荐词这个推荐词不一定保证正确但应该在词性和长度上都符合规则。点击“AI 生成”按钮会开启一局目标词由 AI 生成的新游戏。如果 AI 服务未配置或调用失败页面不会白屏而是显示“AI 服务暂时不可用”的提示普通游戏功能仍然可以正常使用。这种降级设计在生产环境非常重要它保证了核心功能的可用性。5. 常见问题与排查思路在实际运行过程中最容易遇到的问题集中在环境变量、依赖版本和 AI 调用三个方面。下面整理了一份常见问题排查表。问题现象常见原因解决思路Flask 启动时报错找不到render_template项目结构缺少templates目录或 HTML 文件放错位置确认index.html位于templates目录下页面能打开但提交猜测返回 400输入单词长度与目标词不一致后端校验长度后返回错误提示检查前端是否限制了maxlengthAI 调用失败后台提示AuthenticationErrorOPENAI_API_KEY未填写或 Key 无效检查.env文件和 API Key 权限AI 提示返回空字符串模型返回格式不符合预期在ai_chat中增加日志查看原始回复AI 生成的单词长度不是 5模型没有严格遵循指令后端已做回退处理也可以优化 Prompt 增加示例每次刷新页面目标词都变浏览器没有携带 session cookie确认未使用无痕模式或 Flasksecret_key是否固定代码中导入OpenAI报错openaiSDK 版本低于 1.0检查requirements.txt是否安装成功如果问题仍然无法解决建议先在后端日志中查看完整报错信息再根据报错关键字去搜索解决方案。排查顺序可以按照依赖 - 环境变量 - 网络连通性 - Prompt 格式 - 数据校验依次进行。另外调试 API 时可以使用浏览器开发者工具打开 Network 面板观察请求和返回的 JSON 结构这通常是定位前后端不一致的最快手段。6. 最佳实践与工程建议6.1 异常处理与降级在 AI 应用中模型调用是最不稳定的环节。无论你使用哪家模型服务都应该在设计时预留好降级方案。本文示例中当 AI 调用返回None时后端会返回 502 错误前端会显示提示信息但游戏的核心猜词功能不受影响。对于更复杂的场景你还可以引入本地缓存、备用模型或固定回复保证用户体验。6.2 日志与监控不要把日志只写在except中。对于 Prompt 请求和模型返回建议记录到结构化日志中方便后期分析和调优。尤其在开发阶段可以打印出完整的请求消息和响应内容帮助快速定位 Prompt 格式问题。生产环境则需要脱敏后再存储避免 API Key 或用户敏感信息泄露。6.3 安全边界API Key 永远不要出现在前端代码中。本文所有 AI 调用都在 Flask 后端完成这是一种默认安全的分层方式。如果你要部署到公网还需要注意开启 HTTPS、配置请求频率限制、校验输入长度避免恶意用户超量调用 AI 接口。尽量遵循最小权限原则给后端服务单独配置一个专用于当前项目的 Key而不是使用最高权限的管理员 Key。6.4 成本控制与缓存模型调用按 token 计费如果不加控制一个简单的猜词游戏也可能产生较高的费用。建议在后端加入请求频率限制例如每个 IP 每分钟最多调用 10 次 AI 接口。对于 AI 生成谜题这类结果可以缓存的接口可以把生成结果保存到 Redis 或内存中减少重复调用。Prompt 本身也要精简只传上下文必要的数据不要每次都把用户的所有历史记录全部发送。7. 总结与后续扩展通过这个项目你可以把 Wordle 的游戏规则、Flask 接口设计、OpenAI API 调用和基础 Prompt 工程串联起来。整个项目从判断逻辑到前端交互都是可控的适合反复修改和扩展。你可以尝试把目标词的长度从 5 改成 6也可以把词表换成中文成语甚至可以把 AI 提示从“只给一个词”升级成“给一段策略分析”。下一步推荐学习方向是函数调用和流式输出。在 Chat Completions 接口中开启流式响应可以让 AI 提示逐字显示提升交互体验。你还可以把项目的ai_chat函数抽象成独立模块接入更多小游戏形成一套属于自己的 AI 应用工具库。如果你在运行过程中遇到了卡在依赖安装、AI 返回异常或颜色判断不对的问题欢迎在评论区留下你的现象和日志摘要。动手改一改代码比只看文章印象深得多。