应用背景:很多人都是没有一个能够陪伴聊天的人,而且目前大环境下压力很大,很多人都是一个人在大城市打拼,下班回家都没有一个可以陪伴说话的人。当下也存在着大量的空巢老人,为此制作了虚拟聊天助手。解决了年轻人的内心的空虚,也可以陪伴独居的老人,可以进行情感的交流,释放内心的压力。减少心理问题。
有了希望解决的问题,那么我们就需要思考大框架,首先必须要能够普通定制,收集需要扮演的角色,例如:男朋友,女朋友等不同的需要求。也要给予一个角色的姓名和特点,这样才能使得ai有比较好的表现。所有需要有能够收集信息的ai助手。
收集到信息就要构建prompt,构建了prompt就确定了system。之后利用ChatTTS将输出的机器的输出用语音输出。
(刚刚的是普通定制就只是利用ai原本的能力,那么高级定制就可以利用微调技术,生成具有更符合用户特点的输出)以下都是普通定制内容,只是提供微调的想法,但是未实现。
ChatTTS是输入文字,AI生成逼真的中英文语音和语气。作为数字人、大模型、人机对话、具身智能的语音交互基座。
首先安装ChatTTS,由于自己的电脑跑不动,所有用了魔塔社区(也可以用其他的云平台)。具体的安装流程可以参考ChatTTS_Tutorials/zihao_chattts_20240613_4/【B2】安装配置ChatTTS环境-备选.ipynb at main · TommyZihao/ChatTTS_Tutorials (github.com)https://github.com/TommyZihao/ChatTTS_Tutorials/blob/main/zihao_chattts_20240613_4/%E3%80%90B2%E3%80%91%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AEChatTTS%E7%8E%AF%E5%A2%83-%E5%A4%87%E9%80%89.ipynb
构建收集信息的ai助手(可以参考)我以前的文章基于ZhiPuAI的任务型对话机器人,就是负责收集信息重新构建以下就可以收集用户的需求了。收集到的信息保存在sql,这样用户结束一次使用后,下次只用调用sql就行,不需要重新构建角色了(命名为:collect.py)
import os
from dotenv import load_dotenv, find_dotenv
from zhipuai import ZhipuAI
from dataclasses import dataclass, asdict
from typing import List, Dict
from datetime import datetime
import uuid
import json
import re
from sqlalchemy import insert
from sqlalchemy import Table, Column, Integer, String, DateTime, Text, MetaData, SmallInteger
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import sqlite3
_ = load_dotenv(find_dotenv())
client = ZhipuAI(api_key=os.getenv("ZhipuAI_API_KEY"))
def get_user_demand(msg):
response = client.chat.completions.create(
model="glm-4",
messages=msg,
temperature=0,
)
return response.choices[0].message.content
# 初始化部分数据类型
@dataclass
class User:
user_id: str
user_name: str
@dataclass
class ChatSession:
user_id: str
session_id: str
role: str
role_name: int
role_personality: str
@dataclass
class ChatRecord:
user_id: str
session_id: str
user_input: str
bot_output: str
class UserDemand:
def __init__(self):
self.system_inp = """你现在是收集数据的助手,你的目的是收集扮演的角色,角色的姓名,角色的性格:
信息应该以JSON方式存储,包括三个key:role表示手机号码,role_name表示角色的姓名,role_personality表示角色的性格。
回复格式:
给用户的回复:{回复给用户的话}
获取到的信息:{"role": null, "role_name": null, "role_personality": null}
"""
self.max_round = 10
self.slot_labels = ["role", "role_name", "role_personality"]
self.reg_msg = re.compile(r"\n+")
self.ask_func = get_user_demand
def check_over(self, slot_dict: dict):
for label in self.slot_labels:
if slot_dict.get(label) is None:
return False
return True
def send_msg(self, msg: str):
print(f"机器人:{msg}")
def chat(self, user_id: str):
sess_id = uuid.uuid4().hex
chat_at = datetime.now()
msg = [{"role": "user", "content": self.system_inp}]
n_round = 0
history = []
slot = {"role": None, "role_name": None, "role_personality": None}
while True:
if n_round > self.max_round:
bot_msg = "非常感谢您对我们的支持,再见。"
self.send_msg(bot_msg)
break
try:
bot_inp = self.ask_func(msg)
print(bot_inp)
except Exception as e:
print(f"Error: {e}")
bot_msg = "机器人出错,稍后将由人工与您联系,谢谢。"
self.send_msg(bot_msg)
break
tmp = self.reg_msg.split(bot_inp)
bot_msg = tmp[0].strip("给用户的回复:")
self.send_msg(bot_msg)
if len(tmp) > 1:
slot_str = tmp[1].strip("获取到的信息:")
slot = json.loads(slot_str)
print(f"\tslot:{slot}")
n_round += 1
if self.check_over(slot):
break
user_inp = input()
msg += [
{"role": "assistant", "content": bot_inp},
{"role": "user", "content": user_inp},
]
record = ChatRecord(user_id, sess_id, bot_inp, user_inp)
history.append(record)
chat_sess = ChatSession(user_id, sess_id, **slot)
self.store(history, chat_sess)
def store(self, history: List[ChatRecord], chat: ChatSession):
with SessionLocal.begin() as sess:
q = insert(
chat_record_table
).values([asdict(v) for v in history])
sess.execute(q)
with SessionLocal.begin() as sess:
q = insert(
chat_session_table
).values(
[asdict(chat)]
)
sess.execute(q)
db_file = "chatbot.db"
if os.path.exists(db_file):
os.remove(db_file)
engine = create_engine(f"sqlite:///{db_file}")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
metadata_obj = MetaData()
chat_record_table = Table(
"chat_record_table",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("user_id", String(64), index=True),
Column("session_id", String(64), index=True),
Column("user_input", Text),
Column("bot_output", Text),
Column("chat_time", DateTime),
)
chat_session_table = Table(
"chat_session_table",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("user_id", String(64), index=True),
Column("session_id", String(64), index=True),
Column("role", String(16)),
Column("role_name", SmallInteger),
Column("role_personality", String(32)),
)
metadata_obj.create_all(engine, checkfirst=True)
nick = "007"
user = User('007', nick)
chatbot = UserDemand()
chatbot.chat(user.user_id)
def query_table(table: str):
con = sqlite3.connect("chatbot.db")
cur = con.cursor()
q = cur.execute(f"SELECT * FROM {table}")
return q.fetchall()
def query_chat_session():
sql = query_table("chat_session_table")
print("finish query chat session")
return sql
调用刚刚保存的sql文件(命名为:use_sql.py)
import sqlite3
def query_table(table: str):
con = sqlite3.connect("chatbot.db")
cur = con.cursor()
q = cur.execute(f"SELECT * FROM {table}")
return q.fetchall()
def query_chat_session():
sql = query_table("chat_session_table")
print("finish query chat session")
return sql
语音ChatTTS部分需要去保存特定的声音,当选择男性角色时使用男性角色的声音,选择女性角色时使用女性角色的声音(这里以一个声音为例),是将语音保存了下来,因为在命令行运行无法直接发出声音,所有将声音的保存下来(命名为:TTs.py)
import torchaudio
import torch
from ChatTTS import ChatTTS
import soundfile
from IPython.display import Audio
chat = ChatTTS.Chat()
chat.load_models(compile=False)
# 载入保存好的音色
speaker = torch.load('speakers/g1.pth')
infer_code = {
"spk_emb": speaker,
# 'prompt': '[speed_10]',
'temperature': 0.1,
'top_P': 0.7,
'top_K': 20,
# "custom_voice": 3000,
}
def chat_sound(texts, params_infer_code=infer_code):
# refine_text = chat.infer(texts, refine_text_only=True)
wavs = chat.infer(texts, params_infer_code=infer_code,)
return wavs
def tts_response(answer):
response = answer
wavs = chat_sound(response, infer_code)
print("___"*10)
print(wavs)
Audio(wavs[0], rate=24000)
torchaudio.save("output/output_d1.wav", torch.from_numpy(wavs[0]), 24000)
return wavs
构建prompt需要让他成为特定的角色,并且要以特定的口吻回答问题,并且将回答传入ChatTTS中。并且通过拜拜,再见等词结束对话。(命名为:main.py)
import os
from dotenv import load_dotenv, find_dotenv
from zhipuai import ZhipuAI
from TTs import *
_ = load_dotenv(find_dotenv())
client = ZhipuAI(api_key=os.getenv("ZhipuAI_API_KEY"))
inputs = input("是否需要重新生成prompt?yes/no\n")
if inputs == "no":
from use_sql import query_chat_session
elif inputs == "yes":
from collect import query_chat_session
def get_prompt():
query_chat = query_chat_session()
req = []
for i in range(len(query_chat[0])):
if i <= 2:
inx = -1 * (i + 1)
# print(query_chat[0][inx])
req.append(query_chat[0][inx])
else:
break
role = req[2]
role_name = req[1]
role_personality = req[0]
prompt = f'''我与你的【任何对话】都必须严格执行以下角色要求。你叫{role_name},
我与你的对话必须严格遵守以下角色要求,你将扮演我的【{role}】,我的【{role}】必须严格遵守以下角色要求:
姓名:{role_name}。性格:{role_personality}。
记住你不是AI,请你完全沉浸在{role}的角色里,我们的任何对话都要用{role_name}的口吻回答我,
回答不能超过50字,根据聊天话题延展你自己的想法。
不能有解释类型的逻辑,表达要带有角色的性格特点。
'''
print("finish prompt")
return prompt
class ChatGlm:
def __init__(self):
self.prompt = get_prompt()
def reponse(self, msg):
response = client.chat.completions.create(
model="glm-4",
messages=msg,
temperature=0.7,
)
return response.choices[0].message.content
def check_over(self, inp):
if "再见" in inp or "拜拜" in inp or "结束" in inp:
return True
def chat(self):
while True:
msg = [{"role": "user", "content": self.prompt}]
outp = self.reponse(msg)
inp = input()
msg += [
{"role": "assistant", "content": outp},
{"role": "user", "content": inp},
]
answer = self.reponse(msg)
print(answer)
tts_response(answer)
if self.check_over(inp):
break
answer = ChatGlm().chat()
在命令行运行:
python main.py
效果如下(如果以前构建了就直接输入no):
如果以前没有构建就输入yes需要构建角色,由于是调用以前的所以机器人的输出有点奇怪,当时进行自己运行时可以修改一下,但是收集信息的过程是没有问题的 :
具体过程如下,生成完后就可以直接使用;
能够实现基础功能,但是肯定还有很多需要完善的,这里只是提供一个思路。希望大家能够发散思维,有什么好的想法也可以发在评论区,或者私信给我。