简介:面向毕业设计、课程设计与项目开发场景,提供一套基于 Python 的 Rasa 中文聊天机器人完整方案,包含可运行源码、开发文档、代码解析与模型训练成果。整个压缩包共 24 个文件、4.42MB,主要类型包括 Markdown 文档、YAML 配置、Python 脚本、bash 启动脚本以及训练好的模型文件,结构清晰,便于按文档顺序完成意图识别、实体提取、对话管理和 API 对接等环节的学习与实践。项目更新日志展示了完整迭代过程:从初始模型训练成功,到优化 NLU 样本、引入同义词/正则/查找表,再到使用 Interactive Learning 构建样本、新增 MITIE 管道及身份查询案例,并将 Rasa 升级至 1.9.5 解决 Win10 下 TensorFlow 异常,不仅有代码,还有对应开发指南与排错思路。目前已有 249 人学习下载,可直接在本地启动测试,并可在此基础上继续扩展图灵闲聊、心知天气查询等功能。
1. 毕设选题撞上“Rasa中文聊天机器人”:这是最不容易翻车的方向
做毕业设计或者课程设计,只要选了“智能问答”“对话系统”这类方向,绕不开的就是“Rasa中文聊天机器人”。Rasa 是当前开源对话框架里文档最完整、组件可替换性最好的一档,它把聊天机器人拆成两个清晰层次:自然语言理解(NLU)负责“听懂人话”,对话管理(Core)负责“决定怎么接话”。这套架构的妙处是,你既能在答辩时讲清楚原理,又能实打实跑通一个可交互的中文对话系统,还能把项目代码解析、模型训练、开发文档整理成一套完整的交付物。这篇文章按我自己做项目的顺序,从 Python 环境讲起,一路到模型训练、坑位排查和答辩验证,全程可复现。
2. 先把环境和源码结构跑通:从 Python 安装到 rasa init
2.1 Python 环境怎么选:3.8 这条线最稳
Rasa 对 Python 版本比较挑剔。我经手过的项目里,Python 3.8 是兼容性最稳的版本,3.9 勉强可用,3.10 以上容易遇到部分依赖包编译失败。做这个项目,第一步不是急着写代码,而是把 Python 版本和环境变量配置好,否则后面装依赖时会成片报错。
python -m venv rasa_venv # Windows 下激活 rasa_venv\Scripts\activate # Linux / macOS 下激活 source rasa_venv/bin/activate pip install rasa --default-timeout=60 -i https://pypi.tuna.tsinghua.edu.cn/simplepython -m venv rasa_venv创建一个独立的虚拟环境,这一步不能省。装依赖时如果直接用默认源,Rasa 依赖的包数量大、体积大,很容易超时中断;--default-timeout=60把超时时间拉长到 60 秒,-i指定国内镜像源,这两个参数同时用能省掉大半网络玄学问题。装完后用rasa --version确认版本,看到 3.x 就说明环境没有问题了。
2.2 用 rasa init 把官方脚手架拉下来
环境就绪后,我最常用的做法是先不写任何代码,直接让 Rasa 自己生成一套可运行的项目骨架,这是理解“源码”最快的一条路。
cd my_chatbot_project rasa init --no-prompt--no-prompt表示跳过交互式问答,全部选默认项。生成完后,项目目录长这样:
my_chatbot_project/ ├── actions/ │ ├── __init__.py │ └── actions.py ├── config.yml ├── credentials.yml ├── data/ │ ├── nlu.yml │ ├── rules.yml │ └── stories.yml ├── domain.yml ├── endpoints.yml ├── models/ │ └── ... └── tests/ └── test_stories.yml这个目录就是整套“源码+开发文档+项目代码解析”的入口。config.yml是训练管线的总配置,决定用什么算法做分词、意图识别、实体提取;domain.yml是“角色表”,注册意图、实体、槽位、回复文案;data/下三个文件分别存放训练语料和对话流;actions/actions.py是自定义动作的入口。把这几个文件读懂,整个项目就拆完了一大半。
2.3 源码里真正要动的是哪几个文件
拿到骨架后,按优先级去改动,而不是每个文件都翻一遍。我一般按下面这个顺序:
| 文件 | 作用 | 必改程度 |
|---|---|---|
config.yml | 设定 NLU 和 Core 的算法组件 | 必改,中文必须换分词器 |
data/nlu.yml | 标注意图和实体样本 | 必改,换成你自己的语料 |
domain.yml | 注册意图、实体、槽位和回复 | 必改,新增内容都要同步到这里 |
data/stories.yml | 写多轮对话的“剧本” | 必改,这是对话逻辑的核心 |
data/rules.yml | 写不依赖上下文的固定规则 | 按需,兜底回复用 |
actions/actions.py | 自定义动作,调用外部接口或查数据 | 按需,做毕业设计通常要写 |
credentials.yml/endpoints.yml | 配置渠道和外部服务 | 按需,本地跑可以不动 |
改动任何语料之后,都必须重新训练模型才会生效。后面所有训练和排错,都是围绕这几个文件的修改展开的。
3. 中文意图与实体:把 NLU 部分做成能答辩的样子
3.1 中文分词器:Jieba 与默认 Whitespace 的差别
Rasa 默认的WhitespaceTokenizer按空格分词,对英文很自然,对中文就是灾难。中文句子没有天然空格,必须换用JiebaTokenizer。这是中文聊天机器人和英文项目在配置层面最核心的差异。
language: "zh" pipeline: - name: "JiebaTokenizer" dictionary_path: "data/dict/jieba_dict.txt" - name: "LanguageModelFeaturizer" model_name: "bert-base-chinese" - name: "DIETClassifier" epochs: 100 learning_rate: 0.001 - name: "EntitySynonymMapper"dictionary_path指向自定义词典文件,词典格式是“词语 频数”,每行一个词。加自定义词典的目的是让 Jieba 正确切分你业务里的专有名词,比如学校名、课程名、人名。LanguageModelFeaturizer用 BERT 系列中文预训练模型做句子向量化,你也能替换成规模更大的 RoBERTa 中文预训练模型,但推理速度会明显变慢,毕设本地跑的话bert-base-chinese是性价比最好的选择。DIETClassifier是意图分类和实体提取的联合模型,epochs和learning_rate决定训练收敛情况,数据量少于几百条时不要把epochs拉太高。
| 分词器 | 对中文效果 | 自定义词典 | 适用场景 |
|---|---|---|---|
WhitespaceTokenizer | 整句被当成一个词,意图识别基本失效 | 不支持 | 英文项目 |
JiebaTokenizer | 按中文语义切分,配合自定义词典可调优 | 支持 | 中文项目,首选 |
3.2 意图、实体与同义词:一份能进答辩的 nlu.yml
data/nlu.yml是整个项目里最值得反复打磨的文件。答辩时老师最常问的问题就是“你的训练数据怎么来的、怎么标注的”,所以这里要做出规范感。
version: "3.1" nlu: - intent: query_weather examples: | - 今天[北京](city)天气怎么样 - [上海](city)明天会下雨吗 - 帮我查一下[广州](city)的天气 - 我想知道[深圳](city)气温多少度 - intent: query_course examples: | - [数据结构](course)这门课什么时候上 - [操作系统](course)的考试范围是什么 - 我们[人工智能](course)课的作业在哪交意图命名用query_前缀区分“查天气”和“查课程”这两类动作;方括号里是实体文本,圆括号里是实体类型。每条样本都要覆盖一种真实表达方式,用词不能太相似,否则模型会偷懒,只认其中一两个关键词。每个意图至少给 15 到 30 条不重复的中文表达,样本越接近真人说话的口吻,训练出来的模型越准。
同义词在处理中文别称时非常有用。比如用户说“首都”指的就是“北京”,在nlu.yml里加一段synonym映射,就能让模型把“首都”和“北京”归到同一个实体值上,这也是中文场景里最常用的技巧之一。
- synonym: 北京 examples: | - 首都 - 帝都3.3 实体提取的边界:“北京天气”能过,“北京今天的天气”开始翻车
中文实体提取最容易出问题的地方不是意图,而是实体的边界切分。Jieba 在“北京天气”这种短语上表现很好,但遇到“北京今天的天气怎么样”这种带修饰词的句子,实体边界就开始抖动。
- intent: query_weather examples: | - 北京[今天](date)天气怎么样 - 帮我查下[明天](date)上海的天气这里把“今天”“明天”也标注成date实体,模型才能学会区分“查询时间”和“查询城市”。如果漏标这类实体,DIETClassifier会把“今天”和“北京”粘连在一起,导致实体识别结果变成一串整词。除了在样本里标注,还可以用lookup列表补充常见实体名单——但要注意,lookup只影响实体提取的候选范围,不会自动把词加入分词词典,两者需要配合使用。
4. 对话流程编排:stories、rules 与自定义 action 的配合
4.1 写故事先写“经历”:stories 的作用
NLU 解决“听懂话”,Core 解决“怎么接话”。data/stories.yml里记录的是完整的对话经历,从用户第一句话开始,到机器人回复结束,每一步都写清楚。Rasa 用这些故事训练对话策略模型,学会在不同情境下该选择哪个动作。
version: "3.1" stories: - story: 查询天气完整流程 steps: - intent: query_weather entities: - city: 北京 slot_was_set: - city: 北京 - action: action_query_weather - slot_was_set: - city: 北京 - action: utter_weather_resultintent和entities描述用户这句话表达了什么;slot_was_set记录槽位的变化,槽位相当于对话里的临时变量,存住用户说过的城市名;action是机器人执行的下一步动作。注意action_query_weather这个动作前缀带了action_,意味着它是自定义动作,需要在actions.py里写代码;反过来,utter_weather_result是纯文本回复动作,直接在domain.yml的responses里写文案就行。
4.2 domain.yml 把“角色表”注册好
domain.yml是整个项目的注册中心。意图、实体、槽位、回复文案,全都必须在这里声明,漏掉任何一个,训练时都会报错。
version: "3.1" intents: - query_weather - query_course entities: - city - course - date slots: city: type: text influence_conversation: true mappings: - type: from_entity entity: city responses: utter_weather_result: - text: "好的,{city}今天的天气是晴天,气温 22 到 28 度。" utter_default: - text: "抱歉,这个问题我还没有学会,换个说法试试?"slots里type: text表示槽位存的是普通文本;influence_conversation: true表示槽位值会参与对话决策,这个参数在同一个意图对应不同城市、不同课程时非常关键,如果不设成true,模型不会根据槽位内容区分后续回复。mappings指明槽位从哪里取值,from_entity的意思是当识别到city实体时,自动把这个实体值存入city槽位。responses里的文案支持模板语法,{city}会被槽位值替换成具体城市名——这是做毕业设计时显得项目很“完整”的一个细节。
4.3 非要写代码时:自定义 action 怎么落地
纯文本回复撑不起一个聊天机器人的门面。查天气要调接口、查课程要去数据库,这些都得靠自定义 action 写代码。actions/actions.py是 Rasa 项目里唯一真正写 Python 业务逻辑的地方。
from typing import Any, Dict, List, Text from rasa_sdk import Action, Tracker from rasa_sdk.executor import CollectingDispatcher class ActionQueryWeather(Action): def name(self) -> Text: return "action_query_weather" def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[Dict[Text, Any]]: city = tracker.get_slot("city") # 这里写真实的天气接口调用逻辑 weather_info = f"{city}今天晴,22~28 度" dispatcher.utter_message(text=weather_info) return []name()方法返回的动作名必须和stories.yml里写的action_query_weather完全一致,不区分大小写,但拼写不能错。run()方法里,tracker.get_slot("city")取出用户之前说过的城市名;dispatcher.utter_message(text=...)把结果发给用户。返回一个空列表代表动作执行完毕。完成后还要在endpoints.yml里确认 action 服务地址,本地运行默认是http://localhost:5055,用rasa run actions启动。
5. 模型训练与避坑排查:从 rasa train 到 rasa test
5.1 训练命令与模型产出
所有语料和配置改完,就可以训练模型了。训练是整个流程里最吃耐心的一环,也是“模型训练”相关搜索里问题最多的地方。
rasa train rasa train nlu --fixed-model-name my_nlu_model rasa train core --fixed-model-name my_core_modelrasa train同时训练 NLU 和 Core 两套模型,适合每次完整改动后执行;如果想只调意图和实体,rasa train nlu更快,不用重新跑对话策略。--fixed-model-name给模型指定固定名字,避免每次训练生成带时间戳的新文件,写自动化测试脚本时非常有用。训练完成后模型打包成 tar.gz 文件放在models/目录下,里面同时包含 NLU 模型和 Core 模型,后续rasa shell交互时直接加载这个包。训练日志里如果出现 loss 为nan,通常不是命中的玄学问题,而是学习率偏高或者某个意图的样本数量太少,先调低learning_rate,再补语料。
5.2 先用 rasa test 测一轮,再谈调优
训完不能直接rasa shell就完事,应该先用rasa test跑一遍自动评测,用指标说话。
rasa test --nlu --model models/my_nlu_model.tar.gz--nlu表示只评测 NLU 部分。Rasa 会拿训练数据里留出的测试集做交叉验证,输出结果在results/目录下。重点看intent_report.json里的precision、recall、f1-score,以及intent_confusion_matrix.png混淆矩阵图。如果某个意图的召回率明显低于其他意图,说明这个意图的样本表达太单一,回去补几条不同说法的句子再重新训练。数据总量不到几百条时,F1 值在 0.8 左右已经算健康,不必盲目追求 0.95 以上——那是大厂用几万条标注数据才能堆出来的数字。
5.3 避坑:Rasa 中文项目最常见的五个坑
以下是做 Rasa 中文项目最高频的五个踩坑位置,每一条都是“现象 → 原因 → 解决”的真实路径。
坑一:rasa init后启动就报错,提示缺依赖或版本冲突。现象是安装过程顺利,但运行时报module not found或protobuf相关错误。原因是 Python 版本过高,部分 Rasa 依赖编译不通过。解决方法是把 Python 降到 3.8,重新创建虚拟环境安装,不要尝试逐个手修依赖版本。
坑二:中文句子被当成一个整词,意图识别完全失效。现象是用户说什么都命中同一个意图,训练报告里特征很稀。原因是config.yml里没换分词器,还在用默认的WhitespaceTokenizer。解决方法是把 pipeline 换成JiebaTokenizer,并确认language: "zh"已设置。
坑三:训练过程 loss 变成nan,模型无法收敛。现象是训练到某一轮后损失值直接变nan,之后模型完全不能用。原因是学习率偏高,或者某个意图/实体的样本量极少,模型在稀疏数据上梯度爆炸。解决方法是把DIETClassifier的learning_rate调到0.0005一档,再给每个意图补充至少 15 条样本,两者同时做才最有效。
坑四:实体提取结果总是一长串,或者丢掉后半部分。现象是“北京今天的天气”被整体识别成一个city实体,或只提取出“北京”。原因是语料里没有标注date这类伴随实体,模型无法区分边界。解决方法是在nlu.yml里把“今天”“明天”等词也标注为对应实体,用lookup列表补充候选词。
坑五:对话总是落到utter_default,不走预设的 story。现象是测试对话时,用户问什么都被默认回复兜底,预设的查询流程一次都没触发。原因是stories.yml里的样本与用户实际表达差距大,或者 NLU 置信度阈值太高。解决方法是先看rasa shell --debug输出的意图识别置信度,如果置信度普遍在 0.5 以下,就把config.yml里的intent阈值调低或者补语料;同时确认rules.yml里没有冲突的规则抢占路由。
6. 把项目从“能跑”推到“能答辩”:自测与验收清单
做到这里,项目已经是一个能交互的中文聊天机器人了。但毕业设计和课程设计通关的最后一公里,是验证方法是否专业。我会在交稿前跑一遍完整的自测清单,每一栏都有明确的验收物:
| 验证项 | 操作 | 通过标准 |
|---|---|---|
| NLU 意图评测 | rasa test --nlu | 每个意图 F1 >= 0.8 |
| 实体抽取抽查 | rasa shell手动输入 20 条变体表达 | 城市、日期实体无边界错误 |
| 多轮对话回放 | rasa test core --stories tests/test_stories.yml | 故事完成率 100% |
| 兜底回复 | 故意输入无关内容 | 落到utter_default且不报错 |
| 模型体积 | 查看models/下 tar.gz 文件 | 控制在 200MB 内,便于拷贝演示 |
rasa test core --stories这条命令是多数人忽略的,它能把tests/test_stories.yml里写好的对话剧本逐条回放,检验对话流程是否稳定,是答辩时最有说服力的“自动化测试证据”。如果测试故事通过率不是 100%,回到 4.1 节的 stories 写法重新核对slot_was_set和action的顺序。
我经手的每个 Rasa 中文项目,上线前都会强迫自己用真人语气多聊二十轮,而不是只测预设样本。曾经有一次我以为模型训练一切正常,结果用户说“北京呢”三个字,系统完全没接住——因为语料里全是完整句子,没有人说过省略句。后来我把这类省略表达补进nlu.yml,再训练后效果立刻不一样。做对话系统的项目,多花时间在“收集真实表达”上,永远比调参数划算。希望帮到你。
本文还有配套的精品资源,点击获取