【项目实践】智能聊天机器人

实现功能

我实现的AI管理工具功能有执行系统命令和智能聊天。程序启动后,开始录音。录音成功后使用百度语音识别平台进行识别,判断是命令还是普通对话。如果是命令,进入系统命令执行模块,执行完毕,看到执行结果,一次交互完成。如果是对话信息,推送到图灵机器人,图灵机器人会进行智能对话,得到图灵机器人响应的文本,调用百度语音合成,在本地合成语音,然后程序启动播放器,播放语音信息,完成对话信息。支持语音退出。

准备工作

1.由于开发环境的需求,gcc的版本至少要在6.x以上,需要准备高版本的gcc

# sudo yum install centos-release-scl
# sudo yum install devtoolset-8
# scl enable devtoolset-8 bash

2.图灵机器人

图灵机器人能进行智能对话。接入图灵机器人,依赖的是http协议,请求和相应的数据都是json格式。http本次不写,直接使用第三方库httplib。json进行数据序列号和反序列化功能需要我们完成,选择一个开源的C++库:jsoncpp。

3.百度语音识别

百度语音识别可以识别、合成语音。使用百度语音识别,首先,在官方网站下载C++ SDK压缩包,将下载的aip-cpp-sdk-version.zip解压, 其中文件为包含实现代码的头文件。然后,安装依赖库libcurl(需要支持https) openssl jsoncpp(>1.6.2版本,0.x版本将不被支持,本次使用的是1.8.3版本)。编译工程时添加 C++ 11 支持 (gcc/clang 添加编译参数 -std=c++11), 添加第三方库链接参数 lcurl, lcrypto, ljsoncpp。最后,在源码中include speech.h,引入压缩包中的头文件以使用aip命名空间下的类和方法。

4.准备Centos 7录音和播放工具

采用arecord进行语音录制,使用vlc/cvlc进行播放。
arecord是系统默认自带的,直接使用就好。vlc/cvlc播放器需要安装。

什么是json

json是一种传递对象的语法,对象可以是name/value对,数组和其他对象。

语法规则:
json 的语法规则十分简单:
数组(Array)用方括号(“[]”)表示。
对象(Object)用大括号(”{}”)表示。
名称/值对(name/value)组合成数组和对象。
名称(name)置于双引号中,值(value)有字符串、数值、布尔值、null、对象和数组。
并列的数据之间用逗号(“,”)分隔
输入参数

