Rasa(非Pro)开源意图识别聊天机器人本地部署及调试,从零到一构建学习

发布于:2025-03-22 ⋅ 阅读:(14) ⋅ 点赞:(0)

前言

当前语境下,大模型可以完成各种对话的需求,各种智能体+知识库也能完成各种各样nlp任务,但是幻觉问题,让很多场景无法直接使用大模型,对话机器人中rasa框架比较著名,rasa pro版(商用)也是融入了大模型,通过CALM扩展了开源Rasa的功能

为了能够快速理解和学习,从简单传统的开源rasa搭建和使用进行展开,因为非ai科班出身,也想探究一下,传统的语义识别与现在的大模型究竟差距有多大

Rasa简介

Rasa是一套用来构建基于上下文的AI小助手和聊天机器人框架。

分为两个主要的模块:

  • NLU:自然语言理解模块,实现意图识别以及槽值的提取,将用户的输入转化为结构性数据,在训练过程中,为了提高从用户信息的实体识别能力,采用了预先训练的实体提取器Pre-trained Entity Extractors,正则表达式Regexes,同义词Synonyms等
  • Rasa Core:对话管理模块,也是一个平台,用来预测决定下一步做什么,其中对话数据包括了stories以及rules
    • 其中设计对话数据时分为happy and unhappy paths,前者表示用户按照预期来进行会话,但用户通常会通过提问,闲聊或其他要求来进入后者。
  • RasaX:一个交互工具,帮助用户构建,改进和部署由Rasa框架搭成的聊天机器人

其中,rasa版本已经发展到了3.0,与之前的1.0,2.0都有较大的差距,在这里会直接介绍最新版本,感兴趣的可以了解其他版本。

docker compose启动

# docker-compose.yml
version: '3.8'

services:
  rasa:
    image: rasa/rasa:latest
    ports:
      - "5005:5005"
    volumes:
      - ./:/app
    command: 
      - run
      - --enable-api
      - --debug
    depends_on:
      - action-server
    networks:
      - rasa-network

  action-server:
    image: rasa/rasa-sdk:latest
    ports:
      - "5055:5055"
    volumes:
      - ./actions:/app/actions
    networks:
      - rasa-network

networks:
  rasa-network:
    driver: bridge

目录结构

启动后,rasa会初始化目录结构

.
├── actions
|   |__ actions.py        # 自定义动作
├── config.yml            # 模型配置文件
├── credentials.yml       # 认证配置(例如用于连接 SocketIO 等)
├── data
│   ├── nlu.yml           # 自然语言理解训练数据
│   └── stories.yml       # 对话故事
├── domain.yml            # 对话域配置:意图、实体、槽位和响应
└── endpoints.yml         # 服务端点配置

定义意图与实体

编辑 data/nlu.yml 文件,添加自定义的意图和实体。例如,我们构建一个简单的问候机器人:

version: "3.1"

nlu:
- intent: greet
  examples: |
    - hey
    - hello
    - hi
    - hello there
    - good morning
    - good evening
    - moin
    - hey there
    - let's go
    - hey dude
    - goodmorning
    - goodevening
    - good afternoon

- intent: goodbye
  examples: |
    - cu
    - good by
    - cee you later
    - good night
    - bye
    - goodbye
    - have a nice day
    - see you around
    - bye bye
    - see you later

- intent: affirm
  examples: |
    - yes
    - y
    - indeed
    - of course
    - that sounds good
    - correct

- intent: deny
  examples: |
    - no
    - n
    - never
    - I don't think so
    - don't like that
    - no way
    - not really

- intent: mood_great
  examples: |
    - perfect
    - great
    - amazing
    - feeling like a king
    - wonderful
    - I am feeling very good
    - I am great
    - I am amazing
    - I am going to save the world
    - super stoked
    - extremely good
    - so so perfect
    - so good
    - so perfect

- intent: mood_unhappy
  examples: |
    - my day was horrible
    - I am sad
    - I don't feel very well
    - I am disappointed
    - super sad
    - I'm so sad
    - sad
    - very sad
    - unhappy
    - not good
    - not very good
    - extremly sad
    - so saad
    - so sad

- intent: bot_challenge
  examples: |
    - are you a bot?
    - are you a human?
    - am I talking to a bot?
    - am I talking to a human?
    
- intent: greet_zh
  examples: |
    - 你好
    -- 您好
    - 早上好
    - 晚上好
    - 嗨,你好
