news 2026/10/1 13:17:17

从问答到教学:用LangGraph打造带记忆与多Agent协作的英语情景Agent

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从问答到教学:用LangGraph打造带记忆与多Agent协作的英语情景Agent

这次花了大概两周时间,把一个原本只会“你问我答”的聊天模型,做成了一个能陪你练口语、模拟机场值机、餐厅点餐、酒店入住的英语情景教学Agent。项目里最重要的不是接了大模型API,而是把“情景剧本、记忆系统、语音交互、评分反馈”这些零散模块串成了一个完整的Agent闭环。整个过程中我反复改架构、调Prompt、清上下文,踩了不少坑,也把Agent开发里几个关键概念——比如ReAct循环、Agent记忆、多Agent协作、工具调用——全部实践了一遍。

这篇文章更适合两类人看:一类是刚接触LLM应用开发,想搞明白Agent到底是怎么跑起来的新手;另一类是已经会调API,但不知道如何把一个Demo做成有教学价值、能稳定多轮对话系统的开发者。我会把设计思路、代码细节、常见报错和排查过程都写在里面,尽量做到可以直接照着复现。

1. 项目背景与整体设计思路

1.1 为什么选英语情景教学作为切入点

英语学习有个很老的痛点:语法书背得再熟,一到真实场景就张不开嘴。传统的口语练习要么找外教一对一,成本高而且时间固定;要么用纯录音跟读,完全没有交互感。大模型出现后,我第一个反应就是它可以做“虚拟场景对话”,但直接扔给用户一个聊天框,效果其实很糟糕。

原因是普通聊天模型没有教学约束。你跟它说“模拟机场值机”,它可能第一轮就问你“Can I have your passport, please?”,然后你回一句“I forgot my passport”,它就开始自由发挥,画风逐渐跑偏,最后甚至变成英文写作辅导。这不是模型不够好,而是缺少一个Agent该有的状态管理和目标管理。

把这个问题拆开,真正需要的不是“一个能聊天的模型”,而是一个能推进教学流程的智能体:它要清楚当前在哪个教学阶段、用户犯过哪些错误、哪些知识点必须覆盖、什么时候可以推进到下一环节、什么时候需要停下来纠正。这就是我决定做英语情景教学Agent的原因——它天然需要状态、记忆、工具调用和评价反馈,几乎覆盖了Agent开发的所有主干知识点。

1.2 目标用户与核心使用场景

我在设计初期没有贪多,先锁定了三个最典型的场景:机场值机、餐厅点餐、酒店入住。这三个场景有几个共同特点:

  • 流程相对固定,适合设计教学节点
  • 有大量高频实用表达,用户学了就能用
  • 试错成本低,用户可以反复练习同一个对话

目标用户主要是有一定英语基础但缺少口语环境的人,也包括准备出国旅游、临时要应对真实场景的“突击型”用户。对这类人来说,Agent需要做到三件事:听懂用户的英文回答、判断回答是否符合场景要求、在对话结束后给出可落地的反馈。

我之前见过一些类似产品,最大的问题是一股脑“全部纠错”。用户刚说一句“I go airport yesterday”,系统立刻弹出一堆语法错误,对话节奏完全被打断,用户根本没有完整体验一次流畅对话的机会。所以我在设计上特意把“对话体验”和“教学评价”分离,后面会详细展开这一点。

2. Agent架构与核心模块拆解

2.1 Agent骨架:ReAct循环与状态机

很多初学者会把Agent理解成一个更智能的聊天机器人,其实差得很远。普通聊天机器人是一次性的“输入-输出”,而Agent的核心是循环:模型需要理解当前状态、决定下一步动作、调用工具或更新记忆,然后再次观察结果、继续推理,直到任务结束。

这个循环在学术上叫ReAct,也就是Reason + Act,推理和行动交替进行。我在项目里没有从零手写这个循环,而是用了LangGraph来做状态流转,因为教学场景不太适合完全开放式的自由对话,我需要在循环外面包一层“教学关卡”。

打个比方:Agent像一个聪明的现场演员,但剧本的起承转合必须可控。用户不能从“点菜”直接跳到“买单”,中间至少要经过“询问推荐”“确认菜品”这些关键节点。所以我在LangGraph里定义了一个状态对象,记录了当前教学进度:

class TeachingState(TypedDict): session_id: str scenario: str stage: str history: list user_profile: dict pending_items: list

stage字段就是我的状态机核心,它的值可能是start、order、pay、end。Agent每次生成回应之前,都要先看当前stage,生成回应之后,再决定是否跳转到下一个stage。这样既保留了LLM的灵活性,又不会让对话失控。

