Skip to content

The project shows how to develop an interactive chatbot using Lex and open APIs. Lex provides operations using User Intent but it may response the dupulicated answers for unknown intents. Thus the proposed architecture uses Open APIs such as ChatGPT.

Notifications You must be signed in to change notification settings

kyopark2014/interactive-chat-using-Lex-and-ChatGPT

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Amazon Lex에서 Open API를 이용한 대화형 Chatbot 구현하기

Amazon Lex는 애플리케이션에 대화형 인터페이스를 설계, 구축, 테스트, 배포할 수 있도록 자연어 모델을 사용하는 완전관리형 인공지능(Managed AI) 서비스 입니다. 이와 같이 Amazon Lex로 만든 chatbot은 연속적인 대화를 주고 받을 수 있도록 의도(intent)를 파악하여, 해당 의도를 이행하는 데 필요한 정보를 사용자에게 표시할 수 있습니다. 또한, Amazon Lex에서 파악되지 않은 의도에 대한 답변을 위하여, Amazon Kendra를 사용할 수 있습니다. 마찬가지로 3rd party Open API를 이용하여도 유사한 효과를 얻을 수 있습니다. 2022년 11월에 ChatGPT가 출시되어 우수한 대화 능력을 보여줌으로 인해, Kendra 뿐 아니라 ChatGPT를 Open API로 사용하는 것이 가능하게 되었습니다. 본 게시글에서는 Open API로 ChatGPT를 이용하여 미리 정의되지 않은 의도(intent)에 답변을 할 수 있는 대화형 chatbot을 구현하는 방법을 설명하고자 합니다.

Chatbot Architecture

여기에서 구현하는 Architecture는 아래와 같습니다. Amazon CloudFront를 이용하여 채팅을 위한 웹페이지를 제공합니다. 사용자가 입력한 채팅 메시지는 Amazon API GatewayAWS Lambda를 이용해 Lex에서 의도(intent)를 파악후 답변을 합니다. 그런데, Lex에서 인식되지 못한 의도가 있다면 Lambda 함수를 이용하여 ChatGPT에 질의를 하고, 그 결과를 채팅창에 표시하게 됩니다. 이러한 대화형 Chatbot을 구성하기 위한 인프라는 AWS CDK를 이용해 생성 및 관리됩니다. 모든 인프라는 서버리스(Serverless)로 구성되므로 유지보수면에서 효율적이며 변동하는 트래픽에도 자동 확장(Auto Scaling)을 통해 안정적으로 시스템을 운용할 수 있습니다.

image

상세한 동작은 아래를 참조합니다.

단계1: 사용자는 CloudFront의 도메인으로 Chatbot 웹페이지를 접속을 시도하여, S3에 저장된 HTML, CSS, Javascript를 로드합니다.

단계2: 웹페이지에서 채팅 메시지를 입력합니다. 이때 "/chat"리소스에 POST Method으로 JSON 포맷으로 된 text 메시지를 RESTful 형태로 요청하게 됩니다.

단계3: CloudFront는 API Gateway로 요청을 전송합니다.

단계4: API Gateway는 /chat 리소스에 연결되어 있는 Lambda 함수를 호출합니다.

단계5: Lambda 함수는 Lex V2 API를 이용하여 채팅 메시지를 Lex에 전달합니다.

단계6: Lex는 미리 정의한 의도(intent)가 있는 경우에 해당하는 동작을 수행합니다. 의도를 인식할 수 없는 메시지라면, ChatGPT로 문의하는 요청을 보냅니다.

단계7: ChatGPT에서 답변을 하면, 응답이 이전 단계의 역순으로 전달되어서 사용자에게 전달됩니다.

대화형 Chatbot의 구현

Lambda 함수를 이용해 Lex로 메시지 전송하기