- intent: goodbye_zh
  examples: |
    - 再见
    - 拜拜
    - 下次见
    - 走了
  • 其中以 -开头的行就是用户定义的 intents(意图),examples: |下面是一组有相同意图的消息内容,大致格式就是这样的
  • 在这其中可以包括带注释的实体,同义词,正则表达式,查阅表格等,这些都是用来改进意图分类和实体识别的方法。
  • 以上提到的方法需要用到组件,组件的使用一般是在domain.yml文件中

定义对话故事

编辑 data/stories.yml 文件,定义用户与机器人的对话流程。例如:

version: "3.1"

stories:

- story: happy path
  steps:
  - intent: greet
  - action: utter_greet
  - intent: mood_great
  - action: utter_happy

- story: sad path 1
  steps:
  - intent: greet
  - action: utter_greet
  - intent: mood_unhappy
  - action: utter_cheer_up
  - action: utter_did_that_help
  - intent: affirm
  - action: utter_happy

- story: sad path 2
  steps:
  - intent: greet
  - action: utter_greet
  - intent: mood_unhappy
  - action: utter_cheer_up
  - action: utter_did_that_help
  - intent: deny
  - action: utter_goodbye

- story: greet and goodbye zh
  steps:
  - intent: greet_zh
  - action: utter_greet_zh
  - intent: goodbye_zh
  - action: utter_goodbye_zh
  • 故事由以下部分组成:
    • story:故事的名称。该名称是任意的,不在训练中使用
    • metadata:任意和可选,不用于训练,
    • steps:构成故事的用户消息和操作steps,组成——用户的消息,机器人的action,一个表单,设置槽点
  • 在用户的消息中,实体虽然不是必需的,但是学习过程会根据意图和实体的组合来预测下一个操作。可以使用use_entities属性更改此行为
  • action:有两种形式
    • 一:Responses:是以utter_开头,发送特定的消息
    • 二:Custom actions:是以action_开头,自定义发送任意数量的消息
  • slot_was_set——设置槽值键值对的形式,设置槽值不是必须的,如果槽位的值对会话无影响或不重要则可以只插入一个槽名。
  • 检查点checkpoint的使用,可以作为故事的开头,可以作为故事的结尾。

配置对话域

在 domain.yml 中定义意图、实体、槽位、响应和动作:

version: "3.1"

intents:
  - greet
  - goodbye
  - greet_zh
  - goodbye_zh
  - affirm
  - deny
  - mood_great
  - mood_unhappy
  - bot_challenge

responses:
  utter_greet:
  - text: "Hey! How are you?"

  utter_cheer_up:
  - text: "Here is something to cheer you up:"
    image: "https://i.imgur.com/nGF1K8f.jpg"

  utter_did_that_help:
  - text: "Did that help you?"

  utter_happy:
  - text: "Great, carry on!"

  utter_goodbye:
  - text: "Bye"

  utter_iamabot:
  - text: "I am a bot, powered by Rasa."

  utter_greet_zh:
  - text: "你好!很高兴见到你。"
  
  utter_goodbye_zh:
  - text: "再见!祝你有美好的一天。"
  
actions:
  - action_enhanced_greet  # 添加自定义动作

session_config:
  session_expiration_time: 60
  carry_over_slots_to_new_session: true

domain域定义了您的助手在其中操作,指定机器人应了解的意向、实体、槽位、响应、表单和操作。它还定义会话的配置。

文件内容组件的介绍

  • intents:包含了文件的所有意图,你的对话训练数据,以及nlu中的data

  • entities:实体,可以有忽略以及不用忽略。

#这是忽略了所有的实体
intents:
  - greet:
      use_entities: []
#这是忽略了一部分的实体,被排除的实体不会影响下一轮的预测
intents:
- greet:
    use_entities:
      - name
      - first_name
    ignore_entities:
      - location
      - age

如果想要的实体不想要影响预测,则可以在slots下加入influence_conversation: false,指定其是否会影响下一轮的预测

  • slots:可以说是机器人的记忆,充当键值存储,存储用户提供的信息
    • 插槽影响会话的方式将取决于它的插槽类型。槽值类型有text,bool,categorical,float,list,any.
    • 此外还可以自定义槽值类型。自定义槽值类型示例
# from_entity根据实体来填充槽值entity_name
entities:
- entity_name
slots:
  slot_name:
    type: any
    mappings:
    - type: from_entity
      entity: entity_name
      role: role_name
      group: group name
      intent: intent_name
      not_intent: exc
      luded_intent
  • Forms:表单——表单的工作原理是提示用户输入信息,直到用户收集了所有必需的信息。信息存储在插槽slots中。填满所有必需的槽位后,机器人将满足用户的原始请求。