2.2 记忆体系:短期记忆、工作记忆与长期记忆

“Agent记忆”是Agent开发里最容易翻车的地方。英文教学场景尤其明显:用户上一轮说自己要去伦敦,这一轮如果模型忘了,会让用户觉得“你在跟我演什么”;但如果把几十分钟的对话全部堆进上下文,又很容易超出窗口限制,或者被无关信息干扰。

我在项目里把记忆分成三层来设计。第一层是短期记忆,也就是当前对话窗口里的消息列表,每轮对话都追加。第二层是工作记忆,用来存放当前任务的关键信息,比如用户已经掌握的词、需要加深练习的知识点、当前场景的目标清单。工作记忆用一个JSON结构跟随状态一起传递,每轮结束时由大模型决定是否更新。

第三层是长期记忆,存在本地SQLite和向量数据库里。对话结束后,系统会提取用户的常见错误、已掌握表达、偏好场景这些信息并持久化。下次同一用户再来练习时,系统可以加载一句话概况,例如“该用户对过去式不敏感,餐厅点餐时容易漏掉礼貌用语”,让Agent在开场时就能调整教学难度。

长期记忆还有一个很重要的用途:跨场景迁移。用户第一次练机场值机时学会了“Could I see your boarding pass?”这个句型,第二次练酒店入住时,Agent就可以主动使用同类句式,帮助用户巩固而非重复教学。

2.3 场景引擎与角色扮演逻辑

英语情景教学Agent不能靠写死剧本,因为用户不会按剧本走。用户有可能答非所问,有可能直接用中文回答,也有可能突然问一句“你说太快了,可以慢一点吗”。所以场景引擎的设计逻辑是:定义场景骨架,把具体台词的生成交给大模型。

场景骨架用JSON或YAML描述。以机场值机为例:

scenario: airport_check_in goal: - check_passport - confirm_flight_info - check_boarding_pass stages: - name: start expected_skills: [greeting, identify_passport] - name: check_info expected_skills: [answer_flight_number, destination] - name: baggage expected_skills: [baggage_weight, window_seat] end_condition: user_confirmed_departure

这里我特意没有写具体台词,只写了“必须覆盖的教学点”。这样大模型在扮演值机员时,可以针对用户的回答随机应变,但最终会引导用户走完整个流程。用户如果说“Can I put my bag here?”,Agent就会自然地引入行李重量的话题,正好落在baggage教学点上。这种动态生成比固定剧本自然得多,也更符合教学需要。

2.4 工具调用与外部服务集成

Agent不能只靠大模型“记住一切”,还需要主动查工具、查资料。我在这套系统里接入的工具主要有三类:语音识别工具、词汇查询工具、场景推进工具。

词汇查询工具比较典型。用户说了一个生词,Agent可以调用一个lookup_vocabulary函数,从向量数据库里检索词义、例句和发音音标,再把检索结果注入上下文。这比让大模型凭记忆乱解释要可靠得多,尤其是一些专业词汇和场景术语。

工具调用走的是OpenAI兼容的Function Calling协议。大模型在生成回复时,如果判断需要查词,就会返回一个tool_calls结构,而不是直接回复用户。程序拿到这个结构后执行对应函数,再把函数结果作为消息返回给模型,模型据此生成最终回答。这个流程做多了之后你就会发现,工具调用是Agent区别于普通聊天机器人的真正分水岭。

我做了一个音频处理模块:浏览器端录音,传到后端使用ASR模型转成文本,Agent处理完文本后用TTS合成语音返回。语音流程加进来之后,整个体验立刻不一样了,用户会更自然地开口说英语,而不是盯着键盘打字。

3. 从零开始搭建:关键实现与代码细节

3.1 环境准备与依赖选型

整条技术链路我用的是Python 3.11 + FastAPI + LangGraph,模型接口统一走OpenAI兼容协议,这样以后换国产模型或本地模型时不需要改动核心代码。ASR用的Whisper系列,TTS选择的是延迟较低的云端服务,前端用简单的Web页面加麦克风权限。

为什么用LangGraph而不用自己手写循环?因为教学流程天然是一个有向图,从开场、对话到评估有多条分支路径。LangGraph的StateGraph可以清晰表达这些节点和边的关系,同时还能保留足够的自定义空间。如果只是做一个简单问答Agent,确实没必要上框架,但要做多Agent协作和状态管理,LangGraph是目前比较顺手的选择。

选型时还考虑过AutoGen和CrewAI。AutoGen更适合多个模型之间自由对话的实验室场景,对生产环境的状态控制比较弱;CrewAI更偏任务分发和角色扮演,但我的场景需要和语音、记忆系统深度耦合,LangGraph的底层控制力最强。