{
	"reqType":0,
    "perception": {
        "inputText": {
            "text": "你好吗"
        },
    "userInfo": {
        "apiKey": "",
        "userId": ""
    }
}

项目模块

主要分四个大的模块:工具类、图灵机器人交互式类、访问百度语音识别类、完成核心逻辑类。

  • 工具类:
class Util{ //tool
	private:
		static pthread_t id;
	public: 
	   //在Linux中执行指定命令,采用popen。
		static bool Exec(std::string command, bool is_print)
		{
			if(!is_print){
				command += ">/dev/null 2>&1";
			}
			FILE *fp = popen(command.c_str(), "r");
			if(nullptr == fp){
				std::cerr << "popen exec \'" << command << "\' Error" << std::endl;
				return false;
			}
			if(is_print){
				char ch;
				while(fread(&ch, 1, 1, fp) > 0){
					fwrite(&ch, 1, 1, stdout);
				}
			}
			pclose(fp);
			return true;
		}
		//编写进度条,监测录音进度
		static void* ThreadRun(void* arg)
		{
			const char* tips = (char*)arg;
			int i = 0;
			char bar[103] = {0};
			const char* label = "|/-\\";
			for(; i <= 50; i++){
				printf("%s[%-51s][%d%%][%c]\r",tips, bar, i*2, label[i%4]);
				fflush(stdout);
				bar[i] = '=';
				bar[i+1] = '>';
				usleep(49000*2);
			}
			printf("\n");
		}
		static void PrintStart(std::string tips)
		{
			pthread_create(&id, NULL, ThreadRun, (void*)tips.c_str());
		}
		static void PrintEnd()
		{
			pthread_cancel(id);
		}
};
pthread_t Util::id;
  • 图灵机器人交互式类

采用图灵机器人,进行智能对话,机器人最重要的功能:message转成json、请求图灵、拿回请求的消息

class Robot{
	private:
		std::string url;
		std::string api_key;
		std::string user_id;
		//使用百度语音识别speech自带的Http Client发起相关请求
		aip::HttpClient client;

	private:
		bool IsCodeLegal(int code)
		{
			bool result = false;
			switch(code){
				case 5000:
				case 6000:
				case 4000:
				case 4001:
				case 4002:
				case 4003:
				case 4005:
				case 4007:
				case 4100:
				case 4200:
				case 4300:
				case 4400:
				case 4500:
				case 4600:
				case 4602:
				case 7002:
				case 8008:
				case 0:
					break;
				case 10004:
					result = true;
					break;
				default:
					result = true;
					break;
			}
			return result;
		}
		//序列化
		std::string MessageToJson(std::string &message)
		{
			Json::Value root;
			Json::StreamWriterBuilder wb;
			std::ostringstream ss;

			Json::Value item_;
			item_["text"] = message;

			Json::Value item;
			item["inputText"] = item_;

			root["repType"] = 0;
			root["perception"] = item; 

			item.clear();
			item["apiKey"] = api_key;
			item["userId"] = user_id;
			root["userInfo"] = item;

			std::unique_ptr sw(wb.newStreamWriter());
			sw->write(root, &ss);
			std::string json_string = ss.str();
		//	std::cout << "debug: " << json_string << std::endl;
			return json_string;
		}
		//请求图灵
		std::string RequestTL(std::string &request_json)
		{
			std::string response;
			int status_code = this->client.post(url, nullptr, request_json, nullptr, &response);
			if(status_code != CURLcode::CURLE_OK){
				std::cerr << "post error" << std::endl;
				return "";
			}
			return response;
		}
		//反序列化
		std::string JsonToEchoMessage(std::string &str)
		{
		//	std::cout << "JsonToEchoMessage:"< const cr(rb.newCharReader());
			
			bool res = cr->parse(str.data(), str.data()+str.size(), &root, &errs);
			if(!res || !errs.empty()){
				std::cerr << "parse error!" << std::endl;
				return "";
			}
			int code = root["intent"]["code"].asInt();
			if(!IsCodeLegal(code)){
				std::cerr << "reponse code error" << std::endl;
				return "";
			}
			Json::Value item = root["result"][0];
			std::string msg = item["values"]["text"].asString();
			return msg;
		}
	public:
		Robot(std::string id = "1")
		{
			this->url = "http://openapi.tuling123.com/openapi/api/v2";
			this->api_key = "4c7e0cfef1c74e41969b8476bf6fab7c";
			this->user_id = id;
		}
		std::string Talk(std::string message)
		{
			std::string json = MessageToJson(message);
			std::string response = RequestTL(json);
			std::string echo_message = JsonToEchoMessage(response);
			return echo_message;
		}
		~Robot()
		{

		}
};

访问百度语音识别类

class SpeechRec{
	private:
		std::string app_id;
		std::string api_key;
		std::string secret_key;
		aip::Speech *client;
	private:
		 bool IsCodeLegal(int code)
		 {
			 bool result = false;
			 switch(code){
				 case 0:
					result = true;
					break; 
				 default:
					break;
   			 }       
  			 return result;
   		 }   
	public:
		SpeechRec()
		{
			app_id = "16945671";
			api_key = "D3uRSK2xh7Ur5de5F4DLvRb7";
			secret_key = "wAiZYUnbnLmgs2GbGXSdGLx7yIDpuzlx";
			client = new aip::Speech(app_id, api_key, secret_key);
		}
		
		//语音识别Automatic Speech Recognition
	    bool ASR(std::string path, std::string &out)
		{
			std::map options;
			options["dev_pid"] = "1536";
			std::string file_content;
			aip::get_file_content(ASR_PATH, &file_content);
            Json::Value result = client->recognize(file_content, "wav", 16000, options);
		//	std::cout <<"debug: "<< result.toStyledString() << std::endl;
			int code = result["err_no"].asInt();
			if(!IsCodeLegal(code)){
				std::cout << "Recognize error" << std::endl;
				return false;
			}
			out = result["result"][0].asString();
			return true;
		}
		
		//语音合成Text To Speech
		bool TTS(std::string message)
		{
			bool ret;
			std::ofstream ofile;
			std::string ret_file;
			std::map options;
			options["spd"] = "5";
			options["pit"] = "5";
			options["vol"] = "5";
			options["per"] = "5";
			options["aue"] = "6";

			ofile.open(TTS_PATH, std::ios::out | std::ios::binary);
			//语音合成,将文本转成语音,放到指定目录,形成指定文件
			Json::Value result = client->text2audio(message, options, ret_file);
			if(!ret_file.empty()){
				ofile << ret_file;
				ret = true;
			}
			else{	
				std::cerr << result.toStyledString() << std::endl;
				ret = false;
			}
			ofile.close();
			return ret;
		}	
		~SpeechRec()
        {

        }
};

完成核心逻辑类

class Jarvis{
	private:
		Robot rt;
		SpeechRec sr;
		std::unordered_map commands;
	private:
		bool Record()
		{
			Util::PrintStart("录音中: ");
			//std::cout << "debug: " << "Record..." << std::endl;
			std::string command = "arecord -t wav -c 1 -r 16000 -d 5 -f S16_LE ";
			command += ASR_PATH;
			bool ret = Util::Exec(command, false);
			//std::cout << "debug: Record... done" << std::endl;
			return ret;
		}
		bool play()
		{
			std::string command = "cvlc --play-and-exit ";
			command += TTS_PATH;
			return Util::Exec(command, false);
		}
	public:
		Jarvis()
		{
		}
		//加载命令执行配置文件
		bool LoadEtc()
		{
			std::ifstream in(CMD_ETC);
			if(!in.is_open()){
				std::cerr << "open error" << std::endl;
				return false;
			}

			std::string sep = ":";
			char line[256];
			while(in.getline(line, sizeof(line))){
				std::string str = line;
				std::size_t pos = str.find(sep);
				if(std::string::npos == pos){
					std::cerr << "not find :" << std::endl;
					continue;
				}
				std::string k = str.substr(0, pos);
				std::string v = str.substr(pos+sep.size());
				k += "。";

				commands.insert(std::make_pair(k,v));
			}
			std::cerr << "Load command etc done ... success" << std::endl;
			in.close();
			return true;
		}
		
		//判定消息是否是需要执行的命令,如果是命令,需要执行它,而不需要交给图灵机器人进行对话
		bool IsCommand(std::string message, std::string &cmd)
		{
			auto iter = commands.find(message);
			if(iter == commands.end()){
				return false;
			}
			cmd = iter->second;
		}
		void Run()
		{
#ifdef _LOG_
			int fd = open(LOG, O_WRONLY|O_CREAT, 0644);
			dup2(fd, 2);
#endif

			volatile bool quit = false;
			while(!quit){
				if(Record()){
					std::string message;
					if(sr.ASR(ASR_PATH, message)){
						//1.command
						std::string cmd="";
						if(IsCommand(message, cmd)){
							std::cout << "[Jarvis@localhost]$ " << cmd << std::endl;
							Util::Exec(cmd, true);
							continue;
						}
						//2.tuling
						std::cout << "我# " << message << std::endl;
						if(message == "你走吧。"){
							std::string quit_message = "那我走了!";
							std::cout << "Jarvis# " << quit_message << std::endl;
							if(sr.TTS(quit_message)){
								play();
							}
							exit(0);
						}
						std::string echo = rt.Talk(message);
						std::cout << "Jarvis# " << echo << std::endl;
						if(sr.TTS(echo)){
							play();
						}
					}
				}
				else{
					std::cerr << "Record error..." << std::endl;
				}
			}
#ifdef _LOG_
			close(fd);
#endif
		}
		~Jarvis()
		{

		}

调用逻辑:

#include "Jarvis.hpp"
using namespace std;

int main()
{
    Jarvis *js = new Jarvis();
	if(!js->LoadEtc()){
		return 1;
	}
	js->Run();
	return 0;
}

makefile文件

CC=g++
bin=Jarvis
src=Jarvis.cc
INCLUDE=-Ispeech
LIB=-ljsoncpp -lcurl -lcrypto -lpthread
FLAGS=-D_LOG_

$(bin):$(src)
	$(CC) -o $@ $^ $(INCLUDE) $(LIB)
.PHONY:clean
clean:
rm -f $(bin)

你可能感兴趣的:(项目)