stories:
- story: interrupted food
  steps:
    - intent: request_restaurant
    - action: restaurant_form
    - intent: chitchat
    - action: utter_chitchat
    - active_loop: restaurant_form
    - active_loop: null
    - action: utter_slots_values

  • Actions动作——是机器人执行的操作,可以是响应用户,调用外部的API,查询数据库等,且所有的自定义操作全在domain文件中
responses:
  utter_greet:
  # 用大括号来表示变量,插入槽值后,将值填入
  - text: "Hey, {name}. How are you?"

  • session_config默认会话配置——下面是默认的配置,表示如果用户在60分钟不活动后发送第一条消息,就会触发一个新的会话,而任何现有的插槽都将被转移到新的会话中。将session_expiration_time的值设置为0意味着会话不会结束。
session_config:
  session_expiration_time: 60  # value in minutes, 0 means infinitely long
  carry_over_slots_to_new_session: true  # set to false to forget slots between sessions

slots:
  logged_in:
    type: bool
    influence_conversation: False
    mappings:
    - type: custom
  name:
    type: text
    influence_conversation: False
    mappings:
    - type: custom
# 还可以有约束相应的变体
responses:
  utter_greet:
    - condition:
        - type: slot
          name: logged_in
          value: true
      text: "Hey, {name}. Nice to see you again! How are you?"

    - text: "Welcome. How is your day going?"

响应可以直接返回给用户,也可以直接在action.py中定义,以Rasa SDK为操作的服务器

from rasa_sdk.interfaces import Action

class ActionGreet(Action):
    def name(self):
        return 'action_greet'

    def run(self, dispatcher, tracker, domain):
        dispatcher.utter_message(template="utter_greet")
        return []

包含这种的文件可以有多个,但应在同一目录下,同时训练的时候的命令

rasa train --domain path_to_domain_directory

训练模型

使用以下命令训练 Rasa 模型:

rasa train

在这里插入图片描述

训练完成后,模型文件将保存在 models/ 目录中

在这里插入图片描述
训练完成后,重启一下rasa容器

测试

因为是部署在docker中,无法直接通过 rasa shell等方式进行使用,本文中采用API
接口调用的方式进行

curl --location 'http://localhost:5005/webhooks/rest/webhook' \
--header 'Content-Type: application/json' \
--data '{
    "message": "中午好",
    "sender": "user1"
}'

扩展与优化

配置文件——config.yml

language: "zh"

pipeline:
# 指定语言模型
- name: "MitieNLP"
# 训练模型
  model: "data/total_word_feature_extractor_zh.dat"
# 分词器,此处的是jieba中文分词器
- name: "JiebaTokenizer"
# 实体的提取
- name: "MitieEntityExtractor"
- name: "EntitySynonymMapper"
# 文本特征化
- name: "RegexFeaturizer"
- name: "MitieFeaturizer"
# 意图分类
- name: "SklearnIntentClassifier"

policies:
  - name: KerasPolicy
    epochs: 500
    max_history: 5
  - name: FallbackPolicy
    fallback_action_name: 'action_default_fallback'
  - name: MemoizationPolicy
    max_history: 5
  - name: FormPolicy

实现自定义动作(可选)

如果需要实现更复杂的逻辑,可以在 actions.py 中编写自定义动作。例如,我们可以实现一个简单的问候增强逻辑:

# actions.py
from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher

class ActionEnhancedGreet(Action):

    def name(self) -> str:
        return "action_enhanced_greet"

    def run(self, dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: dict):
        user_name = tracker.get_slot("user_name")
        if user_name:
            greeting = f"你好,{user_name}!欢迎回来。"
        else:
            greeting = "你好!欢迎使用我们的聊天机器人。"
        dispatcher.utter_message(text=greeting)
        return []

在 domain.yml 中将自定义动作添加到 actions 列表,并在 data/stories.yml 中调整故事流程,调用 action_enhanced_greet。

固定问题固定回答

更新配置

对于每次提出的相同类型的问题,都希望能以相同的方式回答,这就需要用到Rules,具体用法是在配置文件config.yml中将下面的内容写入

policies:
# other policies
- name: RulePolicy

之后在配置文件的 NLU 管道中包含 ResponseSelector。ResponseSelector 需要特征化程序和意向分类器才能工作,因此它应该位于管道中的以下组件之后,例如