서울 리전은 Lex V1을 지원하지 않고, Lex V2만을 지원합니다. 따라서, Lex에 사용자의 입력을 메시지로 전송하기 위해서는 Lex V2의 RecognizeText을 이용합니다. Lex Runtime V2 client를 아래와 같이 정의합니다.

import { LexRuntimeV2Client, RecognizeTextCommand} from "@aws-sdk/client-lex-runtime-v2"; 

Lambda 함수는 event에서 text를 분리하여 아래와 같이 botAliasId, botId를 이용해 메시지를 전달하게 되며, Lex에서 전달한 응답에서 메시지를 추출하여 전달합니다.

const text = event.text;

let lexParams = {        
  botAliasId: process.env.botAliasId,
  botId: process.env.botId,
  localeId: process.env.localeId,
  text: text,
  sessionId: process.env.sessionId,
};
const lexClient = new LexRuntimeV2Client();
const command = new RecognizeTextCommand(lexParams);

const data = await lexClient.send(command);

return {
  statusCode: 200,
  msg: data['messages'][0].content,
};

Lambda 함수를 이용해 ChatGPT API를 이용하기

2023년 3월에 ChatGPT의 공식 오픈 API가 공개되었습니다. 새로운 API의 경로는 "/v1/chat/completions"이며, "gpt-3.5-turbo" 모델을 사용합니다. 이 모델은 기존 모델인 "text-davinci-003"에 비하여, 90% 낮은 비용으로 활용할 수 있으나 ChatGPT에서 날씨를 검색한거나 하는 작업은 할 수 없습니다. 여기서는 ChatGPT 공식 API와 함께 채팅중 검색을 지원하는 "text-davinci-003" 모델을 사용하는 방법을 설명합니다.

gpt-3.5-turbo 모델 사용하기

OpenAI가 제공하는 ChatGPT API인 "v1/chat/completions"로 HTTPS POST로 요청을 수행합니다. 이를 위해 여기서는 fetch를 사용합니다. 이때 ChatGPT에 전달하는 요청의 header에는 아래와 같이 Authorization과 Content-Type을 포함하여야 합니다. Authorization에 필요한 API Key는 OpenAI: API Key에서 발급받아서 환경변수로 저장하여 사용합니다. 메시지 요청시 role은 ChatGPT API Transition Guide에 따라 "user", "system", "assistant"로 지정할 수 있습니다. 상세 코드는 여기(index.mjs)에서 확인할 수 있습니다.

import fetch from 'node-fetch';

const apiKey = process.env.OPENAI_API_KEY

let msg = "";
const res = await fetch('https://api.openai.com/v1/chat/completions',{
  method: "POST",
  headers: {
    "Authorization": "Bearer "+apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "model": "gpt-3.5-turbo",
    "messages": [
      {"role": "user", "content": prompt},
    ],
  }),
});

ChatGPT가 보내온 응답 메시지를 꺼내서, Lex에 보낼때에는 아래의 포맷으로 전송하여야 합니다. 이때 sessionState에는 dialogAction, intent가 포함하여야 하며, intent name을 입력(event)에서 추출하여 넣어주어야 합니다. 또한 ChatGPT의 응답메시지는 "messages"의 "content"에 넣어서 아래처럼 전달합니다.

if (res.ok) {
  const data = await res.json();
  console.log("output: ", data.choices[0]);

  msg = `[ChatGPT] ${data.choices[0].message.content}`;
  console.log("msg: "+ msg);

  const intentName = event.interpretations[0].intent.name; // intent name
  response = {
    "sessionState": {
      "dialogAction": {
        "type": "Close"
      },
      "intent": {
        "confirmationState": "Confirmed",
        "name": intentName,
        "state": "Fulfilled",            
      },          
    },
    "messages": [
      {
        "contentType": "PlainText",
        "content": msg,            
      }
    ]
  }
} 

text-davinci-003 모델 사용하기

"text-davinci-003" 모델은 Completion API에 따라 "v1/completions"을 사용합니다. 여기서는 OpenAI Node.js Library을 이용해 구현합니다. 상세 코드는 여기(index-davinch.mjs)에서 확인할 수 있습니다.

