设计智能客服机器人(华为云在线智能问答接口)

1. 前言

问答机器人现在很多场合都有使用,比如:网页智能客服、微信公众号智能回复、淘宝的售后客服,QQ聊天机器人等等。有了这些客户机器人就能帮我们回答很多预置的一些问题,帮助用户解决常见问题,还可以进行自主训练,得到一个适合自己使用的机器人。机器人也可以关联很多其他的技能,玩小游戏,查询天气、查询节假日、查询很多其他的信息,非常方便。

这篇文章就采用华为云提供的智能问答机器人设计一个小软件,采用华为云提供的API接口完成数据交互,与机器人进行问答交互,通过这个例子可以了解到智能问答机器人的基本功能、使用场景、使用方法等等。

华为云的智能问答机器人特点介绍

提供问答引擎、机器人管理平台来方便客户快速、低成本构建智能问答服务。智能问答能满足用户快速上线、高度定制、数据可控的需求,具有问答准确率高、自主学习等特点。能够帮助企业节省客服人力,大大降低客服响应时间。

具备如下优势点:

  • 智能的问答管理

    • 热点问题、趋势、知识自动分析统计。
    • 支持未知问题自动聚类,匹配相似问答,辅助人工不断扩充知识库。
    • 支持问答调测,点对点的监测智能应答过程。
    • 支持领域知识挖掘,提供易用的标注工具挖掘领域词。
  • 全面的对话管理

    • 支持自然语言多能力融合,智能对话中控。
    • 灵活的知识库管理,支持对知识的批量操作。
    • 支持嵌入多轮对话技能,满足复杂的任务型对话场景。
  • 高效训练部署

    • 基于modelarts的底层算法能力,提供更快的模型训练、部署能力。
    • 支持多算法模型效果验证,验证不同数据、参数、模型对问法效果的影响。
    • 支持模型最优参数组合推荐,保证问答效果。

2. 使用问答机器人服务

2.1 开通服务

地址: www.huaweicloud.com/product/cbs...

点击立即使用会进入到购买页面,可以免费体验14天,对于技术评估,场景测试已经足够。

2.2 配置机器人

(1)机器人购买之后,点击进入管理页面,对机器人的属性、技能进行配置,训练。

(2)可以添加预置的技能,还可以添加自定义技能

预置的技能有查询天气、成语接龙、查星座、查节日、猜数字游戏等等。也可以自己自定义技能标注训练发布。

2.3 对话体验

在管理页面右上角可以在线体验与机器人对话,可以快速调试问答效果。

2.4 接口调试

地址: support.huaweicloud.com/api-cbs/cbs...

在调用API测试之前,可以先使用在线调试接口测试,了解请求如何发出,有哪些必填参数,请求参数怎么填,返回的结果格式是怎样的。

2.5 API请求总结

(1)请求的URL格式

cpp 复制代码
 请求的URL格式: POST /v1/{project_id}/qabots/{qabot_id}/chat
 其中参数介绍: 
 project_id  是项目ID。
 qabot_id 是机器人标识符,qabot编号,UUID格式。如:303a0a00-c88a-43e3-aa2f-d5b8b9832b02。
 登录对话机器人服务控制台,在智能问答机器人列表中就可以查看到abot_id。
     
 最终拼接的URL格式: https://cbs-ext.cn-north-4.myhuaweicloud.com/v1/0e5957be8a00f53c2fa7c0045e4d8fbf/qabots/5c889415-6834-4ada-aa51-ea5000941e25/chat    

(2)请求头与请求参数总结

