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

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();
 }
相关推荐
陈随易10 分钟前
前端项目部署只要30秒
前端·后端·程序员
YIAN14 分钟前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana14 分钟前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端
学渣超15 分钟前
记一次分布式事务数据不一致的排查之旅:从超时到索引,层层剥茧
java·后端·架构
北冥you鱼1 小时前
Go 语言读取链上数据:从基础到实战
开发语言·后端·golang
程序员cxuan1 小时前
Claude Code 为了封禁中国用户,竟然在代码里下毒
人工智能·后端·程序员
郡杰1 小时前
Git基础与开发平台搭建
git·后端
ServBay1 小时前
MCP (Model Context Protocol) 深度指南,协议原理、Python 实现与生产级安全实践
后端·ai编程·mcp
道友可好2 小时前
Claude Code 泄露源码里的 89 个秘密
前端·人工智能·后端
用户8356290780512 小时前
使用 Python 保护和取消保护 Excel 工作表和工作簿
后端·python