这篇文章上次修改于 794 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

咕咕咕了几个月,我又回来啦。在上一篇文章里,我们利用 Chatterbot + CoolQ 搭建了一个简单的 QQ 聊天机器人。这一篇文章可以看作是对上一篇文章的补充,主要介绍我在编写机器人时用到的一些处理逻辑和小技巧。

上一篇文章:用 Python 来做一个聊天机器人吧!(一)


群聊中@的判断

当我们把机器人邀请到群聊里的时候,肯定要保证机器人不会回复每一条消息,否则……

龙王永远属于你.jpg
龙王永远属于你
那么我们肯定要让机器人有选择性地回复,一个比较简单的办法就是只回复@机器人的信息

CoolQ 的消息转义可以帮助我们方便地实现这一功能。

CoolQ 在收到消息的非文本部分(比如图片,@某人,表情等)时,会将这一部分消息转义为「CQ码」。CQ码是一类形如[CQ:xx,xxxxx]的字符串,比如[CQ:face,id=176]就是一个可爱的表情。CQ码不仅可以在接受消息时使用,在发送消息时,消息中的CQ码也会被 CoolQ 自动转义

@某人对应的CQ码的格式是这样的:[CQ:at,qq=被@人的qq号]

因为CQ码也是一段字符串,因此我们可以在收到消息时,通过字符串匹配的方式加以判断:

import re

@bot.on_message('group')
async def handle_msg_group(context):
    msg = context['message']
    if '[CQ:at,qq=机器人的QQ号]' in msg:
        msg = re.sub(r'\[.*\]', '', msg).strip() # 用正则表达式去掉一切CQ码
        reply = get_reply(msg) # 获取回复
        return {'reply': reply}

如果不想把机器人的QQ号写死在代码里,可以另外加一段初始化的代码。不过,由于反向 WebSocket 模式收不到插件启用的消息,因此只能把初始化写在第一次收到消息的时候。(以上斜体字已过时)

(2020/2/17更新)在最新版的 CoolQ HTTP API(v4.14及以上)中,新增了 lifecycle 元事件的 connect 子类型,这个事件表示 Python 和 CoolQ HTTP API 建立 WebSocket 连接成功。因此,我们在使用反向 WebSocket 时,可以把初始化代码写在这个事件中。

注意,为了避免事件连接刚建立时,方法连接还没有建立,需要在配置文件中加入"ws_reverse_use_universal_client": true ,具体请看第一篇文章。

import re

self_ID = 0

@bot.on_meta_event('lifecycle') # 反向 WebSocket 目前只能收到这一个 lifecycle 事件,所以不需要再进行类型判断
async def init(context):
    global self_ID
    rep = await bot.get_login_info()
    self_ID = rep['user_id']

@bot.on_message('group')
async def handle_msg_group(context):
    msg = context['message']
    if f'[CQ:at,qq={self_ID}]' in msg:
        msg = re.sub(r'\[.*\]', '', msg).strip() # 用正则表达式去掉一切CQ码
        reply = get_reply(msg) # 获取回复
        return {'reply': reply}

不过,这样还有一点小问题,如果别人只是@一下机器人,就会把一个空字符串送到输入里面,这种情况可以单独处理一下:

import re

self_ID = 0

@bot.on_meta_event('lifecycle')
async def init(context):
    global self_ID
    rep = await bot.get_login_info()
    self_ID = rep['user_id']

@bot.on_message('group')
async def handle_msg_group(context):
    msg = context['message']
    if f'[CQ:at,qq={self_ID}]' in msg:
        msg = re.sub(r'\[.*\]', '', msg).strip() # 用正则表达式去掉一切CQ码
        if msg == '':
            return {'reply': '你在叫我吗?'}
        rep = chatterbot.get_response(s) # 获取回复
        return {'reply': rep.text}

利用 Chatterbot 返回的 confidence 属性

如果机器人在群聊里只是被动地回复,@一句说一句,这样就太没有意思了。真正沙雕的灵魂,会在大家聊的火热的时候突然插入一句人工智障的回复,引起全场的关注。

要让机器人只回复一部分非@消息,最简单的办法是使用随机数:

import random, re

@bot.on_message('group')
async def handle_msg_group(context):
    msg = context['message']
    reply = False
    if f'[CQ:at,qq={self_ID}]' in msg:
        reply = True
        msg = re.sub(r'\[.*\]', '', msg).strip()
        if msg == '':
            return {'reply': '你在叫我吗?'}
    else:
        msg = re.sub(r'\[.*\]', '', msg).strip()
    if msg != '':
        rep = chatterbot.get_response(msg)
        if 1 - random.random() < 0.05 or reply:
            return {'reply': rep.text}

这里使用一个变量reply,可以把两部分获取回复的代码统一到一处。

5%是我测试的一个比较合适的回复概率,大家也可以根据自己的需要调整。

当然,我们还有比随机数更好的方法,那就是使用 chatterbot 返回的responseconfidence属性。

大家有没有注意到,当我们使用chatterbot.get_response()时 ,得到的并不是一个字符串,而是一个response对象。

response对象除了有text属性之外,还有一个常用的属性confidence, 字面意思就是「可信度」,实际代表的是输入的语句和语料库中语句的匹配程度。confidence的范围是0~1,一般来说,其值越接近1, 越有可能作出符合逻辑的回答。

Confidence1.jpg
confindence=1,代表这句话在语料库中有原句
Confidence0.jpg
confidence=0,代表这句话无从查证,只能瞎蒙
因此,我们可以通过判断response对象的confidence属性,只回答那些“能接上话”的句子,这样机器人的表现就会好一些。

import re

@bot.on_message('group')
async def handle_msg_group(context):
    msg = context['message']
    reply = False
    if f'[CQ:at,qq={self_ID}]' in msg:
        reply = True
        msg = re.sub(r'\[.*\]', '', msg).strip()
        if msg == '':
            return {'reply': '你在叫我吗?'}
    else:
        msg = re.sub(r'\[.*\]', '', msg).strip()
    if msg != '':
        rep = chatterbot.get_response(msg)
        if (rep.confidence > 0.68 and len(msg) > 3) or reply:
            return {'reply': rep.text}

这里顺便加了一条不回复过短的消息的规则。

同样,这里的0.68是我自己测试得出的经验值,大家也可以根据自己的需要修改。

(最近我测试 Chatterbot 时,发现了 confidence 出现了非0即1的问题,不知道是不是个例,如果使用 confidence 效果不好,还是用随机数吧。)


这篇文章比较短,因为 chatterbot 实在没什么好写的了……

所以,在下一篇文章里,我们将抛弃 chatterbot 框架,进入真正的机器学习。


现已加入知乎专栏:

用 Python 来做一个聊天机器人吧!