cpp 复制代码
 请求头:  
 "X-Auth-Token": "------------",  这是API接口鉴权用的,所有的API请求都要这个参数
 "Content-Type": "application/json"
  
 请求体: 
 {
  "question": "北京天气"  这是给机器人提交的问题,随后机器人会返回答案
 }
 ​
 响应结果:
 {
  "request_id": "e3ab440c-0bb2-455b-aff8-07e4cc4115f4",
  "reply_type": 1,
  "taskbot_answers": {
   "answer": "当前北京天气晴,最高8摄氏度,最低-5摄氏度,日间南风≤3级,夜间南风≤3级。",
   "skill_id": "22a20348-aa8b-44d2-96df-dcae1b8d92c2",
   "skill_responses": [
    {
     "frame": {
      "intention": "weather_query",
      "confidence": 1,
      "reply": "当前北京天气晴,最高8摄氏度,最低-5摄氏度,日间南风≤3级,夜间南风≤3级。",
      "intention_alias": "查天气",
      "candidate_words": [],
      "task_complete": true,
      "flow_complete": true,
      "current_slots": [
       {
        "slot_id": "a9ee29df-8f60-4ff1-863e-60e9412a1f95",
        "slot_name": "地点",
        "slot_identification": "loc",
        "slot_values": [
         {
          "word": "北京",
          "norm_word": "北京",
          "begin_position": 0,
          "end_position": 1
         }
        ]
       }
      ],
      "history_slots": []
     },
     "candidate": {
      "candidate_confidence": 0
     },
     "skill_id": "22a20348-aa8b-44d2-96df-dcae1b8d92c2",
     "skill_version": "v50",
     "locked": false,
     "related_intentions": [
      {
       "intention": "weather_query",
       "confidence": 1
      }
     ]
    },
    {
     "frame": {
      "confidence": 0,
      "reply": "你太难理解了,我需要一些信息才能知道呢,哼!",
      "candidate_words": [],
      "task_complete": true,
      "flow_complete": true,
      "current_slots": [],
      "history_slots": []
     },
     "candidate": {
      "candidate_confidence": 0
     },
     "skill_id": "8b71d740-aedb-4c01-8948-460dab64fd22",
     "skill_version": "v67",
     "locked": false,
     "related_intentions": [
      {
       "intention": "constellation",
       "confidence": 0.513
      }
     ]
    },
    {
     "frame": {
      "confidence": 0,
      "reply": "对不起,我没明白,请再多教我一些吧",
      "candidate_words": [],
      "task_complete": true,
      "flow_complete": true,
      "current_slots": [],
      "history_slots": []
     },
     "candidate": {
      "candidate_intention": "chengyu",
      "candidate_confidence": 0.507154
     },
     "skill_id": "9d2aa6d4-8461-4ca7-9db8-af32fdbfde57",
     "skill_version": "v12",
     "locked": true,
     "related_intentions": [
      {
       "intention": "chengyu",
       "confidence": 0.507
      }
     ]
    },
    {
     "frame": {
      "confidence": 0,
      "reply": "对不起,我没明白,请再多教我一些吧",
      "candidate_words": [],
      "task_complete": true,
      "flow_complete": true,
      "current_slots": [],
      "history_slots": []
     },
     "candidate": {
      "candidate_confidence": 0
     },
     "skill_id": "4a93acd4-5a29-4188-b033-9fffd932e5df",
     "skill_version": "v31",
     "locked": true,
     "related_intentions": [
      {
       "intention": "sys.other",
       "confidence": 0.555
      }
     ]
    },
    {
     "frame": {
      "confidence": 0,
      "reply": "对不起,我没明白,请再多教我一些吧",
      "candidate_words": [],
      "task_complete": true,
      "flow_complete": true,
      "current_slots": [],
      "history_slots": []
     },
     "candidate": {
      "candidate_confidence": 0
     },
     "skill_id": "25ad99ee-8a13-40a2-8fa1-0a18370e2ef5",
     "skill_version": "v34",
     "locked": false,
     "related_intentions": [
      {
       "intention": "sys.other",
       "confidence": 0
      }
     ]
    }
   ]
  },
  "session_id": "4b105ca2-28e2-4ec8-bd4b-87c8d7c6a322"
 }

请求头里的X-Auth-Token字段在之前的文章已经介绍过,获取方法看这里: bbs.huaweicloud.com/blogs/31775... 翻到2.3小节。

(3)请求参数介绍

详细的参数可以看官方文档介绍: support.huaweicloud.com/api-cbs/cbs...

bash 复制代码
 请求参数里一般主要填下面两个字段:
 ​
 question 这是必填的参数,填用户的问题。如:查天气。长度为1~512。
 ​
 session_id 填会话标识符,UUID格式。如:c04e6f7b-61d7-4a2d-a0c8-f9ecd2f62359。
 每次对话开启,机器人创建会话id,下次请求中传入该id表示继续该轮对话,每轮会话有效时间为2分钟。
 若传入的会话id已过期或者为空,则机器人会重新创建新的会话id(重新创建会话id会消耗一定时间)。
 比如: 玩成语接龙游戏,就需要填会话标识ID,这样才可以接着上一次的对话继续问答。

