这篇文章上次修改于 794 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
咕咕咕了几个月,我又回来啦。在上一篇文章里,我们利用 Chatterbot + CoolQ 搭建了一个简单的 QQ 聊天机器人。这一篇文章可以看作是对上一篇文章的补充,主要介绍我在编写机器人时用到的一些处理逻辑和小技巧。
上一篇文章:用 Python 来做一个聊天机器人吧!(一)
群聊中@的判断
当我们把机器人邀请到群聊里的时候,肯定要保证机器人不会回复每一条消息,否则……
龙王永远属于你
那么我们肯定要让机器人有选择性地回复,一个比较简单的办法就是只回复@机器人的信息。
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 返回的response
的confidence
属性。
大家有没有注意到,当我们使用chatterbot.get_response()
时 ,得到的并不是一个字符串,而是一个response
对象。
response
对象除了有text
属性之外,还有一个常用的属性confidence
, 字面意思就是「可信度」,实际代表的是输入的语句和语料库中语句的匹配程度。confidence
的范围是0~1,一般来说,其值越接近1, 越有可能作出符合逻辑的回答。
confindence=1,代表这句话在语料库中有原句
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 框架,进入真正的机器学习。
现已加入知乎专栏:
没有评论