import { Configuration, OpenAIApi } from "openai";

const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});

const openai = new OpenAIApi(configuration);

const models = ['text-davinci-003','code-davinci-002'];
const frequency_penalty = 0.5;
const max_tokens = 2000;
const presence_penalty = 0.1;
const temperature = 0;
const top_p = 1;
const model_name = models[0];
const prompt = event.text;

const params = {
  model: model_name,
  prompt: prompt,
  temperature: temperature, 
  max_tokens: max_tokens, 
  top_p: top_p, 
  frequency_penalty: frequency_penalty,
  presence_penalty: presence_penalty, 
};

const result = await openai.createCompletion(params);
const choices = result.data.choices;
return {
  statusCode: 200,
  id: result.data.id,
  msg: choices[0].text,
};    

Client에서 Chat API 활용하기

Client는 Chat 서버에 RESTful 방식으로 아래와 같이 채팅 메시지를 전송하고 응답이 오면 수신 채팅 버블에 표시 합니다. 여기서 채팅서버의 주소는 CloudFront의 도메인입니다. 상세코드는 여기(chat.js)에서 확인합니다.

function sendRequest(text) {
    const uri = "/chat";
    const xhr = new XMLHttpRequest();

    xhr.open("POST", uri, true);
    xhr.onreadystatechange = () => {
        if (xhr.readyState === 4 && xhr.status === 200) {
            response = JSON.parse(xhr.responseText);
            console.log("response: " + JSON.stringify(response));
            
            addReceivedMessage(response.msg)
        }
    };

    var requestObj = {"text":text}
    console.log("request: " + JSON.stringify(requestObj));

    var blob = new Blob([JSON.stringify(requestObj)], {type: 'application/json'});

    xhr.send(blob);            
}

AWS CDK로 리소스 생성 준비

여기서는 typescript를 이용하여 AWS CDK를 구성합니다. 상세 코드는 여기(cdk-chatbot-stack.ts)에서 확인할 수 있습니다.

Lex에 대한 Lambda 함수는 아래와 같이 정의합니다. environment에 botId, botAliasId를 포함하여야 합니다. 여기서는 한국어로 된 chatbot을 이용하므로 아래와 같이 localeId로 "ko_KR"를 지정합니다. 이 Lambda 함수는 Lex와 API Gateway에 대한 퍼미션을 가져야 합니다.

// Lambda for lex
const lambdaLex = new lambda.Function(this, 'lambda-function-lex', {
  description: 'lambda for chat',
  functionName: 'lambda-function-lex',
  handler: 'index.handler',
  runtime: lambda.Runtime.NODEJS_18_X,
  code: lambda.Code.fromAsset(path.join(__dirname, '../../lambda-lex')),
  timeout: cdk.Duration.seconds(120),
  environment: {
    botId: "BSZQXD0ABN",
    botAliasId: "TSTALIASID",
    localeId: "ko_KR", // en_US
    sessionId: "mysession-01",
  }
});     
const lexPolicy = new iam.PolicyStatement({  
  actions: ['lex:*'],
  resources: ['*'],
});
lambdaLex.role?.attachInlinePolicy(
  new iam.Policy(this, 'rekognition-policy', {
    statements: [lexPolicy],
  }),
);
// permission for api Gateway
lambdaLex.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com'));    

Lex의 입력은 API Gateway를 이용하여 아래와 같이 "/chat" 리소스로 POST method를 통해 받게 설정합니다.

const api = new apiGateway.RestApi(this, 'api-chatbot', {
  description: 'API Gateway for chatbot',
  endpointTypes: [apiGateway.EndpointType.REGIONAL],
  deployOptions: {
    stageName: stage,
  },
});  

