rasa会话数据存储 RedisTrackerStore 连接哨兵

rasa会话数据存储

原文链接

背景

rasa 会话日志默认存储在内存中,但是如果想要支持多个实例机器人,则需要将rasa会话数据存储在数据库中,,而官方文档中支持的的存储方式为单实例的redis, 无法支持redis集群。

因此这里给出了使用redis集群进行会话日志存储的方法。此处连接的是redis哨兵集群。根据示例可扩展连接redis集群,修改相应代码部分即可。
此代码支持rasa3版本

一. 修改endpoints.yml配置文件
lock_store:
    type: 'sentinel_lock_store.RedisSentinelLockStore'
    master: mymaster
    host: 191.161.6.191 #示例127.0.0.1
    port1: 6379
    port2: 6380
    port3: 6381
    db: 0
    password: password  # 输入密码
    key_prefix: rasa
    socket_timeout: 0.5
    #设置为0.5秒, 所以阻塞0.5秒后会触发超时异常
    
tracker_store:
    type: 'sentinel_tracker_store.RedisSentinelTrackerStore'
    url: 191.161.6.191 #示例127.0.0.1
    master: mymaster
    port1: 6379
    port2: 6380
    port3: 6381
    db: 5
    password: password  # 输入密码
    key_prefix: rasa
    socket_timeout: 5
    record_exp: 300 #以秒为单位记录过期时间

二. 增加类文件

可以看到endpoints.yml配置文件中,增加了自定义的type, 因此需要增加类文件。

在endpoints.yml的相同目录下:增加三个python文件,文件内容如下:

在这里插入图片描述

# sentinel_lock_store.py 对应sentinel_lock_store.RedisSentinelLockStore
import asyncio
import json
import logging
import os

from async_generator import asynccontextmanager
from typing import Text, Union, Optional, AsyncGenerator

from rasa.shared.exceptions import RasaException, ConnectionException
import rasa.shared.utils.common
from rasa.core.constants import DEFAULT_LOCK_LIFETIME
from rasa.core.lock import TicketLock
from rasa.utils.endpoints import EndpointConfig

logger = logging.getLogger(__name__)

from redis.sentinel import Sentinel

def _get_lock_lifetime() -> int:
    return int(os.environ.get("TICKET_LOCK_LIFETIME", 0)) or DEFAULT_LOCK_LIFETIME


LOCK_LIFETIME = _get_lock_lifetime()
DEFAULT_SOCKET_TIMEOUT_IN_SECONDS = 10

DEFAULT_REDIS_LOCK_STORE_KEY_PREFIX = "lock:"
import redis
from redis.sentinel import Sentinel
from rasa.core.lock_store import LockStore
from redis_sentinel_helper import redisSentinelHelper


class RedisSentinelLockStore(LockStore):
    """Redis store for ticket locks."""
    def __init__(
        self,
        endpoint_config: EndpointConfig
    ) -> None:
        
        self.red = redisSentinelHelper(endpoint_config.kwargs)
        key_prefix=endpoint_config.kwargs['key_prefix']
        self.key_prefix = DEFAULT_REDIS_LOCK_STORE_KEY_PREFIX
        if key_prefix:
            logger.debug(f"Setting non-default redis key prefix: '{key_prefix}'.")
            self._set_key_prefix(key_prefix)
        super().__init__()

    def _set_key_prefix(self, key_prefix: Text) -> None:
        if isinstance(key_prefix, str) and key_prefix.isalnum():
            self.key_prefix = key_prefix + ":" + DEFAULT_REDIS_LOCK_STORE_KEY_PREFIX
        else:
            logger.warning(
                f"Omitting provided non-alphanumeric redis key prefix: '{key_prefix}'. "
                f"Using default '{self.key_prefix}' instead."
            )

    def get_lock(self, conversation_id: Text) -> Optional[TicketLock]:
        """Retrieves lock (see parent docstring for more information)."""
        serialised_lock = self.red.get_key(self.key_prefix + conversation_id)
        if serialised_lock:
            return TicketLock.from_dict(json.loads(serialised_lock))
        return None

    def delete_lock(self, conversation_id: Text) -> None:
        """Deletes lock for conversation ID."""
        deletion_successful = self.red.delete_key(self.key_prefix + conversation_id)
        self._log_deletion(conversation_id, deletion_successful)

    def save_lock(self, lock: TicketLock) -> None:
        self.red.set_key(self.key_prefix + lock.conversation_id, lock.dumps())
# sentinel_tracker_store.py 对应type  'sentinel_tracker_store.RedisSentinelTrackerStore'
import contextlib
import itertools
import json
import logging
import os

from time import sleep
from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    Iterator,
    List,
    Optional,
    Text,
    Union,
    TYPE_CHECKING,
    Generator,
)

from boto3.dynamodb.conditions import Key
from pymongo.collection import Collection

