天天看點

智能問答機器人

1. 前言

問答機器人現在很多場合都有使用,比如:網頁智能客服、Weixin公衆号智能回複、淘寶的售後客服,QQ聊天機器人等等。有了這些客戶機器人就能幫我們回答很多預置的一些問題,幫助使用者解決常見問題,還可以進行自主訓練,得到一個适合自己使用的機器人。機器人也可以關聯很多其他的技能,玩小遊戲,查詢天氣、查詢節假日、查詢很多其他的資訊,非常友善。

這篇文章就采用華為雲提供的智能問答機器人設計一個小軟體,采用華為雲提供的API接口完成資料互動,與機器人進行問答互動,通過這個例子可以了解到智能問答機器人的基本功能、使用場景、使用方法等等。

智能問答機器人

華為雲的智能問答機器人特點介紹

提供問答引擎、機器人管理平台來友善客戶快速、低成本建構智能問答服務。智能問答能滿足使用者快速上線、高度定制、資料可控的需求,具有問答準确率高、自主學習等特點。能夠幫助企業節省客服人力,大大降低客服響應時間。

具備如下優勢點:

  • 智能的問答管理
    • 熱點問題、趨勢、知識自動分析統計。
    • 支援未知問題自動聚類,比對相似問答,輔助人工不斷擴充知識庫。
    • 支援問答調測,點對點的監測智能應答過程。
    • 支援領域知識挖掘,提供易用的标注工具挖掘領域詞。
  • 全面的對話管理
    • 支援自然語言多能力融合,智能對話中控。
    • 靈活的知識庫管理,支援對知識的批量操作。
    • 支援嵌入多輪對話技能,滿足複雜的任務型對話場景。
  • 高效訓練部署
    • 基于modelarts的底層算法能力,提供更快的模型訓練、部署能力。
    • 支援多算法模型效果驗證,驗證不同資料、參數、模型對問法效果的影響。
    • 支援模型最優參數組合推薦,保證問答效果。

2. 使用問答機器人服務

2.1 開通服務

位址: https://www.huaweicloud.com/product/cbsqa.html

點選

立即使用

會進入到購買頁面,可以使用14天,對于技術評估,場景測試已經足夠。

智能問答機器人
智能問答機器人
智能問答機器人
智能問答機器人

2.2 配置機器人

(1)機器人購買之後,點選進入管理頁面,對機器人的屬性、技能進行配置,訓練。

智能問答機器人
智能問答機器人

(2)可以添加預置的技能,還可以添加自定義技能

預置的技能有查詢天氣、成語接龍、查星座、查節日、猜數字遊戲等等。也可以自己自定義技能标注訓練釋出。

智能問答機器人
智能問答機器人

2.3 對話體驗

在管理頁面右上角可以線上體驗與機器人對話,可以快速調試問答效果。

智能問答機器人
智能問答機器人

2.4 接口調試

位址: https://support.huaweicloud.com/api-cbs/cbs_03_0115.html

在調用API測試之前,可以先使用線上調試接口測試,了解請求如何發出,有哪些必填參數,請求參數怎麼填,傳回的結果格式是怎樣的。

智能問答機器人

2.5 API請求總結

(1)請求的URL格式

請求的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)請求頭與請求參數總結

請求頭:  
"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字段在之前的文章已經介紹過,擷取方法看這裡: https://bbs.huaweicloud.com/blogs/317759 翻到2.3小節。

(3)請求參數介紹

詳細的參數可以看官方文檔介紹: https://support.huaweicloud.com/api-cbs/cbs_03_0115.html

請求參數裡一般主要填下面兩個字段:

question 這是必填的參數,填使用者的問題。如:查天氣。長度為1~512。

session_id 填會話辨別符,UUID格式。如:c04e6f7b-61d7-4a2d-a0c8-f9ecd2f62359。
每次對話開啟,機器人建立會話id,下次請求中傳入該id表示繼續該輪對話,每輪會話有效時間為2分鐘。
若傳入的會話id已過期或者為空,則機器人會重新建立新的會話id(重新建立會話id會消耗一定時間)。
比如: 玩成語接龍遊戲,就需要填會話辨別ID,這樣才可以接着上一次的對話繼續問答。
           

(4)響應參數介紹

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)查星座

智能問答機器人
智能問答機器人

3.2 核心代碼

#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();
}