const chat = api.root.addResource('chat');
chat.addMethod('POST', new apiGateway.LambdaIntegration(lambdaLex, {
  passthroughBehavior: apiGateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
  credentialsRole: role,
  integrationResponses: [{
    statusCode: '200',
  }], 
  proxy:false, 
}), {
  methodResponses: [   
    {
      statusCode: '200',
      responseModels: {
        'application/json': apiGateway.Model.EMPTY_MODEL,
      }, 
    }
  ]
}); 

CORS를 우회하기 위하여 CloudFront에 아래와 같이 "/chat" 리소스에 대한 behavior를 등록합니다.

distribution.addBehavior("/chat", new origins.RestApiOrigin(api), {
  cachePolicy: cloudFront.CachePolicy.CACHING_DISABLED,
  allowedMethods: cloudFront.AllowedMethods.ALLOW_ALL,  
  viewerProtocolPolicy: cloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
});

ChatGPT에 텍스트를 전송하여 응답을 받는 Lambda 함수를 아래와 같이 준비합니다. 여기서 OPENAI_API_KEY는 OpenAI에서 발급받은 API Key 입니다.

const lambdachat = new lambda.Function(this, 'lambda-chatgpt', {
  description: 'lambda for chatgpt',
  functionName: 'lambda-chatgpt',
  handler: 'index.handler',
  runtime: lambda.Runtime.NODEJS_18_X,
  code: lambda.Code.fromAsset(path.join(__dirname, '../../lambda-chatgpt')),
  timeout: cdk.Duration.seconds(120),
  environment: {
    OPENAI_API_KEY: "123456",
  }
});   

직접 실습 해보기

Cloud9 개발환경 준비하기

편의상 서울 리전에서 Cloud9을 이용하여 배포준비를 합니다. Cloud9은 브라우저에서 코드를 작성, 실행 및 디버깅을 할 수 있는 편리한 환경을 제공합니다. Cloud9 console로 진입하여 [Create environment]를 선택한 후에 아래처럼 Name을 입력합니다. 여기서는 "Chatbot"이라고 입력하였습니다. 이후 나머지는 기본값을 유지하고 [Create]를 선택합니다.

noname

Cloud9이 생성되면 [Open]후 아래처럼 Terminal을 준비합니다.

noname

전체 코드 다운로드 및 CDK 배포 준비

아래와 같이 소스를 다운로드합니다.

git clone https://github.com/kyopark2014/interactive-chat-using-Lex-and-ChatGPT

CDK 폴더로 이동하여 필요한 라이브러리를 설치합니다. 여기서 aws-cdk-lib은 CDK 2.0 라이브러리입니다.

cd interactive-chat-using-Lex-and-ChatGPT/cdk-chatbot && npm install

CDK를 처음 사용하는 경우에는 아래와 같이 bootstrap을 실행하여야 합니다. 여기서 account-id은 12자리의 Account Number를 의미합니다. AWS 콘솔화면에서 확인하거나, "aws sts get-caller-identity --query account-id --output text" 명령어로 확인할 수 있습니다.

cdk bootstrap aws://account-id/ap-northeast-2

Lex에서 Chatbot의 구현

Amazon Lex 한국어 챗봇 빌드 워크숍Hello World Bot에 따라 HelloWorld Bot을 생성합니다. "Hello World Bot"은 "안녕"이라고 입력하면, 이름을 물어보고 확인하는 간단한 인사봇입니다.

"Hello World Bot" 생성을 완료한 후에, Bot Console에 접속해서 "HelloWorldBot"을 선택합니다. 아래와 같이 botId는 "BSZQXD0ABN"임을 알수 있습니다.

noname

"HelloWorldBot"의 [Aliases]를 선택하면 아래와 같이 Aliases를 알 수 있습니다. 여기서는 "TestBotAlias"를 선택합니다.

noname

아래와 같이 botAliasId가 "TSTALIASID"임을 알 수 있습니다.

noname

환경변수 업데이트