(4)响应参数介绍

cpp 复制代码
 reply_type 表示当前回答的类型
             0 知识库回复。
             1 技能回复。
             2 闲聊回复。
             3 图谱回复。
             4 文档回复。
             5 表格回复。
 ​
 session_id  这是当前的会话id,每次对话开启,机器人创建会话id,下次请求中传入该id表示继续该对话,每轮会话有效时间为2分钟。
 ​
 以技能回复为例:  
 "taskbot_answers": {
 "answer": "当前北京天气晴,最高8摄氏度,最低-5摄氏度,日间南风≤3级,夜间南风≤3级。",
 }

3. 实现效果与案例代码

3.1 实现效果

(1)成语接龙

(2)天气查询

(3)查星座

(4)猜数字游戏

3.2 核心代码

cpp 复制代码
 #include "widget.h"
 #include "ui_widget.h"
 ​
 Widget::Widget(QWidget *parent)
     : QWidget(parent)
     , ui(new Ui::Widget)
 {
     ui->setupUi(this);
 ​
     this->setWindowTitle("智能问答机器人");
 ​
 ​
     //读取之前保存的token数据
     QString data_token=ReadDataFile();
     if(!data_token.isEmpty())
     {
         Token=data_token.toUtf8();
         qDebug()<<"读取到之前的数据:"<<Token;
     }
 ​
     /*网络请求设置*/
     manager = new QNetworkAccessManager(this);
     connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
 ​
     //机器人
     ui->listWidget->addItem(new QListWidgetItem(QIcon(QObject::tr(":/res/2.ico")), QObject::tr("您好,很高兴为你服务.")));
 ​
 }
 ​
 ​
 Widget::~Widget()
 {
     delete ui;
 }
 ​
 /*
 功能: 保存数据到文件
 */
 void Widget::SaveDataToFile(QString text)
 {
     /*保存数据到文件,方便下次加载*/
     QString file;
     file=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
     QFile filesrc(file);
     filesrc.open(QIODevice::WriteOnly);
     QDataStream out(&filesrc);
     out << text;  //序列化写字符串
     filesrc.flush();
     filesrc.close();
 }
 ​
 /*
 功能: 从文件读取数据
 */
 QString Widget::ReadDataFile(void)
 {
     //读取配置文件
     QString text,data;
     text=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
 ​
     //判断文件是否存在
     if(QFile::exists(text))
     {
         QFile filenew(text);
         filenew.open(QIODevice::ReadOnly);
         QDataStream in(&filenew); // 从文件读取序列化数据
         in >> data; //提取写入的数据
         filenew.close();
     }
     return data; //返回值读取的值
 }
 ​
 /*
 功能: 获取token
 */
 void Widget::GetToken()
 {
     //表示获取token
     function_select=3;
 ​
     QString requestUrl;
     QNetworkRequest request;
 ​
     //设置请求地址
     QUrl url;
 ​
     //获取token请求地址
     requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens")
                  .arg(SERVER_ID);
 ​
     //自己创建的TCP服务器,测试用
     //requestUrl="http://10.0.0.6:8080";
 ​
     //设置数据提交格式
     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8"));
 ​
     //构造请求
     url.setUrl(requestUrl);
 ​
     request.setUrl(url);
 ​
     QString text =QString("{"auth":{"identity":{"methods":["password"],"password":"
     "{"user":{"domain": {"
     ""name":"%1"},"name": "%2","password": "%3"}}},"
     ""scope":{"project":{"name":"%4"}}}}")
             .arg(MAIN_USER)
             .arg(IAM_USER)
             .arg(IAM_PASSWORD)
             .arg(SERVER_ID);
 ​
     //发送请求
     manager->post(request, text.toUtf8());
 }
 ​
 ​
 //解析反馈结果
 void Widget::replyFinished(QNetworkReply *reply)
 {
     QString displayInfo="";
     int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
 ​
     //读取所有数据
     QByteArray replyData = reply->readAll();
 ​
     qDebug()<<"状态码:"<<statusCode;
     qDebug()<<"反馈的数据:"<<QString(replyData);
 ​
     //更新token
     if(function_select==3)
     {
         displayInfo="token 更新失败.";
         //读取HTTP响应头的数据
         QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs();
         qDebug()<<"HTTP响应头数量:"<<RawHeader.size();
         for(int i=0;i<RawHeader.size();i++)
         {
             QString first=RawHeader.at(i).first;
             QString second=RawHeader.at(i).second;
             if(first=="X-Subject-Token")
             {
                 Token=second.toUtf8();
                 displayInfo="token 更新成功.";
 ​
                 //保存到文件
                 SaveDataToFile(Token);
                 break;
             }
         }
         QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok);
         return;
     }
 ​
     //判断状态码
     if(200 != statusCode)
     {
         //解析数据
         QJsonParseError json_error;
         QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
         if(json_error.error == QJsonParseError::NoError)
         {
             //判断是否是对象,然后开始解析数据
             if(document.isObject())
             {
                 QString error_str="";
                 QJsonObject obj = document.object();
                 QString error_code;
                 //解析错误代码
                 if(obj.contains("error_code"))
                 {
                     error_code=obj.take("error_code").toString();
                     error_str+="错误代码:";
                     error_str+=error_code;
                     error_str+="\n";
                 }
                 if(obj.contains("error_msg"))
                 {
                     error_str+="错误消息:";
                     error_str+=obj.take("error_msg").toString();
                     error_str+="\n";
                 }
 ​
                 //显示错误代码
                 QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok);
             }
          }
         return;
     }
 ​
     //对话返回
     if(function_select==0)
     {
         //解析数据
         QJsonParseError json_error;
         QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
         if(json_error.error == QJsonParseError::NoError)
         {
             //判断是否是对象,然后开始解析数据
             if(document.isObject())
             {
                 QString error_str="";
                 QJsonObject obj = document.object();
                 QString answer;
 ​
                 //解析对话ID
                 if(obj.contains("session_id"))
                 {
                     session_id=obj.take("session_id").toString();
                     qDebug()<<"持续对话ID: "<<session_id;
                 }
 ​
                 //解析答案
                 if(obj.contains("taskbot_answers"))
                 {
                     QJsonObject obj1=obj.take("taskbot_answers").toObject();
                     if(obj1.contains("answer"))
                     {
                         answer=obj1.take("answer").toString();
                     }
                 }
                 qDebug()<<"答案:"<<answer;
 ​
                 //机器人
                 ui->listWidget->addItem(new QListWidgetItem(QIcon(QObject::tr(":/res/2.ico")), answer));
 ​
             }
          }
     }
 }
 ​
 ​
 //获取对话结果
 void Widget::getProblemResult(QString session_id,QString send_text)
 {
     //表示获取对话结果
     function_select=0;
 ​
     QString requestUrl;
     QNetworkRequest request;
 ​
     //设置请求地址
     QUrl url;
 ​
     //获取token请求地址
     requestUrl = QString("https://cbs-ext.%1.myhuaweicloud.com/v1/%2/qabots/%3/chat")
                  .arg(SERVER_ID)
                  .arg(PROJECT_ID)
                  .arg(ROBOT_ID);
 ​
     //设置数据提交格式
     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
 ​
     //设置token
     request.setRawHeader("X-Auth-Token",Token);
 ​
     //构造请求
     url.setUrl(requestUrl);
 ​
     request.setUrl(url);
 ​
     QString text =QString("{"question": "%1","session_id":"%2"}").arg(send_text).arg(session_id);
 ​
     //发送请求
     manager->post(request, text.toUtf8());
 }
 ​
 //发送问答
 void Widget::on_pushButton_send_clicked()
 {
     QString text=ui->lineEdit->text();
     if(text.isEmpty())
     {
       return;
     }
 ​
     QListWidgetItem *item;
     item=new QListWidgetItem(text);
     item->setTextAlignment(Qt::AlignRight);
     item->setTextColor(QColor("#FF1493"));
     ui->listWidget->addItem(item);
 ​
     getProblemResult(session_id,text);
 }
 ​
 //更新token
 void Widget::on_pushButton_token_clicked()
 {
     GetToken();
 }
相关推荐
程序猿麦小七18 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~26 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong27 分钟前
SpringBoot后端解决跨域问题
spring boot·后端·python
.生产的驴29 分钟前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq
小扳32 分钟前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器
v'sir42 分钟前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
李少兄1 小时前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
码上一元6 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
枫叶_v8 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
杜杜的man8 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang