ChatGpt SlackApp with AWS CloudFormation

‍최정민·2023년 4월 18일
3
post-thumbnail

이번 글에서는 AWS CloudFormation을 이용해, 별도의 코딩 없이 간단하게 ChatGPT SlackApp을 만드는 방법을 소개하겠습니다.!


1. Overview

  • Slack에서 사용자가 Slash Command를 이용해 AWS APIGATEWAY로 접근
  • AWS APIGATEWAY에서 메시지와 함께, AWS Lambda를 실행시킴
  • 첫번째 AWS Lambda에서는 두번째 람다를 호출한 뒤, Slash Command에 응답을 함.
  • 두번째 AWS Lambda에서는 OpenAI API 호출한 뒤, 응답을 Slack에 Post

    AWS 환경에 있는 Resource는 AWS CloudFormation을 통해 전부 설치됨 (별도의 설정 X)

Slack Slash Command는 3초안에 응답이 이루어져야함
PT엔진으로 검색할시 대부분 3초이상이 걸림
응답을 하는 AWS Lambda 따로, 검색 한 뒤, 결과를 Slack에 Post하는 AWS Lambda 따로


2. Slack App

1) Slack App 생성

  • api.slack.com에 접속하여 Create an app 클릭

  • From scratch 선택

  • SlackApp Name & workspace 설정

2) Slack APP Scope 설정

  • chat:write 추가
    (slach command는 AWS CloudFomation을 이용해 AWS APIGATEWAY를 설치한 뒤 설정)

3) Install to Workspace

  • Install to Workspace

  • Token을 획득한다
    (추후 AWS Lambda의 환경 변수로 설정 될 예정)


3. Open AI API keys

1) OpenAI 회원가입

  • OpenAI에서 회원가입을 한다

    • 일정 수준까지는 무료로 사용가능
  • OpenAI apikey 에서 api keys 생성
    (추후 AWS Lambda의 환경 변수로 설정 될 예정)


4. AWS CloudFormation

위 링크 클릭시 다음과 같은 화면이 나옵니다.

Parameters
  • ApiGatewayName
    • AWS ApiGateway의 이름
  • LambdaFunctionName1
    • Slack->ApiGateway를 거쳐서 실행되는 AWS Lambda Function의 이름
    • Default : SlackToLambda
  • Lambda FunctionName2
    • LambdaFunctionName1에서 호출되어, OpenAPI요청을하고 Slack으로 보내는 AWS Lambda Function의 이름
    • Default : LambdaToChatGPT
  • LambdMemorySize1
    • LambdaFunctionName1의 MemorySize
    • Default : 1024 (mb)
  • LambdaMemorySize2
    • LambdaFunctionName2의 MemorySize
    • Default : 1024 (mb)
  • LambdaTimeOut1
    • LambdaFunctionName1의 Timeout
    • 3초이상 필요
    • Default : 5 (s)
  • LambdaTimeOut2
    • LambdaFunctionName2의 Timeout
    • 약 2분이상 필요
    • Default : 300(s)
  • OpenaiKey
    • 이전단계에서 발급받은 Openai API KEY
  • SlackCannel
    • 메시지를 입력하고 받을 Slack Channel
    • #포함
  • SlackToken
    • 이전 단계에서 발급받은 Slack App Token

AWS CloudFormation Template
Parameters:
  ApiGatewayName:
    Type: String
    Default: ChatGPTAPI
  OpenaiKey:
    Description: "OpenAI Key"
    Type: String
  SlackToken:
    Description: "Slack Token" 
    Type: String
  SlackChannel:
    Description: "Slack Channel name import #"
    Type: String
    Default: "#mayfgpt"
  LambdaFunctionName1:
    Type: String
    Default: "SlackToLambda"
  LambdaFunctionName2:
    Type: String
    Default: "LambdaToChatGPT"
  LambdaMemorySize1:
    Type: Number
    Default: 1024
    MinValue: 128
    MaxValue: 3000
  LambdaTimeOut1:
    Type: Number
    Default: 5
  LambdaMemorySize2:
    Type: Number
    Default: 1024
    MinValue: 128
    MaxValue: 3000
  LambdaTimeOut2:
    Type: Number
    Default: 300
  
  
  


