RexUniNLU 集成 Rasa 对话系统:零样本 NLU 实战
引言:为什么需要零样本 NLU?
搭建智能客服时,传统流程往往卡在数据标注上。你需要收集成千上万条对话,人工标注意图和槽位。一旦业务变更,比如新增'预约维修',就得重新跑一遍标注流程。
有没有办法像搭积木一样,直接告诉系统'我需要识别'出发地'、'目的地'和'订票意图'',它就能立刻理解,无需训练数据?
这就是 RexUniNLU 的价值。基于 Siamese-UIE 架构的零样本自然语言理解框架,它实现了'定义即识别'。今天我们来聊聊,如何把这个引擎无缝集成到 Rasa 对话系统中,作为前端 NLU 组件。这样既能享受 Rasa 强大的对话管理,又能获得零数据标注的快速迭代能力。
环境准备与项目结构
假设你已有 Python 3.8+ 环境。我们先把基础依赖理清楚。
安装 RexUniNLU
核心依赖是 ModelScope。建议创建一个独立的项目目录来隔离依赖。
# 创建并进入项目目录
mkdir rasa_rexnlu_integration && cd rasa_rexnlu_integration
# 创建虚拟环境(推荐)
python -m venv venv
# Windows 激活:venv\Scripts\activate
# Linux/Mac 激活:source venv/bin/activate
# 安装基础依赖
pip install modelscope torch
为了集成方便,我们将 RexUniNLU 的核心推理逻辑抽象成一个模块。在 rexnlu 目录下创建 core.py:
# rexnlu/core.py
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
class RexUniNLUEngine:
"""RexUniNLU 核心引擎封装类"""
def __init__(self, model_repo='damo/nlp_structbert_siamese-uie_nano_zh'):
print(f"正在加载模型:{model_repo}... (首次运行会下载)")
self.pipeline = pipeline(
task=Tasks.siamese_uie,
model=model_repo,
model_revision='v1.0.0'
)
print("模型加载完毕!")
def parse(self, text: str, schema: list):
"""解析用户语句"""
if not text or not schema:
return {'intent': None, 'entities': []}
raw_result = self.pipeline(input=text, schema=schema)
intent = None
entities = []
# 格式化结果,适配 Rasa NLU 输出格式
for label, values in raw_result.items():
if values:
if '意图' in label:
intent = {'name': label, 'confidence': 1.0}
else:
for value in values:
entities.append({
'entity': label,
'value': value,
'start': text.find(value),
'end': text.find(value) + len(value),
'confidence': 1.0
})
if intent is None:
intent = {'name': 'nlu_fallback', 'confidence': 0.5}
return {
'text': text,
'intent': intent,
'entities': entities
}
def create_engine(model_repo='damo/nlp_structbert_siamese-uie_nano_zh'):
return RexUniNLUEngine(model_repo)
别忘了初始化包文件 rexnlu/__init__.py:
# rexnlu/__init__.py
from .core import RexUniNLUEngine, create_engine
安装 Rasa
在同一环境中安装 Rasa 并初始化项目:
pip install rasa
rasa init --no-prompt
此时项目结构大致如下:
rasa_rexnlu_integration/
├── venv/
├── rexnlu/ # 封装的 RexUniNLU 模块
│ ├── __init__.py
│ └── core.py
└── your_rasa_project/ # Rasa 项目目录
├── actions/
├── data/
├── config.yml
└── ...
创建自定义 Rasa NLU 组件
Rasa 的管道由多个组件串联而成。我们需要写一个自定义组件来桥接 Rasa 和 RexUniNLU。
在 Rasa 项目根目录下创建 custom_components/rexnlu_component.py:
# custom_components/rexnlu_component.py
from typing import Any, Text, Dict, List, Type
from rasa.engine.recipes.default_recipe import DefaultV1Recipe
from rasa.engine.graph import ExecutionContext, GraphComponent
from rasa.engine.storage.resource import Resource
from rasa.engine.storage.storage import ModelStorage
from rasa.shared.nlu.training_data.message import Message
from rasa.shared.nlu.training_data.training_data import TrainingData
import sys
import os
# 将上级目录加入路径以便导入
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
from rexnlu import create_engine
@DefaultV1Recipe.register(
[DefaultV1Recipe.ComponentType.INTENT_CLASSIFIER],
is_trainable=False
)
class RexUniNLUComponent(GraphComponent):
"""自定义 NLU 组件,使用 RexUniNLU 进行零样本识别"""
@classmethod
def create(cls, config, model_storage, resource, execution_context):
return cls(config, model_storage, resource, execution_context)
def __init__(self, config, model_storage, resource, execution_context):
super().__init__()
self._config = config
# 从配置读取 Schema
self.schema = config.get("rexnlu_schema", [])
model_repo = config.get("rexnlu_model_repo", "damo/nlp_structbert_siamese-uie_nano_zh")
self.engine = create_engine(model_repo)
print(f"RexUniNLU 组件初始化完成,Schema: {self.schema}")
def process(self, messages: List[Message]) -> List[Message]:
for message in messages:
text = message.get("text")
if text:
nlu_result = self.engine.parse(text, self.schema)
if nlu_result['intent']:
message.set("intent", nlu_result['intent'], add_to_output=True)
if nlu_result['entities']:
message.set("entities", nlu_result['entities'], add_to_output=True)
return messages
def process_training_data(self, training_data: TrainingData) -> TrainingData:
return training_data
@classmethod
def get_default_config(cls) -> Dict[Text, Any]:
return {
"rexnlu_schema": ["意图", "实体"],
"rexnlu_model_repo": "damo/nlp_structbert_siamese-uie_nano_zh"
}
这里的关键点在于 @DefaultV1Recipe.register 装饰器,它将类注册为不可训练的意图分类器。process 方法负责调用 RexUniNLU 引擎并将结果回填到 Rasa 的消息对象中。
配置 Rasa 使用自定义组件
修改 config.yml,启用我们的组件并定义业务 Schema。
# config.yml
version: "3.1"
recipe: default.v1
language: zh
pipeline:
- name: "custom_components.rexnlu_component.RexUniNLUComponent"
rexnlu_schema: ["查询天气意图", "查询新闻意图", "播放音乐意图", "城市", "日期", "音乐类型", "歌手"]
# 可选:指定其他模型
# rexnlu_model_repo: "damo/nlp_structbert_siamese-uie_base_zh"
policies:
- name: MemoizationPolicy
- name: RulePolicy
- name: TEDPolicy
max_history: 5
epochs: 100
- name: UnexpecTEDIntentPolicy
max_history: 5
epochs: 100
注意 rexnlu_schema 的设计至关重要。标签要语义清晰,最好明确区分意图(如含'意图'后缀)和实体。
定义领域与规则
更新 domain.yml 以匹配新的意图和实体:
# domain.yml
version: "3.1"
intents:
- nlu_fallback
- 查询天气意图
- 查询新闻意图
- 播放音乐意图
entities:
- 城市
- 日期
- 音乐类型
- 歌手
slots:
城市:
type: text
mappings:
- type: from_entity
entity: 城市
responses:
utter_greet:
- text: "你好!我是你的智能助手。"
utter_ask_city_for_weather:
- text: "你想查询哪个城市的天气呢?"
utter_provide_weather:
- text: "好的,正在为你查询{city}的天气。"
utter_play_music:
- text: "好的,即将为你播放{音乐类型}风格的歌曲,歌手是{歌手}。"
utter_fallback:
- text: "抱歉,我还没学会处理这个请求。"
actions:
- action_query_weather
- action_play_music
- utter_greet
- utter_ask_city_for_weather
- utter_provide_weather
- utter_play_music
- utter_fallback
接着在 data/rules.yml 中编写简单的规则:
# data/rules.yml
version: "3.1"
rules:
- rule: 激活问候
steps:
- intent: greet
- action: utter_greet
- rule: 处理查询天气意图
steps:
- intent: 查询天气意图
- action: utter_ask_city_for_weather
- rule: 默认回退
steps:
- intent: nlu_fallback
- action: utter_fallback
运行与测试
一切就绪后,先启动 NLU 服务测试解析效果:
rasa shell nlu
输入一些句子观察输出:
- '今天北京天气怎么样?' → 期望意图=
查询天气意图, 实体=城市='北京',日期='今天' - '播放一首周杰伦的流行音乐' → 期望意图=
播放音乐意图, 实体=歌手='周杰伦',音乐类型='流行'
如果解析正确,再训练完整模型并进行对话测试:
rasa train
rasa shell
调试与优化建议
集成过程中可能会遇到一些问题,这里分享几点排查思路:
- Schema 设计:零样本效果高度依赖标签定义。确保标签语义无歧义,例如用'出发城市'代替'出发地'。
- 性能考虑:RexUniNLU 模型在 CPU 上可能较慢。生产环境建议部署为微服务,通过 HTTP 调用,避免阻塞主进程。
- 混合管道:如果某些领域有大量标注数据,可以保留 Rasa 原生组件(如 DIETClassifier),让 RexUniNLU 仅处理冷启动或长尾需求。
- 日志调试:在
config.yml中开启 DEBUG 级别日志有助于定位问题。
总结
本教程演示了如何将 RexUniNLU 零样本 NLU 框架集成到 Rasa 中。通过自定义组件,我们实现了无需标注数据即可识别意图和实体。这种方式特别适合新业务场景的快速验证和原型开发。当然,对于高精度要求的复杂场景,结合少量标注数据进行微调仍是必要的补充手段。
