如何使用Dialogflow Twilio集成的WhatsApp情感分析

374 阅读9分钟

使用Dialogflow Twilio集成进行WhatsApp情感分析

本教程将告诉你如何建立一个WhatsApp聊天机器人,利用情感分析预测人们对某个特定话题的感受。

我们将使用Dialogflow和谷歌云自然语言处理API来分析推文,构建这个聊天机器人。

我们还将使用Twilio来整合WhatsApp。

前提条件

要跟随我们,你需要。

  • 一个[Twilio]账户。
  • 一个[谷歌云]账户。
  • 一个[Twitter开发者]账户。
  • 一个[WhatsApp]账户。
  • 一个[Dialogflow]账户。
  • 安装有[Node.js]、[npm]和[ngrok]。

创建一个谷歌云账户

你需要登录或创建一个谷歌云账户来使用云自然语言API。

用你的信用卡信息创建一个账户,但除非你超过免费层的限制,否则不会向你收费。

此外,新用户可获得[300美元的免费点数]。

在你创建了你的账户或登录后,创建一个New Project

对于现有用户,浏览你的仪表板app bar ,点击当前项目名称下拉菜单。

Cloud account

接下来,点击NEW PROJECT 按钮。

GCP Project

然后,为你的项目添加所需的细节,并点击CREATE ,生成一个新项目。

Google cloud project

接下来,你需要为该项目启用云自然语言API,以进行情感分析。

要启用它,在你的新项目的主仪表板上找到Getting Started 卡。

点击Explore and Enable APIs ,并在顶部搜索栏中搜索Cloud Natural Language API

Cloud NLP

如果提示你激活计费,选择ENABLE BILLING 。你将不会被收取任何费用。

在Dialogflow中建立一个意图

由谷歌支持的Dialogflow提供了一个理解自然语言的框架。它使我们能够创建沟通的界面。

我们将使用Dialogflow和一个履行网络钩子。履行网络钩子允许你在Dialogflow的在线编辑器中编写JavaScript代码,并将其作为云函数部署。

我们将用它来为WhatsApp聊天机器人建立一个对话体验。如果你没有Dialogflow账户,你应该创建一个。

登录Dialogflow后,点击屏幕左上方的**+创建代理**按钮,创建一个代理。

命名您的代理,然后在谷歌项目标签下,选择您的谷歌云项目来导入。然后点击屏幕顶部的蓝色CREATE按钮。

注意,你需要对Dialogflow有一个基本的了解。

接下来,点击Intents旁边的+按钮,为对话创建一个意图。你会看到一个看起来像这样的屏幕。

Create Intent

在 "培训短语"标题下,点击 "添加培训短语"按钮。

要添加一个training phrase 键入一个用户可能问的问题。例如,人们对Twilio的感觉如何?

在上面的例子中,用户想了解更多关于Twilio的信息。使用Dialogflow自然语言处理,我们可以用一个接受不同参数的实体来替换搜索词。

现在,训练短语可以被改写成这样;人们对X的感觉如何?X ,在这种情况下,是一个可以接受任何参数的实体。

要做到这一点,请输入短语:人们对X的感觉如何,然后用光标突出字母X

这时,一个包含实体列表的对话框就会出现。选择@sys.any 作为实体,如下图所示。按return 键,提交训练短语。

Training phrases on Dialogflow

对下面的训练词组重复这个过程。

  • 什么是X
  • 搜索X
  • 评价X
  • 告诉我关于X的情况

Intent in Dialogflow

随意创建另一个意图并添加更多的培训短语。

行动和参数

训练短语中的X 是一个实体类型,可以从用户表达中提取任何参数。

换句话说,我们把X ,变成一个可以代表任何东西的变量。

Dialogflow提供了预先定义的系统实体,可以匹配常见的数据类型。

例如,有一些系统实体用于匹配日期、时间、颜色、电子邮件地址等。

Entity in Dialogflow

响应

我们还需要向聊天机器人添加默认的回应,以防我们的查询没有返回任何结果。

向下滚动到 "回应"部分,点击ADD RESPONSES按钮。

你可以添加以下语句作为意图的默认回应。

  • 我还不清楚人们对$sys.any 的感觉!
  • 对不起,我无法得到任何关于$sys.any
  • 试着搜索除此之外的另一个词$sys.any

Responses In Dialogflow