3.2 核心Agent循环的实现

Agent循环是整个系统的发动机。我用LangGraph定义了一个简化的状态图,核心节点包括agent_node、tool_node、memory_node和evaluate_node。其中agent_node负责调用大模型,tool_node负责执行工具调用,memory_node负责读取和更新记忆。

核心的Agent节点大致长这样(关键代码简化):

from langgraph.graph import StateGraph, END def agent_node(state: TeachingState): messages = build_messages(state) response = llm.invoke(messages, tools=tool_schemas) if response.tool_calls: state["pending_calls"] = response.tool_calls return {"action": "call_tools", "state": state} else: state["history"].append(("assistant", response.content)) return {"action": "update_memory", "state": state} def tool_node(state: TeachingState): for call in state["pending_calls"]: result = execute_tool(call) state["history"].append(("tool", call.name, result)) return {"action": "continue_loop", "state": state} graph = StateGraph(TeachingState) graph.add_node("agent", agent_node) graph.add_node("tools", tool_node) graph.add_edge("agent", "tools", condition=lambda s: s["action"] == "call_tools") graph.add_edge("agent", "memory", condition=lambda s: s["action"] == "update_memory")

这里最关键的细节是max_iterations。很多人在调试Agent时遇到agent execution terminated due to error,其实就是模型和工具之间反复循环,一直没有输出最终回复,被框架强行掐断。我在每次调用前都会给agent_node传一个迭代计数,超过5轮就强制让模型输出当前过渡回复,避免死循环。

3.3 情景剧本与动态剧情分支

写完Agent循环后,我又把每个教学场景做成了更细致的“动态剧情分支”。这里参考了游戏里的对话树设计思路,但比对话树灵活得多:每个节点不是写死的台词选项,而是一个“教学意图”。

以餐厅点餐为例,系统提示词里会写清楚:你是一个伦敦小餐馆的服务员,当前在“starter”阶段,你的目标是引导用户点一道前菜。如果用户点了前菜但语法不完整,你不用立刻指出,而是自然接一句“Anything else for the starter?”,让对话继续。在这个阶段,用户的完整对话体验比语法纠正更重要。

等到整个场景结束后,评价Agent才会把用户在前菜环节说过的所有句子拿出来统一分析。这就像真正的外教课:当面聊天不打断,课后写评语。

动态分支还体现在“求助机制”。用户一旦说“Can you help me?”,Agent会切换到一个explainer节点,先用中文或英文简单解释当前任务,再给出一个示范句型,然后重新回到角色扮演。这个分支在教学中非常重要,能有效降低用户的挫败感。

3.4 语音交互与评测打分

语音交互的流程比纯文本复杂一些,因为要处理录音格式、网络延迟、断句和TTS缓存。我采用的方案是前端用MediaRecorder录webm格式,后端统一转成16kHz的wav再喂给Whisper。这里有一个坑:webm直接传给某些ASR模型会报编码错误,所以格式转换不能省略。

评测打分环节我单独拆了一个evaluator节点。每轮对话结束后,该节点并行执行,不会阻塞主对话。评分维度包括语法正确率、词汇丰富度、任务完成度、语音流畅度。其中语音流畅度由ASR返回的置信度和停顿次数综合估算。

为了不让评分变成“模型乱打分数”,我设计了一个结构化的评分提示词:先让模型以JSON格式输出每一维度的评分和例句摘录,再给一个总分。通过几次压测发现,加入“必须摘录用户原句作为评分依据”后,评分稳定性明显提高,因为模型不能再凭空给分,必须引用实际对话内容。

4. 多Agent协作与教学编排

4.1 为什么需要多个Agent

最初版本里,我是一个Agent既当值机员又当英语老师,结果一团糟。它上一句还在正常扮演角色,下一句突然说“Your pronunciation of ‘passport’ is not clear, please try again”,用户完全出戏。这就是典型的职责耦合问题。

后来我参考了一些多Agent协作框架的设计思路,把系统拆成三个角色:角色扮演Agent、教学纠错Agent、评价Agent。角色扮演Agent只负责扮演场景里的NPC,永远用英语回复,哪怕用户说中文也不切换。教学纠错Agent只在特定时机插入,负责解释语法、提供示范。评价Agent完全在后台运行,只在会话结束时输出报告。

有人可能会问,这不就是一个Prompt里多写几句话的事吗?实际不是。三个Agent用的是不同的提示词、不同的模型参数甚至不同的记忆域。角色扮演Agent的temperature可以调到0.8,让台词更自然;评价Agent的temperature必须接近0,保证评分稳定。这些差异在同一个上下文里很难优雅共存。

