首页 / 资讯中心 / 文章详情

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

Flask+Rasa中文任务型对话机器人:从部署到避坑实战指南 ★ FEATURED ARTICLE
简介基于Flask与Rasa的中文任务型对话机器人完整工程包属于自然语言处理与Web服务结合的典型实践项目适合NLP初学者、Python全栈开发者和需要搭建智能客服的团队。项目包含可运行的源码、部署文档、训练语料与前端交互界面适配Python 3.7以上环境并已通过运行验证可直接替换数据使用降低二次开发门槛。压缩包内共109个文件整体仅7.51MB涵盖35个文本配置、13个JSON数据、7个Python脚本、6个YAML管道、5个Markdown文档等核心类型另有HTML、CSS、JS前端页面以及图片和模型文件目录分类明确。已有117人学习下载适合通过实际项目理解Rasa对话管理、Flask服务发布及中文NLU任务流程。资源内附前端聊天样式、意图实体数据、模型档案和部署说明能够支撑从环境准备到定制功能开发的完整实践。1. Flaskrasa中文任务型对话机器人这套源码解决的不只是“能跑”刚拿到这个“Python优秀项目 基于Flaskrasa的中文任务型对话机器人源码部署文档全部数据资料.zip”多数人的第一个动作是打开app.py看接口怎么写然后在第一次rasa train时被中文分词和模型版本卡住。这个项目的技术选型非常典型rasa承担意图识别、槽位提取和多轮对话管理Flask负责把对话能力包装成HTTP接口前端、业务系统、数据库都通过它来对接。资料里如果带了训练好的模型和中文语料你已经省掉了最耗时的一块。这套技术栈适合有Python基础、想在一周内把中文任务型对话机器人跑通并改造成业务demo的开发者。下面按“骨架是什么→怎么跑通→踩了哪些坑→怎么接到真实数据”的顺序展开。2. 先把任务型对话的骨架看懂rasa管对话Flask管API2.1 为什么是rasa Flask而不是只用一个任务型对话机器人和我们平时用的闲聊机器人有一个本质区别它要完成任务比如查余额、订机票、办业务。这类机器人的核心是一个循环用户输入自然语言→识别意图和实体槽位→决策下一步动作→执行动作并回话→等待下一轮输入。rasa在这个循环里承担前两步和决策部分Flask则承担对外暴露和服务包装的部分。rasa本身自带HTTP接口也就是rest channel直接启动后就可以通过/webhooks/rest/webhook收发消息。那为什么还要在外面套一层Flask常见原因有四个一是生产环境不会把rasa的5005端口直接暴露给公网或内部业务系统需要在前面加一层统一入口二是Flask可以顺手做鉴权、日志、限流和请求格式转换三是任务型机器人几乎都要连业务数据库或内部系统Flask作为轻量服务层比在rasa action里写一堆路由逻辑要干净四是如果要做管理后台或H5页面Flask自带模板和静态资源托管rasa在这块帮不上忙。所以我通常把Flask理解成“门面”把rasa理解成“脑子”。门面不参与理解也不参与对话决策只负责把请求安全地转发给脑子再把脑子的回复带给用户。这个分层的好处是将来rasa模型升级Flask不用改前端换框架Flask也不用改。2.2 中文NLU pipeline怎么配从jieba到BERTrasa对英文的默认处理比较省心但中文落地时分词和特征抽取直接影响意图识别准确率这是任务型对话里最容易翻车的一环。常见的pipeline配置是jieba分词加CountVectors加DIETClassifier我一般会先在项目的config.yml里改成类似下面这样language: zh pipeline: - name: JiebaTokenizer - name: RegexFeaturizer - name: CountVectorsFeaturizer - name: CountVectorsFeaturizer analyzer: char_wb min_ngram: 1 max_ngram: 4 - name: DIETClassifier epochs: 100 constrain_similarities: true - name: EntitySynonymMapper policies: - name: MemoizationPolicy - name: RulePolicy - name: TEDPolicy max_history: 5 epochs: 100这段配置里有几个参数值得细看。第一JiebaTokenizer负责中文分词没有它后面所有特征抽取器都在做无用功。第二CountVectorsFeaturizer用了两次一次是词级别一次是char_wb字符级别min_ngram和max_ngram控制字符窗口长度这样即使遇到词库里没有的新词也能从字符组合里提取特征。第三DIETClassifier是rasa 3.x默认的意图分类和实体识别模型epochs我一般设在100左右太大容易过拟合太小意图学不完整。constrain_similarities设置为true能让模型对没见过的句式判断更保守减少“乱认意图”。如果项目里中文语料比较充足训练环境也有GPU还可以把CountVectors换成TransformersNLP权重用bert-base-chinese。但这会带来两个新问题模型文件大CPU推理慢训练依赖也多。我一般建议先把上面那套能跑通再看准确率缺口值不值得上BERT。2.3 Flask在这里的真实角色API透传、状态隔离与前端对接rasa的多轮对话状态是依赖sender_id来区分的同一个sender_id的对话会被rasa保留上下文。这个设计到了Flask层就变成一个很自然的参数透传逻辑。我的做法是在Flask里写一个统一对话接口接收前端传来的用户标识和消息转发给rasa再把回复整理成统一格式返回。from flask import Flask, request, jsonify import requests app Flask(__name__) RASA_URL http://localhost:5005/webhooks/rest/webhook app.route(/api/chat, methods[POST]) def chat(): data request.get_json() sender data.get(sender, user-001) message data.get(message, ) resp requests.post( RASA_URL, json{sender: sender, message: message}, timeout10 ) replies [item.get(text, ) for item in resp.json()] return jsonify({reply: \n.join(replies), sender: sender}) if __name__ __main__: app.run(host0.0.0.0, port5000)从逻辑上讲这个接口做的事情很单纯取请求里的sender和message原样转给rasa把rasa返回的若干条文字回复拼接后返回。timeout10是必须加的rasa在第一次冷启动推理时可能比较慢如果不加超时限制前端会一直挂着等响应这在生产环境是很糟糕的体验。sender参数要用业务系统里的真实用户ID而不是写死否则所有用户的对话上下文会串在一起A用户问过的槽位会被B用户继承这是任务型机器人最典型的会话串号问题。Flask层还能做的增强包括把请求日志写到文件、检查调用方身份、把回复里的文字再做一层敏感词过滤。项目原版可能只做了基础透传但你在改造成业务系统时这几个点是高概率要加的。3. 从zip到能对话环境、训练与启动的最小路径3.1 环境准备Python版本、虚拟环境和依赖安装rasa对Python版本很敏感rasa 3.x通常要求Python在3.7到3.10之间直接用3.11或3.12安装会因为依赖冲突报出一片红色错误。我一般会先建一个干净的虚拟环境再按项目资料里的依赖清单安装而不是直接往系统Python里塞包。conda create -n rasa-chat python3.9 -y conda activate rasa-chat cd /path/to/unzip/project pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple用conda建环境的好处是Python版本切换干净不会污染系统环境。如果资料里没有requirements.txt也可以手动安装rasa和flask但版本容易漂移第一次启动时会发现哪个包缺失就补哪个比较被动。国内网络环境下载依赖时建议用清华或阿里云的PyPI镜像不然安装rasa这种大包体会很慢甚至会下载到一半失败。安装完成之后先用两个命令验证环境是否正常python --version看版本rasa --version看rasa版本及其依赖的Python版本范围。看到版本输出一致再进入下一步。3.2 训练模型rasa train与数据文件的关系官方资料里通常会有data目录里面有nlu.yml、stories.yml、rules.yml以及domain.yml和config.yml。rasa的训练就是把这几类文件整合成模型。命令很简单但理解训练对象是排查问题的基础。rasa train这一条命令等价于同时训练NLU和对话管理两部分。如果只想重新训练意图识别模型可以只跑rasa train nlu如果只改了对话策略或stories则跑rasa train core为什么单独拆出来有意义因为任务型对话的开发节奏是先攒一批中文语料做意图识别意图识别准了再做多轮对话策略。两者互不影响时每次全量train比较浪费时间。rasa train完成后会生成一个models目录里面是一个tar.gz模型包文件名带时间戳。后续启动rasa服务时可以不指定模型让它自动加载最新时间戳的那个。训练日志里如果出现“No training data found”或者“Missing mandatory key: intents”基本是yml格式缩进错了。rasa的yml对格式很敏感用高版本PyYAML解析时一个tab键缩进都会让训练直接终止。我习惯用vscode的YAML插件先校验这两个文件再跑训练。3.3 启动顺序action server - rasa serve - flask run 一个都不能少很多新人第一次部署这套项目时只启动了rasa服务和Flask然后发现对话里凡是要执行动作的回复都没有后台日志还出现“Failed to execute custom action”。原因是项目里的动作脚本通常是actions.py是独立运行在action server里的rasa serve本身不会加载这些Python动作它通过endpoints.yml里配置的地址去找action server。所以在本地完整跑起来至少需要三个终端。# 终端一启动自定义动作服务默认端口 5055 rasa run actions --actions actions --port 5055# 终端二启动 rasa 主服务开启 HTTP API 并允许前端跨域访问 rasa serve --enable-api --cors http://localhost:5000 --port 5005# 终端三启动 Flask 应用 python app.py三个命令的执行顺序有讲究。action server必须先起因为rasa serve在运行过程中一旦要执行自定义动作发现5005端口连不上就会直接报错并跳过该动作最后用户端只能看到一句话都没回复。rasa serve里的--cors参数用来解决浏览器跨域问题如果不加前端页面调用Flask接口再转发到rasa时会出CORS错误。这个参数的值要跟Flask实际运行地址一致写http://localhost:5000还是写IP得看你前端从哪个地址访问。还有一种情况是项目里的endpoints.yml没有配置action_endpoint。那就需要手动确认文件里有action_endpoint: url: http://localhost:5055/webhook没有这个配置动作服务启动了也是白搭。3.4 最小验证一条curl打通全链路全部启动完成后不要急着写前端页面先用curl验证数据通路。第一步直接打rasa接口确认模型能响应。curl -X POST http://localhost:5005/webhooks/rest/webhook \ -H Content-Type: application/json \ -d {sender: test-user, message: 我要查话费}看到返回json里带text字段说明rasa侧正常。接着验证Flask接口curl -X POST http://localhost:5000/api/chat \ -H Content-Type: application/json \ -d {sender: test-user, message: 我要查话费}如果Flask返回了同样的回复链路就通了。这里能排查出一个很隐蔽的问题如果直接调rasa接口正常但调Flask接口超时大概率是Flask所在的防火墙或代理没放行5005端口。如果Flask返回了“Internal Server Error”则去看Flask终端里的traceback常见的是requests没安装或者resp.json()解析失败。这套验证做完项目已经在本地活起来了。下面几个坑是后续把数据换成自己业务内容时最常遇到的。4. 中文任务型对话机器人避坑指南5个让新人翻车的现场4.1 坑一pip安装依赖时冲突刷屏现象执行pip install -r requirements.txt后终端输出大量“ERROR: Cannot install rasax.x.x because these package versions have conflicting dependencies”或者numpy、transformers版本反复回退。原因最常见的是Python版本太高比如3.11、3.12在rasa 3.x的依赖矩阵里没有完全兼容。另一个原因是本地之前已经安装过一套深度学习相关的包版本被锁定在新版本上pip无法自动降级。还有一种情况是requirements.txt里把版本写死为“”但某个依赖已停止维护导致解析失败。解决先把Python切换到3.9创建全新虚拟环境再装。如果仍然报冲突用pip install rasa你需要的版本放前面装再装flask和其余依赖因为rasa的依赖解析相对强势先装它会让其他包尽量回避冲突。优先级排序是rasa版本→Python版本→其他web框架版本按这个顺序调整百分之八十的依赖冲突能消掉。4.2 坑二中文意图识别“玄学不准”现象语料里写了“我要查话费”测试时也一切正常但换成“帮我看看这个月话费还剩多少”机器人就把意图识别成了greet或者fallback前端回复答非所问。原因这个现象通常在两个地方出问题。第一pipeline里没有加JiebaTokenizer英文tokenizer对中文按空格切分等于每个完整句子被当成一个token特征抽取完全失真。第二nlu.yml里的同义句式太少模型只见过“查话费”几个字没见过“还剩多少”“余额”这种变体。还有一个隐蔽原因是训练epochs设太小比如默认的50次对中文语料不够用。解决先把config.yml确认有中文tokenizer和char_wb特征然后回到nlu.yml扩充表达方式每种意图我给到至少30条真实中文表达把口语省略、带地名单、带时间表达各写几条。改完语料后重新跑rasa train nlu再用测试工具rasa shell interactive或rasa test nlu去跑验证集看意图的置信度分布。如果置信度整体低于0.7优先加语料其次再考虑调epochs到100以上。4.3 坑三多轮对话从第三轮开始“失忆”现象用户第一句说“我要订明天去北京”第二句说“后天回来”前两轮还能正常回答到了第三轮问“那帮我看看机票”机器人突然说“抱歉我还没学会这个功能”对话被强行终结。原因这是stories和rules没覆盖全的典型症状。rasa的对话策略靠stories记录完整路径但新人写stories时往往只写了一轮或两轮的样本第三轮以上的组合路径模型没见过。这个时候RulePolicy优先级又高如果rules.yml里没有对应规则就会走到fallback。另一种情况是使用Form收集槽位时form的激活状态没有被写进stories导致对话在槽位未填满时被策略认为“任务已结束”。解决在stories.yml里把完整任务路径补齐比如“query_flight意图填日期填目的地触发查询动作回复结果”整个链条写成一条story。同时检查rules.yml里是否有一条默认的fallback规则至少要保证未知输入时有兜底回复而不是直接终结。Form场景下要确认domain.yml里的forms定义里有required_slots且utter_ask_前缀的回复存在每缺一个槽位都会有对应的追问文案。4.4 坑四Flask调rasa时报CORS跨域错误页面请求被浏览器拦截现象浏览器控制台提示“Cross-Origin Request Blocked”POST请求在Network面板里显示已发出但前端代码拿不到响应。直接用curl调Flask却完全正常。原因这是浏览器同源策略导致的跨域问题。前端页面地址是http://localhost:5000FlaskFlask在转发给rasa时rasa返回的响应里没有携带Access-Control-Allow-Origin头浏览器就拒绝前端读取。很多人只改了Flask这边却没管rasa那边。解决最直接的方式是在rasa serve启动时带上--cors参数值是对应的Flask地址写成--cors http://localhost:5000。如果Flask将来部署在别的域名下地址也对应改。还有一层保险是在Flask侧使用flask_cors库给所有路由加上允许跨域的响应头。两层都配置后浏览器不再拦截。前端开发调试时也可以临时用浏览器的禁用安全策略模式但生产环境不能依赖这个。4.5 坑五自定义动作执行失败机器人无回复现象对话流走到需要查数据库或调接口的动作时机器人一句话也不回。后台rasa日志出现“Failed to execute custom action”或“No WebHookChannel found for conversation”。原因动作服务没有启动或者endpoints.yml里没有正确绑定action服务地址。还有一个常见情况是actions.py里类的name()方法返回的名字和domain.yml里action列表中的custom action名字对不上导致rasa调度不到这个动作。另外如果action里执行了数据库查询但连接串错误异常抛给rasa后也会被当作动作执行失败处理。解决先按第3章的启动顺序确认三个服务都在跑再确认endpoints.yml的action_endpoint指向5055端口然后对照domain.yml里的actions字段和actions.py里的name()返回值逐字核对大小写和下划线。最后在action的run方法里用try-except把数据库异常捕获并返回一个兜底话术至少让用户知道“查询失败了”而不是永远沉默。排查时不要只盯着rasa主日志动作服务的命令行窗口里如果有traceback那才是真实错误来源。5. 从能用到好用把任务型对话接到真实数据和验证方法5.1 用custom action查询真实数据库替换掉写死的示例回复资料里的demo往往用utter_xxx直接回复写好的文案这在演示环境没问题但接业务时必须改成查库。常见做法是在actions.py里写一个Action类在run方法里连接数据库并返回查询结果。以sqlite为例import sqlite3 from rasa_sdk import Action, Tracker from rasa_sdk.executor import CollectingDispatcher class ActionQueryOrder(Action): def name(self): return action_query_order def run(self, dispatcher, tracker, collector): order_id tracker.get_slot(order_id) if not order_id: dispatcher.utter_message(text请提供订单号) return [] conn sqlite3.connect(orders.db) conn.row_factory sqlite3.Row cur conn.cursor() cur.execute(SELECT status FROM orders WHERE order_id ?, (order_id,)) row cur.fetchone() conn.close() if row: dispatcher.utter_message(textf您的订单{order_id}当前状态是{row[status]}) else: dispatcher.utter_message(text没查到该订单请核对单号) return []这段逻辑的关键点是tracker.get_slot从rasa的对话状态里取槽位槽位值来自NLU识别的实体或Form追问后的用户回复。查无结果时也要返回明确提示不要丢给异常。如果业务库是MySQL或Oracle把sqlite3换成pymysql或python-oracledb连接方式和参数略有不同但整体的“取槽位→查库→回话”结构不变。这里的name()方法和domain.yml里配置的自定义动作名必须完全一致。5.2 验证清单别等上线才被用户发现机器人在梦游任务型机器人上线前我一般会跑一遍下面这张验证表每项都实际发一次请求而不是只看模型日志。用例类型输入示例预期结果检查点意图混淆“我想查话费”“帮我看看余额”都识别为查余额意图NLU置信度与Top1槽位抽取“订明天北京到上海的机票”日期、出发地、目的地槽位全填满查看Tracker槽位多轮继承“订明天去北京”“后天回”第二轮正确更新返回日期多轮上下文不丢失异常输入“啊啊啊”触发fallback并提示重新表达兜底回复生效会话隔离两个用户同时轮流发消息各自的槽位互不覆盖sender_id隔离每跑完一项建议同时记录rasa返回事件里的confident值和动作名称。置信度低于0.7的用例要回到nlu.yml和stories.yml里补样本而不是靠调阈值硬洗白。5.3 一个值得长期坚持的习惯给对话留日志我接手任务型机器人项目后的第一件事不是看代码而是确认日志体系在不在。至少要记录每个sender_id、每轮用户输入、识别的意图和置信度、执行的action、机器人回复缺一块出问题就无从查起。用Flask的日志模块在转发层做统一记录就够了rasa侧的事件日志可以交叉对照。部署环境的坑往往是“本地能跑、服务器跑不起来”。真实服务器内存小于4G时rasa的BERT模型加载会非常吃力有条件就先用轻量pipeline等机器升级再切重型模型。把这个项目从本地搬到服务器的过程最重要的不是拷代码而是把依赖版本一顿锁死然后严格按部署文档跑一遍启动脚本。这些年做对话机器人我最大的体会是模型再强也离不开一层能干活的壳。rasa负责聪明Flask负责稳当数据负责真实。每一次改完配置或语料我都会先把完整验证表跑一遍再收工这个习惯救过我很多次。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站