🚀 原文地址:
Rasa 默认打开的 API 接口是 http://localhost:5005/webhooks/rest/webhook,我们可以通过 POST 数据到该端口下,从而拿到 Rasa 机器人的回复。例如,你可以 POST 如下一个请求,并拿到机器人的回复。
$ curl -XPOST http://localhost:5005/webhooks/rest/webhook \-d '{"sender": "user1", "message": "你好"}' \-H "Content-type: application/json"# Terminal 中得到的响应[{"recipient_id":"user1","text":"Hello World!"}]
上述便是 Rasa 默认开启的 Rest 通道,但在实际场景中,我们并不会使用这个 Rest 通道,因为所传进去的不可能是简单的 {"sender": "user1", "message": "你好"} JSON 格式,可能包含更多的键值对,且传进去的数据我们大概率也是需要在程序中进行使用的。
Rasa 支持我们自定义通道,它可以帮助我们实现特定的 API 接口,也就是说我们可以新开启 一个http://localhost:5005/webhooks/bird/webhook API 端口,然后传进去 JSON 数据也可以在通道中进行处理。
当然,Rasa 中提供了多个通道连接器,包括 Facebook Messager、Slack、Telegram、Twilio、Google Hangouts Chat、Microsoft Bot Framework、Cisco Webex Teams、RocketChat、MatterMost,同时你还可以连接你自己的个人网站。目前我并没有使用过这些通道,就不多作介绍了。
在 Rasa 中是通过定义一个 Python 类来实现自定义通道,可以使用 rasa.core.channels.rest.RestInput 类作为模板代码进行修改,能让你快速上手定义自定义通道。
1. name 方法
所实现的类必须继承自 InputChannel 类,且必须包含 name 方法,它的作用是定义连接器 webhook 中的 URL前缀,例如上面的 URL 中的 bird。同时,name 方法也定义了通道的名称,以便你使用任何通道下特殊变种响应。同样地,你应该将通道的名称传给 output_channel 中查询参数来触发意图。
下面我们将自定义一个名为 bird 的通道,具体 name 方法定义如下:
from rasa.core.channels.channel import InputChannelclass BirdInput(InputChannel):def name() -> Text:"""Name of your custom channel."""return "bird"
如果你希望使用 bird 通道来实现一个特定的回复,例如在 domain.yml 文件中这样进行使用:
responses:utter_greet:- text: Hi! I'm the default greeting.- text: Hi! I'm the custom channel greetingchannel: myio
这样定义之后,我们自定义通道的 webhook 将会是 http://localhost:5005/webhooks/bird/webhook。
2. blueprint 方法
蓝图的方法的使用需要创建一个基于 Sanic 服务器的 blueprint,所实现的 blueprint 至少要包含两种路由:
- health:对应
/路由 - receive:对应
/webhook路由
先来看一个完整的示例,我们即将所发出的请求是这样的:
curl -XPOST http://localhost:5005/webhooks/bird/webhook \-H "Content-type: charaset=utf-8" \-d '{"sender_id": "9a05df6b","question": "are you a bot?","metadata": {"name": "张三","gender": "男","age": 16,"occ": "student","school": "北京大学"},"sendTime": "202109081625300214","answerResult": {}}'
接下来我们使用 BirdInput 通道(利用 CollectingOutputChannel 类)来接收它,且打印出传进来的内容:
import jsonimport asyncioimport inspectimport rasa.utils.endpointsfrom sanic import Sanic, Blueprint, responsefrom sanic.request import Requestfrom sanic.response import HTTPResponsefrom typing import Text, Dict, Any, Optional, Callable, Awaitable, NoReturnfrom rasa.core.channels.channel import (InputChannel,CollectingOutputChannel,UserMessage,)class BirdInput(InputChannel):@classmethoddef name(cls) -> Text:"""Name of your custom channel."""return "bird"def get_metadata(self, request: Request) -> Dict[Text, Any]:content_type = request.headers.get("content-type")if content_type == "application/json":return request.json.setdefault("metadata", {})else:return {}def blueprint(self,on_new_message: Callable[[UserMessage], Awaitable[None]]) -> Blueprint:bird_webhook = Blueprint("bird_webhook_{}".format(type(self).__name__),inspect.getmodule(self).__name__,)@bird_webhook.route("/", methods=["GET"])async def health(request: Request) -> HTTPResponse:return response.json({"status": "ok"})@bird_webhook.route("/webhook", methods=["POST"])async def receive(request: Request) -> HTTPResponse:sender_id = request.json.get("sender")text = request.json.get("question")send_time = request.json.get("send_time")metadata = self.get_metadata(request)input_channel = self.name()collector = CollectingOutputChannel()await on_new_message(UserMessage(text=text,output_channel=collector,sender_id=sender_id,input_channel=input_channel,metadata=metadata,))output_json = [{"senderId": sender_id,"question": text,"metadata": metadata,"sendTime": send_time,"answerResult": {"answer": collector.messages[0]["text"],}}]return HTTPResponse(json.dumps(output_json, ensure_ascii=False),content_type="application/json;charset=utf-8")return bird_webhook
health 部分的实现比较简单,主要是用于测试访问是否正常。
receive 部分的实现中,你需要告诉 Rasa 如何去处理用户所发出的消息,对应到上述代码部分为:
on_new_message(rasa.core.channels.channel.UserMessage(text=text,output_channel=collector,sender_id=sender_id,input_channel=input_channel,metadata=metadata,))
调用 on_new_message 时,将会把用户消息传给 handle_message 方法。output_channel 参数是指实现 OutputChannel 类的输出通道,你也可以使用方法来实现自己输出通道类(例如发送文本和图像的方法),或者当机器人在处理的消息时,你也可以使用 CollectingOutputChannel 收集机器人的响应,并将它们作为返回端点响应中的一部分,这就是 RestInput 通道的实现方式。在实现自定义输出通道时,参考一下其他输出通道的实现方式,例如 rasa.core.channels.slack 中的 SlackBot,将对你非常有帮助。
:::info
💡 值得一提的是,如果使用 response.json(collector.messages) 的话,使用 CURL 得到结果将会是 Unicode 字符,这对于终端调试显然不太友好,所以使用 HTTPResponse(...) 可以中文字符显示出来。
:::
3. 消息中的元数据
如果你需要在自定义操作中使用来自前端的额外信息,可以在消息中通过定义 metadata 键来进行传递。如果适用的话,元数据信息将随着用户消息从 Rasa Server 进入 Action Server,它将存储在 **tracker** 中。消息中元数据并不会直接影响 NLU 分类或者动作的预测。
💡 注意一下,我们所传递进来的元数据是存储在
tracker中的,后面我们将会根据元数据信息来进行个性化的回复。
InputChannel 类中 get_metadata 实现方法默认忽略了所有元数据,如果想在自定义连接器中提取元数据,那么需要实现 get_metadata 方法。 SlackInput 类中 get_metadata 方法实现了根据通道的响应格式提取元数据,你可以将其参考作为样例。
4. 自定义通道的凭证
要使用自定义通道的话,你需要在 credentials.yml 配置文件提供对应的凭证,必须包含通道模块路径(非通道的名称)以及必需的配置参数。
例如上面的 BirdInput 的自定义连接器类,我们将其保存在文件 addons/bird_channel.py 中,对应的路径为 addons.bird_channel.BirdInput,对应的凭证如下:
addons.bird_channel.BirdInput:username: "user_name"another_parameter: "some value"
为了让 Rasa 知道自定义通道,在启动 Rasa Server 前,我们需要在 credentials.yml 指定路径,并通过命令行参数 --credentials 来加载该文件。
5. 测试自定义连接器的Webhook
为测试自定义连接器,我们可以 POST 消息到对应的 Webhook,相应的 JSON 数据如下:
{"sender_id": "9a05df6b","question": "are you a bot?","metadata": {"name": "张三","gender": "男","age": 28,"occ": "student","schoOl": "北京大学"},"sendTime": "202109081625300214","answerResult": {}}
完成上述所有操作之后,我们来运行 Rasa Server:
rasa run -p 5005 --enable-api \--endpoints ./endpoints.yml \--credentials ./credentials.yml \-m models/
我们通过如下请求来获取机器人的回复:
$ curl -XPOST http://localhost:5005/webhooks/bird/webhook \-H "Content-type: application/json" \-d '{"sender_id": "9a05df6b","question": "are you a bot?","metadata": {"name": "张三","gender": "男","age": 16,"occ": "student","school": "北京大学"},"sendTime": "202109081625300214","answerResult": {}}'# 得到的结果[{"senderId": null, "question": "are you a bot?", "metadata": {"name": "张三", "gender": "男", "age": 16, "occ": "student", "school": "北京大学"}, "sendTime": null, "answerResult": {"answer": "I am a bot, powered by Rasa."}}]