pipeline:
  - name: WhitespaceTokenizer
  - name: RegexFeaturizer
  - name: LexicalSyntacticFeaturizer
  - name: CountVectorsFeaturizer
  - name: CountVectorsFeaturizer
    analyzer: char_wb
    min_ngram: 1
    max_ngram: 4
  - name: DIETClassifier
    epochs: 100
  - name: EntitySynonymMapper
  - name: ResponseSelector
    epochs: 100

创建Rules

只需要为每个检索意图编写一个规则Rules。然后,将以相同的方式处理在该检索意图下分组的所有意向。操作名称以检索意图的名称开头和结尾。编写响应常见问题解答和闲聊的规则:utter_

rules:
  - rule: respond to FAQs
    steps:
    - intent: faq
    - action: utter_faq
  - rule: respond to chitchat
    steps:
    - intent: chitchat
    - action: utter_chitchat

之后将使用响应选择器的预测来返回实际的响应消息。

更新NLU训练数据

ResponseSelector 的 NLU 训练示例看起来与常规训练示例相同,只是它们的名称必须引用它们所分组的检索意图

nlu:
  - intent: chitchat/ask_name
    examples: |
      - What is your name?
      - May I know your name?
      - What do people call you?
      - Do you have a name for yourself?
  - intent: chitchat/ask_weather
    examples: |
      - What's the weather like today?
      - Does it look sunny outside today?
      - Oh, do you mind checking the weather for me please?
      - I like sunny days in Berlin.

定义响应

响应选择器的响应遵循与检索意图相同的命名约定。

responses:
  utter_chitchat/ask_name:
  - image: "https://i.imgur.com/zTvA58i.jpeg"
    text: Hello, my name is Retrieval Bot.
  - text: I am called Retrieval Bot!
  utter_chitchat/ask_weather:
  - text: Oh, it does look sunny right now in Berlin.
    image: "https://i.imgur.com/vwv7aHN.png"
  - text: I am not sure of the whole week but I can see the sun is out today.

总结

完成以下操作后,可以训练机器人并试用闲聊对话。

  • 将规则策略RulePolicy添加到您的策略,并将响应选择器 ResponseSelector 添加到管道pipeline中config.yml
  • 添加至少一条规则rule以响应常见问题解答/闲聊
  • 为您的常见问题解答/闲聊意图添加示例examples
  • 为您的常见问题解答/闲聊意图添加回复 responses
  • 更新域domain中的意向intents

处理业务逻辑

会话助手通常支持用户目标,这些目标涉及在为用户执行某些操作之前从用户那里收集所需的信息。例如,餐厅搜索机器人需要收集有关用户偏好的一些信息,以便为他们找到合适的餐厅

定义表单

  • Slot mappings: 插槽映射
  • Responses: 机器人如何请求每条信息

可以通过在表单名称下指定所需槽位的列表来定义domain.yml中的表单,由表单填充的插槽通常不应影响对话

比如订购餐厅的菜人数以及是否想在外面

forms:
  restaurant_form:
    required_slots:
        - cuisine
        - num_people
        - outdoor_seating

这些插槽需要添加到domain.yml的插槽部分,以及定义如何填充插槽的插槽映射。对于填充了from_entity的任何槽位,也需要将该实体添加到域中

entities:
  - cuisine
  - number
slots:
  cuisine:
    type: text
    mappings:
    - type: from_entity
      entity: cuisine
  num_people:
    type: float
    mappings:
    - type: from_entity
      entity: number
  outdoor_seating:
    type: bool
    mappings:
    - type: from_intent
      intent: affirm
      value: true
      conditions:
       - active_loop: restaurant_form
         requested_slot: outdoor_seating
    - type: from_intent
      intent: deny
      value: false
      conditions:
      - active_loop: restaurant_form
        requested_slot: outdoor_seating

其中只有当用户回答“你想坐在外面吗?”这个问题时,插槽才应该设置为true或false。

请求插槽

要指定机器人应该如何请求所需的信息,你可以在你的domain.yml文件中中定义名为utter_ask_{slotname}的响应

responses:
  utter_ask_cuisine:
    - text: "What cuisine?"
  utter_ask_num_people:
    - text: "How many people?"
  utter_ask_outdoor_seating:
    - text: "Do you want to sit outside?"

更新配置

表单的Happypath应定义为规则Rules,这意味着您需要将规则策略添加到策略中,与闲聊对话模式的更新配置相同

