由於最近上面的大老闆對AI的議題非常感興趣,身為一個不專業的「高級打工仔」自然而然被指派研究AI相關的議題
既然要研究AI,那就從比較好入手的Chat Bot開始吧
以下紀錄Chat Bot的開發與架設過程:
Step 1 至Line Developer頁面https://developers.line.me/en/ 申請Line@測試帳號,申請完成後會出現以下畫面
Step 1.1 Channel ID、Channel Secret、Channel access token 及 Webhock URL 會在開發Chat Bot時用到
Step 2 Dialogflow 帳號申請,連結: https://dialogflow.com
Step 3 在Dialogflow上建立Agent,此部份請參考連結: https://tinyurl.com/y9h4lstr
Step 4 進行Chat Bot 開發
Step 4.1 程式架構
Maven POM
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.foxlink</groupId>
<artifactId>FoxlinkChatBot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- Line SDK -->
<dependency>
<groupId>com.linecorp.bot</groupId>
<artifactId>line-bot-api-client</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.linecorp.bot</groupId>
<artifactId>line-bot-model</artifactId>
<version>1.11.0</version>
</dependency>
<!-- log -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<!-- Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
<!-- Jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.2-jre</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>0.10.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-digester</groupId>
<artifactId>commons-digester</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>ai.api</groupId>
<artifactId>libai</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-util</artifactId>
<version>8.5.23</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- Oracle JDBC -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src</directory>
<includes>
<include>Beans.xml</include>
<include>log4j.xml</include>
</includes>
</resource>
</resources>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step 4.2 Controller建立
特別注意,在Line Developer文件中有提到:
Response
Your server should return the status code 200
for a HTTP POST request sent by a webhook.
package com.foxlink.chatbot.controller;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.ServletContextAware;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import ai.api.AIConfiguration;
import ai.api.AIDataService;
import ai.api.model.AIRequest;
import ai.api.model.AIResponse;
@Controller
public class CallbackController implements ServletContextAware {
private String DialogflowToken,LineID,LineSecret,LineToken;
private static Logger logger = Logger.getLogger(CallbackController.class);
@RequestMapping(value="/callback",method=RequestMethod.POST)
public ResponseEntity handleLineCallBack(HttpServletRequest request, HttpServletResponse response) {
String replyToken=null;
LineMessageUtils lineUtils=new LineMessageUtils();
List<LineClientMsg> LineResponseParams=null;
try {
request.setCharacterEncoding("UTF-8");
LineResponseParams=lineUtils.FilterLineClient2ServerMsg(request, LineSecret);
logger.info("Line Message from Client: "+LineResponseParams);
System.out.println("Line Message from Client: "+LineResponseParams);
Iterator<LineClientMsg> ClientMsgIterator=LineResponseParams.iterator();
AiUtils aiUtil=new AiUtils(this.DialogflowToken);
while(ClientMsgIterator.hasNext()) {
LineClientMsg clientMsg=ClientMsgIterator.next();
replyToken=clientMsg.getReplyToken();
System.out.println("Message from Client: "+clientMsg.getMsgsFromClient());
AiResponse AiAgentResponse=aiUtil.filterAiResponseMsg(clientMsg.getMsgsFromClient());
String[] systemName=AiAgentResponse.getIntentName().split("-");
logger.info("Ai Intent : "+AiAgentResponse.getIntentName()+" System Name: "+systemName[0]);
System.out.println("Ai Intent : "+AiAgentResponse.getIntentName()+" System Name: "+systemName[0]);
IAiService aiService=null;
JsonArray MsgArray=null;
JsonObject Msg2LineServer=null;
switch (systemName[0]) {
case "SFC":
aiService=new SFCAi();
break;
case "RealTime":
aiService=new RealTimeAi();
break;
case "ERP":
aiService=new ERPAi();
break;
case "Agentflow":
aiService=new AgentflowAi();
break;
case "Notes":
aiService=new NotesAi();
break;
case "BI":
aiService=new BIAi();
break;
case "PLM":
aiService=new PLMAi();
break;
default:
break;
}
if(aiService !=null) {
//有找到對應的AI Service
aiService.setParameters(AiAgentResponse.getParameters(),AiAgentResponse.getIntentName());
MsgArray=lineUtils.GenerateMsgArray(aiService.SendSingleResponseToClient(systemName[1]));
logger.info("Message to line client: "+MsgArray);
}
else {
JsonObject noAiFound=new JsonObject();
MsgArray=new JsonArray();
noAiFound.addProperty("type", "text");
if(systemName[0].equals("SAY_HELLO")) {
noAiFound.addProperty("text", "Hello 您好~有任何問題都能問我");
}
else {
noAiFound.addProperty("text", "我不太懂您的意思,請再輸入一次");
}
MsgArray.add(noAiFound);
}
Msg2LineServer=lineUtils.GenerateLineReplyClientMsg(replyToken, MsgArray);
logger.info("Message to Line Server: "+Msg2LineServer);
System.out.println("Message to Line Server: "+Msg2LineServer+" , and reply token: "+replyToken);
lineUtils.ReplyLineMsg2Client(replyToken, LineToken, Msg2LineServer);
}
}
catch(Exception ex) {
logger.error("error",ex);
System.out.println(ex);
}
return new ResponseEntity(HttpStatus.OK);
}
}
Step 4.3 實作Line Utils,這邊列出接收並解析訊息的function 及 回傳訊息至Client的function
package com.foxlink.chatbot.Utils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class LineMessageUtils {
private static Logger logger=Logger.getLogger(LineMessageUtils.class);
public void ReplyLineMsg2Client(String replyToken,String LineAccessToken,JsonObject Message2Client) {
try {
HttpHeaders headers=new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers.add("Authorization", "Bearer {"+LineAccessToken+"}");
HttpEntity<String> entity=new HttpEntity<String>(Message2Client.toString(), headers);
//Send Request and Parse Result
RestTemplate restTemplate=new RestTemplate();
ResponseEntity<String> lineServerResponse = restTemplate
.exchange("https://api.line.me/v2/bot/message/reply", HttpMethod.POST, entity, String.class);
if(lineServerResponse.getStatusCode()==HttpStatus.OK) {
logger.info("Send Message to Line Server is success.");
}
else {
logger.info("Send Message to Line Server is failed, due to: "+lineServerResponse.getStatusCode()+", "+lineServerResponse.getBody());
}
}
catch(Exception ex) {
logger.error("ReplyLineMsg2Client failed ",ex);
ex.printStackTrace();
}
}
public JsonObject GenerateLineReplyClientMsg(String LineReplyToken,JsonArray Reply2ClientMsgs) {
JsonObject Msg2Client=new JsonObject();
try {
Msg2Client.addProperty("replyToken", LineReplyToken);
Msg2Client.add("messages", Reply2ClientMsgs);
}
catch(Exception ex) {
System.out.println("GenerateLineReplyClientMsg Failed , due to: "+ex.toString());
ex.printStackTrace();
}
return Msg2Client;
}
public List<LineClientMsg> FilterLineClient2ServerMsg(HttpServletRequest request,String lineChannelSecret){
List<LineClientMsg> LineClientMsgs=null;
String httpRequestBody=null;
try {
LineClientMsgs=new ArrayList<LineClientMsg>();
InputStream inputStream=request.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
httpRequestBody=IOUtils.toString(reader);
JsonParser parser=new JsonParser();
JsonObject lineMsg=(JsonObject) parser.parse(httpRequestBody);
JsonArray lineEvents=lineMsg.get("events").getAsJsonArray();
Iterator<JsonElement> lineEventIterator=lineEvents.iterator();
while(lineEventIterator.hasNext()) {
LineClientMsg lineClientMsg=new LineClientMsg();
JsonElement lineEvent=lineEventIterator.next();
lineClientMsg.setReplyToken(lineEvent.getAsJsonObject().get("replyToken").getAsString());
String msgType=lineEvent.getAsJsonObject().get("message").getAsJsonObject().get("type").getAsString();
if(msgType.equals("text")) {
lineClientMsg.setMsgsFromClient(lineEvent.getAsJsonObject().get("message").getAsJsonObject().get("text").getAsString());
}
LineClientMsgs.add(lineClientMsg);
}
/*Decrypt*/
SecretKeySpec key = new SecretKeySpec(lineChannelSecret.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
byte[] source = httpRequestBody.getBytes("UTF-8");
}
catch(Exception ex) {
ex.printStackTrace();
}
return LineClientMsgs;
}
}
Step 4.4 實作Dialogflow 的 Utils,列出解析Dialogflow回傳的訊息
package com.foxlink.chatbot.Utils;
import java.util.HashMap;
import com.google.gson.JsonElement;
import ai.api.AIConfiguration;
import ai.api.AIDataService;
import ai.api.model.AIRequest;
import ai.api.model.AIResponse;
public class AiUtils {
private String DialogFlowToken;
public AiUtils(String AiToken) {
this.DialogFlowToken=AiToken;
}
public AiResponse filterAiResponseMsg(String msg2AiAgent) {
AIConfiguration config=null;
AIDataService dataService=null;
AIRequest request=null;
AIResponse response=null;
AiResponse responseContent=null;
try {
config=new AIConfiguration(DialogFlowToken);
dataService=new AIDataService(config);
request=new AIRequest(msg2AiAgent);
request.setLanguage("zh-TW");
response=dataService.request(request);
if(response.getStatus().getCode()==200) {
String intent=response.getResult().getMetadata().getIntentName();
HashMap<String,JsonElement> parameters=response.getResult().getParameters();
responseContent=new AiResponse();
responseContent.setIntentName(intent);
responseContent.setParameters(parameters);
System.out.println("AI Response Content: "+responseContent);
}
else {
System.err.println(response.getStatus().getErrorDetails());
}
}
catch(Exception ex) {
ex.printStackTrace();
}
return responseContent;
}
}
Step 5 實際測試
以上,就是簡易版本的Line Chat Bot 結合Dialogflow AI的作法,提供給大家參考