履约(Fulfillment

这个意图也需要一个履行的webhook来为用户的查询提供正确的回应。为了使其发挥作用,请将启用该意图的webhook调用切换为开

Webhook in Dialogflow

启用网络钩子后,点击屏幕上方的蓝色SAVE按钮。

接下来,从左边的菜单中点击 "履行"。将Inline Editor右边的Disabledtoggle设置为Enabled。这允许你在Dialogflow里面写你的webhook代码。

Editor for Dialogflow

在内联编辑器中添加代码

index.js文件标签中,添加以下代码。

// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
"use strict";
const functions = require("firebase-functions");
const language = require("@google-cloud/language");
const httpRequest = require("request-promise-native");
const { dialogflow } = require('actions-on-google');

上面的代码导入了所有需要的依赖性。

接下来,用你的实际project ID ,替换占位符,如下图所示。

// Create an app instance
const app = dialogflow();
const languageClient = new language.LanguageServiceClient({
   projectId: "YOUR_GCLOUD_PROJECTID"
});

上面的代码创建了一个新的Dialogflow 应用程序的实例和一个自然语言服务客户端的实例。注意,它需要你的项目ID才能工作。

创建一个Twitter开发者账户

在继续前进之前,你需要用你现有的Twitter账户或新账户创建一个开发者账户。

导航到developer.twitter.com来创建一个开发者账户。

Twitter developer

选择你的应用程序的用例和你想使用Twitter API的原因,填写你的应用程序的描述。

由于申请数量较多,你的应用程序可能需要一些时间才能被批准。

一旦你的申请被批准,继续并创建一个应用程序。

选择密钥和令牌,然后将你的API密钥复制并保存在你可以轻松检索到的地方。

Search Tweets: 30-Days/Sandbox 下建立一个开发环境,并命名该环境。

我将我的环境命名为testing

Dev environment

在获得你的Twitter API KeyAPI Secret 之后,回到Dialogflow的内联编辑器。

index.js 文件中,添加以下代码。

// This is the environment for the Twitter premium search api.
// See: https://developer.twitter.com/en/docs/tweets/search/api-reference/premium-search.html
const TWITTER_ENV = "testing";
// This is the endpoint for the Twitter premium search api.
// See: https://developer.twitter.com/en/docs/tweets/search/api-reference/premium-search.html
const TWITTER_SEARCH_ENDPOINT = "30day";
// Constructed a complete base url for the API call.
const TWITTER_SEARCH_URL = "https://api.twitter.com/1.1/tweets/search/"
   .concat(TWITTER_SEARCH_ENDPOINT)
   .concat("/")
   .concat(TWITTER_ENV)
   .concat(".json");
// Load the API credentials into constants for readability’s sake
const CONSUMER_KEY = 'YOUR_TWITTER_API_KEY';
const CONSUMER_SECRET = 'YOUR_TWITTER_SECRET_KEY';
process.env.DEBUG = "dialogflow:debug"; // enables lib debugging statements

接下来,添加下面的代码。

/*
 Filter out the retweets from the results returned from the Twitter premium search API.
* @param searchResults
* @returns {Array}
*/
function filterRetweets(searchResults) {
   let filtered = [];
   searchResults.forEach((result) => {
      if (!result.retweeted_status) {
      // Check if details about a retweet exist, if they do, do not execute this block
      filtered.push(result); // Since this is not a retweet, push it
      }
   });
   return filtered;
}
/*
* Pluck the ids from the result array containing tweet objects returned from the Twitter premium search API.
* @param searchResults
* @returns {Array}
*/
function extractText(searchResults) {
   let tweets = [];
      searchResults.forEach((result) => {
      tweets.push(result.text); // Push the tweet's ID into the array
   });
   return tweets;
}

这段代码定义了一个辅助函数,用于从搜索结果中删除所有转发,并将其保存到一个数组中。

上述代码还从API响应中提取所有推文,并将它们添加到一个数组中。

接下来,添加以下代码来访问你的应用程序上的intent() 方法。它传递意图的名称和两个参数。

然后它将输入值设置为params.any ,并请求Twitter的API。

// The Sentiment intent
app.intent('Sentiment', (agent, params) => {
const inputEntity = params.any;
   let request_options = {
   url: TWITTER_SEARCH_URL,
   oauth: { consumer_key: CONSUMER_KEY, consumer_secret: CONSUMER_SECRET },
   json: true,
   headers: {
   "content-type": "application/json"
   },
   body: { query: inputEntity.concat(" lang:en"), }
};
// Send a request to the twitter api, then return the results (body)
return httpRequest.post(request_options).then((body) => {
   // Get the results without all the retweets and extract the text of the tweet to be analyzed
   let tweetText = extractText(filterRetweets(body.results));
   // Create a nlpClient request to detect the sentiment of all the tweets fetched
   return languageClient.analyzeSentiment({
      document: {
      content: tweetText.join(" "),
      type: "PLAIN_TEXT"
   }
 }).then(results => {
   // Get the overall document score (all the tweets concatenated together)
   const sentiment = results[0].documentSentiment.score;
   // 0 -> 0.1 is a somewhat neutral score
   if (sentiment >= 0 && sentiment <= 0.1) {
         agent.add(`People have mixed feelings about ${inputEntity}.`);
         // Less than 0 is usually negative
      } else if (sentiment < 0) {
         agent.add(`People feel negatively about ${inputEntity}.`);
         // Greater than 0.1 usually indicates positive.
      } else {
         agent.add(`People feel positively about ${inputEntity}.`);
      }
   }).catch(err => {
      console.log(err);
      agent.add("Sorry, something went wrong.");
   });
   }).catch(err => {
      console.log(err);
      agent.add("Sorry, something went wrong.");
   });
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

上述代码将Twitter的响应提交给自然语言处理API。

NLP返回一个情感分数(结果),你把它保存在一个变量中。

它还根据NLP API的评分是积极的还是消极的,从Action中获取一个响应。

conv.close 每当Action作出反应时,结束对话。

将你的代码添加到package.json文件中

在你的内联编辑器中,切换到package.json 标签。

在这个文件内复制并粘贴以下内容。

{
   "name": "dialogflowFirebaseFulfillment",
   "description": "This is the default fulfillment for a Dialogflow agent using Cloud Functions for Firebase",
   "version": "0.0.1",
   "private": true,
   "license": "Apache Version 2.0",
   "author": "Google Inc.",
   "engines": {
   "node": "8"
   },
   "scripts": {
      "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
      "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
   },
   "dependencies": {
      "firebase-admin": "^5.13.1",
      "firebase-functions": "^2.0.2",
      "request": "^2.87.0",
      "request-promise-native": "^1.0.5",
      "dialogflow-fulfillment": "^0.5.0",
      "@google-cloud/language": "2.0.0"
   }
}

上述内容保存了你的项目的所有相关元数据和依赖关系。

在这一点上,你已经完成了Dialogflow履行Webhook的设置。按下内嵌编辑器中的DEPLOY按钮,部署你的云功能。

将Twilio与Dialogflow集成

在你的电脑上创建一个新的目录。

在这个文件夹中,初始化一个Node.js项目。

mkdir whatsapp-sentiments
cd whatsapp-sentiments
npm init -y

我们需要安装以下软件包。

  • [Nodemon]是一个帮助开发基于Node.js的应用程序的工具,它可以在文件发生变化时自动重新启动服务器。
  • [Dialogflow]包是一个Node.js的API客户端。
  • [Twilio Node Helper库]提供对Twilio API的访问。
  • [Express]可以让你构造一个Web应用来处理特定URL的多个HTTP请求。
  • [Body-parser]是一个中间件,用于从传入的请求中提取主体。
  • [UUID]用于识别需要独特的信息。
npm install twilio nodemon express body-parser dialogflow uuid

接下来,在你的_whatsapp-sentiments_目录下创建这些文件。

touch index.js && touch twilio.js && touch dfservice.js

获取你的谷歌云项目服务密钥

在你的谷歌云项目仪表板上,启动菜单栏,并导航到IAM & ADMIN ,在其下拉菜单上,点击Service Accounts

你会看到你的项目ID服务账户,点击它,打开服务账户的详细信息。

Keys 下,点击ADD KEY ,你会看到一个弹出的对话框,如下图所示。

勾选JSON 密钥类型并点击CREATE 按钮。JSON文件将被自动下载。

Gcp service account

为WhatsApp设置Twilio沙盒

导航到Twilio控制台的WhatsApp部分,激活Twilio沙箱。

沙盒允许你使用一个共享号码测试Twilio WhatsApp API,而无需等待WhatsApp批准你的Twilio号码。

勾选激活沙盒后,您需要从您的WhatsApp号码发送一条特定的信息到提供的通用Twilio WhatsApp号码。

将Twilio WhatsApp号码添加到您的联系人列表中并发送消息。

完成这一步后,你应该看到下面的成功画面。

Twilio WhatsApp sandbox

添加代码到文件中

将你最近下载的Google cloud Project service account key 的JSON文件添加到你的代码目录,并将其重命名为config.js

进入你的主仪表板Twilio控制台,找到你的Account SIDAuth Token

复制并粘贴这些值到config.js 文件中,如下所示。

Twilio developer console

module.exports = {
   accountSid: 'YOUR_TWILIO_ACCOUNT_SID',
   authToken: 'YOUR_TWILIO_AUTH_TOKEN',
   "DF_LANGUAGE_CODE": "en-US", //Dialogflow default language
   //The rest of the code below will contain all the details of your Google Cloud service account keys
}

用这些占位符替换你的Twilio account SIDAuth token :'YOUR_TWILIO_ACCOUNT_SID' 和 'YOUR_TWILIO_AUTH_TOKEN'。

index.js文件中添加这段代码。

const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('uuid');
const DF = require("./dfservice");
const twilio = require('./twilio');
const app = express();
//port for local host
const port = 80;
//creates a new sessionID
const sessionIds = new Map();

这段代码允许你的应用程序使用express,body-parser,uuid,Dialogflow file, 和Twilio 库。

app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.post('/', async(req, res) => {
   const sender = req.body.From;
   const text = req.body.Body;
   const from = req.body.To;
   setSessionAndUser(sender);
   let response;
   try {
      /// Response from Dialogflow
      response = await DF.sendTextQueryToDialogFlow(sessionIds, sender, text);
      /// Response sent to Twilio
      await twilio.sendText(from, response.fulfillmentText || response.queryText, sender).then(m => console.log(m.sid))
   } catch (error) {
      console.log(error)
   }
});
/// To check if theres an existing sessionID or not
function setSessionAndUser(senderID) {
   if (!sessionIds.has(senderID)) {
   sessionIds.set(senderID, uuid.v1());
   }
}
app.listen(port, () => console.log(`Magic on ${port}`));

每当一个消息被发送,一个新的会话开始,你会看到发送者的号码,接收者(Universal Twilio号码)和消息。

文本查询将被发送到Dialogflow,然后响应以履行文本的形式发送到Twilio,你将从Twilio收到一条WhatsApp消息。

dfservice.js文件中加入这段代码。

const dialogflow = require('dialogflow');
const config = require('./config');
const credentials = {
   client_email: config.client_email,
   private_key: config.private_key,
};
const sessionClient = new dialogflow.SessionsClient(
   {
      projectId: config.project_id,
      credentials
   }
);
module.exports = {
   async sendTextQueryToDialogFlow(sessionIds, sender, text, params = {}) {
   const sessionPath = sessionClient.sessionPath(config.project_id, sessionIds.get(sender));
      ///Request payload
      const request = {
         session: sessionPath,
         queryInput: {
         text: {
         text: text,
         languageCode: config.DF_LANGUAGE_CODE, ///Dialogflow default language
      },
   },
      queryParams: {
         payload: {
         data: params
         }
      }
   };
   const responses = await sessionClient.detectIntent(request);
      return responses[0].queryResult;
   },
      async sendEventToDialogFlow(sessionIds, handleDialogFlowResponse, sender, event, params = {}) {
         const sessionPath = sessionClient.sessionPath(config.GOOGLE_PROJECT_ID, sessionIds.get(sender));
            const request = {
               session: sessionPath,
               queryInput: {
               event: {
               name: event,
               parameters: structjson.jsonToStructProto(params), //Dialogflow's v2 API uses gRPC. You'll need a jsonToStructProto method to convert your JavaScript object to a proto struct.
               languageCode: config.DF_LANGUAGE_CODE,
            },
         }
       };
   const responses = await sessionClient.detectIntent(request);
   const result = responses[0].queryResult;
   handleDialogFlowResponse(sender, result);
   }
}

上面的代码使用接收文本和sessionPath的默认请求有效载荷,向Dialogflow发送文本查询。

将检测文本的意图,并将响应发送到Twilio。

twilio.js文件中加入这段代码。

const config = require('./config');
const client = require('twilio')(config.accountSid, config.authToken);
   function sendText(from, message, to) {
      console.log(from,message, to);
      return client.messages
         .create({
         from: from,
         body: message,
         to: to
   })
}
module.exports = { sendText }

上面的代码加载Twilio库,并从通用的Twilio WhatsApp号码发送一个消息到你的号码。

用Ngrok创建一个webhook

使用Ngrok,你可以不费吹灰之力将你的本地托管网络服务器部署到互联网上。

你可以下载Ngrok命令行界面(CLI)来开始使用。

打开你的命令行,用这个命令在localhost上运行你的项目。

Nodemon index.js

运行这个命令后,你会得到Magic on 80 的成功信息。

接下来,打开你的Ngrok CLI ,用这个命令启动一个HTTP隧道,转发到你的本地80端口。

Ngrok http 80

在你的终端,你应该看到这个。

Ngrok terminal

复制Ngrok提供的URL,因为我们将在webhook中使用它。

添加webhook到Twilio

到你的Twilio控制台,导航到可编程的消息标签。

导航到sandbox settings ,在WHEN A MESSAGE COMES IN 字段中粘贴Ngrok URL ,并保存它。

你的屏幕应该看起来像这样。

Twilio WhatsApp sandbox

测试webhook

你已经成功地将你的webhook与Twilio连接起来,现在你可以在WhatsApp上测试聊天机器人,问它一些问题,看看它的反应。

下面是我得到的结果。

WhatsApp chat conversation

WhatsApp chat conversation

结论

恭喜你!你已经成功地使用Twilio创建了一个聊天机器人。你已经成功地使用Dialogflow、WhatsApp和谷歌云自然语言处理API创建了一个聊天机器人,使用Twitter的API对Twitter上的推文进行情感分析。

有了这些知识,你现在可以使用外部API、Twilio、谷歌云API和Dialogflow为不同类型的应用程序建立聊天机器人。