4.2 角色Agent与教学评价分离的收益

这个拆分让项目质量提升了一个档次。最明显的变化是对话自然度上来了,用户不再觉得和自己对话的是一个“机器老师”,而是一个真正的角色。有用户反馈说,练到一半都忘了这是AI,完全沉浸在“办登机手续”的紧张感里。

评价Agent的独立还解决了另一个问题:对话历史里不会混入评价信息。之前单Agent多点角色时,模型经常把评价内容当作用户说的话,导致后续上下文污染,现在评价结果全部单独存储,不进入角色Agent的上下文,彻底规避了这个问题。

多Agent协作也不是越多越好,拆到后面的“记忆管家Agent”后我及时停了手。记忆写入只要一个简单的提取函数就能完成,再单独起一个Agent会平白增加一次大模型调用,成本和延迟都不划算。切分粒度应该跟着“状态复杂度”走,而不是盲目追概念。

4.3 用LangGraph实现多Agent编排

我用LangGraph的StateGraph做了一个三层编排结构。第一层是router节点,决定当前应该调用哪个Agent;第二层是各Agent的具体执行节点;第三层是assembler节点,负责汇总各Agent的输出并传给下一轮对话。

def router(state): if state["need_evaluation"]: return "evaluator" if state["need_explain"]: return "teacher" return "role_player"

这里比较关键的是need_explain标志。它来自哪里?来自角色扮演Agent的返回结果。角色扮演Agent在生成回复时,除了生成英文台词,还会额外输出一个JSON标记,比如{"need_help": true, "reason": "user_says_help"}。我通过输出解析拿到这个标记,再触发路由跳转。

这个设计的精髓在于:角色扮演Agent不需要知道“英语讲解员Agent”的存在,它只需要诚实上报“我觉得用户需要帮助”,系统的编排逻辑会替它完成后续任务。Agent之间松耦合,才不会互相干扰。

5. 常见问题与排查实录

5.1 模型返回不符合预期的调试方法

我遇到最多的报错之一是agent execution terminated due to error。排查日志后发现,问题出在某次工具调用返回的结果字段太大,塞满了上下文,导致模型后续生成失败。解决方法是给工具返回结果做了截断,只保留前300个字符,并在工具描述里注明“返回内容为摘要”。

另一个高发问题是模型不按角色说话。比如要求它扮演值机员,它却回答“这是一个模拟场景,我可以帮助你练习英语”。这类情况在大模型上很常见,根本原因是我在某轮对话后不小心把系统提示词里的角色指令覆盖了。检查后发现,我的build_messages函数在处理工具结果时,把系统消息重新拼了一遍,但拼的时候读的scenario字段已经变了,等于角色中途换了人。

调试这类问题,一个非常实用的技巧是:把每次Agent循环的完整消息结构存成日志,出问题时直接看最后几轮messages列表,判断模型是否被上下文误导。不要只盯报错信息,而是盯模型看到了什么。

5.2 记忆串场与上下文污染的实战排查

多场景连续练习时,我遇到过用户明明在练“酒店入住”,Agent却突然聊起“你上次在机场值机时没有带行李”。这个问题的根源是长期记忆被无差别注入。我的记忆模块一开始把所有历史对话摘要都写进了System Prompt,导致模型分不清当前场景和旧场景。

解决办法是给记忆打标签:scope字段标记该记忆属于哪个场景,只有和当前场景相关的记忆才会被加载。全局性质的记忆,比如“用户偏好英式英语”“用户对数字听力比较弱”,则单独放一个global_notes字段。这样既保留了跨场景学习能力,又不会串场。

上下文污染还有一个小细节:角色扮演Agent虽然只生成英文台词,但评测Agent的临时输出有时会被意外追加到消息历史里。我专门写了一个清理函数,在每轮对话结束时把evaluator写入的临时键全部删除,只保留三个字段:user、assistant、tool_result。

5.3 评测不准与Prompt依赖问题

评分功能上线后有个怪现象:同一段对话,第一次评88分,第二次评72分。排查发现是模型对评分标准的理解受上下文影响,尤其是当用户某一句出现多个错误时,模型会随机挑一个错误放大。

解决办法是给评分环节加了明确的锚点:“先列出用户说得最棒的三个句子”“再列出需要改进的句子”“最后再打总分”。我用少样本示例固定了这个输出格式,模型终于稳定下来。

