机器人开发文档
-
FinoChat Botkit机器人开发框架
机器人框架主要基于Matrix聊天协议接入FinoChat应用并通过FSM(有限状态机)管理会话状态,以及FinoChat ConvoUI协议扩展实现的聊天机器人。
1. 安装
1.1 npm 安装
npm install finochat-botkit
nodejs版本要求为v9.4.0
2. 原理:
2.1 大致流程
- 基于本框架的应用程序在启动时,就启动matrix聊天协议的客户端,并保持连接,并进行聊天服务器事件的监听
- 用户添加机器人后,机器人受邀自动加入房间,机器人可以发送欢迎消息提示
- 状态机以某个状态进行初始化,机器人接收到用户消息后进行匹配,成功后发给用户发送会话提问消息
- 用户在ConvoUI操作或者文字回复,机器人根据回复消息选择是否推进会话到下一个状态,或者停留在本状态继续会话,甚至结束会话
2.2 ConvoUI协议
- ConvoUI是指聊天对话当中的富文本消息(文字、图片、视频、菜单等等的组合)
- 机器人框架通过封装ConvoUI工厂方法向客户端发送满足协议的扩展消息体,由客户端实现对扩展消息体的具体渲染
3. 使用
3.1 基本用法
3.1.1 配置文件
config.js说明
- homeserver:finoChat聊天服务器接口
- loginUrl:机器人中心登录接口
- fcid:机器人登录ID
- password:机器人登录密码
- ENABLE_MONITOR:是否开启该机器人的监控,默认false
module.exports = { // 以下配置接口和机器人账号仅供演示测试用 homeserver: process.env.HOMESERVER || "https://api.finolabs.club", loginUrl: process.env.LOGIN_URL || "https://api.finolabs.club/api/v1/registry/botlogin", fcid: process.env.FCID || "@test-bot:finolabs.club", password: process.env.PASSWORD || "123456", ENABLE_MONITOR: process.env.ENABLE_MONITOR || false logLevel: 'debug', timeout: 30000, routelist: [], whitelist: [], blacklist: [], };
3.1.2 引入依赖
const botkit = require('finochat-botkit');
3.1.3 定义状态
const States = { INIT: 'INIT', STEP1: 'STEP1', STEP2: 'STEP2' };
3.1.4 机器人开发
继承botkit.Bot类
class demoBot extends botkit.Bot
- 机器人开发者只需继承botkit.Bot类,然后在状态机定义函数 describe()里面进行状态描述和消息匹配,即可集中于书写机器人业务处理逻辑。
- describe(fsm, bot)接收两个参数,fsm状态机实例,bot实例
引入状态机和匹配 API
// 引入状态机API const { startWith, when, goto, stay, stop } = botkit.DSL(fsm); // 引入匹配API const { match, BodyCase, ActionCase, CommandCase, DefaultCase } = botkit.Matcher;
状态机API基本用法
// 初始化状态,接收一个状态参数 startWith(States.INIT); // 描述某个状态,接收一个状态参数,返回一个asnyc回调函数 // sender发送消息的用户信息,content用户发送的消息体(注意:content.body才是消息体文本内容) when(States.STEP1)(asnyc (sender, content) => { // 机器人发送消息接口,第一个参数为房间ID,第二个参数为JSON消息体 bot.sendMessage(sender.roomId, { body: 'hello from bot' }); // 机器人发送文本消息接口,第一个参数为房间ID,第二个参数为具体文本 bot.sendMessage(sender.roomId, 'hello from bot'); // 描述状态转移,全部都可以链式调用withConvoMsg方法给用户附带发送消息,其中goto接收下一个状态作为参数 return stop()/stay()/goto(States.STEP2).withConvoMsg('Hi there!'); });
匹配API基本用法
when(States.STEP1)(asnyc (sender, content) => { // 匹配函数,接收第一个参数(content消息体)和其他具体匹配方法作为后续参数传入 return match(content, // 普通聊天文本消息匹配,支持字符串和正则表达式,匹配成功后做对应的逻辑处理 BodyCase('Hey bot!')(() => { return goto(States.STEP1).withConvoMsg('Hi there!'); }) ); });
3.1.5 机器人运行
new demoBot(require('config')).run();
3.2 代码demo
// 引入机器人框架依赖 const botkit = require('finochat-botkit'); // 定义状态机的各种状态 const States = { INIT: 'INIT', STEP1: 'STEP1', STEP2: 'STEP2' }; // 创建自己的机器人 class demoBot extends botkit.Bot { // 机器人被邀请加入房间 onJoinRoom(bot, roomId, userId, displayName) { return `亲,我是DemoBot【roomId: ${roomId}, userId: ${userId}, displayName: ${displayName}】`; } // 用户被邀请加入房间 onUserJoinRoom(bot, roomId, userId, displayName) { return this.onJoinRoom(bot, roomId, userId, displayName); } // 用户当前视图切换入房间 onUserEnterRoom(bot, roomId, userId, displayName) { return this.onJoinRoom(bot, roomId, userId, displayName); } // 会话超时结束 onTimeout(bot, roomId, userId, displayName) { return `会话结束【roomId: ${roomId}, userId: ${userId}, displayName: ${displayName}】`; } // 状态机定义函数, 主要的逻辑写在这里 describe(fsm, bot) { /** * 状态机 DSL * startWith 描述状态机的初始状态和初始 data,只需调用一次 * when 描述某个状态下,发生Event时,Bot业务执行与状态迁移的细节 * goto 用于生成when()函数的返回值,返回 nextState * stay goto(CurrentState)的另一种形式,停留在本状态 * stop goto(Done)的另一种形式,结束会话 */ const { startWith, when, goto, stay, stop } = botkit.DSL(fsm); /** * matcher API * BodyCase 匹配普通聊天中的string, 支持变长 pattern(String or RegExp type) * ActionCase 匹配convoUI消息的action,支持变长 pattern(String or RegExp type) * CommandCase 匹配convoUI消息的command类型,支持变长pattern(String or RegExp type) * DefaultCase 模式匹配的Default分支 */ const { match, BodyCase, ActionCase, CommandCase, DefaultCase } = botkit.Matcher; // 初始化状态 startWith(States.INIT); // INIT状态描述 when(States.INIT)(async (sender, content) => { // 匹配函数,接收第一个参数(content消息体)和其余参数(消息匹配方法) return match(content, // 匹配消息成功后回调,返回客户端消息(withConvoMsg方法)并转移到STEP1状态 BodyCase('Hey bot!')(() => { return goto(States.STEP1).withConvoMsg('Hi there!'); }) ); }); when(States.STEP1)(async (sender, content) => { return match(content, ActionCase('action1')(() => { return goto(States.STEP2).withConvoMsg('U r in Step2 now'); }), DefaultCase(() => { // ConvoUI工厂方法创建Assist消息 const ui = botkit.ConvoFactory.ui() // body文本在ConvoUI无法渲染时显示,类似HTML中img标签的alt提示属性 .setBody('assist demo') // Layout工厂方法创建带两个按钮的Assist消息 .setPayload( botkit.LayoutFactory.assist().setTitle('assist').addItems( botkit.ActionFactory.button('button1', 'action1') botkit.ActionFactory.button('button2', 'action2') ) ); return stay().withConvoMsg(ui); }) ); }); when(States.STEP2)(async (sender, content) => { return match(content, BodyCase('apple')(() => { return stop().withConvoMsg('You can buy an Apple product in https://www.apple.com/'); }) ); }); } } // 机器人运行 new demoBot(require('config')).run();
4. 接口
4.1 会话事件
- onJoinRoom(bot, roomId, userId, displayName) :机器人被邀请加入房间
- onUserJoinRoom(bot, roomId, userId, displayName):用户被邀请加入房间
- onUserEnterRoom(bot, roomId, userId, displayName):用户当前视图切换入房间
- onTimeout(bot, roomId, userId, displayName):会话超时结束
4.2 状态机DSL
- startWith:描述状态机的初始状态和初始 data,只需调用一次
- when:描述某个状态下,发生Event时,Bot业务执行与状态迁移的细节
- goto:用于生成when()函数的返回值,返回 nextState
- stay:goto(CurrentState)的另一种形式,停留在本状态
- stop:goto(Done)的另一种形式,结束会话
4.3 消息匹配API
- BodyCase:匹配普通聊天中的string, 支持变长 pattern(String or RegExp type)
- ActionCase:匹配convoUI消息的action,支持变长 pattern(String or RegExp type)
- CommandCase:匹配convoUI消息的command类型,支持变长pattern(String or RegExp type)
- DefaultCase:模式匹配的Default分支
4.4 状态机data的共享
状态机
describe()
的方法体内可以使用如下两种方式共享变量(session scope)- 常规方式是在状态迁移时,将修改后的data对象传递给
using()
. 这里建议通过spread-rest
语法构造 immutable object 对象。后续会方便利用到状态跟踪,重演,TimeTravel Debugging 等很多玩法。 - 还有一种可行的方式是,直接将变量挂在 fsm 上面
4.5 底层API
在回调函数内,可以不借助 matcher API,通过判断 content 或者 data 的具体细节来控制分支走向, 获得最大的灵活度:
when(MyStates.IDLE)(async (sender, content, data) => { if(content.body === "step1") { return goto(MyStates.STEP1).withConvoMsg({body: "I goto step1!"}); } else if (content.body === "开始业务2") { return goto(MyStates.STEP2) } return stay().withConvoMsg({body: "Stay!"}); });