1. 一个家长的技术自救:为什么要自己动手做AI英语工具我家孩子上小学三年级,英语成绩一直在班里中游晃荡。不是他不聪明,而是传统的英语学习方式对他完全不起作用——背单词像嚼蜡,读课文像念经,每次辅导作业都是一场亲子关系的消耗战。我试过市面上几乎所有主流的英语学习App,要么是游戏化太重、孩子只顾着玩里面的小游戏,要么是内容太枯燥、打开两次就再也不想碰。更关键的是,这些工具大多是“一刀切”的标准化内容,根本没法根据孩子的实际水平和兴趣点做调整。我本身是一名后端开发工程师,日常工作就是跟代码和系统架构打交道。大概一个月前,我突然冒出一个念头:既然市面上的工具都不合适,那我为什么不自己做一个?而且现在AI编程辅助工具已经相当成熟,我完全可以用“vibe coding”的方式——也就是跟着感觉走、边想边写、让AI帮我补全大量代码——快速把想法落地。说干就干,我花了整整一个月的业余时间,从零开始搭建了一个专门给我家孩子用的AI英语学习工具。这个工具的核心逻辑很简单:用大语言模型做底层能力支撑,把“背单词、读句子、练对话、改作文”这四件事串成一个完整的学习闭环,并且所有内容都根据孩子的实际水平动态生成。它不是一个通用的英语学习平台,而是一个“千人千面”的私人辅导系统。举个例子,孩子最近对恐龙特别感兴趣,我就可以让工具生成一篇关于霸王龙的英文小短文,生词量控制在他当前词汇量+5%的范围内,读完之后自动生成三道阅读理解题,再让他用英文回答“你最喜欢哪种恐龙,为什么”。整个过程不需要我手动备课,AI全包了。这篇文章我会把这个工具的设计思路、技术选型、核心模块的实现细节、以及我在开发过程中踩过的坑,全部拆开来讲清楚。如果你也是一名开发者,或者是一个懂点技术的家长,想给孩子做一个类似的东西,这篇文章可以直接当作参考方案来用。即使你完全不懂编程,也可以看看这个思路,了解一下现在的AI工具到底能做到什么程度。2. 整体架构设计:一个月从零到能用的技术选型思路2.1 为什么选择“轻前端+重AI后端”的架构在动手之前,我先花了两天时间做技术选型。核心原则只有一条:我一个人开发,一个月要能用,所以能简则简,把复杂度尽量推给AI模型本身。前端我选了最熟悉的React+Tailwind CSS组合。原因很直接:React的组件化开发模式非常适合这种功能模块清晰的应用(单词卡片、对话窗口、作文批改结果展示都是独立组件),而Tailwind能让我在不写一行自定义CSS的情况下快速搭出还算顺眼的界面。孩子用的东西,界面不能太丑,但我也没时间精雕细琢,Tailwind的预设样式刚好够用。后端我选了Python的FastAPI框架。这里有一个关键考量:AI相关的生态工具几乎都是Python优先的。无论是调用大模型API的SDK,还是做文本处理、分词、难度评估的库,Python的选项都最丰富。FastAPI本身性能足够,异步支持好,写起来也快,对于我这种一个人维护的项目来说,开发效率比什么都重要。数据库方面,我用了SQLite。你没看错,就是那个“玩具级”的SQLite。为什么不用PostgreSQL或者MySQL?因为这个工具目前只有我家一个孩子在用,并发量约等于零,SQLite完全扛得住,而且零配置、单文件、备份方便。等以后如果真的有多人使用的需求,再迁移到PostgreSQL也就是改几行连接配置的事。这里分享一个选型心得:个人项目千万不要一上来就追求“生产级架构”。什么微服务、消息队列、Redis缓存,在用户量只有个位数的时候全是负担。先把功能跑通,等真的有性能瓶颈了再优化,这才是个人开发者最务实的路径。2.2 AI能力层:大语言模型到底怎么选这是整个项目最核心的决策。我对比了当时市面上几个主流的大语言模型API,从四个维度做了评估:英文生成质量、中文理解能力、响应速度、价格。英文生成质量是首要指标,因为工具的核心功能就是生成英语学习内容。我实测下来,不同模型在英文写作的自然度上差距很明显。有些模型生成的英文句子语法没问题,但读起来就是“不对劲”,像是教科书里硬凑出来的例句,缺乏真实语境下的语感。而另一些模型生成的英文更接近母语者的表达习惯,这对培养孩子的语感至关重要。中文理解能力同样重要,因为我要用中文给AI下指令,比如“生成一篇适合三年级学生阅读的关于恐龙的短文,生词量控制在5个以内”。如果模型对中文指令的理解有偏差,生成的内容就会跑偏。响应速度直接影响使用体验。孩子做一道题等个十几秒,注意力早就飞走了。我实测发现,流式输出(streaming)是必须的,让文字一个字一个字地蹦出来,孩子等待的感知时间会大幅缩短。价格方面,我算了一笔账:假设每天生成20篇短文、批改10篇作文、进行50轮对话练习,每轮平均消耗500个token,一个月下来大概消耗几十万到百万级别的token。不同模型的单价差异很大,但总体来说,这个量级的成本完全在个人可承受范围内,每个月几十块钱就够了。最终我选择了一个在英文生成质量和价格之间平衡得比较好的模型,并且做了双模型备份的设计——主模型不可用时自动切换到备用模型。这个设计后来救了我好几次,有一次主模型服务临时波动,备用模型无缝接管,孩子完全没感觉到异常。2.3 数据层设计:怎么记录孩子的学习轨迹数据模型的设计直接决定了工具能不能“越用越懂孩子”。我设计了四张核心表:用户画像表:记录孩子的年级、当前词汇量估算值、兴趣爱好标签、学习偏好(比如喜欢看图还是喜欢读文字)。学习记录表:每次学习行为的详细记录,包括学习内容、耗时、正确率、AI评估的掌握程度。词汇库表:记录孩子已经掌握和正在学习的单词,每个单词有熟悉度评分,采用类似间隔重复算法的逻辑来安排复习。生成内容缓存表:AI生成的内容不会用完就扔,而是存下来。如果孩子过几天想重读之前那篇恐龙文章,可以直接从缓存读取,不用重新调用API,既省钱又快速。这四张表的设计有一个核心逻辑:让数据形成闭环。孩子每次的学习行为都会更新用户画像和词汇库,而更新后的数据又会作为下一次AI生成内容的输入参数。比如孩子今天学了“fossil”这个词,词汇库里就会记录,下次生成恐龙相关文章时,AI会自然地复现这个词,形成复习效果。3. 核心功能模块拆解:四个闭环是怎么跑通的3.1 单词模块:从死记硬背到语境化学习传统背单词最大的问题是脱离语境。孩子背了“apple”是苹果,但在句子里看到“apple”还是要反应半天。我的做法是:每个单词都放在一个完整的、有趣的句子里呈现,而且这个句子是根据孩子的兴趣动态生成的。具体流程是这样的:孩子点击“学新词”,系统从词库中选取一个适合他当前水平的生词(基于词频和难度分级),然后调用AI生成一个包含这个单词的短句或小段落。如果孩子喜欢足球,AI就会生成“The player kicked the ball so hard that it flew over the fence.”这样的句子,而不是干巴巴的“He kicked the ball.”孩子读完句子后,系统会问三个问题:这个词在句子中是什么意思?你能用这个词造一个新句子吗?你觉得这个词和之前学过的哪个词意思相近?这三个问题分别对应理解、应用、关联三个认知层次,比单纯的选择题有效得多。实操心得:AI生成例句时,一定要在prompt里明确要求“句子长度不超过15个单词,使用小学阶段常见语法结构”。我一开始没加这个限制,AI生成了一堆带从句的长难句,孩子根本读不懂。后来加了限制,效果立刻好了很多。单词的复习机制我参考了间隔重复的思路,但没有完全照搬Anki那套复杂的算法。我的简化版逻辑是:每个单词有一个熟悉度分数(0-100),答对一次加10分,答错一次减20分。分数低于30的每天出现,30-60的隔两天出现,60-80的隔一周出现,80以上的基本不再主动推送,但会在生成阅读材料时自然复现。这套逻辑简单粗暴,但实测下来效果不差。3.2 阅读模块:AI生成的分级读物这是整个工具里孩子最喜欢的功能。传统分级读物的问题是:级别是固定的,但孩子的兴趣是变化的。RAZ读到C级,所有C级的内容都是同样的难度,但孩子可能对其中某些主题完全不感兴趣。我的做法是:难度由系统控制,主题由孩子选择。孩子可以从一个主题列表里选(恐龙、太空、足球、动物、美食等),系统根据他的词汇量水平自动调整生成内容的难度。难度控制的核心参数有三个:参数说明典型值(三年级)目标词汇量文章中不重复单词的总数80-120词生词比例超出孩子当前词汇的单词占比5%-8%句子平均长度每个句子的平均单词数8-12词生成阅读材料时,我会把这些参数写进prompt里,同时附上孩子已经掌握的词汇列表(从词汇库表里取),让AI尽量使用已知词汇,只在必要时引入生词。生成的文章会附带三个阅读理解问题,问题的难度也分三个层次:细节理解、推理判断、个人观点表达。孩子读完文章、答完题之后,系统会自动把文章中的生词加入词汇库,并根据答题正确率调整这些生词的初始熟悉度。答对了,熟悉度从40起步;答错了,从20起步。这样下次复习时,答错的词会更早出现。3.3 对话模块:跟AI练口语的降级方案说实话,真正的口语练习需要语音识别和语音合成,技术复杂度高,而且孩子的发音识别准确率一直是个难题。我一开始也想做语音对话,但实测了几个语音识别方案后发现,对儿童发音的识别效果都不太理想,经常把“three”识别成“free”,把“rabbit”识别成“rapid”,反而打击孩子的积极性。所以我做了一个降级方案:文字对话为主,语音输入为辅。孩子可以用键盘打字跟AI对话,也可以用语音输入(调用系统自带的语音转文字功能),但AI的回复以文字形式呈现,同时提供朗读功能(调用浏览器的SpeechSynthesis API)。对话的场景也是AI动态生成的。比如孩子选了“在餐厅点餐”的场景,AI会扮演服务员,用简单的英语跟孩子对话。对话的难度会根据孩子的回应动态调整——如果孩子连续几次回答得又快又准,AI会逐渐增加句子的复杂度和词汇难度;如果孩子卡壳了,AI会主动降低难度,或者给出提示。这里有一个很关键的细节:AI在对话中要“装傻”。我一开始让AI扮演一个全知全能的对话者,结果孩子说“I want a hamburger”,AI回了一大段“Great choice! Our hamburgers are made with 100% beef and served with fresh lettuce...”孩子直接懵了。后来我在prompt里明确要求“每次回复不超过两个句子,使用简单词汇,必要时用中文解释”,效果就好多了。3.4 写作模块:从批改到引导的转变写作是最难做好的模块。传统的AI批改就是找出语法错误、拼写错误,然后给个分数。但这对提高写作能力帮助有限——孩子看到满屏红字,第一反应是沮丧,而不是学习。我的做法是把批改变成引导。孩子写完一篇小短文后,AI不会直接指出错误,而是先给一个整体评价(“你的故事很有趣,我特别喜欢关于恐龙化石的那段描述”),然后提出一两个引导性问题(“你能告诉我为什么主人公决定去挖掘化石吗?我觉得这里可以写得更详细一些”),最后才指出最关键的1-2个语言问题(“注意一下‘dinosaur’的拼写,你写成了‘dinosour’”)。这种方式的逻辑是:先保护表达欲,再纠正语言错误。孩子写作文最怕的就是被打击,如果每次写完都是一堆错误,他很快就会失去写作的兴趣。而先肯定内容、再引导完善、最后纠正关键错误的三步法,让孩子觉得“我写的东西有人认真读了,而且他觉得我写得好”。AI批改的prompt里我加了几个约束条件:每次最多指出两个语言错误、必须用鼓励性语言开头、引导性问题不超过两个。这些约束看起来简单,但效果非常明显。我家孩子现在每周主动写两三篇英文小短文,这在以前是完全不可想象的。4. 实操过程:从零搭建的完整步骤和关键代码4.1 环境准备与项目初始化整个项目的搭建过程我尽量做到“最小依赖”。后端只需要Python 3.10+和几个pip包,前端用Vite创建React项目即可。后端依赖清单如下:pip install fastapi uvicorn openai sqlalchemy pydantic python-dotenv这里解释一下每个包的作用:FastAPI是Web框架,uvicorn是ASGI服务器,openai是调用大模型API的官方SDK,sqlalchemy是ORM工具(虽然我用SQLite,但ORM能让代码更清晰),pydantic做数据校验,python-dotenv管理环境变量(API密钥不能硬编码在代码里)。前端初始化:npm create vite@latest english-tool -- --template react cd english-tool npm install npm install axios tailwindcss项目结构我保持了最简单的分层:backend/ main.py # FastAPI入口 models.py # 数据库模型 ai_service.py # AI调用封装 prompts.py # 所有prompt模板 routers/ words.py # 单词模块接口 reading.py # 阅读模块接口 dialogue.py # 对话模块接口 writing.py # 写作模块接口 frontend/ src/ components/ # 可复用组件 pages/ # 页面 api/ # API调用封装4.2 AI调用的核心封装所有AI调用我都通过一个统一的函数来走,这样方便做错误处理、重试、日志记录和成本统计。核心代码如下:import openai from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)) async def call_ai(prompt: str, system_prompt: str = "", temperature: float = 0.7) -> str: try: response = await openai.ChatCompletion.acreate( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} ], temperature=temperature, max_tokens=1500 ) return response.choices[0].message.content except Exception as e: logger.error(f"AI call failed: {e}") raise这里有几个关键设计点。temperature参数我根据不同模块做了区分:生成阅读材料时用0.7(需要一定的创造性),批改作文时用0.3(需要稳定和一致),对话练习时用0.8(需要更多的变化和趣味性)。重试机制用了tenacity库,指数退避策略,最多重试3次。异步调用是必须的,因为AI响应通常需要几秒钟,同步调用会阻塞整个服务。4.3 阅读材料生成的完整实现这是最核心的功能,我把完整的prompt模板和调用逻辑贴出来:READING_PROMPT_TEMPLATE = """ 你是一位专业的少儿英语教师。请根据以下要求生成一篇英语阅读材料: 主题:{topic} 目标词汇量:{target_vocab_count}个不重复单词 生词比例:不超过{new_word_ratio}% 句子平均长度:{avg_sentence_length}个单词 孩子已掌握的词汇(尽量使用这些词):{known_words} 要求: 1. 文章长度控制在{article_length}词左右 2. 语言要生动有趣,符合{grade}年级学生的认知水平 3. 生词要自然融入上下文,便于通过语境猜测词义 4. 文章末尾附3道阅读理解题,分别考察细节理解、推理判断、个人观点 5. 标注出所有生词及其在文中的含义 请以JSON格式输出,包含以下字段: - title: 文章标题 - content: 文章正文 - new_words: 生词列表,每个词包含word和meaning - questions: 问题列表,每个问题包含question和type """调用时,我会从数据库里取出孩子的词汇库和用户画像,填充到模板里:async def generate_reading(user_id: int, topic: str): user = get_user_profile(user_id) known_words = get_known_words(user_id, limit=500) prompt = READING_PROMPT_TEMPLATE.format( topic=topic, target_vocab_count=user.target_vocab_count, new_word_ratio=user.new_word_ratio, avg_sentence_length=user.avg_sentence_length, known_words=", ".join(known_words), article_length=user.article_length, grade=user.grade ) result = await call_ai(prompt, temperature=0.7) data = json.loads(result) # 存入缓存 cache_reading(user_id, topic, data) # 更新词汇库 update_vocabulary(user_id, data["new_words"]) return data实操心得:让AI输出JSON格式时,一定要在prompt里明确写出JSON的字段结构,并且加上“请以JSON格式输出”这句话。我试过不写字段结构,AI返回的JSON字段名每次都不一样,解析起来非常痛苦。另外,建议在代码里加一层JSON解析的容错处理,因为AI偶尔会在JSON外面包一层markdown代码块标记,需要先strip掉。4.4 对话模块的状态管理对话模块的难点在于维护对话上下文。每次孩子发消息,都需要把之前的对话历史一起发给AI,否则AI会“失忆”。但对话历史不能无限增长,否则token消耗会越来越大,而且AI的注意力会被稀释。我的做法是:保留最近10轮对话,更早的对话做摘要压缩。具体实现是,当对话轮次超过10轮时,把最早的两轮对话交给AI做一句话摘要,然后把摘要作为系统消息的一部分保留,原始对话丢弃。async def chat(user_id: int, message: str): history = get_chat_history(user_id, limit=10) summary = get_chat_summary(user_id) system_prompt = f"""你是一位耐心的少儿英语对话伙伴。 当前对话场景:{get_current_scene(user_id)} 对话摘要:{summary} 要求:每次回复不超过两个句子,使用简单词汇,必要时用中文解释。""" messages = [{"role": "system", "content": system_prompt}] for h in history: messages.append({"role": h.role, "content": h.content}) messages.append({"role": "user", "content": message}) response = await call_ai_with_messages(messages, temperature=0.8) save_chat_message(user_id, "user", message) save_chat_message(user_id, "assistant", response) return response4.5 前端交互的关键细节前端我重点做了两件事:降低操作门槛和增加即时反馈。降低操作门槛方面,所有功能入口都在首页以卡片形式呈现,孩子点一下就能进入对应模块。每个模块内部的操作步骤不超过三步。比如阅读模块:选主题→读文章→答题,没有多余的设置页面。即时反馈方面,所有AI调用都做了loading状态和流式输出。孩子点击“生成文章”后,文字会一个字一个字地出现,而不是等好几秒后突然全部显示。这个细节对保持孩子的注意力非常关键。流式输出的前端实现核心代码:const response = await fetch('/api/reading/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ topic, userId }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const text = decoder.decode(value); setContent(prev => prev + text); }5. 踩坑实录与常见问题排查5.1 AI生成内容不可控怎么办这是最常见的问题。AI有时候会“自由发挥”,生成超出孩子水平的内容,或者偏离主题。我总结了几个有效的约束技巧:第一,在prompt里给出具体的负面示例。比如“不要生成类似‘The intricate ecosystem of the Cretaceous period...’这样的句子”,比单纯说“使用简单词汇”有效得多。第二,用JSON schema约束输出格式。让AI按照固定的字段结构输出,可以大幅减少格式混乱的问题。第三,加一层后处理校验。生成的内容我会用代码检查几个指标:句子平均长度是否超标、生词比例是否在范围内、是否包含敏感内容。如果校验不通过,自动重新生成一次。第四,temperature不要设太高。我一开始设了1.0,结果AI生成的内容天马行空,后来降到0.7就稳定多了。5.2 孩子觉得“跟AI说话很傻”怎么破这个问题我一开始没预料到。我家孩子第一次跟AI对话时,回了一句“我不想跟机器人说话”,然后就跑了。后来我做了几个调整:给AI一个具体的角色和名字。不要叫“AI助手”,而是叫“Alex老师”或者“Dino(恐龙伙伴)”,让孩子觉得是在跟一个角色对话,而不是跟一个机器。设计有明确目标的对话任务。不要让孩子“随便聊”,而是给他一个任务,比如“帮Alex老师找到去图书馆的路”、“向Dino介绍你最喜欢的恐龙”。有目标感,孩子才有参与的动力。加入游戏化元素。完成一轮对话后给星星奖励,攒够星星可以解锁新的对话场景。这个机制虽然简单,但对小学生非常有效。5.3 API调用成本失控的预防我第一个月试用期间,因为没做缓存和限流,API费用比预期高了三四倍。后来做了几个优化:优化措施效果实现方式内容缓存减少40%重复调用相同参数的内容直接读缓存对话历史压缩减少30% token消耗超过10轮做摘要请求合并减少15%调用次数批量生成多个单词的例句模型分级减少50%成本简单任务用便宜模型模型分级这个策略特别值得说一下。不是所有任务都需要用最贵的模型。比如生成单词例句这种相对简单的任务,用便宜的小模型完全够用;而批改作文、生成阅读材料这种需要较强理解能力的任务,才用贵的大模型。我把这个逻辑封装成了一个路由函数,根据任务类型自动选择模型。5.4 常见问题速查表问题现象可能原因排查方法解决方案AI返回内容为空API密钥失效或额度用完检查API控制台余额更换密钥或充值生成内容难度忽高忽低prompt中难度参数不稳定打印每次请求的prompt固定参数值,增加校验对话历史丢失数据库写入失败检查SQLite文件权限修复权限或迁移数据库前端流式输出卡顿网络延迟或后端阻塞查看浏览器Network面板增加超时重试,优化后端异步生词重复出现词汇库更新逻辑有bug检查update_vocabulary函数修复去重逻辑孩子使用时间过长缺乏时间限制无增加每日使用时长提醒最后分享一个我踩过的最大的坑:不要试图一次性做完所有功能。我一开始想同时做单词、阅读、对话、写作四个模块,结果每个都做了一半,哪个都不好用。后来我改变策略,先把阅读模块做完整、做稳定,让孩子先用起来,然后再逐个添加其他模块。这种“单点突破、逐步扩展”的方式,比“全面铺开、齐头并进”高效得多。这个工具到现在已经跑了快两个月,我家孩子的英语成绩从班里中游提到了前几名,更重要的是,他现在会主动打开这个工具去读一篇恐龙文章或者跟AI聊几句。作为一个开发者,能用自己写的代码解决自己孩子的问题,这种感觉比完成任何一个公司项目都有成就感。如果你也有类似的需求,我建议你不要想太多,先从一个最小的功能开始做起来,剩下的会在做的过程中自然清晰。
1. 一个家长的技术自救:为什么要自己动手做AI英语工具我家孩子上小学三年级,英语成绩一直在班里中游晃荡。不是他不聪明,而是传统的英语学习方式对他完全不起作用——背单词像嚼蜡,读课文像念经,每次辅导作业都是一场亲子关系的消耗战。我试过市面上几乎所有主流的英语学习App,要么是游戏化太重、孩子只顾着玩里面的小游戏,要么是内容太枯燥、打开两次就再也不想碰。更关键的是,这些工具大多是“一刀切”的标准化内容,根本没法根据孩子的实际水平和兴趣点做调整。我本身是一名后端开发工程师,日常工作就是跟代码和系统架构打交道。大概一个月前,我突然冒出一个念头:既然市面上的工具都不合适,那我为什么不自己做一个?而且现在AI编程辅助工具已经相当成熟,我完全可以用“vibe coding”的方式——也就是跟着感觉走、边想边写、让AI帮我补全大量代码——快速把想法落地。说干就干,我花了整整一个月的业余时间,从零开始搭建了一个专门给我家孩子用的AI英语学习工具。这个工具的核心逻辑很简单:用大语言模型做底层能力支撑,把“背单词、读句子、练对话、改作文”这四件事串成一个完整的学习闭环,并且所有内容都根据孩子的实际水平动态生成。它不是一个通用的英语学习平台,而是一个“千人千面”的私人辅导系统。举个例子,孩子最近对恐龙特别感兴趣,我就可以让工具生成一篇关于霸王龙的英文小短文,生词量控制在他当前词汇量+5%的范围内,读完之后自动生成三道阅读理解题,再让他用英文回答“你最喜欢哪种恐龙,为什么”。整个过程不需要我手动备课,AI全包了。这篇文章我会把这个工具的设计思路、技术选型、核心模块的实现细节、以及我在开发过程中踩过的坑,全部拆开来讲清楚。如果你也是一名开发者,或者是一个懂点技术的家长,想给孩子做一个类似的东西,这篇文章可以直接当作参考方案来用。即使你完全不懂编程,也可以看看这个思路,了解一下现在的AI工具到底能做到什么程度。2. 整体架构设计:一个月从零到能用的技术选型思路2.1 为什么选择“轻前端+重AI后端”的架构在动手之前,我先花了两天时间做技术选型。核心原则只有一条:我一个人开发,一个月要能用,所以能简则简,把复杂度尽量推给AI模型本身。前端我选了最熟悉的React+Tailwind CSS组合。原因很直接:React的组件化开发模式非常适合这种功能模块清晰的应用(单词卡片、对话窗口、作文批改结果展示都是独立组件),而Tailwind能让我在不写一行自定义CSS的情况下快速搭出还算顺眼的界面。孩子用的东西,界面不能太丑,但我也没时间精雕细琢,Tailwind的预设样式刚好够用。后端我选了Python的FastAPI框架。这里有一个关键考量:AI相关的生态工具几乎都是Python优先的。无论是调用大模型API的SDK,还是做文本处理、分词、难度评估的库,Python的选项都最丰富。FastAPI本身性能足够,异步支持好,写起来也快,对于我这种一个人维护的项目来说,开发效率比什么都重要。数据库方面,我用了SQLite。你没看错,就是那个“玩具级”的SQLite。为什么不用PostgreSQL或者MySQL?因为这个工具目前只有我家一个孩子在用,并发量约等于零,SQLite完全扛得住,而且零配置、单文件、备份方便。等以后如果真的有多人使用的需求,再迁移到PostgreSQL也就是改几行连接配置的事。这里分享一个选型心得:个人项目千万不要一上来就追求“生产级架构”。什么微服务、消息队列、Redis缓存,在用户量只有个位数的时候全是负担。先把功能跑通,等真的有性能瓶颈了再优化,这才是个人开发者最务实的路径。2.2 AI能力层:大语言模型到底怎么选这是整个项目最核心的决策。我对比了当时市面上几个主流的大语言模型API,从四个维度做了评估:英文生成质量、中文理解能力、响应速度、价格。英文生成质量是首要指标,因为工具的核心功能就是生成英语学习内容。我实测下来,不同模型在英文写作的自然度上差距很明显。有些模型生成的英文句子语法没问题,但读起来就是“不对劲”,像是教科书里硬凑出来的例句,缺乏真实语境下的语感。而另一些模型生成的英文更接近母语者的表达习惯,这对培养孩子的语感至关重要。中文理解能力同样重要,因为我要用中文给AI下指令,比如“生成一篇适合三年级学生阅读的关于恐龙的短文,生词量控制在5个以内”。如果模型对中文指令的理解有偏差,生成的内容就会跑偏。响应速度直接影响使用体验。孩子做一道题等个十几秒,注意力早就飞走了。我实测发现,流式输出(streaming)是必须的,让文字一个字一个字地蹦出来,孩子等待的感知时间会大幅缩短。价格方面,我算了一笔账:假设每天生成20篇短文、批改10篇作文、进行50轮对话练习,每轮平均消耗500个token,一个月下来大概消耗几十万到百万级别的token。不同模型的单价差异很大,但总体来说,这个量级的成本完全在个人可承受范围内,每个月几十块钱就够了。最终我选择了一个在英文生成质量和价格之间平衡得比较好的模型,并且做了双模型备份的设计——主模型不可用时自动切换到备用模型。这个设计后来救了我好几次,有一次主模型服务临时波动,备用模型无缝接管,孩子完全没感觉到异常。2.3 数据层设计:怎么记录孩子的学习轨迹数据模型的设计直接决定了工具能不能“越用越懂孩子”。我设计了四张核心表:用户画像表:记录孩子的年级、当前词汇量估算值、兴趣爱好标签、学习偏好(比如喜欢看图还是喜欢读文字)。学习记录表:每次学习行为的详细记录,包括学习内容、耗时、正确率、AI评估的掌握程度。词汇库表:记录孩子已经掌握和正在学习的单词,每个单词有熟悉度评分,采用类似间隔重复算法的逻辑来安排复习。生成内容缓存表:AI生成的内容不会用完就扔,而是存下来。如果孩子过几天想重读之前那篇恐龙文章,可以直接从缓存读取,不用重新调用API,既省钱又快速。这四张表的设计有一个核心逻辑:让数据形成闭环。孩子每次的学习行为都会更新用户画像和词汇库,而更新后的数据又会作为下一次AI生成内容的输入参数。比如孩子今天学了“fossil”这个词,词汇库里就会记录,下次生成恐龙相关文章时,AI会自然地复现这个词,形成复习效果。3. 核心功能模块拆解:四个闭环是怎么跑通的3.1 单词模块:从死记硬背到语境化学习传统背单词最大的问题是脱离语境。孩子背了“apple”是苹果,但在句子里看到“apple”还是要反应半天。我的做法是:每个单词都放在一个完整的、有趣的句子里呈现,而且这个句子是根据孩子的兴趣动态生成的。具体流程是这样的:孩子点击“学新词”,系统从词库中选取一个适合他当前水平的生词(基于词频和难度分级),然后调用AI生成一个包含这个单词的短句或小段落。如果孩子喜欢足球,AI就会生成“The player kicked the ball so hard that it flew over the fence.”这样的句子,而不是干巴巴的“He kicked the ball.”孩子读完句子后,系统会问三个问题:这个词在句子中是什么意思?你能用这个词造一个新句子吗?你觉得这个词和之前学过的哪个词意思相近?这三个问题分别对应理解、应用、关联三个认知层次,比单纯的选择题有效得多。实操心得:AI生成例句时,一定要在prompt里明确要求“句子长度不超过15个单词,使用小学阶段常见语法结构”。我一开始没加这个限制,AI生成了一堆带从句的长难句,孩子根本读不懂。后来加了限制,效果立刻好了很多。单词的复习机制我参考了间隔重复的思路,但没有完全照搬Anki那套复杂的算法。我的简化版逻辑是:每个单词有一个熟悉度分数(0-100),答对一次加10分,答错一次减20分。分数低于30的每天出现,30-60的隔两天出现,60-80的隔一周出现,80以上的基本不再主动推送,但会在生成阅读材料时自然复现。这套逻辑简单粗暴,但实测下来效果不差。3.2 阅读模块:AI生成的分级读物这是整个工具里孩子最喜欢的功能。传统分级读物的问题是:级别是固定的,但孩子的兴趣是变化的。RAZ读到C级,所有C级的内容都是同样的难度,但孩子可能对其中某些主题完全不感兴趣。我的做法是:难度由系统控制,主题由孩子选择。孩子可以从一个主题列表里选(恐龙、太空、足球、动物、美食等),系统根据他的词汇量水平自动调整生成内容的难度。难度控制的核心参数有三个:参数说明典型值(三年级)目标词汇量文章中不重复单词的总数80-120词生词比例超出孩子当前词汇的单词占比5%-8%句子平均长度每个句子的平均单词数8-12词生成阅读材料时,我会把这些参数写进prompt里,同时附上孩子已经掌握的词汇列表(从词汇库表里取),让AI尽量使用已知词汇,只在必要时引入生词。生成的文章会附带三个阅读理解问题,问题的难度也分三个层次:细节理解、推理判断、个人观点表达。孩子读完文章、答完题之后,系统会自动把文章中的生词加入词汇库,并根据答题正确率调整这些生词的初始熟悉度。答对了,熟悉度从40起步;答错了,从20起步。这样下次复习时,答错的词会更早出现。3.3 对话模块:跟AI练口语的降级方案说实话,真正的口语练习需要语音识别和语音合成,技术复杂度高,而且孩子的发音识别准确率一直是个难题。我一开始也想做语音对话,但实测了几个语音识别方案后发现,对儿童发音的识别效果都不太理想,经常把“three”识别成“free”,把“rabbit”识别成“rapid”,反而打击孩子的积极性。所以我做了一个降级方案:文字对话为主,语音输入为辅。孩子可以用键盘打字跟AI对话,也可以用语音输入(调用系统自带的语音转文字功能),但AI的回复以文字形式呈现,同时提供朗读功能(调用浏览器的SpeechSynthesis API)。对话的场景也是AI动态生成的。比如孩子选了“在餐厅点餐”的场景,AI会扮演服务员,用简单的英语跟孩子对话。对话的难度会根据孩子的回应动态调整——如果孩子连续几次回答得又快又准,AI会逐渐增加句子的复杂度和词汇难度;如果孩子卡壳了,AI会主动降低难度,或者给出提示。这里有一个很关键的细节:AI在对话中要“装傻”。我一开始让AI扮演一个全知全能的对话者,结果孩子说“I want a hamburger”,AI回了一大段“Great choice! Our hamburgers are made with 100% beef and served with fresh lettuce...”孩子直接懵了。后来我在prompt里明确要求“每次回复不超过两个句子,使用简单词汇,必要时用中文解释”,效果就好多了。3.4 写作模块:从批改到引导的转变写作是最难做好的模块。传统的AI批改就是找出语法错误、拼写错误,然后给个分数。但这对提高写作能力帮助有限——孩子看到满屏红字,第一反应是沮丧,而不是学习。我的做法是把批改变成引导。孩子写完一篇小短文后,AI不会直接指出错误,而是先给一个整体评价(“你的故事很有趣,我特别喜欢关于恐龙化石的那段描述”),然后提出一两个引导性问题(“你能告诉我为什么主人公决定去挖掘化石吗?我觉得这里可以写得更详细一些”),最后才指出最关键的1-2个语言问题(“注意一下‘dinosaur’的拼写,你写成了‘dinosour’”)。这种方式的逻辑是:先保护表达欲,再纠正语言错误。孩子写作文最怕的就是被打击,如果每次写完都是一堆错误,他很快就会失去写作的兴趣。而先肯定内容、再引导完善、最后纠正关键错误的三步法,让孩子觉得“我写的东西有人认真读了,而且他觉得我写得好”。AI批改的prompt里我加了几个约束条件:每次最多指出两个语言错误、必须用鼓励性语言开头、引导性问题不超过两个。这些约束看起来简单,但效果非常明显。我家孩子现在每周主动写两三篇英文小短文,这在以前是完全不可想象的。4. 实操过程:从零搭建的完整步骤和关键代码4.1 环境准备与项目初始化整个项目的搭建过程我尽量做到“最小依赖”。后端只需要Python 3.10+和几个pip包,前端用Vite创建React项目即可。后端依赖清单如下:pip install fastapi uvicorn openai sqlalchemy pydantic python-dotenv这里解释一下每个包的作用:FastAPI是Web框架,uvicorn是ASGI服务器,openai是调用大模型API的官方SDK,sqlalchemy是ORM工具(虽然我用SQLite,但ORM能让代码更清晰),pydantic做数据校验,python-dotenv管理环境变量(API密钥不能硬编码在代码里)。前端初始化:npm create vite@latest english-tool -- --template react cd english-tool npm install npm install axios tailwindcss项目结构我保持了最简单的分层:backend/ main.py # FastAPI入口 models.py # 数据库模型 ai_service.py # AI调用封装 prompts.py # 所有prompt模板 routers/ words.py # 单词模块接口 reading.py # 阅读模块接口 dialogue.py # 对话模块接口 writing.py # 写作模块接口 frontend/ src/ components/ # 可复用组件 pages/ # 页面 api/ # API调用封装4.2 AI调用的核心封装所有AI调用我都通过一个统一的函数来走,这样方便做错误处理、重试、日志记录和成本统计。核心代码如下:import openai from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)) async def call_ai(prompt: str, system_prompt: str = "", temperature: float = 0.7) -> str: try: response = await openai.ChatCompletion.acreate( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} ], temperature=temperature, max_tokens=1500 ) return response.choices[0].message.content except Exception as e: logger.error(f"AI call failed: {e}") raise这里有几个关键设计点。temperature参数我根据不同模块做了区分:生成阅读材料时用0.7(需要一定的创造性),批改作文时用0.3(需要稳定和一致),对话练习时用0.8(需要更多的变化和趣味性)。重试机制用了tenacity库,指数退避策略,最多重试3次。异步调用是必须的,因为AI响应通常需要几秒钟,同步调用会阻塞整个服务。4.3 阅读材料生成的完整实现这是最核心的功能,我把完整的prompt模板和调用逻辑贴出来:READING_PROMPT_TEMPLATE = """ 你是一位专业的少儿英语教师。请根据以下要求生成一篇英语阅读材料: 主题:{topic} 目标词汇量:{target_vocab_count}个不重复单词 生词比例:不超过{new_word_ratio}% 句子平均长度:{avg_sentence_length}个单词 孩子已掌握的词汇(尽量使用这些词):{known_words} 要求: 1. 文章长度控制在{article_length}词左右 2. 语言要生动有趣,符合{grade}年级学生的认知水平 3. 生词要自然融入上下文,便于通过语境猜测词义 4. 文章末尾附3道阅读理解题,分别考察细节理解、推理判断、个人观点 5. 标注出所有生词及其在文中的含义 请以JSON格式输出,包含以下字段: - title: 文章标题 - content: 文章正文 - new_words: 生词列表,每个词包含word和meaning - questions: 问题列表,每个问题包含question和type """调用时,我会从数据库里取出孩子的词汇库和用户画像,填充到模板里:async def generate_reading(user_id: int, topic: str): user = get_user_profile(user_id) known_words = get_known_words(user_id, limit=500) prompt = READING_PROMPT_TEMPLATE.format( topic=topic, target_vocab_count=user.target_vocab_count, new_word_ratio=user.new_word_ratio, avg_sentence_length=user.avg_sentence_length, known_words=", ".join(known_words), article_length=user.article_length, grade=user.grade ) result = await call_ai(prompt, temperature=0.7) data = json.loads(result) # 存入缓存 cache_reading(user_id, topic, data) # 更新词汇库 update_vocabulary(user_id, data["new_words"]) return data实操心得:让AI输出JSON格式时,一定要在prompt里明确写出JSON的字段结构,并且加上“请以JSON格式输出”这句话。我试过不写字段结构,AI返回的JSON字段名每次都不一样,解析起来非常痛苦。另外,建议在代码里加一层JSON解析的容错处理,因为AI偶尔会在JSON外面包一层markdown代码块标记,需要先strip掉。4.4 对话模块的状态管理对话模块的难点在于维护对话上下文。每次孩子发消息,都需要把之前的对话历史一起发给AI,否则AI会“失忆”。但对话历史不能无限增长,否则token消耗会越来越大,而且AI的注意力会被稀释。我的做法是:保留最近10轮对话,更早的对话做摘要压缩。具体实现是,当对话轮次超过10轮时,把最早的两轮对话交给AI做一句话摘要,然后把摘要作为系统消息的一部分保留,原始对话丢弃。async def chat(user_id: int, message: str): history = get_chat_history(user_id, limit=10) summary = get_chat_summary(user_id) system_prompt = f"""你是一位耐心的少儿英语对话伙伴。 当前对话场景:{get_current_scene(user_id)} 对话摘要:{summary} 要求:每次回复不超过两个句子,使用简单词汇,必要时用中文解释。""" messages = [{"role": "system", "content": system_prompt}] for h in history: messages.append({"role": h.role, "content": h.content}) messages.append({"role": "user", "content": message}) response = await call_ai_with_messages(messages, temperature=0.8) save_chat_message(user_id, "user", message) save_chat_message(user_id, "assistant", response) return response4.5 前端交互的关键细节前端我重点做了两件事:降低操作门槛和增加即时反馈。降低操作门槛方面,所有功能入口都在首页以卡片形式呈现,孩子点一下就能进入对应模块。每个模块内部的操作步骤不超过三步。比如阅读模块:选主题→读文章→答题,没有多余的设置页面。即时反馈方面,所有AI调用都做了loading状态和流式输出。孩子点击“生成文章”后,文字会一个字一个字地出现,而不是等好几秒后突然全部显示。这个细节对保持孩子的注意力非常关键。流式输出的前端实现核心代码:const response = await fetch('/api/reading/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ topic, userId }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const text = decoder.decode(value); setContent(prev => prev + text); }5. 踩坑实录与常见问题排查5.1 AI生成内容不可控怎么办这是最常见的问题。AI有时候会“自由发挥”,生成超出孩子水平的内容,或者偏离主题。我总结了几个有效的约束技巧:第一,在prompt里给出具体的负面示例。比如“不要生成类似‘The intricate ecosystem of the Cretaceous period...’这样的句子”,比单纯说“使用简单词汇”有效得多。第二,用JSON schema约束输出格式。让AI按照固定的字段结构输出,可以大幅减少格式混乱的问题。第三,加一层后处理校验。生成的内容我会用代码检查几个指标:句子平均长度是否超标、生词比例是否在范围内、是否包含敏感内容。如果校验不通过,自动重新生成一次。第四,temperature不要设太高。我一开始设了1.0,结果AI生成的内容天马行空,后来降到0.7就稳定多了。5.2 孩子觉得“跟AI说话很傻”怎么破这个问题我一开始没预料到。我家孩子第一次跟AI对话时,回了一句“我不想跟机器人说话”,然后就跑了。后来我做了几个调整:给AI一个具体的角色和名字。不要叫“AI助手”,而是叫“Alex老师”或者“Dino(恐龙伙伴)”,让孩子觉得是在跟一个角色对话,而不是跟一个机器。设计有明确目标的对话任务。不要让孩子“随便聊”,而是给他一个任务,比如“帮Alex老师找到去图书馆的路”、“向Dino介绍你最喜欢的恐龙”。有目标感,孩子才有参与的动力。加入游戏化元素。完成一轮对话后给星星奖励,攒够星星可以解锁新的对话场景。这个机制虽然简单,但对小学生非常有效。5.3 API调用成本失控的预防我第一个月试用期间,因为没做缓存和限流,API费用比预期高了三四倍。后来做了几个优化:优化措施效果实现方式内容缓存减少40%重复调用相同参数的内容直接读缓存对话历史压缩减少30% token消耗超过10轮做摘要请求合并减少15%调用次数批量生成多个单词的例句模型分级减少50%成本简单任务用便宜模型模型分级这个策略特别值得说一下。不是所有任务都需要用最贵的模型。比如生成单词例句这种相对简单的任务,用便宜的小模型完全够用;而批改作文、生成阅读材料这种需要较强理解能力的任务,才用贵的大模型。我把这个逻辑封装成了一个路由函数,根据任务类型自动选择模型。5.4 常见问题速查表问题现象可能原因排查方法解决方案AI返回内容为空API密钥失效或额度用完检查API控制台余额更换密钥或充值生成内容难度忽高忽低prompt中难度参数不稳定打印每次请求的prompt固定参数值,增加校验对话历史丢失数据库写入失败检查SQLite文件权限修复权限或迁移数据库前端流式输出卡顿网络延迟或后端阻塞查看浏览器Network面板增加超时重试,优化后端异步生词重复出现词汇库更新逻辑有bug检查update_vocabulary函数修复去重逻辑孩子使用时间过长缺乏时间限制无增加每日使用时长提醒最后分享一个我踩过的最大的坑:不要试图一次性做完所有功能。我一开始想同时做单词、阅读、对话、写作四个模块,结果每个都做了一半,哪个都不好用。后来我改变策略,先把阅读模块做完整、做稳定,让孩子先用起来,然后再逐个添加其他模块。这种“单点突破、逐步扩展”的方式,比“全面铺开、齐头并进”高效得多。这个工具到现在已经跑了快两个月,我家孩子的英语成绩从班里中游提到了前几名,更重要的是,他现在会主动打开这个工具去读一篇恐龙文章或者跟AI聊几句。作为一个开发者,能用自己写的代码解决自己孩子的问题,这种感觉比完成任何一个公司项目都有成就感。如果你也有类似的需求,我建议你不要想太多,先从一个最小的功能开始做起来,剩下的会在做的过程中自然清晰。