ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

英语情景教学Agent:从LangGraph状态机到纠错反馈的工程实践

英语情景教学Agent:从LangGraph状态机到纠错反馈的工程实践 1. 项目定位这个英语情景教学Agent到底解决什么问题1.1 用户真正缺的不是词汇而是在场景里开口的经验我一直觉得市面上的英语学习产品大多在解决记住的问题而很少解决敢说和会说的问题。背单词、刷语法题、看美剧学英语本质上都是在积累知识但一到真实的对话场景——出国点餐、入住酒店、参加英文面试——大脑就一片空白。这是因为语言知识从认识到运用中间还隔着一个叫情景反应的环节。英语情景教学Agent想做的事情就是把这个环节补上。它模拟一个具体的场景Agent扮演对话中的另一方比如前台接待员、咖啡店店员、面试官用户通过和它对话完成一次情景演练。和普通ChatBot最大区别在于普通ChatBot的目标是回答用户的问题而这个Agent的核心目标是让用户完成一次有教学目标的对话练习。所以这个项目从一开始就不能套用投喂一个System Prompt然后聊天的简单模式。情景教学对Agent有三层要求第一必须限制在场景内不能聊着聊着跑题变成闲聊第二必须能够给出教学反馈而不只是对话回应第三需要根据学习者的水平动态调整对话难度。这三层要求直接决定了后面所有的架构设计。1.2 MVP功能边界设定目标时先学会做减法做这个项目之前我列了个很长的愿望清单发音评分、动态生成情景、多Agent模拟群聊、VR沉浸式……后来全部划掉了。一个从零到一的教学Agent第一版最该做的是验证情景对话教学反馈这个核心闭环到底能不能跑通。我的MVP最终只保留了四个功能预设情景脚本先内置10个高频生活场景比如机场值机、咖啡馆点单、酒店入住、英文面试角色扮演对话Agent按场景脚本扮演指定角色用户自由输入回答实时纠错反馈每轮对话结束后Agent对表达中的明显语法错误和用词问题进行反馈学习档案记录记录用户每轮表现生成阶段性薄弱点小结刻意砍掉发音评分。原因很简单发音评分牵涉到ASR的声学特征分析需要单独引入音频层面的模型和语言教学Agent的逻辑不属于同一条技术线。强行塞进MVP会在联调阶段消耗大量精力。先集中精力把文本对话闭环做扎实发音能力放到后续版本通过独立模块接入。关于功能边界还有一个容易被忽略的点不要把Agent设计成全知全能的自由对话者。在情景教学场景里Agent的自由度越高教学失控的风险越大。用户说一句我昨天看了一部电影如果Agent顺着他聊起电影剧情这堂机场值机练习课就跑偏了。所以第一版的Agent必须是有边界的、有剧本约束的这个设计思路贯穿了后面的整个技术方案。2. 技术选型别一上来就堆多Agent编排2.1 框架对比LangChain、LangGraph与最小自研方案要写Agent的时候很多人第一反应是选一个Agent框架把这个词用上去。但是我个人的建议是先确定你的应用到底需不需要那种多Agent自主规划、相互调用的重型架构。英语情景教学本质上是一个强流程、弱规划的应用——对话阶段是确定的教学动作是确定的真正需要LLM发挥的是生成符合场景的表达和判断表达质量而不是让AI自行规划学习路径。我对比过三条路线方案状态管理能力学习成本可控性适合场景LangChain经典链式弱靠外部变量拼接低中简单问答、文档流程LangGraph图编排强原生支持状态节点流转中高高有明确多轮对话状态机的应用自研轻量引擎完全自己控制低不依赖复杂API最高状态简单、逻辑明确的应用最终我选择了LangGraph作为主框架但没有把整条链路都交给它。LangGraph在对话状态流转这件事上做得非常合适每个对话阶段是一个节点用户输入触发一次状态迁移迁移前后可以附加数据处理逻辑。这和情景教学Agent天然一一对应——正在询问航班号是一个阶段用户提供了错误答案需要纠偏是另一个阶段。有人问为什么不干脆自研我的判断是这个项目里状态节点有十几个但如果自己实现中断续执行、状态持久化和回滚工作量会明显增加。LangGraph把这些能力内置了我只需要关注业务逻辑。2.2 单Agent核心链路先跑通完整数据流很多人被多Agent协作这个概念吸引觉得多个Agent角色扮演一个老师一个学生才够高级。实际开发中我建议第一版坚持单Agent核心链路。原因很简单多Agent的通信开销和一致性维护成本在MVP阶段会拖慢迭代速度。我的核心数据流是这样的用户语音 - STT转写 - 情景状态机判断当前阶段 - 组装当前情景信息 - LLM生成Agent回应与反馈 - 解析结构化输出 - 更新状态机 - TTS播放可选这个链路里真正调用LLM的环节只有一个根据当前状态生成教学回应。前期先把这一条链路打磨好等数据积累够了再去考虑要不要拆一个出题Agent和一个点评Agent。有个细节值得单独说LLM的输出不要直接当最终结果。我会要求LLM返回固定格式的JSON包含reply情景角色说的话、correction如果有错误需要纠正、error_type错误类型等字段。这样状态机可以稳定解析并决定下一步动作。很多Agent项目跑飞就是因为把LLM的自由文本直接丢给逻辑判断最后正则都救不回来。3. 情景引擎把真实对话场景变成可执行脚本3.1 情景脚本的数据结构让教学剧本不再靠Prompt硬撑这是整个项目里我最想强调的部分。很多教程教人做Agent时会把场景信息全部塞进System Prompt比如你是前台接待员用户正在办理入住。这种方式Demo阶段跑得很欢但一旦场景变多、分支变复杂Prompt会越来越臃肿LLM的发挥越来越不稳定而且教学知识点根本没法结构化统计。我的做法是为每个情景设计一个独立的JSON脚本作为整套系统的剧本。{ scene_id: airport_checkin_001, title: 机场值机英文实战, target_level: intermediate, roles: { agent: 地勤工作人员, user: 办理值机的乘客 }, objectives: [询问航班号, 确认座位偏好, 托运行李相关表达], key_points: { vocabulary: [boarding pass, window seat, check-in baggage], grammar: [Could I..., Id like to...] }, stages: [ { id: greeting, description: 地勤主动打招呼并询问航班号, agent_action: greet_and_ask_flight, expected_user_behavior: 说出航班号, next_stage: seat_preference, error_prompt: 如果你不确定航班号怎么说可以这样表达My flight number is... }, { id: seat_preference, description: 询问座位偏好, agent_action: ask_seat_preference, expected_user_behavior: 表达靠窗或靠过道等偏好, next_stage: baggage_check } ] }这个脚本结构的核心价值在于教学目标和对话流程不再是隐性的而是显式的数据。系统可以随时知道当前要练习什么知识点、用户应该给出什么类型的回应、如果卡住了应该给什么提示。这也让后续的评测和数据分析有了抓手——我可以统计用户在哪一个stage反复卡壳从而知道他的薄弱环节。3.2 对话状态机防止Agent跑偏的保险绳用脚本定义了应该发生什么还需要一个状态机控制实际发生什么。我的实现思路是LLM的每一轮输出不是从零生成回应而是在当前stage规定的范围内生成。状态机维护了几个核心字段current_stage当前对话阶段IDstage_history已经走过的阶段列表user_errors本场景内用户出现的错误记录hint_count当前阶段已经给出提示的次数每一轮处理大致是这样的逻辑用户回复进入后先做一次轻量级的意图判断——判断用户是否完成了当前阶段的目标。完成则推进到下一个stage没完成则留在当前stage并决定是给提示还是给纠错。这个意图判断任务我仍然交给LLM但要求它只输出一个结构化的判断结果from typing import Literal def decide_next_step( user_input: str, current_stage: dict, llm_client ) - dict: prompt f 你是英语教学系统里的对话策略模块。以下是当前教学情景的对话阶段信息。 阶段ID{current_stage[id]} 阶段目标{current_stage[expected_user_behavior]} 用户刚刚说{user_input} 请判断 1. 用户是否已经完成了本阶段的交流目标YES 或 NO 2. 如果未完成主要原因是A. 理解不了场景 B. 表达不完整 C. 词汇/语法错误 D. 沉默或跑题 3. 下一步动作ADVANCE推进下一阶段/ STAY留在本阶段/ HINT给提示/ CORRECT纠错 输出JSON格式不要有额外解释。 raw llm_client.chat(prompt) # 这里做解析和schema校验失败则默认STAY return parse_json_with_schema(raw)这段代码里有个容易被忽略的设计如果LLM返回的JSON解析失败默认动作是STAY而不是报错。英语对话场景里用户输入口语化、不完整、带噪音的概率很高加上LLM本身输出偶尔会不守规矩兜底策略一定要稳。宁可让对话原地多转一轮也不能让状态机崩掉。3.3 阶段推进逻辑一个安全阀设计状态机的另一个关键设计是融入了教学干预的机制。如果用户在同一个阶段连续触发Hint超过两次System Prompt会切换角色模式——Agent从情景角色切换到陪练教练主动给出示范表达让用户跟读并复述。这样做是为了防止用户在一个点上卡太久产生挫败感。这个机制被放在状态机层面而不是放在LLM的System Prompt里好处是逻辑可控。不然你写一万句如果用户不会表达就耐心示范LLM也很难每次稳定做到。从工程角度讲能放到代码里的确定性逻辑就别交给模型去赌概率。4. 记忆系统短期上下文与长期用户画像的落地方案4.1 短期记忆既要上下文连贯又要控制Token成本英语情景对话有一个特点每轮对话的用户输入普遍很短——Yes、Window seat, please、I have one suitcase。但整个场景对话可能持续15到20轮如果把所有对话历史全塞进Prompt会让Agent对最近的内容失去敏感度还会显著增加成本和延迟。我的短期记忆方案是双轨制结构化状态状态机里维护的阶段信息、错误记录、当前目标这些是机器能理解的记忆原始对话缓冲保留最近四轮用户输入和Agent回应作为模型需要的上下文关键点是结构化状态是所有记忆的核心原始对话只是辅助信息。因为状态机通常只需要知道用户当前在哪个阶段刚才的错误类型是时态还是词汇就足够生成合适的回应了。原始对话留四轮是为了保证对话衔接自然——毕竟用户上一句说了Actually I prefer aisle seat下一句Agent不能假装没听到。场景结束后短期记忆会做一次摘要压缩并归档然后清空缓冲。这里有个教训一开始我把摘要直接喂回下一场场景的Prompt导致Agent出现记忆穿越在前台场景问用户您的行李箱需要托运吗——这是上一场机场场景的聊天内容。后来统一改为每次场景切换只保留用户档案不保留对话摘要问题彻底解决。4.2 长期记忆用学习档案而不是无限堆积历史长期记忆是Agent类项目里话题度极高的概念有哪些记忆类型怎么实现永久记忆之类的讨论很多。就英语情景教学场景来说长期记忆不需要记录用户说过什么原文比如三周前他说过I go to school by bus这个原文没有长期保存价值有长期价值的是由此抽取出的结论一般现在时第三人称单数容易漏加s。我把长期记忆设计成一份结构化的用户学习档案字段示例更新策略常错知识点一般现在时三单连续两个场景出现同类错误时记录已掌握知识点can 情态动词用法连续三个场景正确使用后标记偏好场景英语面试用户主动选择频率统计难度等级intermediate正确率和反馈推进速度综合计算敏感反馈用户不习惯被连续纠正用户反馈问卷获得档案的更新不能实时进行。如果每轮对话都更新档案系统会显得人格漂移——这轮用户犯了个低级错误难度等级立刻下调下一轮Agent的对话难度突然变得过于简单用户体验很差。我的做法是用户档案只在每个情景结束后异步更新。对话过程中的所有表现先记录到临时列表场景结束后统一分析、更新档案。这样既保证教学策略稳定也不会拖慢实时对话响应。4.3 记忆安全教学数据也有一个最小化原则Agent记忆越丰富教学越个性化但代价是隐私风险。英语对话天然包含大量个人信息——用户可能会在练习中说自己的航班号、家庭住址、工作单位。我在设计记忆模块时给自己定了几条规矩用户原始对话不持久化只保留抽取后的教学特征用户档案中不存姓名、电话、地址等身份信息Agent在对话中收到真实个人信息时自动将角色回应拉回教学主线不追问细节比如用户在练习酒店入住时随口说我的护照号是E12345678Agent的回应应该礼貌承接然后转向教学任务而不是表现出对护照号本身有任何兴趣。这些限制不是靠开发人员自觉而是在状态机层面做了模式匹配拦截并在训练提示里明确说明。5. 纠错与反馈教学环节的分寸感最不好调5.1 纠错时机和粒度每轮都打断是教学灾难做教学Agent很容易陷入一个误区把LLM当成语法检查器用户每说一句话都要纠正一遍。试想一下如果你说一句Can I get a window seatAgent立刻回一句注意此处应该用Could I更正式而且每轮都这样——用户练五分钟就不想练了。我最终采用的纠错策略是两层第一层即时纠错。只处理那些导致理解受阻或与当前教学目标强相关的错误。比如用户说I no have luggage这个句子很可能让Agent角色值机柜员理解困难必须即时纠正。再比如当前教学目标恰好是过去式用户说I go to London yesterday这个错误直接攻击教学目标也即时纠正。第二层回合末总结。把那些不影响理解的、偏表达优化的建议比如试着用Could I替换Can I会显得更礼貌放到整个场景对话结束后统一给出。{ reply: Sure, your flight is on time. Do you have any baggage to check in?, correction: { triggered: false } }那么是否触发即时纠错怎么判断前面提到的策略判断模块返回的next_step就是依据。如果next_step是CORRECT则走即时纠错分支否则Agent的回应里不夹带纠错内容。这样纠错不再是一刀切而是有策略的、伺机而动。5.2 防止两个让用户崩溃的反馈风格我在跑通初版demo后做了个小范围试用暴露出两个高频问题。正好这两个问题都有对应的调优经验。第一个问题是AI过度表扬。几乎所有轮次都以Great!、Perfect!开头哪怕用户说得明显不自然。一开始觉得这是鼓励式教育但用户反馈却说感觉像敷衍不认真。后来我调整了提示词只在用户确实完成阶段目标时给简短肯定普通回应直接给出自然的角色反应。同时加入了肯定等级控制great / good / well done / nice try让LLM按用户表现动态选择。第二个问题是AI夹带中文。模型在生成纠错内容时偶尔会蹦出你这句话说得不错但注意这里要用过去时——这个注意倒没什么问题是它可能会用一整段中文来解释语法。对英语学习者来说某些情境下中文解释是有用的但既然定位是情景沉浸式我会在生成回复的提示里显式约束回复正文和点评文字默认使用英文除非用户的主动提问使用中文。这个约束用few-shot示例比单纯强调更有效。示例1 用户输入I have been to Beijing last year. Agent角色回应Youve been to Beijing? When did you go there? 点评注意last year是明确的过去时间词用一般过去时更合适比如 I went to Beijing last year. 示例2 用户输入不好意思 我不太会表达 Agent角色回应No worries. You can say: Id like to book a single room.第二个示例非常关键——用户用中文求助Agent可以允许中文但必须同时给出英文表达并且要给出在这个场景下马上能用的表达而不是一个冷冰冰的语法规则。教学导向要时刻体现在Agent的输出里。5.3 Prompt的温度参数教学场景不能太随意还有一个细节很多人不会注意参数设置。情景对话Agent我建议temperature设置在0.7左右而纠错点评模块单独设置更低的值比如0.2到0.3。原因是情景角色对话需要一点自然变化和灵活性但纠错点评追求的是一致性和准确性。同一个LLM、同一个项目不同模块手滑用一个参数实际效果差的不是一点半点。这种模块级的参数拆分是从传统聊天机器人转向教育产品时必须补的一课。6. 从能跑到好用联调、评测与真实场景打磨6.1 搭建离线评测集不要靠肉眼感觉验收Agent类应用最让人头疼的是感觉好像还行——自己测对话轮次时觉得挺自然但交给别人用就不是那么回事了。所以我从开发中后期开始搭建了一个小型的离线评测集。评测集结构不复杂每个条目包含情景ID比如airport_checkin_001用户输入尽量覆盖口语化的、有错误的、跑题的输入当前阶段ID期望策略动作ADVANCE / STAY / HINT / CORRECT期望Agent回应里必须出现的知识点或词汇我会用LLM作为裁判来跑自动测评按几个指标统计指标含义合格线策略准确率next_step判断是否与预期一致90%以上知识点覆盖率单场景内教学目标关键词在Agent回应中出现的比例80%以上中文串扰率不应出现中文的回复中是否夹带中文低于5%无效回应率是否出现答非所问、逻辑断裂的回应低于5%自动测评通过之后再找人做人工体验重点看的是教学感而不是对话感——也就是用户练完这个场景有没有感觉到自己学到了东西。自动指标和人工感受经常出现偏差比如一次对话里纠错频率太高指标全绿但体验分很低。这些只有靠真实用户反馈才能发现问题。6.2 我在实际开发中踩过的几个典型坑很多问题是在真实联调时浮出来的我在这里记一下省得后面有人重复踩。第一个坑是状态机没有超时和熔断机制。之前设计的状态机只是等待用户输入-处理-状态迁移但用户可能中途不说话了、直接乱打一通、或者网断了重连。这些情况不做处理状态机会卡在某个阶段不出来用户再回来时体验非常差。解决办法是在状态机外再加了会话级心跳检测如果一段时间没有输入就主动用Agent的话术引导用户继续比如Are you still there? Would you like to continue the practice?第二个坑是ASR的错误文本被大模型当成正规英语来教学。语音转写口语文本往往没有标点、带语气词、有识别错误比如用户说Can I get a window sitASR可能转成Can I get a window seat结果模型以为用户说对了。这个问题的根治很麻烦短期方案是在LLM生成求判断之前先拼接一段提示以下是语音转写结果可能存在识别偏差请结合上下文判断不要因为转写错误而误判用户水平。这只一个缓解手段但实测能有效降低误判率。第三个坑是用户档案的误更新导致教学行为突变。有一次系统在用户连续失误后把难度等级从intermediate调到beginner结果接下来的整场面试练习变成了蹦单词式的一问一答用户被当成了完全零基础。问题就出在前面说的场景结束后异步更新的策略被临时调试代码破坏了——有个试验开关把实时更新打开了。此后我再不敢在线上随便改记忆模块的更新策略。测试可以开灰度但不能拿一套逻辑同时跑两种模式。6.3 后续可以扩展的几个方向这个项目目前跑通的是预设脚本单Agent教学闭环但如果继续往下做我认为有三个明确方向。一个是动态情景生成根据用户的档案和学习目标让Agent自己设计新的场景脚本这一步会真正用到规划能力。第二个是多角色扩展比如面试场景里除了面试官再加入一个观察者角色在面试结束后给出点评——这是比较自然的多Agent使用方式而不是为了用而用。第三个是把分析能力做成课后报告让用户看到自己这个场景里错误类型的分布趋势、词汇缺口和进步轨迹。不过这些扩展都有一个共同前提先把现状的基础评估跑明白。教学类应用最忌讳的就是无限堆功能结果核心体验却没做好。一个新场景上线之前至少要跑过十轮真实用户对话把策略准确率、知识点覆盖率这些指标都过一遍确认不会出现明显的地质性崩坏再放量测试。这个流程比我一开始想象中的评测复杂度要高不少但对教学效果的把控非常值得。从我个人的开发体验来看英语情景教学Agent并不是一个单纯调大模型的项目。它的难点在于把教育学的分寸感转化成工程上的确定性逻辑——什么时候推进、什么时候纠偏、什么时候给提示、什么时候鼓励每一个决策节点都要能被代码控制、被数据验证。也恰恰是这部分才是我做完之后觉得最有价值、最值得拿出来分享的地方。如果这个项目能给你一些启发那大概就是同一个思路Agent不是越自由越好越贴合场景、越有边界才越有可能成为真正可用的产品。
返回列表