Resources:
  ChatGPTApiGateway:
    Type: AWS::ApiGateway::RestApi
    Properties:
      ApiKeySourceType: HEADER
      Description: ChatGPT APIGATEWAY
      BinaryMediaTypes:
        - application/json
      EndpointConfiguration:
        Types:
          - EDGE
      Name: 
        Ref : ApiGatewayName
  
  ApiGatewayResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt ChatGPTApiGateway.RootResourceId
      PathPart: 'gpt'
      RestApiId: !Ref ChatGPTApiGateway

  ApiGatewayMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      ApiKeyRequired: false
      AuthorizationType: NONE
      HttpMethod: POST
      Integration:
        ConnectionType: INTERNET
        Credentials: !GetAtt ApiGatewayIamRole.Arn
        IntegrationHttpMethod: POST
        PassthroughBehavior: WHEN_NO_MATCH
        TimeoutInMillis: 29000
        Type: AWS_PROXY
        Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SlackToLambda.Arn}/invocations'
      OperationName: 'lambda'
      ResourceId: !Ref ApiGatewayResource
      RestApiId: !Ref ChatGPTApiGateway

  ApiGatewayModel:
    Type: AWS::ApiGateway::Model
    Properties:
      ContentType: 'application/json'
      RestApiId: !Ref ChatGPTApiGateway
      Schema: 
        title: DataModel
        type: object
        properties:
          username:
            type: string
          text:
            type: string
           
  ApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref ApiGatewayDeployment
      Description: Stage for chatGPT
      RestApiId: !Ref ChatGPTApiGateway
      StageName: 'chat'

  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: ApiGatewayMethod
    Properties:
      Description: Lambda API Deployment
      RestApiId: !Ref ChatGPTApiGateway 

  ApiGatewayIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ''
            Effect: 'Allow'
            Principal:
              Service:
                - 'apigateway.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      Policies:
        - PolicyName: LambdaAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action: 'lambda:*'
                Resource: !GetAtt SlackToLambda.Arn

  ApiGatewayPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref SlackToLambda
      Principal: "apigateway.amazonaws.com"
      SourceArn: 
        !Join 
          - ''
          - - 'arn:aws:execute-api'
            - ':'
            - !Ref AWS::Region
            - ':'
            - !Ref AWS::AccountId
            - ':'
            - !Ref ChatGPTApiGateway
            - '/*/POST/gpt'

  

  LambdaExecutionRole1:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              #Lambda Basic Excution Role
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
              - Effect: Allow
                Action:
                  - "lambda:InvokeFunction"
                Resource: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaToChatGPT}"
          PolicyName: chatgptpolicy1

  LambdaExecutionRole2:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              #Lambda Basic Excution Role
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
          PolicyName: chatgptpolicy2

  SlackToLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName :
        Ref : LambdaFunctionName1
      Handler: lambda_function.lambda_handler
      Runtime: python3.9
      Code:
        S3Bucket: !Sub 'chatgptcode'
        S3Key: !Sub 'code1.zip'
      MemorySize: 
        Ref : LambdaMemorySize1
      Timeout: 
        Ref : LambdaTimeOut1
      Role: !GetAtt LambdaExecutionRole1.Arn
      Environment:
        Variables:
          LambdaToChatGPT: 
            Ref : LambdaFunctionName2

  LambdaToChatGPT:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName :
        Ref : LambdaFunctionName2
      Handler: lambda_function.lambda_handler
      Runtime: python3.9
      Code:
        S3Bucket: !Sub 'chatgptcode'
        S3Key: !Sub 'code2.zip'
      MemorySize: 
        Ref : LambdaMemorySize2
      Timeout: 
        Ref : LambdaTimeOut2
      Role: !GetAtt LambdaExecutionRole2.Arn
      Environment:
        Variables:
          OPENAI_KEY: 
            Ref : OpenaiKey
          SLACK_TOKEN:
            Ref : SlackToken
          SLACK_CHANNEL:
            Ref : SlackChannel
      Layers:
        - arn:aws:lambda:ap-northeast-2:858869084011:layer:openai:1
        - arn:aws:lambda:ap-northeast-2:858869084011:layer:slack:1

Outputs:
  ApiEndponit:
    Description: "API Endpoint"
    Value: !Sub "https://${ChatGPTApiGateway}.execute-api.${AWS::Region}.amazonaws.com/chat/gpt"

AWS CloudFormation Designer


2) CloudFormation Result

  • 설치완료시 출력에서 Slack App Slash Command에서 사용할 API URL 획득 가능
AWS Lambda SlackToLambda Code
import json
import boto3
import os

def lambda_handler(event, context):
    bodys=event['body'].split("&")
    username=""
    text=""
    for data in bodys:
        if "user_name" in data:
            user_name_split=data.split('=')
            username=user_name_split[1]
        if "text" in data:
            text_split= data.split('=')
            text=text_split[1]
    str=username+"님의 질문: "+text+"이 접수되었습니다."
    print(str)
    
    lambda_client = boto3.client('lambda')    
    
    response = lambda_client.invoke(
            FunctionName=os.environ['LambdaToChatGPT'],
            InvocationType='Event',
            Payload=json.dumps({"username": username, "text": text})
            )    
    
    return {
            'statusCode': 200,
            "headers": {
                "Content-Type": "application/json"
            },
            "body": str
        }

AWS Lambda LambdaToChatGPT Code
import json
import openai
import slack_sdk
import os

def lambda_handler(event, context):
    
    openai.api_key=os.environ['OPENAI_KEY']
    slack_token=os.environ['SLACK_TOKEN']
    channel_name=os.environ['SLACK_CHANNEL']
    
    print(event)
    
    completion = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": event['text']}]
    )
    str=event['username']+"님 의 질문에 대한 답변"+"\n\n"+completion["choices"][0].message.content
    
    print(completion["choices"][0].message.content)
    
    client=slack_sdk.WebClient(token=slack_token)
    client.chat_postMessage(channel=channel_name, text=str)
    
 

5. Slack Slash Command

3) Slack Slash Command 추가

  • Slack App Slash Command를 추가

  • RequestURL에는 이전 단계에서 AWS CloudFormation 설치 획득한 URL 입력

  • Slack Command 추가 시 Workspace에 재설치가 필요함

4) Slack Channel에 APP 추가

5. Result

  • 위의 단계를 모두 거쳐 설정이 완료될 시, /gpt 질문 형식으로 원하는 정보를 얻을 수 있습니다.

최근 ChatGPT의 등장으로 이를 이용한 다양한 시도가 이어지고 있는 것 같습니다.
Slack을 이용한다면 좀 더 편하게 업무와 연결을 시킬 수 있을 것이라 생각해 해당 포스트를 작성하였습니다.
또한 코딩을 모르시는 분들도 간편하게 Slack에서 ChatGPT를 사용하기 위해 AWS CloudFormation을 활용했습니다.

profile
Junior DevOps Engineer

0개의 댓글