@fraqjs/plugin-conversation
@fraqjs/plugin-conversation 提供了对多轮对话的支持,帮助开发者编写交互式机器人功能。
安装与配置
将插件添加至 dependencies,然后在创建 Context 时引入并配置插件:
import ConversationPlugin from '@fraqjs/plugin-conversation';
ctx.install(ConversationPlugin, {
// 在这里传入 ConversationService 的配置选项
});ConversationPlugin 有如下配置项:
defaultTimeout:默认的对话超时时间,单位是毫秒。默认值为 30000(30 秒)。如果用户在这个时间内没有回复,当前对话会自动结束。onCollision:当用户触发一个新的会话时,如何处理旧的会话。可选值有:reject-incoming:默认值,拒绝建立新的会话,抛出一个ConversationRejectionError。abort-existing:结束旧的会话,建立新的会话,并且在旧的会话处抛出一个ConversationAbortionError。
如果你是插件开发者,请将本插件添加到项目的 peerDependencies 中,并在自己的插件中声明依赖:
import { ConversationService } from '@fraqjs/plugin-conversation';
definePlugin({
name: 'my-plugin',
inject: {
conversation: ConversationService,
},
apply(ctx) {
// 使用 ctx.conversation 来访问 ConversationService
},
});开发交互功能
考虑下面的对话场景:
用户: 天气
机器人: 你想查询哪个城市的天气呢?
用户: 北京
机器人: 北京今天晴,最高气温...传统的 ctx.router.command 遵循单纯的命令-响应模式,不适合处理多轮对话。但使用 ctx.conversation.command,我们可以轻松地实现这个功能。与前者不同,ctx.conversation.command 的回调函数接受第三个参数 scope,它包含一个函数 open,我们可以通过它来启动一个新的会话:
ctx.conversation.command('天气', {}, async (session, _, { open }) => {
await session.reply(msg`你想查询哪个城市的天气呢?`);
const city = await open<string>(({ router, done }) => {
router.rawPattern({ city: param.str() }, async (_, { city }) => {
done(city);
});
}); // city: string | null
if (city === null) {
return;
}
const weatherInfo = await getWeatherForCity(city);
await session.reply(msg`${weatherInfo}`);
});在这个例子中,插件接收到 “天气” 这个输入时,会先回复一个问题,然后通过 open 启动一个新的会话。open<R> 函数传入的回调函数接受一个 ConversationContext<R> 对象,包含以下属性:
router:一个新的Router实例,用于在会话中定义输入模式和对应的处理函数。在这个Router中定义的路由只会在当前会话中生效,不会影响全局的ctx.router。此外,传入这个router的回调函数的session中,raw字段会被替换为触发当前路由的原始输入内容,其余字段与你传入open的session相同。done(result: R):一个函数,用于结束会话并返回结果。当你调用done时,ctx.conversation.open返回的 Promise 会被 resolved,值就是你传入done的参数。abort(reason?: string):一个函数,用于主动终止会话,并且可以传入一个字符串作为终止原因。当你调用abort时,ctx.conversation.open返回的 Promise 会被 rejected,错误类型是ConversationAbortionError,错误消息包含你传入的终止原因。
我们在代码中使用了其中的 router 来定义了一个 rawPattern 路由,这个路由会匹配用户的任意输入,并且将输入内容作为字符串参数 city 传入回调函数。当用户输入符合这个模式时,我们调用 done(city) 来结束会话,并将用户输入的城市名称作为结果返回。
可选配置
open 函数还可以接受一个可选的配置对象,支持以下配置项:
timeout:会话的超时时间,单位是毫秒。默认值为插件配置中的defaultTimeout。如果配置了这个值,它将会覆盖插件配置中的defaultTimeout,成为当前会话的超时时间。
处理特殊情况
超时情况
从代码中可以看出,open 方法有可能返回 null,这表示会话超出了配置的等待时间限制,你需要显式地检查这种情况来处理超时。代码中的处理是直接 return,即不再执行后续逻辑;但你也可以发送一个提示消息,或使用 while 循环来重新发起会话等。
会话冲突情况
open 方法还可能抛出两种错误:
ConversationRejectionError:当发生会话冲突且onCollision配置为reject-incoming时抛出,表示新的会话请求被拒绝。ConversationAbortionError:- 当发生会话冲突且
onCollision配置为abort-existing时,旧的会话会被结束,并在旧的会话处抛出这个错误。 - 当调用
abort方法时抛出,表示当前会话被主动终止,并且包含终止原因。
- 当发生会话冲突且
一般情况下这些错误会被框架捕获并忽略,但你也可以通过 try...catch 来捕获这些错误,并进行相应的处理。