对话升级,语音沟通,流程自定制
本帖最后由 xhtdtk 于 2022-7-23 02:50 编辑前话
上次利用百度UNIT智能对话写了一个有界面的闲聊机器人,看到有坛友说做成语音聊天会更有趣,于是我问了一下客服。
发现百度智能云除了能帮忙完成智能对话外,还能实现“语音识别”和“语音合成”两个功能,那么我只需要用pyaudio实现“录音”和“播放音频”这两个功能就能做出语音对话,思维导图如下
# # # 实现的效果可以查看我放在bilibili上的视频 # # #
语音对话基本功能代码介绍
录音
# 计算声音大小函数
def rms(data):
count = len(data)/2
format = "%dh"%(count)
shorts = struct.unpack( format, data )
sum_squares = 0.0
for sample in shorts:
n = sample * (1.0/32768)
sum_squares += n*n
return math.sqrt( sum_squares / count )
'''
通过声音的大小判断是否结束录音,详情在本代码搜索'录制音频数据'
'''
# 唤醒录音函数
def autoRecord(setvoice):
# 设置音频文件基本参数
CHUNK = 1024 # 每个缓冲区的帧数
FORMAT = pyaudio.paInt16 # 采样位数
CHANNELS = 1 # 声道: 1,单声道;2,双声道
RATE = 8000 # 采样频率
WAVE_OUTPUT_FILENAME = '录音文件.wav' #输出文件名
p = pyaudio.PyAudio() # 实例化PyAudio
stream = p.open(format=FORMAT,channels=CHANNELS,rate=RATE,input=True,frames_per_buffer=CHUNK)# 创建音频流
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"开始录音") # 提示
# 判断录制是否完成的基本参数
count_voice=0 # 记录次数(int)
frames = [] #存放音频数据
# 唤醒
while True:
data = stream.read(CHUNK)
voice = int(20 * math.log10(rms( data )))+100
if voice>60:
frames.append(data)
break
# 录制音频数据
while count_voice<=10:
data = stream.read(CHUNK)
voice = int(20 * math.log10(rms( data )))+100
if voice < setvoice:
count_voice=count_voice+1
frames.append(data)
time.sleep(0.1) # 灵敏度,和录制时间相关
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"结束录音") # 提示
# 录制完成,关闭流及实例
stream.stop_stream()
stream.close()
p.terminate()
# 输出录音文件
wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()
'''
setvoice(int类型 0-100) 是需要判断声音的大小,当判断小于这个值到一定数值时,为结束录音做准备(作者耳机不说话时是小于 40 其他同学自行测试)
'''pip install pyaudio可能会安装失败,可以直接下载
pyaudio下载地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/
pyaudio安装教程:https://www.jianshu.com/p/3b629f755907
这段代码是作者的demo,我稍作修改,利用rms函数里的数学公式(别的大神写的公式),可以在录音时显示音量的高低,利用音量变换的值可以让程序判断是否存储录音数据和决定是否结束录音,但缺点也非常的明显,会受环境嘈杂度的影响
语音识别
# 语音识别函数
def speech2writting(token):
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"开始语音识别") # 提示
with open('录音文件.pcm', 'rb') as f:
base64_data = f.read()
length = len(base64_data)
if length == 0:
return '音频文件无效'
base64_data = base64.b64encode(base64_data)
base64_data = str(base64_data,'utf-8')
url='http://vop.baidu.com/server_api'
header={'Content-Type':'application/json'}
post_data={
"format":"pcm",
"rate":16000,
"dev_pid":1537,
"channel":1,
"token":token,
"cuid":"123456PYTHON",
"len":length,
"speech":base64_data
}
post_data=json.dumps(post_data)
reponse=requests.post(url,headers=header,data=post_data)
if reponse:
json_=json.loads(reponse.text)
if json_['err_no']==0:
if json_['result']!=[]:
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"结束语音识别") # 提示
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"识别结果:",json_['result']) # 提示
return json_['result']
else:
return '抱歉,没听清楚'
else:
return '语音识别失败,可能是token过期'
# 参考文档 https://cloud.baidu.com/doc/SPEECH/s/qk76b444d
'''
token 是语音识别的token,可能和语音合成一样
技术文档地址 https://cloud.baidu.com/doc/SPEECH/s/2k5dllqxj
示例代码 https://ai.baidu.com/ai-doc/SPEECH/Vk38lxily
'''
写这段请求的时候,没想到表单数据base64b编码后,还要进行utf8编码,调试了半天还是看了demo才知道,demo的代码是同时针对py2和py3写的,这段代码被我进行了简化
然后注意这里语音识别的音频文件格式要求最好是.pcm,但是使用pyaudio进行录音时,不能直接导出为.pcm,否则识别不准确,所以需要用ffmpeg进行转换
ffmpeg官网下载地址:http://ffmpeg.org/download.html
ffmpeg下载和安装教程:https://zhuanlan.zhihu.com/p/434896169
只要下载并安装好就行了,代码已经封装好
# 音频格式转换函数
def wav2pcm():
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"开始转换格式") # 提示
cmd='ffmpeg -y-i 录音文件.wav-acodec pcm_s16le -f s16le -ac 1 -ar 16000 录音文件.pcm'
os.system(cmd)
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"结束转换格式") # 提示
'''
xxx.wav 转 xxx.cpm
'''
完成对话
# 机器人对话函数
def postMessage(newMessage,access_token):
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"开始对话") # 提示
url = 'https://aip.baidubce.com/rpc/2.0/unit/service/v3/chat?access_token=' + access_token
post_data = {
"version":"3.0",
"service_id":"S72063", # 机器人ID,非技能ID
"session_id":"",
"log_id":"xht123456789",
"request":{
"terminal_id":"UNIT_DEV_XHT",
"query":newMessage,
}
}
post_data = json.dumps(post_data)
headers = {'content-type': 'application/x-www-form-urlencoded'}
response = requests.post(url, data=post_data, headers=headers)
lst=[]
if response:
for eachRes in response.json()['result']['responses']:
for eachActions in eachRes['actions']:
lst.append(eachActions['say'])
repMessage=random.choice(lst)
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"结束对话") # 提示
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"对话结果:",repMessage) # 提示
return repMessage
'''
newMessage 是语音识别到的文字信息
access_token 是对话机器人的token
技术文档地址 https://ai.baidu.com/ai-doc/UNIT/Lkipmh0tz
示例代码 https://ai.baidu.com/ai-doc/UNIT/qkpzeloou
'''
机器人对话,上篇帖子有详细的介绍,这里不再多说
语音合成
#语音合成函数
def writting2speech(repMessage,PER,token):
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"开始语音合成") # 提示
# 语速,取值0-15,默认为5中语速
SPD = 4
# 音调,取值0-15,默认为5中语调
PIT = 3
# 音量,取值0-9,默认为5中音量
VOL = 5
# 下载的文件格式, 3:mp3(default) 4: pcm-16k 5: pcm-8k 6. wav
AUE = 6
# 建议wav,不作修改
FORMATS = {3: "mp3", 4: "pcm", 5: "pcm", 6: "wav"}
FORMAT = FORMATS
CUID = "123456PYTHON"
TTS_URL = 'http://tsn.baidu.com/text2audio'
tex = parse.quote_plus(repMessage)# 此处TEXT需要两次urlencode
params = {'tok': token, 'tex': tex, 'per': PER, 'spd': SPD, 'pit': PIT, 'vol': VOL, 'aue': AUE, 'cuid': CUID,
'lan': 'zh', 'ctp': 1}# lan ctp 固定参数
data = parse.urlencode(params)
rep=requests.post(TTS_URL,data=params)
with open('reponse.'+FORMAT, 'wb') as of:
of.write(rep.content)
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"结束语音合成") # 提示
'''
repMessage 是机器人对话返回的文字信息
PER 是选择发音人,详细详见下面的回调函数
token 是语音合成的token,可能和语音识别一样
技术文档地址 https://cloud.baidu.com/doc/SPEECH/s/qknh9i8ed
示例代码 https://cloud.baidu.com/doc/SPEECH/s/Gk38y8lzk
'''
同语音识别一样,demo的代码是同时针对py2和py3写的,这段代码被我进行了简化
播放音频
# 播放音频函数
def playRecord():
CHUNK = 1024 # 每个缓冲区的帧数
wf = wave.open('reponse.wav', 'rb') # 读取音频文件数据
p = pyaudio.PyAudio() # 实例化pyaudio
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),channels=wf.getnchannels(),rate=wf.getframerate(),output=True) # 打开音频流
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"开始播放录音") # 提示
data = wf.readframes(CHUNK) # 读取音频流数据
# 存储音频流数据
datas = []
while len(data) > 0:
data = wf.readframes(CHUNK)
datas.append(data)
# 播放音频流数据
for d in tqdm(datas):
stream.write(d)
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"结束播放录音") # 提示
# 停止音频流
stream.stop_stream()
stream.close()
# 关闭音频文件
p.terminate()
直接拿原作者的demo,不需要特别的改动
关于token
百度UNIT智能对话的token,请查看我上一边帖子使用百度UNIT给孩子做一个有界面的闲聊机器人
语音识别和语音合成,需要自行登录官网https://cloud.baidu.com/product/speech/asr尝试领取,语音识别和语音合成可以是同一token
以上介绍完毕,谢谢大家,现在介绍以下的
既然功能都已经实现了,我们为什么不能有一些疯狂的想法,想想我兄弟单身20多年,我想要用这个技术抚慰他寂寞的心灵
但是,百度UNIT智能对话对于不是他自己预设的功能,是要进行收费的,但是不要着急,我查看了一下他的文档介绍后,苦思冥想自己模拟一个,穷就是我无穷的思考源泉
现在让我们来建立一个流程对话的简单算法,思维导图是这样子的
# # # 在bilibili上再看一下我实现的效果,看中间部分就好 # # #
不仅是女朋友,只要是关乎流程的,都可以使用我的代码来制作一个语音机器人,比如餐厅点餐,预约挂号等
唤醒功能
# 唤醒
def wakeUp(lst_wakeupKeyword,lst_reply,lst_endKeyword,p):
while True:
# 录音
autoRecord(setvoice)
# 格式转换
wav2pcm()
# 语音识别
newMessage=speech2writting(speech2writtingToken)
p.put('吴彦祖:'+newMessage)
# # 识别是否结束词
endKeyword=False
for eachEndKeyword in lst_endKeyword:
lst_word=
count_word=0
for eachWord in lst_word:
if eachWord in newMessage:
count_word=count_word+1
if count_word == len(lst_word):
endKeyword=True
if endKeyword != False:
return 'bye'
# # 识别是否唤醒词
wakeupKeyword=False
for eachWakeupKeyword in lst_wakeupKeyword:
lst_word=
count_word=0
for eachWord in lst_word:
if eachWord in newMessage:
count_word=count_word+1
if count_word == len(lst_word):
wakeupKeyword=True
if wakeupKeyword != False:
# 随机选择回应内容
repMessage=random.choice(lst_reply)
p.put('女朋友:'+repMessage)
# 语音合成
writting2speech(repMessage,PER,writting2speechToken)
# 播放生成的音频
playRecord()
return 'wakeup'
唤醒词,回应内容,结束词,需要在“基本设置.xlsx”的表格进行设置
其中“唤醒词”一列,我设置了三组词,当识别到三组词的其中之一,就可以唤醒,在此列中“|”(竖线)代表逻辑“与”、“和”、“并且”
当唤醒后,在“回应内容”一列,会随机选择一句进行答复
当想结束对话,则说出“结束词”,在此列中“|”(竖线)代表逻辑“与”、“和”、“并且”
当时查看百度的文档介绍,是有唤醒词这个功能的,由于只能部署于Android/ios,那就自己写一个,虽然不像手机一样只识别手机主人的声音,但我已经很满足了
交流功能
# 交流
def exchange(select_workbook,lst_endKeyword,p):
# 加载数据位置
wb_exchange=openpyxl.load_workbook(select_workbook+'.xlsx')
ws_sure=wb_exchange['确定']
ws_prologue=wb_exchange['引导作用语句']
#播放开场白
lst_prologue=[]
for eachPrologue in ws_prologue['A']:
if str(eachPrologue.value) != '开场白':
lst_prologue.append(str(eachPrologue.value))
# 随机选择回应内容
repMessage=random.choice(lst_prologue)
p.put('女朋友:'+repMessage)
# 语音合成
writting2speech(repMessage,PER,writting2speechToken)
# 播放生成的音频
playRecord()
#设定循环的基本参数
classEqualIntention=False
while True:
# 录音
autoRecord(setvoice)
# 格式转换
wav2pcm()
# 语音识别
newMessage=speech2writting(speech2writtingToken)
p.put('吴彦祖:'+newMessage)
# # 识别是否结束词
endKeyword=False
for eachEndKeyword in lst_endKeyword:
lst_word=
count_word=0
for eachWord in lst_word:
if eachWord in newMessage:
count_word=count_word+1
if count_word == len(lst_word):
endKeyword=True
if endKeyword != False:
return 'bye'
# 第一步,在'确定'工作表查找符合的特征
intention=False
for eachFeature in ws_sure['D']:
if str(eachFeature.value) != '特征':
lst_featureKeyword=
for eachFeatureKeyword in lst_featureKeyword:
if eachFeatureKeyword in newMessage or str(ws_sure.cell(eachFeature.row,1).value) in newMessage:
# # 符合特征,查找符合的关键词
row_feature=eachFeature.row
for eachKeyword in ws_sure:
if eachKeyword.column >= 5:
lst_word=
count_word=0
for eachWord in lst_word:
if eachWord in newMessage:
count_word=count_word+1
# # # 符合关键词,返回意图
if count_word == len(lst_word):
# 随机选择回应内容
repMessage=random.choice()
p.put('女朋友:'+repMessage)
# 语音合成
writting2speech(repMessage,PER,writting2speechToken)
# 播放生成的音频
playRecord()
# 返回意图
intention=str(ws_sure.cell(row_feature,1).value)
return intention
# 第二步,可能符合特征,但一定不符合关键词的
if intention == False:
# # 获取'引导'工作表'分类'(key)和'意图'(value)的信息,存储成字典
dct_class={}
for eachClass in ws_sure['B']:
if str(eachClass.value) !='分类':
if str(eachClass.value) in dct_class.keys():
dct_class.append(str(ws_sure.cell(eachClass.row,1).value))
else:
dct_class=[]
dct_class.append(str(ws_sure.cell(eachClass.row,1).value))
# # # 引导到分类语句(相当于第二步的开场白)
lst_leadClass=[]
for eachLeadClass in ws_prologue['B']:
if str(eachLeadClass.value) != '引导到类语句' and str(eachLeadClass.value) != 'None':
lst_leadClass.append(str(eachLeadClass.value))
# 随机选择回应引导到类语句
str_class=''
for eachClass in dct_class.keys():
str_class=str_class+eachClass+','
repMessage=random.choice(lst_leadClass)
repMessage=re.sub('\\{format\\}',str_class[:-1],repMessage)
p.put('女朋友:'+repMessage)
#判断'分类'是否包含在'意图''
for eachClass in dct_class.keys():
if eachClass in dct_class:
classEqualIntention=True
else:
classEqualIntention=False
break
# 语音合成
writting2speech(repMessage,PER,writting2speechToken)
# 播放生成的音频
playRecord()
if classEqualIntention==False:
# 录音
autoRecord(setvoice)
# 格式转换
wav2pcm()
# 语音识别
newMessage=speech2writting(speech2writtingToken)
p.put('吴彦祖:'+newMessage)
# # 识别是否结束词
endKeyword=False
for eachEndKeyword in lst_endKeyword:
lst_word=
count_word=0
for eachWord in lst_word:
if eachWord in newMessage:
count_word=count_word+1
if count_word == len(lst_word):
endKeyword=True
if endKeyword != False:
return 'bye'
# 对比分类
for eachClass in dct_class.keys():
if eachClass in newMessage:
# # # 引导意图语句
lst_intention=[]
for eachLeadIntention in ws_prologue['C']:
if str(eachLeadIntention.value) != '引导到意图语句' and str(eachLeadIntention.value) != 'None':
lst_intention.append(str(eachLeadIntention.value))
str_intention=''
for eachIntetion in dct_class:
str_intention=str_intention+eachIntetion+','
repMessage=random.choice(lst_intention)
repMessage=re.sub('\\{format\\}',str_intention[:-1],repMessage)
p.put('女朋友:'+repMessage)
# 语音合成
writting2speech(repMessage,PER,writting2speechToken)
# 播放生成的音频
playRecord()
break
我们查看一下“交流功能”的表格设置,首先表格的命名其实就是”意图“
我给他设置了三个工作表,分别是“确定”,“引导作用语句”,“收集”,我一个一个介绍
先来看一下“确定”的工作表
此工作表确定女朋友技能的,1判断语句是否符合特征(竖线代表逻辑“或”),2符合特征则判断是否含有关键词(竖线代表逻辑“与“,”和“,”并且“)
如果符合1特征,且符合2关键词,则5回复(竖线隔开句子,随机抽取其中一句进行回复)
如果不符合1特征,或者不符合2关键词,则在”引导作用语句“工作表的作用下,先引导到3分类,再引导到4意图
再来看一下”引导作用语句“工作表
”开场白“是进入这个”意图“后回应的内容,会在这一列随机抽取一句话
”引导到类语句“和”引导到意图语句“刚sh刚已经介绍,其中里面的"{format}",分别是你有多少的”分类“和多少的”意图“,程序会自行替换
”收集“工作表是拿来存储不符合特征的句子的,当达到一定数量后,说明你该完善你流程对话
代码里面我没有添加这个功能,因为只有我一个人在玩这个没必要,有需要且懂py的可自行添加,处理表格的第三方库我用的是openpyxl
当”意图“确定后,可以把他当作参数再写一个exchange了,比如看以下的最后这几行代码
# 启动交流
def start_exchange(p):
# 加载数据位置
wb_base=openpyxl.load_workbook('基本设置.xlsx')
ws_wakeupAndEnd=wb_base['唤醒与结束']
# # 加载唤醒词
lst_wakeupKeyword=[]
for eachWakeupKeyword in ws_wakeupAndEnd['A']:
if str(eachWakeupKeyword.value) != '唤醒词':
lst_wakeupKeyword.append(str(eachWakeupKeyword.value))
# # 加载回应内容
lst_reply=[]
for eachReply in ws_wakeupAndEnd['B']:
if str(eachReply.value) != '回应内容':
lst_reply.append(str(eachReply.value))
# # 加载结束词
lst_endKeyword=[]
for eachEndKeyword in ws_wakeupAndEnd['C']:
if str(eachEndKeyword.value) != '结束词' and str(eachEndKeyword.value) != 'None':
lst_endKeyword.append(str(eachEndKeyword.value))
while True:
intention=wakeUp(lst_wakeupKeyword,lst_reply,lst_endKeyword,p)
if intention == 'bye':
continue
intention=exchange('女朋友',lst_endKeyword,p)
if intention == 'bye':
continue
intention=exchange(intention,lst_endKeyword,p)
if intention == 'bye':
continue
设置聊天框
# chatbox(聊天框)增加内容
def chatbox_insert(p):
top=Tk()
top.title('闲聊机器人-珠海横琴零橙教育制作')
top.geometry('800x600')
studentMessage_img1=ImageTk.PhotoImage(file='老婆.png')
label_img1=Label(top,image=studentMessage_img1)
label_img1.place(x=543,y=10)
studentMessage_img2=ImageTk.PhotoImage(file='吴彦祖.png')
label_img2=Label(top,image=studentMessage_img2)
label_img2.place(x=543,y=277)
sb=Scrollbar(top)
sb.pack(side=RIGHT,fill=Y)
chatBox=Text(top,width=75,height=40,yscrollcommand=sb.set)
chatBox.place(x=10,y=10)
sb.config(command=chatBox.yview)
def updateP():
if not p.empty():
chatBox.insert('end',p.get()+'\n')
top.after(1000,updateP)
chatBox.yview("moveto",1.0)
updateP()
top.mainloop()
有三个难点:
第一个是程序没结束,界面会处于无响应的状态,需要利用threading(多线程)或者multiprocess(多进程)启动程序,我用的是multiprocess(多进程)
第二个是多进程之间的信息共享,不能使用Manager,因为需要等待主进程运行完毕,发现利用Queue队列也能完成信息共享
第三个是更新聊天框信息,因为tkinter的界面在mainloop()后就进入死循环了,发现只要将插入的内容封装成函数,利用after()更新就好
多进程启动
# 多进程
def multi_start():
p=Queue(0)
a=Process(target=start_exchange,args=(p,))
a.start()
b=Process(target=chatbox_insert,args=(p,))
b.start()
确保打包成exe后能正常使用
if __name__ == '__main__':
#确保打包exe后正常使用
multiprocessing.freeze_support()
multi_start()
# 最后,下载附件的朋友们,提醒一下
# 注意代码的263、264、265行,我已经注释了是什么token了,一定要申请好,没有正确的token代码可能报错
# 如果想简单的进行一轮对话,取消273-291行的代码注释,同时注释593-597行的代码,同时将postMessage函数里的service_id改为自己的机器人ID
# 查看效果我上面有BILIBILI的超链接
以上又介绍完毕,谢谢大家,没有以下了 帮忙看一下这个是什么错误造成的,谢谢
D:\Software\Python\python.exe D:/Py_Code/百度对话/闲聊机器人_语音聊天.py
2022-08-15 09:22:36 开始录音
2022-08-15 09:22:41 结束录音
2022-08-15 09:22:41 开始转换格式
'ffmpeg' �����ڲ����ⲿ���Ҳ���ǿ����еij���
���������ļ���
2022-08-15 09:22:41 结束转换格式
2022-08-15 09:22:41 开始语音识别
2022-08-15 09:22:42 结束语音识别
2022-08-15 09:22:42 识别结果: 女朋友在不在?那么回应内容。
2022-08-15 09:22:42 开始语音合成
Process Process-1:
Traceback (most recent call last):
File "D:\Software\Python\lib\multiprocessing\process.py", line 297, in _bootstrap
self.run()
File "D:\Software\Python\lib\multiprocessing\process.py", line 99, in run
self._target(*self._args, **self._kwargs)
File "D:\Py_Code\百度对话\闲聊机器人_语音聊天.py", line 545, in start_exchange
intention=wakeUp(lst_wakeupKeyword,lst_reply,lst_endKeyword,p)
File "D:\Py_Code\百度对话\闲聊机器人_语音聊天.py", line 340, in wakeUp
playRecord()
File "D:\Py_Code\百度对话\闲聊机器人_语音聊天.py", line 101, in playRecord
wf = wave.open('reponse.wav', 'rb') # 读取音频文件数据
File "D:\Software\Python\lib\wave.py", line 510, in open
return Wave_read(f)
File "D:\Software\Python\lib\wave.py", line 164, in __init__
self.initfp(f)
File "D:\Software\Python\lib\wave.py", line 131, in initfp
raise Error('file does not start with RIFF id')
wave.Error: file does not start with RIFF id
2022-08-15 09:22:42 结束语音合成
xlinux 发表于 2022-8-15 09:24
帮忙看一下这个是什么错误造成的,谢谢
D:\Software\Python\python.exe D:/Py_Code/百度对话/闲聊机器人 ...
尝试删除旧的.wav文件 太牛了,佩服佩服! 谢谢大神分享,不错 大佬真牛,不过还是成品适合我{:1_926:} 感谢分享! 学习学习 感谢楼主分享 谢谢分享楼主威武 厉害了,大佬 专业的聊天机器人