我还尝试过用两个模型分别打分再取平均,效果确实更稳,但成本翻倍。后来折中方案是:主评分用大模型,二次校验用规则。规则主要查两类硬伤——单复数、时态一致性,只要规则命中就在相应维度扣固定分值。这比让模型“动脑思考”更可控。

5.4 部署与性能优化实录

整个系统部署在一台4核CPU + 16GB内存的服务器上,没上GPU。语音识别是唯一的重负载,Whisper的小模型在CPU上处理一段5秒录音要2秒左右,用户感知延迟偏高。后来我把ASR替换成更轻量的接口调用,并在前端做了语音活动检测,只有用户停止说话才上传,整体延迟降到1.5秒以内。

TTS部分也要提一下,千万不能让用户等整段回复全部合成完再播放。我改成了流式拼接:先把第一句话的音频返回前端播放,后面几句话后台继续生成。这个用户体验差异非常明显,几乎让人感觉不到是AI在回复。

流式输出对后端还有个额外要求:WebSocket连接要稳定。我一开始用普通的HTTP轮询,每轮对话都要重新握手,延迟高且容易断线。换成WebSocket长连接后,整个对话过程保持在一条连接里,配合心跳机制,基本没有再出现断线问题。

6. 实操总结与扩展方向

写到最后,我想分享几点基于实操的判断。如果你也想做一个类似的Agent,不用急着把功能做全,先跑通一个场景的完整闭环:用户开口说一句英文,Agent扮演角色自然回应,场景结束后给出靠谱的评分报告。这个闭环跑通之后,再逐步加记忆、加多Agent、加新场景,顺序不能乱。

这个项目后续我打算继续做三个扩展:第一个是把场景从“旅行英语”扩展到“职场英语”,比如面试、会议、邮件沟通;第二个是加入“刻意训练模式”,针对用户反复出错的语法点生成专项练习;第三个是做一个用户跨会话的进步曲线,借助长期记忆数据,让Agent知道用户这个月比上个月进步了多少。这三点做完,英语情景教学Agent就不再只是一个对话玩具,而是一个有完整教学周期的数字教练。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 13:16:59

YOLOv8图书馆书籍识别系统:从数据集标注到部署实战

简介:一套基于YOLOv8的图书馆书籍识别系统,面向计算机、人工智能、电子信息等相关专业的学生和开发者,专为毕业设计、课程设计、大作业或项目初期演示打造,聚焦图书馆场景下的书籍目标检测与识别任务。资源压缩包共97个文件&#…

作者头像 李华
网站建设 2026/10/1 13:15:47

告别手写Agent循环:Strands Agents Harness SDK生产级Agent开发指南

1. 为什么“手写 Agent 循环”正在变成一种负债 如果你最近半年在折腾 AI Agent,大概率写过类似这样的东西:一个 while True 循环,里面塞着 LLM 调用、工具解析、结果回填、终止判断,再配上一堆 if/else 处理模型抽风、工具报…

作者头像 李华
网站建设 2026/10/1 13:14:50

英语情景教学Agent开发实战:从架构设计到LangGraph落地

这两年AI圈最热的一个词就是Agent。我自己做应用开发,前后接触了不少Agent项目,也踩过不少坑。最近这一个月做的一个项目,让我觉得最值得拿出来分享——从零到一开发一个英语情景教学Agent。简单说,它就是一个能模拟各种真实场景&…

作者头像 李华
网站建设 2026/10/1 13:14:29

基于Python的PCA人脸识别:原理、实现与避坑完整指南

简介:这份资源提供一套完整的基于Python的PCA人脸识别算法实现与配套讲解,适合计算机专业学生、算法初学者及需要完成课程设计的开发者。资源包含4个Python脚本,分别覆盖PCA算法核心实现、数组运算辅助、人脸识别示例等环节;16张P…

作者头像 李华
网站建设 2026/10/1 13:14:10

Jev开源模型生态爆发:技术拆解、部署实战与避坑指南

最近这两周AI圈最热闹的事,不是什么大厂又发布了旗舰模型,而是一个叫Jev的开源模型悄悄火了。火到什么程度?两个星期时间,GitHub上围绕它长出了28个项目,从命令行工具到WebUI,从代码助手到微调框架&#xf…

作者头像 李华
网站建设 2026/10/1 13:14:09

AutoGen多智能体协作实战:从架构设计到代码自动修复流水线

1. 从“多智能体”说起:AutoGen到底在解决什么问题 如果你最近在折腾大模型应用,大概率会撞上“多智能体协作”这个词。单次问答已经满足不了复杂任务了——写一份行业调研报告、跑通一个数据分析流程、自动修复一段有Bug的代码,这些事让一个…

作者头像 李华