创建规则

Forms本身负责处理要求用户提供所有必需信息的逻辑,因此只需要两个规则Rules来定义表单的Happypath一个定义Forms何时启动,另一个定义填充后会发生什么。对于餐厅搜索示例,在现实生活中,助手会根据用户的偏好查找餐厅。在这种情况下,机器人将发出响应,其中包含将用于搜索的详细信息。

rules:
  - rule: activate restaurant form
    steps:
      - intent: request_restaurant   # 触发激活表单的意图
      - action: restaurant_form      # 激活表单
      - active_loop: restaurant_form # 表单处于活动状态

  - rule: submit form
    condition:
    - active_loop: restaurant_form   # 条件是表单必须是活动的
    steps:
      - action: restaurant_form      # 执行表单
      - active_loop: null            # 表单不再活动
      - action: utter_submit         # 表单完成后要执行的操作
      - action: utter_slots_values   # 表单完成后要执行的操作

分离表单的激活和提交好处:如果用户提供了意外的输入或通过聊天中断了表单,Rules仍然适用

更新NLU训练数据

就是为意图添加示例,同闲聊的对话模式,nlu.yml文件

nlu:
- intent: request_restaurant
  examples: |
    - im looking for a restaurant
    - can i get [swedish](cuisine) food in any area
    - a restaurant that serves [caribbean](cuisine) food
    - id like a restaurant
    - im looking for a restaurant that serves [mediterranean](cuisine) food
    - can i find a restaurant that serves [chinese](cuisine)

如果用户在第一条消息中提供实体,则该槽将在表单开头填充,并且机器人就不会通过再次向他们询问美食来填充槽。根据真实的场景来添加实例数据

nlu:
- intent: affirm
  examples: |
    - Yes
    - yes, please
    - yup
- intent: deny
  examples: |
    - no don't
    - no
    - no I don't want that

- intent: inform
  examples: |
    - [afghan](cuisine) food
    - how bout [asian oriental](cuisine)
    - what about [indian](cuisine) food
    - uh how about [turkish](cuisine) type of food
    - um [english](cuisine)
    - im looking for [tuscan](cuisine) food
    - id like [moroccan](cuisine) food
    - for ten people
    - 2 people
    - for three people
    - just one person
    - book for seven people
    - 2 please
    - nine people

一定要记得在nlu.yml文件中出现的意图都要写在domain.yml文件中

以下是更新之后的domain.yml文件

intents:
  - request_restaurant
  - affirm
  - deny
  - inform

定义响应

添加提交表单后发送的响应,在domain.yml文件中

responses:
  utter_submit:
  - text: "All done!"
  utter_slots_values:
  - text: "I am going to run a restaurant search using the following parameters:\n
            - cuisine: {cuisine}\n
            - num_people: {num_people}\n
            - outdoor_seating: {outdoor_seating}"

总结

表单可以简化收集用户信息的逻辑。要定义一个最小的表单(如上面的餐厅搜索示例),以下是您需要执行的操作的摘要:

  • 将规则rules策略比如RulesPolicy添加到config.yml
  • 在域domain.yml中定义具有所需槽slots的表单forms
  • 为域domain.yml中所有必需的槽slots添加槽映射
  • 添加用于激活和提交表单forms的规则rules
  • 为激活表单的意图添加示例,examples
  • 为意图添加示例以填充所需槽slots
  • 定义机器人在表单填写时要执行的操作或响应action or response
  • 使用您定义的新意图和操作更新您的域domain.yml

其他对话模式

除以上两种对话模式外,还有以下四种对话模式,具体模式的文件配置以及要注意的地方都在下面的网址上面,四种对话模式的配置,操作都是大同小异,感兴趣的可以去了解一下。

  • 处理意外输入
  • 情景对话
  • 联系用户
  • 回退和人工交接

总结

表单可以简化收集用户信息的逻辑。要定义一个最小的表单(如上面的餐厅搜索示例),以下是您需要执行的操作的摘要:

  • 将规则rules策略比如RulesPolicy添加到config.yml
  • 在域domain.yml中定义具有所需槽slots的表单forms
  • 为域domain.yml中所有必需的槽slots添加槽映射
  • 添加用于激活和提交表单forms的规则rules
  • 为激活表单的意图添加示例,examples
  • 为意图添加示例以填充所需槽slots
  • 定义机器人在表单填写时要执行的操作或响应action or response
  • 使用您定义的新意图和操作更新您的域domain.yml