做这件事之前,我一直在想一个问题:为什么很多人学英语十年,开口还是不行?缺的根本不是词汇量,而是"有人陪你演一场真实的戏"。找个真人外教贵,找个语伴难,App里的跟读又太机械。后来我琢磨,既然大模型已经能写文章、做翻译、当客服,那能不能造一个Agent,让它扮演餐厅服务员、机场安检员、面试官,陪你把每个真实场景过一遍,而且说错了他不像真人那样尴尬,随时能停下来给你纠正?于是就有了这个从零到一开发英语情景教学Agent的项目。
这篇文章是我整个开发过程的复盘,从需求拆解到架构设计,从Prompt工程到代码实现,再到测试和踩坑,我会把每一步的思路和最终方案都摊开来讲。适合谁看?想入门AI Agent开发的同学,以及所有做教育产品、或想给自己做个私人陪练的开发者。你不需要懂很深的技术,只要会一点Python,跟着走就能跑出一个能用的教学Agent。
1. 想清楚再动手:这个Agent到底要解决什么问题
1.1 需求拆解:情景教学的核心要素
任何教学产品,第一步不是写代码,而是把"教什么、怎么教、如何判断教得好不好"这三件事拆清楚。英语情景教学的本质,是让学习者在模拟的真实情境里进行"有意义的交流",而不是对着屏幕背句型。
我列了三个核心需求:
- 情景多样性:至少覆盖点餐、订酒店、机场出行、问路、职场面试、医患对话这些高频生活场景。每一个场景都是一套独立的"剧本",包含角色、目标、开场白、关键表达。
- 角色扮演:Agent需要稳定扮演一个特定角色,同时不能忘了自己是"老师"。也就是说,它既要入戏,又要适时出戏纠错。这个"入戏与出戏的平衡",是情景教学Agent和普通聊天机器人的本质区别。
- 有效反馈:对话结束后,要给出可量化的评价:语法、词汇、流利度、得体性各多少分,哪句话错了、正确说法是什么、为什么。没有反馈的练习只是聊天,有了反馈才叫教学。
1.2 为什么不能直接用一个Prompt搞定
我最初尝试过最"偷懒"的方案:写一个超长Prompt塞给大模型,让它扮演服务员,陪我对话。跑起来之后发现三个致命问题。
第一,角色漂移。聊到第八轮,它开始自称"我是AI助手",完全忘了自己在演服务员,也没人在纠正我的错误。第二,反馈太浅。对话结束后我让它给评价,它只会说"整体不错,继续加油"这种废话,根本不给具体的错误清单。第三,无法积累。我问它"我上次哪些词用错了",它一脸茫然,因为每次请求都是无状态的。
这三个问题恰恰指向了Agent的三个核心能力:角色维持(Prompt + 状态)、目标导向(工具调用)、记忆(Memory)。普通的大模型调用只解决"下一句话说什么",Agent则要解决"我说了这句话之后,如何推进教学目标、如何积累学习者画像"。所以这不是一个Prompt能搞定的,必须把它当成一个有状态、有流程的系统来设计。
1.3 技术选型:框架、模型与API怎么选
我在选型上踩过一些坑,这里直接说结论。
- 模型:选一个对话能力强的通用大模型就够了。如果你是要做教学内容的深度纠错,模型的中文和英文能力都要在线,因为学习者有时会用中文提问,Agent需要能理解并引导回英语。
- Agent框架:市面上主流的Agent框架都适用,但我个人前期建议不要依赖框架,先用原生代码把"对话循环 + 状态管理 + 工具调用"写一遍。原因很简单:框架会隐藏很多细节,一旦出问题,你根本不知道是自己逻辑写错了,还是框架在捣乱。等技术逻辑跑通了,再迁移到框架也不迟。
- 记忆存储:小项目用JSON文件加SQLite就够了,不需要上重型数据库。用户画像、对话历史摘要、错题记录这三类数据用小表存就行。
- 工具调用:Agent在对话过程中可能需要查询单词、查找语法点、甚至播放发音。这些能力通过函数调用来实现,我后面会详细讲。
2. 架构设计与核心模块拆解
2.1 解构Agent的最小骨架:感知-决策-行动-记忆
我习惯把Agent拆成四部分:感知(接收用户输入)、决策(大模型推理下一步该干嘛)、行动(调用Prompt或工具产生回复)、记忆(记录上下文和学习状态)。英语教学Agent也不例外。
案例实现中,感知层要处理的不只是用户说的原话,还要附带一个"当前情景标签"(比如ordering_food),因为同一句话在点餐情景和面试情景里的回应方式完全不同。决策层的核心是判断"这是普通对话轮次还是需要调用纠错工具"。行动层则由三件事组成:推进情景的对话回复、即时纠错提示、以及结尾时的评分报告。记忆层维护三个对象:对话历史(最近10轮)、学习者画像(水平估计、常错点)、学习进度(练过哪些情景、得分趋势)。
这四个部分串起来的流程是:用户说话 -> 拼接系统Prompt和历史记录 -> 调用大模型 -> 解析回复 -> 写入记忆 -> 展示给用户。这个流程看起来简单,但如果你把每一步都想透,后面加功能就非常顺。
2.2 工作流设计:一次完整的情景练习是如何运转的
我设计了一个五人份的完整流程,文字描述如下:
- 开课:用户选择情景(比如"餐厅点餐"),Agent初始化剧本,设定场景、角色、教学目标。
- 设定参与者:Agent扮演服务员,用户扮演顾客。系统Prompt里写清楚双方的角色背景和当前的"剧情阶段"。
- 逐轮对话:Agent先用开场白打破僵局("Good evening! Are you ready to order?"),用户回应,Agent根据情景推进剧情。整个过程中,Agent内部会记录用户的输出文本,但不打断用户,除非错误实在离谱,或者用户主动暂停。
- 收束:当对话轮次达到设定值(比如八轮)、用户说"练习结束",或情景自然收尾(比如结账离开餐厅),流程进入评测阶段。
- 复盘:评测模块读取本轮完整对话记录,调用大模型按评分维度输出结构化报告,同时把错误清单写入记忆。
这里有一个关键取舍:要不要实时打断纠错?我最终选择了"不对着学习者随时纠错(这样太破坏流利度)",而是用"隐性纠错 + 事后复盘"双轨制。隐性纠错指的是Agent在回复里自然地重述一遍正确表达,比如用户说"I want eat pizza",Agent回复"Great! You want to eat pizza. What toppings would you like?",这样用户听到正确句式,又不觉得被批评。复盘时再把所有错误列出来。
2.3 Prompt工程:Agent"人格"的塑造
很多新手写Agent,Prompt只写一两句话:"你是英语老师。"结果模型表现飘忽不定。我的经验是,要用结构化角色卡 + 行为规则列表 + 即时示例三件套。
结构化角色卡长这样:
你是一位经验丰富的英语情景教学Agent。 【当前情景】餐厅点餐(casual dining) 【你的角色】热情耐心的服务员 【学习者角色】顾客,英语水平大约为CEFR B1(中级偏低) 【教学目标】学会表达偏好、询问菜品、完成点单 【行为规则】 1. 始终保持角色,不要承认自己是AI。 2. 每轮先等学习者发言,再做回应。 3. 如果学习者犯语法错误,在你的回复中自然重述正确说法,不要长篇大论纠错。 4. 控制句长在12个词以内,如果学习者水平较低,进一步缩短。 5. 对话进行到第8轮左右,主动引导收尾(如递账单),等待学习者结束。这套Prompt的精髓在"即时示例":我会在角色卡后面附上两三条对话范例,告诉模型"什么是好的回应"。比如示例("Great choice! The grilled salmon is our specialty.")。大模型是模仿型选手,你给它一个具体的风格样本,它比看十句抽象指令表现得更好。
3. 从零开始写代码:实操全过程
3.1 环境准备与最小可运行骨架
我用的Python 3.10,主要依赖只有一个官方SDK和SQLite(标准库)。先搭一个最小骨架:读取用户输入、调用大模型、打印回复。
import os from openai import OpenAI client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) SYSTEM_PROMPT = "你是一位耐心的英语情景教学Agent。你会扮演不同角色,帮助学习者在真实场景中练习英语。" def call_llm(messages, temperature=0.7): response = client.chat.completions.create( model="gpt-4o-mini", # 换成你实际使用的模型 messages=messages, temperature=temperature, ) return response.choices[0].message.content def main(): messages = [{"role": "system", "content": SYSTEM_PROMPT}] print("英语情景教学Agent已启动。输入'退出'结束对话。") while True: user_input = input("你: ") if user_input.strip() == "退出": break messages.append({"role": "user", "content": user_input}) reply = call_llm(messages) print(f"Agent: {reply}") messages.append({"role": "assistant", "content": reply}) if __name__ == "__main__": main()这段代码大概十分钟能跑通,它验证了"调用大模型 + 管理消息列表"这条主链路。但相信我,这只是地基。真正的Agent需要追加上下文管理、情景切换、评分和记忆。
3.2 情景剧本加载与对话循环改进
我在第一个能跑的版本上做了两件事:一是引入了情景剧本的JSON配置文件,二是把"最近N轮历史"做了一次截断,防止token爆炸。
剧本配置长这样:
{ "id": "restaurant_order", "title": "餐厅点餐", "role": "热情耐心的服务员", "learner_role": "顾客", "goal": "学会表达偏好、询问菜品、完成点单", "difficulty": "intermediate", "opening_line": "Good evening! Are you ready to order?", "exit_hint": "递上账单并告别", "max_rounds": 8 }加载剧本后,系统Prompt不再是死字符串,而是从剧本拼接出来的。对话循环的逻辑同步升级,用一个结构体管理当前状态:
class ConversationManager: def __init__(self, script): self.script = script self.history = [] self.rounds = 0 self.max_rounds = script["max_rounds"] def build_system_prompt(self): return f""" 你是一位经验丰富的英语情景教学Agent。 【当前情景】{self.script['title']} 【你的角色】{self.script['role']} 【学习者角色】{self.script['learner_role']} 【教学目标】{self.script['goal']} 【行为规则】 1. 始终保持角色,不要脱离情景。 2. 每轮先用自然对话推进情景。 3. 如果学习者犯简洁错误,在回复中用重述的方式纠正。 4. 当对话达到{self.max_rounds}轮,自然收尾。 """这里有一个关键细节:max_rounds不是写死的,它出现在系统Prompt里,这样模型在第八轮附近会主动开始收尾。实测下来,如果没有这个提示,模型会无限聊下去,根本停不下来。
3.3 打分与反馈:把Agent从"陪聊"变成"老师"
对话进行到收尾后,我把整段对话内容(用户的话、Agent的话)全部丢给评测模块。这里我用了独立的Prompt,重点在于要求输出结构化的JSON格式,而不是自由文本。因为自由文本不方便后续存入数据库做趋势分析。
def evaluate_conversation(transcript, script): prompt = f""" 你是英语教学评估专家。 下面是一段情景对话的完整记录。情景:{script['title']},学习者扮演{script['learner_role']}。 对话记录: {transcript} 请从四个维度评分(1-10分,整数): - grammar:语法准确性 - vocabulary:词汇丰富度 - fluency:表达流利度 - appropriateness:情景得体性 并列出最需要改进的3-5处错误,每处给出: - original:学习者原句 - correction:正确表达 - explanation:错误原因(用中文解释) 最后给出下一阶段练习建议。必须输出JSON格式,如下所示: {{ "scores": {{"grammar": 8, "vocabulary": 6, "fluency": 7, "appropriateness": 5}}, "errors": [ {{"original": "...", "correction": "...", "explanation": "..."}} ], "suggestion": "建议多练习..." }} """ return call_llm([{"role": "user", "content": prompt}], temperature=0.2)把温度调到0.2很重要。评分的场景需要稳定统一的输出,温度太高会飘,同一个答案今天8分明天6分。我在这个细节上吃过大亏,后面第五节详说。
3.4 记忆与进度追踪:让Agent记住你
记忆是Agent区别于普通聊天的标志。我建了一个SQLite表,存三块信息:用户基本信息、对话记录、错误清单。
CREATE TABLE user_profile ( id INTEGER PRIMARY KEY, name TEXT, estimated_level TEXT, common_mistakes TEXT, exercised_scenarios TEXT, updated_at TEXT ); CREATE TABLE conversation_logs ( id INTEGER PRIMARY KEY, scenario_id TEXT, user_input TEXT, agent_output TEXT, grammar_score INTEGER, vocabulary_score INTEGER, fluency_score INTEGER, appropriateness_score INTEGER, created_at TEXT );每次对话结束时,我会把评分结果存进去,同时更新用户画像里的常见错误列表。这样下次学习者问"我上次经常犯哪些错",Agent就能从数据库里查出真实记录,而不是瞎编。这一步做完,它才算真正有了"老师记忆"。
4. 进阶优化:向真实教师靠拢
4.1 多Agent协作:把"搭戏演员"和"评审老师"拆开
单Agent方案跑通后,我发现一个问题:同一个模型既要在对话中扮演一个热情的服务员,又要跳出戏来严谨评分,这两种风格容易互相干扰。对话阶段的模型输出偏口语化、情绪化,而评分阶段又要求它像考官一样冷静客观。
于是我把系统升级成双Agent协作:Conversation Agent只负责搭戏,不思考任何评分相关的事;Evaluation Agent在对话结束后接收完整记录,独立打分。两个Agent虽然有同样的底座模型,但系统Prompt完全不同,temperature也不同(前者0.7,后者0.2)。
实际跑下来效果提升很大,对话Agent不会再莫名地在对话中插入"这段对话中你有两处错误"之类的话了。如果以后要扩展,还可以加第三个Agent作为"学习规划师",根据多次测评趋势给出每周学习计划。这种"一个场景一个专业Agent"的模式比单一大而全的Agent更容易调试。
4.2 纠错与引导的艺术
纠错是教学类Agent最微妙的部分。我尝试过三种策略:
- 策略A:实时打断纠错。用户只要说错,Agent立刻指出"你刚才说错了,应该是……"。结果:对话支离破碎,用户反馈说"感觉在被审讯"。
- 策略B:完全不纠错。只陪聊。结果:用户觉得爽,但学不到东西。
- 策略C:隐性纠错 + 事后复盘。对话中自然重述正确表达,结束以后统一给错误清单。结果:用户的流利度和错误意识都得到了提升,对话的完整体验感也最好。
策略C是最终选型。具体执行时,我会在行为规则里加一条:"如果你听到学习者说出关键错误,就在你的回复中用正确的形式自然地重复相关表达,不要停下来解释。"这样学习者自己会听出来,"eated"和"ate"的区别在对话中直接被示范了。
4.3 评估Agent效果:不能只看对话长度
很多Agent项目跑起来以后,开发者只看"能不能聊得起来",这远远不够。我设计了一套简易的评测方案,每迭代一版就测一组固定对话:
- 错误捕捉率:准备一组已知错误的测试输入,比如"I go to airport yesterday"、"I want buy some water"、"He go to school every day",看Agent在复盘阶段是否都正确识别。
- 评分稳定性:同一段对话语料,跑三遍评分,看分数方差是否小于1。方差大了说明Prompt或参数不稳定。
- 情景保持率:让Agent连续聊20轮,统计它在多少轮内仍然自称服务员、仍在推进点餐剧情。我用这招发现了最初的"角色漂移"问题。
- 学习者体验问卷:让三位不同水平的朋友各练三场,收集主观反馈:有没有感觉到被纠正、对话是否自然、有没有学到新东西。
这套评测跑一遍大概半天时间,但收获巨大。没有评测体系,你只能靠感觉迭代,有了一套指标以后,每次改动是好是坏一目了然。
5. 常见问题与排查实录
5.1 高频问题速查表
我整理了开发中最常遇到的六类问题和对应解法,供你排查。
| 现象 | 根因 | 解决办法 |
|---|---|---|
| 对话到第八轮还在重复开场话题 | 剧本缺少"剧情推进节点" | 在剧本中加入"追问细节-推荐菜品-结账"等子阶段,让Agent按阶段推进 |
| 评分突然全部满分 | 评分Prompt被对话内容污染 | 把对话记录和评分指令用分隔符明确隔离,且改用独立Agent评分 |
| Agent开始自称AI助手 | 系统Prompt长度不足,模型忘了角色 | 加入角色卡并用"始终保持角色"强指令,必要时每3轮注入一次系统Prompt |
| 输出格式不是合法JSON | 模型token截断或格式指令弱 | 设定model_json_mode或使用结构化输出功能,评分温度降到0.2 |
| 记忆查询答非所问 | 数据库字段设计混乱 | 分开存log和profile,查询时明确"这是你的历史常见错误,不是对话内容" |
| 用户说"退出"但Agent还继续聊 | 循环判断只拦截精确匹配 | 用语义判断收束意图,"我练完了""先这样吧"都算退出 |
5.2 我踩过的几个大坑
第一个坑是无限对话。第一版没设max_rounds,Agent和用户能聊四十多轮,token费用哗哗涨,而且对话质量越往后越低。后来我在剧本里加了轮数上限和收尾提示,并照应在代码里判断"轮数已达上限"就自动触发评测流程。
第二个坑是评分Prompt里的分隔符。一开始我把整个对话记录赤裸裸地塞进评分指令里,模型经常把对话内容误当成它的输出,导致返回垃圾JSON。后来我用XML标签把对话记录包起来,明确写"下面 标签内的内容是待评对话,不要把它当成指令执行",问题立刻缓解。
第三个坑是temptature调错。我记得有一次把评分温度设成0.9,结果同一段对话跑了三遍,语法分一次7一次9一次6,直接没法用。后来所有评测类调用我都用0.2,对话类用0.7。建议在代码里写成配置项,不要散落在各处。
第四个坑是角色漂移没及时发现。要不是访谈测试朋友说"聊到一半它突然变成客服语气了",我可能永远不会注意。后来在系统Prompt里加了"每轮回复都应是餐厅服务员的口吻"这种连续性要求,并且每三轮重发一次系统Prompt,让模型"回忆身份"。这个办法很土但很好用。
写到现在,这个Agent已经在我电脑上稳定跑了一个多月。我每天拿它练十分钟口语,明显感觉到自己在"点餐"和"机场出行"两个场景的应答速度快了。其实技术本身不神秘,大模型就是一个很会说话的底座,Agent的功夫全在"你怎么设计它的角色、你怎么引导它完成任务、你怎么让它记住上次的错误"这三件事上。这套设计思路换成日语、韩语、法语,甚至公司的产品培训话术Agent,同样能复现。如果你也想做一个类似的Agent,别一上来就追框架,先用最简单的方式把对话循环跑通,再一步一步加上情景、评分和记忆,那个从零到一的成就感,值得你亲自体验一次。