import rasa.core.utils as core_utils
import rasa.shared.utils.cli
import rasa.shared.utils.common
import rasa.shared.utils.io
from rasa.shared.core.constants import ACTION_LISTEN_NAME
from rasa.core.brokers.broker import EventBroker
from rasa.core.constants import (
    POSTGRESQL_SCHEMA,
    POSTGRESQL_MAX_OVERFLOW,
    POSTGRESQL_POOL_SIZE,
)
from rasa.shared.core.conversation import Dialogue
from rasa.shared.core.domain import Domain
from rasa.shared.core.events import SessionStarted
from rasa.shared.core.trackers import (
    ActionExecuted,
    DialogueStateTracker,
    EventVerbosity,
)
from rasa.shared.exceptions import ConnectionException, RasaException
from rasa.shared.nlu.constants import INTENT_NAME_KEY
from rasa.utils.endpoints import EndpointConfig
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta

if TYPE_CHECKING:
    import boto3.resources.factory.dynamodb.Table
    from sqlalchemy.engine.url import URL
    from sqlalchemy.engine.base import Engine
    from sqlalchemy.orm import Session, Query
    from sqlalchemy import Sequence

logger = logging.getLogger(__name__)

DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX = "tracker:"
from rasa.core.tracker_store import TrackerStore
from redis.sentinel import Sentinel
from redis_sentinel_helper import redisSentinelHelper

class RedisSentinelTrackerStore(TrackerStore):
    """Stores conversation history in RedisSentinel"""
    def __init__(
        self,
        domain: Domain,
        host: Text = "localhost",
        master: Text= "mymaster",
        port1: int = 6379,
        port2: int = 6380,
        port3: int = 6381,
        db: int = 0,
        password: Optional[Text] = None,
        key_prefix: Optional[Text] = None,
        socket_timeout: Optional[float] = None,
        record_exp: Optional[float] = None,
        event_broker: Optional[EventBroker] = None,
        **kwargs: Dict[Text, Any],
        ):

        config=dict()
        config["host"]=host
        config["master"]=master
        config["port1"]=port1
        config["port2"]=port2
        config["port3"]=port3
        config["db"]=db
        config["password"]=password
        config["socket_timeout"]=socket_timeout
        self.red = redisSentinelHelper(config)

        self.record_exp = record_exp
        self.key_prefix = DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
        if key_prefix:
            logger.debug(f"Setting non-default redis key prefix: '{key_prefix}'.")
            self._set_key_prefix(key_prefix)
        super().__init__(domain, event_broker, **kwargs)

    def _set_key_prefix(self, key_prefix: Text) -> None:
        if isinstance(key_prefix, str) and key_prefix.isalnum():
            self.key_prefix = key_prefix + ":" + DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
        else:
            logger.warning(
                f"Omitting provided non-alphanumeric redis key prefix: '{key_prefix}'. "
                f"Using default '{self.key_prefix}' instead.")

    def _get_key_prefix(self) -> Text:
        return self.key_prefix

    def save(
        self, tracker: DialogueStateTracker, timeout: Optional[float] = None
    ) -> None:
        """Saves the current conversation state."""
        if self.event_broker:
            self.stream_events(tracker)

        if not timeout and self.record_exp:
            timeout = self.record_exp
        serialised_tracker = self.serialise_tracker(tracker)
        self.red.setex_key(self.key_prefix + tracker.sender_id, serialised_tracker, ex=timeout)
        #self.red.set_key(self.key_prefix + tracker.sender_id, serialised_tracker)

    def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
        """Retrieves tracker for the latest conversation session.
        The Redis key is formed by appending a prefix to sender_id.
        Args:
            sender_id: Conversation ID to fetch the tracker for.
        Returns:
            Tracker containing events from the latest conversation sessions.
        """
        stored = self.red.get_key(self.key_prefix + sender_id)
        if stored is not None:
            return self.deserialise_tracker(sender_id, stored)
        else:
            return None

    def keys(self) -> Iterable[Text]:
        """Returns keys of the Redis Tracker Store."""
        return self.red.get_pattern(self.key_prefix + "*")
# redis_sentinel_helper.py 连接redis 哨兵模式的工具类
import asyncio
import json
import logging
import os
import redis
from redis.sentinel import Sentinel

logger = logging.getLogger(__name__)


class redisSentinelHelper():
    def __init__(self,endpoint_config):
        self.url = endpoint_config['host']
        self.port1 = endpoint_config['port1']
        self.port2 = endpoint_config['port2']
        self.port3 = endpoint_config['port3']
        self.sentinel_list = []
        self.sentinel_list.append((self.url, self.port1))
        self.sentinel_list.append((self.url, self.port2))
        self.sentinel_list.append((self.url, self.port3))


        self.password= endpoint_config['password']
        self.socket_timeout= endpoint_config['socket_timeout']
        self.db= endpoint_config['db']
        self.service_name= endpoint_config['master']
        try:
            self.sentinel = Sentinel(self.sentinel_list, socket_timeout=self.socket_timeout, sentinel_kwargs={'password':self.password})
        
            self.master = self.sentinel.master_for(
            service_name=self.service_name,
            socket_timeout=self.socket_timeout,
            password=self.password,
            db=self.db)
        except redis.ConnectError as err:
            logger.debug(str(err))

    def get_master_redis(self):
        return self.sentinel.discover_master(self.service_name)
    
    def get_slave_redis(self):
        return self.sentinel.discover_slaves(self.service_name)
    
    def setex_key(self, key, value, ex):
        if self.master:
            return self.master.setex(key, ex, value)
        else:
            return None
    def set_key(self, key, value):
        if self.master:
            return self.master.set(key, value)
        else:
            return None
    
    def get_key(self, key):
        if self.master:
            return self.master.get(key)
        else:
            return None
    def delete_key(self, key):
        if self.master:
            return self.master.delete(key)
        else:
            return None
