ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flask+Rasa中文任务型对话机器人实战:从NLU到部署完整指南

Flask+Rasa中文任务型对话机器人实战:从NLU到部署完整指南 简介基于Python、Flask和Rasa的中文任务型对话机器人项目资源包整合完整源码、部署文档与全部数据资料源自高分毕业设计面向自然语言处理与人机交互场景适合计算机相关专业学生用于毕业设计、课程设计或项目初期演示也可供开发者参考学习。压缩包共108个文件体积约7.49MB内含Python源码、对话流程配置、前端页面、数据字典、模型备份以及图文说明等多种类型文件整体结构清晰便于按功能模块查阅和二次开发。目前已有168人学习下载。资源附带前端交互样式、对话流程故事图、实体识别模型数据及演示动图能帮助读者理解从后端对话管理到前端展示的完整链路。同时提供部署文档可直接运行验证效果也可在此基础上扩展新功能作为课设或毕设的可靠基础。1. 中文任务型对话机器人的门槛不在模型而在工程拼装做过对话系统的人都有个体会单点算法意图分类、实体识别现在都很成熟真正耗时的是把这些模型塞进一个能跑的demo还要能对得上导师或甲方的验收点。这个扎着Flask rasa的中文任务型对话机器人源码里带了一套完整的 nlu 数据、stories 对话流、MITIE 实体模型文件以及 web 聊天界面属于“拿来就能启动”的毕业设计型项目。它解决的不是“如何训练一个顶尖模型”而是“如何用 rasa 管理对话状态、用 Flask 暴露 HTTP 接口并把两者接到一个浏览器页面里”。适合两类人一类是正在做课程设计或毕设需要快速复现一个能演示的任务型助手另一类是想搞清楚 rasa 1.x 时代中文对话系统工程链路打算在此基础上替换成 Rasa 3.x 或自定义 NLU 模块的开发者。2. rasa 对话管理从 domain 到 stories 的意图—动作闭环2.1 domain.yml 定义意图、实体与动作先画好边界rasa 的任务型对话核心是 domain。这个文件里声明了对话系统能理解什么intents、entities、能说什么responses、能做什么actions。项目里的domain.yml通常长这样intents: - greet - weather_query - book_ticket entities: - city - date actions: - action_query_weather - action_book_ticket responses: utter_greet: - text: 您好我是助手请问有什么需要这里actions里既有普通 action即直接返回模板文本也有以action_开头的自定义 action它们会触发后端 Python 代码。domain 文件的价值在于它强制把问题域拆成“用户意图”和“系统响应”这比直接写 if-else 分支更容易维护。当你用rasa train训练时rasa 会依据 domain 检查 stories 里引用的 action 是否真实存在缺一个都会报错。在调试这个项目时我一般会先看 domain.yml 里 intent 和 entity 的清单再对照nlu.md和stories.md找对应关系。如果发现识别不准多半不是模型问题而是意图定义得太粗或太细。意图过多容易重叠过少则无法区分“查天气”和“订机票”这种明显不同的请求。合理粒度是每个意图对应一个明确的任务动作。2.2 stories对话流是怎么“画”出来的stories 是任务型对话的状态转移记录。一行故事就是一个用户与系统的交互轨迹。项目里的stories.md类似## weather path * greet - utter_greet * weather_query{city: 北京} - action_query_weather * thanks - utter_welcome含义是用户打招呼后系统回复问候用户发起天气查询并携带实体city北京系统执行自定义 action最后用户道谢系统回复“不客气”。rasa 通过大量这样的轨迹学习出对话策略最终在推理时根据当前意图和槽位状态选择下一个动作。要注意 stories 与 domain 是强耦合的。比如上面故事里出现了action_query_weather如果 domain 的 actions 里没有它训练就会失败。项目里如果出现story_graph.dot那就是把 stories 可视化成状态图所用的中间产物。这个.dot文件可以直接用 Graphviz 转成图片方便检查会话分支是否有死路或冲突。2.3 用 story_graph.dot 排查对话流冲突story_graph.dot是 rasa 在训练或校验过程中生成的图描述文件。常见做法是用命令生成渲染图dot -Tpng story_graph.dot -o story_graph.png这个图会把每个意图、动作、槽位变化显示成节点边表示转移关系。我常用它来排查两类问题一是两个故事开头相同但后续动作不同会导致策略混淆二是某些分支永远到达不了说明 stories 里缺少触发路径。对于有 5 年以上经验的人这个文件的价值在于可以直接识别出对话流的“状态爆炸点”——比如同时使用了 3 个槽位且没有做 fallback图里会出现大量密集分支此时就该考虑用 Form 来收敛对话流程。3. 中文 NLU 流水线MITIE 实体抽取与分词配置3.1 中文 pipeline 怎么配才能兼顾意图和实体中文 NLU 与英文最大差异是分词。rasa 1.x 通常使用jieba分词配合MITIE做实体提取。项目的config.yml里 pipeline 大致是language: zh pipeline: - name: nlp_mitie model: data/total_word_feature_extractor.dat - name: tokenizer_jieba - name: ner_mitie - name: ner_synonyms - name: intent_featurizer_count_vectors - name: intent_classifier_tensorflow_embedding注意nlp_mitie那个model参数指向的是 MITIE 的总词特征文件不是实体模型。这个文件需要单独下载中文一般用total_word_feature_extractor_zh.dat。项目里出现的component_2_MitieEntityExtractor.dat则是训练完成后保存的第 2 个组件即ner_mitie的实体提取模型rasa 在加载时会按 pipeline 顺序从这个文件恢复实体识别器。这里有一个关键点intent_classifier_tensorflow_embedding是嵌入式的意图分类器它把用户输入映射到向量空间再分类对中文这种高变体表达更友好。如果换成intent_classifier_sklearn则依赖词袋特征对未见过的说法容忍度低。新手常犯的错误是只照抄 pipeline 而没注意language: zh漏掉这一行会导致分词和特征提取行为异常。3.2 component_2_MitieEntityExtractor.dat 为什么是“第2号组件”rasa 在训练时会把每个 pipeline 组件的输出持久化到模型目录文件名里的索引号对应配置里的顺序。component_2意味着它是索引为 2 的组件也就是ner_mitie。这个.dat文件是二进制模型体积可能达到几十 MB里面保存了 CRF 参数和实体标签映射。如果你在部署时只拷贝了模型压缩包忽略了component_2_MitieEntityExtractor.dat会出现类似Could not load MITIE NER component的报错。所以拿到这个 zip 资源后第一时间检查models/目录里的文件完整性常见做法是find . -name *.dat -ls确保total_word_feature_extractor.dat和component_2_MitieEntityExtractor.dat都在且不是 0 字节。前者是通用词向量后者是任务相关的实体模型两者缺一不可。3.3 手工训练一遍 NLU理解数据到模型的映射想要复现出“能识别北京是城市实体”的效果需要看nlu.md## intent:weather_query - 北京今天天气怎么样 - 帮我查一下上海的天气 - {city: 深圳}后天会下雨吗训练命令也很简单rasa train nlu训练完成后可以用rasa shell nlu交互式测试。这个 shell 会输出每个意图的置信度和实体识别结果{ intent: {name: weather_query, confidence: 0.98}, entities: [{entity: city, value: 北京, start: 0, end: 2}] }这个命令是排查 NLU 问题最重要的工具。如果识别出实体但 value 不是你想要的比如“北京”被提取成“北京市”那就需要在nlu.md里加入## synonym:北京的映射或者检查ner_synonyms组件是否在 pipeline 中。实体同义词典是这类项目里最容易遗漏的数据直接决定槽值是否干净。4. Flask 作为对话服务层REST 接口与前端对接4.1 为什么用 Flask 包一层 rasa而不是直接连 rasa serverrasa 自带rasa run可以启动 HTTP 服务但默认接口和认证方式对前端不那么友好。这个项目用 Flask 中间层做转发核心目的有三个一是自己控制路由和鉴权逻辑比如只在/webhook开放接口二是把 rasa 的Agent.load()放到内存里避免每个请求重新加载模型三是方便加日志、限流、会话 ID 管理这些生产化要素。加载模型的核心代码片段from rasa.core.agent import Agent agent Agent.load(models/dialogue)Agent.load会同时加载 NLU 模型和对话模型。注意这里models/dialogue是经过rasa train后生成的完整模型包而models/nlu只包含 NLU。任务型对话必须加载 dialogue否则handle_message无法执行 stories 里的动作。4.2 定义 /webhook 接口对齐消息格式Flask 侧最关键的代码是处理 POST 请求并把用户文本传给 rasafrom flask import Flask, request, jsonify app Flask(__name__) app.route(/webhook, methods[POST]) def webhook(): data request.get_json(forceTrue) user_message data.get(message, ) sender_id data.get(sender_id, default) responses agent.handle_text(user_message, sender_idsender_id) reply responses[-1][text] if responses else 我没听清 return jsonify({reply: reply, intent: get_intent(user_message)})这里agent.handle_text返回一个消息列表每条消息可能包含text、image、buttons等键。取最后一条是因为任务型对话中动作链可能包含多个中间输出比如先说“正在查询”再给出结果而前端通常只展示最终一条。sender_id是对话会话标识不传的话 rasa 会默认使用同一个 session多用户切换时就会出现串话。forceTrue表示即使请求头没有application/json也会尝试解析 body前端如果没正确设置 Content-Type 也能工作。实际部署时建议不要用forceTrue而是明确要求前端设置请求头避免吞掉格式错误。4.3 前端 chat_interface.css 和 style.css 在对话流程里的角色这个 zip 里有多个 css 文件chat_interface.css控制聊天窗口布局style.css控制整体页面样式。它们不参与逻辑但直接影响演示效果。如果你要把这个机器人集成到自己的项目里可以只保留聊天面板部分核心是发送请求的 js 代码fetch(/webhook, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({message: text, sender_id: user-123}) }) .then(res res.json()) .then(data { appendMessage(data.reply); });需要留意的是Flask 默认只监听本地地址前端页面与后端同源时通常没有问题。如果你把 html 直接拖到浏览器打开file://协议会触发跨域问题。常见做法是用render_template将页面放到 Flask 的 templates 目录下这样就不需要额外配置 CORS。项目里那些.gif文件很可能就是聊天面板里的表情反馈属于锦上添花的部分。5. 部署清单和三个高频坑从 zip 到可演示5.1 解压后的一次完整启动流程拿到这个资源先按顺序执行以下步骤而不是直接python app.py# 1. 解压并确认目录结构 unzip 基于PythonFlaskrasa的中文任务型对话机器人源码部署文档全部数据资料.zip -d chatbot cd chatbot # 2. 安装依赖建议 Python 3.6/3.7 pip install flask rasa1.10.3 mitie jieba # 3. 加载 rasa 模型如果 models 目录缺失则先训练 rasa train # 4. 启动 Flask 服务 python app.py启动完成后访问http://127.0.0.1:5000。如果页面能正常加载但发送消息无回应先看 Flask 控制台有没有异常栈再用 curl 直连接口验证curl -X POST http://127.0.0.1:5000/webhook \ -H Content-Type: application/json \ -d {message:北京天气怎么样,sender_id:123}这个命令会绕过前端直接测试后端能有效区分问题出在接口层还是页面层。5.2 坑一MITIE 模型路径与 Python 版本不匹配total_word_feature_extractor.dat是 MITIE 的训练产物它依赖 C 库pip 安装的mitie包在 Python 3.8 之后经常编译失败。这个项目标注的时间点数据里有20190618批次说明它大概率是在 Python 3.6 环境下开发的。如果你用 Python 3.10会遇到两个问题一是mitie无法安装二是加载模型时AttributeError。解决方案是创建 Python 3.6 或 3.7 的 conda 环境或者直接使用项目部署文档里指定的环境。不要试图把 MITIE 替换成 spaCy 然后复用同一个component_2文件——实体模型格式与组件强绑定换组件必须重新标注数据并训练。5.3 坑二story_graph.dot 里的图和 stories.md 不一致项目附带story_graph.dot可能是训练时生成的旧文件。你新增故事后如果没有重新执行rasa visualize这个 dot 文件不会自动更新。如果你基于图片来讲解设计务必重新生成rasa visualize --stories data/stories.md --nlu data/nlu.md --out graph.png另外需要注意rasa visualize在老版本里不支持中文文件名如果路径包含中文会报编码错误。把项目放到纯英文路径下能省掉很多莫名其妙的 Bug。5.4 坑三Flask 与 rasa 的端口冲突Flask 默认 5000 端口rasa 默认 5005 端口。如果先启动了rasa run再启动 app.py不会冲突因为端口不同。但如果你在代码里让agent.handle_text调用了自定义 action而 action 服务用了 5055 端口就需要三个端口同时可用。最常见的问题是把 Flask 跑在 5005 上导致 rasa 服务无法启动报错Address already in use。排查方式很简单启动前先看端口占用netstat -tlnp | grep -E 5000|5005|5055如果端口被占用改 Flask 启动参数即可app.run(host0.0.0.0, port5000, debugFalse)另一个容易被忽略的是debugTrue会启动 reloader双击加载Agent.load导致模型被加载两次内存翻倍在内存只有 2G 的服务器上会直接 OOM。演示时建议把 debug 关掉并在加载模型前检查agent是否已初始化避免多线程环境下重复创建。这个项目里的temporary.css和style.css~是编辑器备份文件可以直接删掉不影响运行。本文还有配套的精品资源点击获取
返回列表