ARTICLE DETAIL

资讯详情

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

用Rasa构建中文医疗机器人:意图、槽位与智能问药实战

用Rasa构建中文医疗机器人:意图、槽位与智能问药实战 简介这是一套基于Rasa框架的Python智能医疗机器人完整源码与配套文档资源面向毕业设计、课程设计或真实项目开发的中高级Python学习者聚焦医药问答、智能问药、疾病诊断、病症/症状查询、闲聊及语音对话等医疗场景。压缩包共141个文件、约100.72MB核心包括28个py源码文件、26个yml配置文件、15个txt说明文档以及db数据库、wav语音素材、xml配置、md说明等目录结构清晰便于按模块理解意图识别、实体抽取与回复生成流程。资源配有开发文档、环境配置说明、技术架构和数据库采用Rasa框架结合知识图谱与Neo4j存储并接入语音识别、语音合成与开放天气API形成从文本到语音的完整交互链路。源码经过严格测试可放心参考扩展适合用于药房问药、医院导诊或健康自诊等场景原型。目前已有43人浏览学习对医疗自然语言处理方向的研究者具有直接参考价值。1. 医疗机器人项目从哪下手Rasa 框架的适用边界门诊大厅重复问询最多的问题往往不是疑难杂症而是“这药怎么吃”“发烧还能不能喂奶”这类高频、低风险、话术固定的咨询。基于 Rasa 框架的 Python 智能医疗机器人做的是把这类问答从医生桌上搬走用对话机器人承接医药问答、智能问药、症状查询和基础疾病辅助判断。它不替代诊断也替代不了医生面诊但它能扛住导诊台和用药咨询里七成以上的重复劳动。适合的读者是手里已经有医疗或药事数据、想快速搭一版可用系统的团队也适合想用 Rasa 做垂直领域 NLU 落地的 Python 工程师。2. 对话架构先于代码意图、槽位与医学知识库怎么设计2.1 意图不是越多越好为医疗角色划分 intents 与示例常见误区是照着医疗科室把意图拆成几百个结果训练数据不够模型全线翻车。我一般建议医疗机器人第一版只保留六个核心意图询问药品信息、按症状找药、查询疾病详情、查询症状归因、表达就诊意向、语音对话控制。这个范围已经能覆盖标题里的医药问答、智能问药、疾病诊断、病症查询、症状查询五项业务且每个意图能凑够 30 条以上真实问法。意图设计要区分“用户说什么”和“系统要做什么”。用户说“嗓子疼吃什么药”意图是“按症状找药”槽位是“症状嗓子疼”用户说“阿莫西林一天吃几次”意图是“询问药品信息”槽位是“药名阿莫西林”。同一个实体在不同意图下含义不同所以不要先做实体再做意图而是先把意图定死再为每个意图设计槽位和示例。Rasa 里意图示例的写法会直接影响训练效果。示例要贴近真实口语不要只写规范问句。比如“我想问下这个药饭前还是饭后吃”“头孢和酒一起喝有没有事”这类带口语化前缀的句子比“头孢与酒精相互作用”更能让模型学会泛化。每类意图建议写 40 到 60 条宁可少而真实不要多而书面。我见过不少项目在意图设计阶段就把范围铺开把“医保报销”“挂号流程”也塞进来。这样意图之间会出现大量语义重叠比如“这个药能报销吗”既像药品咨询又像费用咨询。医疗场景的对话机器人第一版要克制意图重叠是后续所有排查里最难处理的问题。2.2 NLU pipeline 的中文选型jieba DIET 与正则实体Rasa 的 NLU pipeline 决定文本解析的质量中文场景下直接套默认配置效果很差。默认配置里的 WhitespaceTokenizer 按空格切词中文没有天然空格切出来就是整句一个词实体和意图都学不好。常见做法是换成 JiebaTokenizer配合停止词表做中文分词实体识别用 RegexEntityExtractor 提取药名、病名这类有限集合的实体再用 DIETClassifier 做意图分类和兜底实体识别。医疗器械和药品名称有不少冷僻词训练语料少时DIET 学不会也正常。把实体词表通过 regex 的方式固化进 pipeline比纯靠模型学更牢靠。比如药品名、科室名、常见症状名全部维护成词典Jieba 加载自定义词典RegexEntityExtractor 负责精确匹配DIET 负责理解“我浑身难受”这种没有标准词的说法。置信度阈值要单独调。医疗场景下答错比不答更伤所以 FallbackClassifier 的阈值我一般调到 0.7 到 0.75宁可让用户觉得“这机器人不会”也不能让它胡猜。这个参数在测试阶段要多试几个值用真实问法去试不要只靠 rasa test 的报告调参。以下是一份常用的中文医疗场景 config.yml 配置epochs 是模型训练轮数小语料下 60 到 80 轮足够多了反而过拟合。language: zh pipeline: - name: JiebaTokenizer dictionary_path: data/user_dict.txt - name: RegexEntityExtractor case_sensitive: false use_lookup_tables: true - name: DIETClassifier epochs: 80 constrain_similarities: true - name: FallbackClassifier threshold: 0.72 policies: - name: RulePolicy - name: TEDPolicy epochs: 60JiebaTokenizer 里的 dictionary_path 指向自定义词典文件每行一个医学术语RegexEntityExtractor 开启 use_lookup_tables 后会从后续定义的 lookup 表中自动生成匹配实体DIETClassifier 的 constrain_similarities 能抑制低质量样本对向量空间的拉扯。RulePolicy 放在 TEDPolicy 前面是为了让固定问答优先走规则不要被模型随机性影响。2.3 规则优先还是模型优先Rasa 与自写问答引擎的取舍在动手写代码前得先回答一个选型问题为什么不直接用 Flask 写个关键词匹配接口非要上 Rasa。我的判断标准是看对话是否多轮。单轮问答用关键词匹配确实更快但医疗咨询天然有多轮性用户会追问“那小孩能吃吗”“和布洛芬能一起吃吗”这些上下文只有对话管理系统才能接住。Rasa 的价值在于把意图、实体、对话策略拆成三个独立模块改问答逻辑不用动模型改模型不用重写接口。自写问答引擎的维护成本在意图增多后会指数上升规则之间互相冲突新需求无从下手。Rasa 的 RulePolicy 虽然也是规则但它把规则变成了显式配置冲突和覆盖关系可查。对比项自写关键词匹配Rasa 对话机器人微调大模型多轮上下文需手写 session 管理原生支持槽位和故事需大量 prompt 工程维护成本规则逐渐失控意图与规则分离数据标注和评测成本高知识库更新改代码改数据文件即可每次更新要重新训练或调优私有化部署最轻量中等依赖 Python 环境对 GPU 和显存要求高医疗场景我通常建议规则优先模型兜底。固定问答如药品说明书、剂量禁忌全走 RulePolicy 加自定义 Action开放式症状描述交给 NLU 模型识别识别后仍然落到规则化的 Action 逻辑上。这样诊断规则的可解释性和可审计性都保住了这也是医疗场景的特殊要求每一个回答都要能追溯依据。3. 中文医疗 NLU 从零跑通环境、训练与最小机器人3.1 环境准备Python 虚拟环境与 Rasa 安装Rasa 对 Python 版本有要求我常用 Python 3.8 或 3.9 建独立虚拟环境不用系统 Python避免依赖冲突毁掉其他项目。先创建虚拟环境再安装这是 Python 入门阶段就该养成的习惯尤其在装了多个 Python 版本的工作机上不建虚拟环境的后果是 pip 装哪去了都说不清。VSCode 配置 Python 环境时记得选择虚拟环境里的解释器否则终端能跑起来调试器却找不到模块。# 创建并激活虚拟环境env 名字随项目走 python3.9 -m venv rasa_medical_env source rasa_medical_env/bin/activate # 安装 Rasa 与 Rasa SDKSDK 是写自定义 Action 必需 pip install rasa3.6.1 rasa-sdk # 检查安装结果 rasa --version创建虚拟环境后所有依赖都会装进 rasa_medical_env 目录不会影响系统里的其他 Python 项目。rasa 与 rasa-sdk 的版本要配套版本不一致时自定义 Action 经常出现“找不到模块”或“Action execution failed”的问题。rasa --version 能同时打印两个包的版本装完后先看一眼再继续。3.2 用 rasa init 搭建骨架并拆解目录结构rasa init 会生成一个完整可训练的项目骨架项目里会自带几个示例意图和故事文件。第一次跑通再删改比从空白目录开始要省事得多。# --no-prompt 跳过交互式提问直接生成默认项目 rasa init --no-prompt生成后的关键文件包括nlu.yml 存意图和示例rules.yml 存固定规则stories.yml 存多轮对话故事domain.yml 定义意图、实体、槽位和响应actions.py 放自定义动作config.yml 控制 NLU 和策略配置。新手最容易忽略的是 domain.yml意图和动作没有在这里登记训练时会被直接丢弃运行时报错还看不出来。我习惯先把 nlu.yml 清空再按上一章的六个意图重写。保留默认项目的闲聊意图也没问题但要注意“问药”“问病”和“闲聊”之间别出现模糊边界。比如用户说“你好”既像打招呼又像咨询开场白这类样本要明确归入一个意图不要两边都放。3.3 写一个能回答“头孢和酒”的最小 NLU 与规则直接用最简场景试通全链路用户问“头孢和阿司匹林能一起吃吗”系统识别药品咨询意图提取药名实体返回一个固定提示。先把链路走通再追求问答精度。nlu.yml 里只需要两个意图加一个实体映射。# nlu.yml最小可用的药品咨询意图 version: 3.1 nlu: - intent: inquire_drug examples: | - 阿莫西林怎么吃 - 头孢和酒能一起喝吗 - [布洛芬](drug_name)一天吃几次 - [阿莫西林](drug_name)是饭前吃还是饭后吃 - intent: ask_symptom examples: | - 发烧咳嗽怎么办 - 拉肚子两天了是什么问题 - 头晕还想吐怎么回事方括号里的内容叫标注实体 布洛芬 表示在示例中标记药物名称实体。这个标注会让 DIET 学会从相似句式中抽出 drug_name。注意量和质要平衡每个意图至少给 20 条标注了实体的样本模型才能稳定识别。rules.yml 里把固定问答用规则定死# rules.yml药品咨询走固定动作 version: 3.1 rules: - rule: 药品咨询直接应答 steps: - intent: inquire_drug action: action_inquire_drug - rule: 症状咨询直接应答 steps: - intent: ask_symptom action: action_triage_symptom规则的含义是一旦识别到 inquire_drug 这个意图不管上下文是什么都执行 action_inquire_drug。对医疗机器人来说规则优先能保证关键问题不被对话策略带偏。至于 action_inquire_drug 这个动作本身做什么第四章会详细展开。3.4 训练、运行与 rasatest 验证配置和样本写好后先训练再启动对话调试。训练不是一键就完事要观察日志里的 loss 曲线正常情况训练集准确率应该接近 1验证集如果明显低于训练集说明过拟合要么减少 epochs要么增加样本量。# 训练模型会同时训练 NLU 和对话管理两部分 rasa train # 命令行启动对话调试--debug 会打印每个意图的置信度打分 rasa shell --debugrasa shell 里输入“布洛芬怎么吃”系统会打印类似 intent: inquire_drug (confidence: 0.93) 的信息entity: drug_name: 布洛芬。这时就能直观看到 NLU 的解析质量。debug 模式是排查问题的主战场不要嫌日志多意图打错、实体漏标、动作执行失败全在里面有记录。跑通之后我强烈建议跑一遍 rasa test这是一个容易跳过但值得养成的验证习惯。rasa testrasa test 会按测试集评估 NLU 意图识别准确率和故事执行成功率生成报告放在 results 目录。这里的指标只能作参考真实场景的表现还要靠你自己准备的一批冷门问法去验证因为测试集和训练集往往长得太像。4. 把问答做成动作药品查询、智能问药与诊断规则落地4.1 知识库组织JSON、CSV 还是图谱知识库的形态决定了开发效率。药品几百条、疾病千条以内用 JSON 或 CSV 足够不需要上图数据库。常见做法是把药品、疾病、症状分别存成 JSON字段保持扁平检索靠倒排索引。数据量超过十万条或关系层级超过三层时再考虑图谱但对第一版医疗机器人来说图数据库的部署和查询成本远大于收益。药品条目的字段建议包含name通用名、alias别名数组、usage用法用量、contraindication禁忌、side_effect不良反应、keywords检索关键词。关键词是给倒排索引准备的可以把“头孢”“头孢类抗生素”“cephalosporin”这类同义表述都塞进去匹配时直接命中。{ drugs: [ { name: 阿莫西林, alias: [阿莫仙, 阿莫西林胶囊], usage: 成人一次0.5g每6至8小时一次。, contraindication: 对青霉素过敏者禁用。, keywords: [阿莫西林, 阿莫仙, 青霉素类] } ] }槽位设计的教训是药品和剂型要分开存。用户说“阿莫西林胶囊怎么吃”药名是阿莫西林剂型是胶囊。如果整个字符串塞进 name 字段后面做剂量换算会很难受。我一般把剂型作为单独字段在 Action 里做匹配时先抽药名再带着完整文本去匹配剂型。4.2 智能问药与药品查询倒排索引传递规则实体自定义 Action 是 Rasa 里真正跑逻辑的地方。它的运行流程是NLU 解析出意图和实体对话策略决定执行哪个 ActionAction 拿实体去查知识库把回答文本交给 dispatcher 发给用户。下面是药品查询 Action 的关键代码。# actions.py药品匹配 倒排索引查询 import json import re from typing import Text, List from rasa_sdk import Action, Tracker from rasa_sdk.executor import CollectingDispatcher from rasa_sdk.events import SlotSet class ActionInquireDrug(Action): def name(self) - Text: return action_inquire_drug def __init__(self): with open(data/drugs.json, r, encodingutf-8) as f: self.drugs json.load(f)[drugs] # 把别名和关键词摊平建立“词 - 药品”的倒排索引 self.index {} for drug in self.drugs: for key in drug[keywords]: self.index.setdefault(key, []).append(drug) def run(self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict) - List[dict]: message tracker.latest_message.get(text, ) matched self._match_by_text(message) if not matched: dispatcher.utter_message( text我暂未查到这种药请咨询药师或前往医院。) return [SlotSet(drug_match, not_found)] drug matched[0] reply (f{drug[name]}{drug[usage]}。 f禁忌{drug[contraindication]}) dispatcher.utter_message(textreply) return [SlotSet(drug_match, drug[name])] def _match_by_text(self, message: str): # 优先用倒排索引精确命中 for key in self.index: if key in message: return self.index[key] # 兜底用正则抽取药品常见通用名 for drug in self.drugs: if re.search(drug[name], message): return [drug] return []这里的逻辑是先拿文本遍历倒排索引的键命中即返回药品列表索引匹配不到再用正则去匹配每条药品的通用名。倒排索引的优势是查询速度快几百条药品时性能差异不明显但到了万条量级优势就出来了。把“阿莫西林”和“阿莫仙”都放进 keywords用户怎么说都能命中。兜底正则只查 name 字段避免把 alias 重复匹配造成多条结果不稳定。智能问药与药品查询不同用户给的是症状要返回药品。常见做法是建一张“症状—药品”映射表在 Action 里先用实体识别提取症状词再映射到对应药品。# symptoms_drugs.json { symptom_mapping: { 发烧: [对乙酰氨基酚, 布洛芬], 咳嗽: [右美沙芬, 氨溴索], 腹泻: [蒙脱石散, 口服补液盐] } }Action 里把用户文本中的症状词逐个对照映射表命中即返回对应药品列表。这类回答要明确加上“建议咨询医师后再服用”之类的提示。因为症状和药品之间不是一一对应关系映射表只能做初筛不能直接开方。4.3 症状查询与疾病诊断场景规则评分与就医兜底症状查询的落地思路是用户描述症状系统匹配可能相关的疾病列表并解释依据。这块我强烈建议用规则评分不要用模型做生成。模型生成的内容不可控医疗场景里一句话说错就可能出事故。规则评分的逻辑透明维护方便依据也讲得清楚。疾病诊断更要谨慎。标题里的“疾病诊断”在实际工程里应该理解成“疑似疾病辅助判断”而不是确诊。常见做法是建一组规则每个规则对应疾病疾病由一组症状触发达到阈值才输出建议。TRIAGE_RULES [ { disease: 上呼吸道感染, symptoms: [发烧, 咳嗽, 喉咙痛, 流鼻涕], require: 2 }, { disease: 急性肠胃炎, symptoms: [腹泻, 呕吐, 发烧, 腹痛], require: 2 } ] def simple_triage(message: str) - List[dict]: hits [] for rule in TRIAGE_RULES: matched [s for s in rule[symptoms] if s in message] if len(matched) rule[require]: hits.append({ disease: rule[disease], matched: matched, detail: 您描述的多个症状与常见表现相似仅供参考。 }) return hits这段逻辑的细节在 require 这个参数上它控制触发疾病所需的最少症状数。设置为 2 是为了避免单个症状就触发诊断误报率太高。匹配到的症状列表会作为依据展示给用户回答必须带“仅供参考”和就医建议这是全项目最重要的兜底。如果用户只描述了单一症状命中数不足系统应该追问补充信息而不是直接给结论。部门和科室信息也可以挂进去比如匹配到上呼吸道感染时给出“建议就诊呼吸内科或全科”的就医指引。这样回答的实用性更高也更符合项目标题里“疾病诊断”“病症查询”的实际业务价值。4.4 语音对话接入问法ASR 文本如何交给 Rasa语音对话的常规路径是麦克风采集音频ASR 识别成文本文本交给 Rasa 处理返回文本或音频语音合成输出。Rasa 只负责文字对话这一层不直接碰音频流集成方式就是调用 Rasa 的 HTTP API。本地可以部署 whisper 这类开源 ASR 模型云端用商用语音识别 SDK 也可以。先启动 Rasa 的 REST Channel 服务然后写一个语音网关把识别文本 POST 过去# 启动 Rasa 服务使用 REST Channel默认端口 5005 rasa run --enable-api --cors *REST Channel 启动后Rasa 提供一个 /webhooks/rest/webhook 的 HTTP 接口。语音网关只要把 ASR 识别出的文本放进 message 字段返回的 JSON 数组里就是机器人应答文本import requests # 将 ASR 识别出的用户话术发送给 Rasa payload { sender: medical-demo, message: recognized_text, } resp requests.post( http://127.0.0.1:5005/webhooks/rest/webhook, jsonpayload, timeout5, ) for item in resp.json(): print(item.get(text, ))sender 字段是会话标识同一个用户每次消息都要用同一个 sender 值。Rasa 靠它维持多轮上下文换成新值会导致对话状态重置。timeout 设置 5 秒是给 ASR 和 NLU 链路的余量响应太慢时做个友好提示不要卡死整个对话。语音接入的体验瓶颈通常在 ASR 的端点检测和语境分段上这一点会在第五章专门展开。5. 医疗对话常见问题与避坑从识别错位到乱答延迟5.1 中文实体错位是头号坑症状词吞掉疾病词现象用户说“我嗓子疼还有点咳嗽”系统把“嗓子疼”识别成症状实体“咳嗽”识别成疾病实体两个实体概念颠倒后续查询全乱。原因是用户文本里同时出现症状词和病名词它们本身有语义重叠比如“关节炎”既是病名又是疼痛部位的表现DIET 模型分不清边界。解决实体设计上区分“结构化实体”和“文本特征”。症状和疾病各用独立的正则词表RegexEntityExtractor 优先抽取DIET 只做补充。在 Action 层加互斥校验同一个文本片段如果同时命中症状表和疾病表以疾病优先并把冲突情况写进日志。文本里出现多个实体时按顺序拼接检索条件而不是把所有实体都塞进一个查询条件。这个坑在测试阶段就能暴露关键是别忽视。我看到不少项目用 rasa test 报告准确率 95% 就以为没问题结果拿真实问法一测发现错的都是这种实体混淆。测试集里专门准备一批“症状和疾病一起出现”的样本比一味堆意图准确率更有用。5.2 乱答与兜底缺失低置信度请求被硬接现象用户问“我最近总是失眠和吃的药有关系吗”系统识别 inquire_drug 意图返回“成人一次0.5g”这种驴唇不对马嘴的回答。原因是模型把这个问题归到了置信度最高的意图且配置里没有设置 FallbackClassifier 阈值所有请求都被强行处理。解决FallbackClassifier 的 threshold 调高到 0.7 以上并自定义默认兜底动作。低置信度下回应用户“这个问题我需要专业药师确认建议您留下电话或前往医院”而不是让系统硬答。兜底动作要写得像人话医疗场景里“不知道”比“乱知道”安全得多。我在生产环境验证过的调参经验是先默认 0.7 跑一周收集被兜底拦截的真实问法再判断拦截原因是置信度阈值太高还是模型欠拟合。如果大部分被拦截的问法本来就该转人工阈值就保持如果明显是常见问题也被挡再去补训练数据。这个调参顺序比一次性调到位更符合实际。5.3 语音链路玄学断句乱、重复触发、延迟高现象语音对话里用户说“我想问一下布洛芬和酒精能不能一起吃”ASR 结果却是“我想问一下布洛芬和酒 精能不能一起吃”中间多出空格或者断句被切开成两条消息。Rasa 收到两条消息第一条“我想问一下布洛芬”被当成闲聊第二条“精能不能一起吃”识别成药品咨询槽位却丢了。原因在 ASR 的端点检测太灵敏一句话没说完就判定静音结束。解决调整 ASR 的静音判定时长把端点静音阈值调到 600 到 800 毫秒让用户停顿不会切断句子。在语音网关侧做合并逻辑300 毫秒内到达的多段识别文本直接拼接成一个完整句子再发给 Rasa。延迟问题常见原因是 ASR 和 Rasa 串行调用各自耗时累加常见做法是把 ASR 客户端做成连接池复用避免每次请求都重新初始化模型。这类问题在本地验证时很少暴露因为自己说话节奏稳定真实用户迟疑、停顿、改口问题全冒出来。所以做语音对话一定要找几个不熟悉项目的人来测试录下他们的真实话术并回放测试。5.4 训练与内存小机器上训练 Transformer 掉速现象本地开发机只有 16G 内存rasa train 跑到一半进程被杀。原因是 pipeline 里用了基于 Transformer 的预训练模型作为特征提取器中文场景下模型文件加载就占好几 G 内存加上训练过程的中间结果内存直接爆掉。这也是很多 Python 入门者在 VSCode 里跑 Rasa 时最先遇到的现实瓶颈。解决小型项目不要用 Transformer 特征提取器。用 JiebaTokenizer 加正则实体DIETClassifier 自身就能干活。如果确实需要预训练语言的语义特征选择 DistilBERT 这类轻量模型并把批量大小调小到 8 或 16。修改合理配置后训练内存通常能控制在 8G 以内开发机能扛住。pipeline: - name: JiebaTokenizer - name: RegexEntityExtractor - name: DIETClassifier epochs: 60 batch_size: 16batch_size 控制每次送入模型的样本数量从 32 降到 16训练时间增加不多但内存峰值能降一截。如果内存仍然不够再往下调还会出现训练精度下降的问题这时最优解是减小语料规模而不是继续降 batch。5.5 槽位残留上一轮的药名带到下一轮现象用户先问“阿莫西林一天吃几次”得到回答后紧接着问“那它和酒精能一起吃吗”系统理解的药名还是阿莫西林但问题本身变成药品交互体系却沿用了上一轮的内容。原因是对话逻辑里槽位没有清空也没有在合适的时机重置。解决在药品查询结束并返回回答后把关键槽位置空或设置槽位有效期。Rasa 提供 SlotSet 和 SlotReset 指令Action 结束时指定清除哪些槽位即可。多轮追问要在规则里声明“当前问题依赖哪个槽位”规则执行完成就重置避免槽位残留污染下一轮意图判断。做医疗问药场景时槽位残留是所有多轮对话翻车里后果最严重的。因为药品剂量错误和禁忌判断错误不是“体验不好”而是安全隐患。所以每轮查询结束强制清槽这个操作要做到无脑执行。6. 让机器人更可靠评测集、语音联调与一个回归脚本把模型调好只算完成一半剩下的一半是让它持续可靠。我在项目里会常备一个小工具把典型问法和期望解析结果写成一个 JSON 数组用脚本调 Rasa 的解析接口做回归验证。每次改 nlu.yml、改词表、调阈值后先跑一遍回归再进对话测试。# review.pyNLU 回归验证脚本 import json import requests RASA_PARSE_URL http://127.0.0.1:5005/model/parse CASES [ {text: 布洛芬一天吃几次, intent: inquire_drug, entity: 布洛芬}, {text: 发烧咳嗽两天了, intent: ask_symptom, entity: None}, {text: 有没有治拉肚子的药, intent: inquire_drug, entity: 拉肚子}, ] def main(): passed 0 for case in CASES: resp requests.post(RASA_PARSE_URL, json{text: case[text]}).json() intent resp[intent][name] entities [e[value] for e in resp.get(entities, [])] ok intent case[intent] if case[entity]: ok ok and case[entity] in entities print(f{case[text]}: intent{intent}, entities{entities}, ok{ok}) passed int(ok) print(fpass{passed}/{len(CASES)}) if __name__ __main__: main()这个脚本的用途是让回归成本低到可忽略。改完词表不用启动 rasa shell 一个个手敲直接跑 review.py 就能看到结果。我把这个习惯坚持了一年多最实用的价值是能第一时间发现词表改动引起的意图漂移。加入新的症状词后原本稳定的药品咨询问法突然被打到 ask_symptom 里这种事只有回归脚本能在几分钟内暴露。语音联调的验证方法另有讲究。不要把 ASR 和 Rasa 分开测就完事要做端到端测试播放一段包含迟疑、停顿的录音看从音频到文本再到最终应答的完整链路是否正常。我在项目里会准备三段固定音频一段带数字剂量、一段带药品别名、一段带口语化症状描述。每周跑一遍重点看两点ASR 是否因为停顿切断了句子Rasa 是否因为 ASR 噪声识别错了意图。医疗机器人的上线标准不应该只看准确率还要看“答不出的概率”和“答错的风险”。我给自己定的标尺是如果这个问题我无法给出确定的药品知识依据宁可承认不知道并劝用户就医。这个习惯救过我很多次医疗场景里的一句保守远比一句自信更有价值。最后分享一个实践细节诊断规则集中放到规则文件里不要散落在各个 Action 中。规则文件用“症状组合疾病名建议科室就医提示”的结构维护改规则不碰代码重启即可生效。这个设计在需求频繁变化时能少踩很多坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表