三. 运行rasa
rasa  run  --endpoints endpoints.yml
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个简单的 Rasa 后端训练程序的示例,您可以参考并根据自己的需求进行修改和扩展。这个示例程序使用了 Rasa 2.x 版本,包括了自然语言理解 (NLU) 和对话管理 (DM) 的功能,并且可以连接到前端的 Rasa Web Chat 项目,提供智能问答服务。 首先,您需要安装 Rasa 和相关的依赖库。您可以使用以下命令来安装: ``` pip install rasa[spacy] python -m spacy download en_core_web_md ``` 然后,您需要编写培训数据,定义意图和实体,并编写自定义操作。这里我们使用一个简单的培训数据,包括一些常见的问答对。您可以将其保存为 `data/nlu.md` 文件。 ``` ## intent:greet - hello - hi - hey - good morning - good afternoon - good evening ## intent:goodbye - bye - goodbye - see you - see you later ## intent:thanks - thank you - thanks - thank you very much - thanks a lot ## intent:ask_name - what is your name - who are you - what should I call you - can you introduce yourself ## intent:ask_age - how old are you - what is your age - when were you born ## intent:ask_location - where are you from - where do you live - what is your location ## intent:ask_weather - what is the weather like today - is it going to rain today - how hot is it today ## intent:default - anything else - what else can you do - I have a question ``` 接下来,您需要定义意图和实体,以及编写自定义操作。这里我们使用了一些简单的意图和实体,并提供了一些自定义操作,用于处理用户的输入和生成相应的回答。您可以将其保存为 `domain.yml` 文件。 ``` intents: - greet - goodbye - thanks - ask_name - ask_age - ask_location - ask_weather - default entities: - location - date responses: utter_greet: - text: "Hello, how can I help you?" utter_goodbye: - text: "Goodbye, have a nice day!" utter_thanks: - text: "You're welcome, happy to help!" utter_ask_name: - text: "My name is ChatBot, what's yours?" utter_ask_age: - text: "I'm a chatbot, so I don't have an age. How can I assist you?" utter_ask_location: - text: "I'm a virtual assistant, so I don't have a physical location. What can I do for you?" utter_ask_weather: - text: "I'm sorry, I'm not able to provide real-time weather information at the moment." utter_default: - text: "I'm not sure I understand. Can you please rephrase your question?" ``` 最后,您需要编写 Rasa 后端程序的代码,启动 Rasa 服务器,并提供 API 接口,用于处理用户的输入和生成相应的回答。这里我们使用了一个简单的 Flask 应用程序,您可以将其保存为 `app.py` 文件。 ``` from flask import Flask, request, jsonify from rasa.core.agent import Agent app = Flask(__name__) agent = Agent.load("models/20210205-005303.tar.gz") @app.route("/webhooks/rasa/webhook", methods=["POST"]) def webhook(): data = request.json message = data["message"] sender_id = data["sender"] response = agent.handle_text(message, sender_id=sender_id) text = response[0]["text"] return jsonify({"text": text}) if __name__ == "__main__": app.run(debug=True) ``` 在上面的代码中,我们首先创建了一个 Flask 应用程序,并加载了之前训练好的 Rasa 模型。然后,我们定义了一个 API 接口,并使用 `handle_text()` 方法处理用户的输入,生成相应的回答。最后,我们启动了 Flask 应用程序,并开启了调试模式。 在启动后端程序之前,您还需要先通过 `rasa train` 命令来训练 Rasa 模型,并生成相应的模型文件。在训练完成后,您可以将模型文件保存到 `models` 目录下,并使用 `tar.gz` 格式进行压缩。然后,您可以使用以下命令来启动后端程序: ``` python app.py ``` 启动后,在您的前端 Rasa Web Chat 项目中,您可以使用以下代码来连接到后端程序,并与聊天机器人进行交互: ``` const widget = window.WebChat.create({ socketUrl: "http://localhost:5000/webhooks/rasa/webhook", title: "ChatBot", initPayload: "/greet", inputTextFieldHint: "请输入您的问题...", connectingText: "正在连接到聊天机器人...", showFullScreenButton: true, hideWhenNotConnected: false, embedded: true, params: { storage: "session" } }); window.WebChat.open("chat-bot", widget, () => { console.log("聊天窗口已打开!"); }); ``` 在上面的代码中,我们使用了 `socketUrl` 参数指定了 Rasa 后端程序的地址和端口号,并使用了一些其他的配置选项,例如聊天窗口的标题、提示文本、样式等等。然后,我们调用了 `window.WebChat.open()` 方法,将聊天窗口嵌入到网页中,并指定了一个 DOM 元素作为容器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值