Cloud9으로 돌아가서 왼쪽 파일 탐색기에서 "interactive-chat-using-Lex-and-ChatGPT/cdk-lex/lib/cdk-lex-stack.ts"을 열어서 "Lambda for lex"의 Environment의 botId, botAliasId를 업데이트 합니다. 여기서 sessionId는 현재의 값을 유지하거나 임의의 값을 입력합니다.

noname

또한, "Lambda for chatgpt"의 environment에서 "OPENAI_API_KEY"을 입력합니다. 미리 받은 Key가 없다면 OpenAI: API Key에서 발급받아서 입력합니다.

noname

배포하기

이제 CDK로 전체 인프라를 생성합니다.

cdk deploy

정상적으로 설치가 되면 아래와 같은 "Output"이 보여집니다. 여기서 distributionDomainName은 "d3ndv6lhze8yc5.cloudfront.net"이고, WebUrl은 "https://d3ndv6lhze8yc5.cloudfront.net/chat.html"임을 알 수 있습니다.

noname

Lex에서 Lambda 함수로 ChatGPT를 호출하도록 설정하기

AWS Lex Console에서 "HellowWorldBot"을 선택하여 "Aliases"에서 [Languages]를 선택하여 아래처럼 [Korean(South Korea)]를 선택합니다.

noname

아래처럼 [Souce]로 "lambda-chatgpt"를 선택하고, [Lambda function version or alias]은 "$LATEST"를 선택하고, [Save]를 선택합니다.

noname

이후 "HellowWorldBot"의 [Intents]에서 아래처럼 [FallbackIntent]를 선택합니다.

noname

이후 아래로 스크롤하여 Fulfillment에서 [Advanced options]를 선택한 후, 아래 팝업의 [Use a Lambda function for fulfillment]을 Enable 합니다.

noname

화면 상단의 [Build]를 선택하여 변경된 내용을 적용합니다.

noname

실행하기

WebUrl의 ""https://d3ndv6lhze8yc5.cloudfront.net/chat.html" 으로 브라우저에서 채팅화면으로 접속합니다. 아래와 같이 웹브라우저에서 Lex와 채팅을 할 수 있습니다. 아래의 첫 입력은 "HelloWorld" Bot에 있는 이름을 확인하는 Intent 동작입니다. 이후 나오는 질문인 "Lex에 대해 설명해줘"는 "HelloWorld" Bot에 의도(intent)로 등록되지 않은 질문이므로 ChatGPT에 문의하여 아래와 같은 결과를 사용자에게 보여줄 수 있었습니다. ChatGPT를 제공하는 OpenAI 서버의 응답속도가 지연되면, 웹 브라우저의 설정에 따라 ChatGPT로의 응답을 일부 수신하지 못할 수 있습니다.

noname

리소스 정리하기

더이상 인프라를 사용하지 않는 경우에 아래처럼 모든 리소스를 삭제할 수 있습니다.

cdk destroy

결론

Amazon Lex와 ChatGPT를 이용하여 대화형 Chatbot을 구현하였고 인프라를 효과적으로 개발 및 운용할 수 있도록 AWS CDK를 이용하는 방법을 설명하였습니다. ChatGPT를 이용함으로써 Lex에서 인식되지 못한 의도(intent)에도 적절한 응답을 사용자에게 줄수 있어서 사용성을 개선할 수 있습니다. ChatGPT는 이미 우수한 대화능력을 증명하였고, 현재 다양한 GPT 모델들이 발표되고 있습니다. 따라서 이러한 인공지능 모델을 Lex와 같은 Chatbot 서비스에 도입함으로써 사용자의 사용성을 개선하고 더 좋은 서비스를 제공할 수 있을것으로 기대됩니다.

About

The project shows how to develop an interactive chatbot using Lex and open APIs. Lex provides operations using User Intent but it may response the dupulicated answers for unknown intents. Thus the proposed architecture uses Open APIs such as ChatGPT.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published