Ssul's Blog

[ChatGPT, DALLE2] 인공지능 카카오챗봇 만들기 본문

dev/기능구현

[ChatGPT, DALLE2] 인공지능 카카오챗봇 만들기

Ssul 2024. 2. 7. 22:08

0. 챗봇을 쉽게 만들수 있는 세상

- 그동안은 NLP연구와 알고리즘을 통해서, 챗봇이 동작하였다.

- 하지만, GPT가 나오면서, 그동안 챗봇기술은 모두 비슷한 출발 선상에 서게 되었음

- 정말로 비슷한 출발 선상에 서게 되었을까? 그렇다면 나만의 상담챗봇, 그림그려주는 챗봇을 제작해보자!

- 카카오챗봇 + openai + lambda(aws서버)를 통해서 24시간 동작하는 챗봇 만들기

AI챗봇!! 근데 토끼를 안그려주넵;;;

 

1. 카카오채널 셋팅

- business.kakao.com 방문

- 오른쪽 상단 내 비즈니스 클릭

 

- 왼쪽 상단 채널 클릭

- 새 채널 만들기 클릭

 

- 작성해서 확인 클릭(채널 생성 완료)

 

 

- 생성한 채널로 들어가서

- 오른쪽 하단에 채널공개, 검색허용 on으로 셋팅

 

 

- 왼쪽상단 채널 클릭 -> 서랍형태로 챗봇나오면 클릭

 

- 봇만들기 클릭

- 카카오톡 챗봇 클릭

- 챗봇 생성

 

 

- 카카오 챗봇 생성완료 화면

왼쪽에 스킬만 기억하면 된다.

 

2. 기능 구현

import json
import openai
import threading
import time
import queue as q
import os

# OpenAI API KEY
openai.api_key = os.environ['OPENAI_API']

- 필요한 라이브러리 가져오고,

- OPENAI 키 가져오기

 

def getTextFromGPT(prompt):
    messages_prompt = [{"role": "system", "content": 'You are a thoughtful assistant. Respond to all input in 25 words and answer in korea'}]
    messages_prompt += [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=messages_prompt)
    message = response["choices"][0]["message"]["content"]
    return message

# DALLE.2에게 질문/그림 URL 받기
def getImageURLFromDALLE(prompt):
    response = openai.Image.create(prompt=prompt,n=1,size="512x512")
    image_url = response['data'][0]['url']
    return image_url

- 프롬프트를 받아서, Chatgpt가 응답해주는 함수

- 프롬프트를 받아서 Dalle2가 그림그려주는 함수

 

# 카카오채널에 gpt의 메세지 전송
def textResponseFormat(bot_response):
    response = {'version': '2.0', 'template': {
        'outputs': [{'simpleText': {'text': bot_response}}], 'quickReplies': []}}
    return response

# 카카오채널에 dalle 이미지 전송
def imageResponseFormat(bot_response, prompt):
    output_text = prompt + "내용에 관한 이미지 입니다"
    response = {'version': '2.0', 'template': {
        'outputs': [{'simpleImage': {'imageUrl': bot_response, 'altText': output_text}}], 'quickReplies': []}}
    return response

- 챗봇에 gpt가 응답한 답을 카카오챗봇 형식에 맞게 변화해서 응답

- 챗봇이 그린 그림을 카카오챗봇 형식에 맞게 변화해서 응답

 

# 응답시간 초과시 먼저 답변
def timeover():
    response = {'version': '2.0', 'template': {
        'outputs': [
            {
                'simpleText': {
                    'text': "아직 제가 생각이 끝나지 않았어요\n잠시 후 아래 말풍선을 눌러주세요"
                }
            }
        ], 'quickReplies': [
            # 생각이 다 끝남? 버튼생성
            {
                'action': 'message',
                'label': '생각이 다 끝남?',
                'messageText': '생각이 다 끝남?'
            }
        ]
    }}
    return response

- 카카오 챗봇의 경우 5초간 응답이 없으면 에러가 발생

- 하지만, 달리나 gpt의 응답같은경우 5초가 넘는 경우가 많음

- 이를 해결하는 방법이 필요.

- 5초 이내에 대답을 못할때, 채널 챗봇에 버튼을 생성하는 함수

 

 

# 메인 함수
def lambda_handler(event, context):

    run_flag = False
    start_time = time.time()
    # 카카오 정보 저장
    kakaorequest = json.loads(event['body'])
    # 응답 결과를 저장하기 위한 텍스트 파일 생성

    filename ="/tmp/botlog.txt"
    if not os.path.exists(filename):
        with open(filename, "w") as f:
            f.write("")
    else:
        print("File Exists")    

    # 답변 생성 함수 실행
    # 큐 생성(퍼스트 인, 퍼스트 아웃)
    response_queue = q.Queue()
    request_respond = threading.Thread(target=responseOpenAI, args=(kakaorequest, response_queue, filename))
    # responseOpenAI 함수를 비동기적으로 만들어서, 실행. 결과 기다리지 않고 다음줄 실행
    request_respond.start()

    # 응답이 3.5초 이내에 오면
    while(time.time() - start_time < 3.5):
        # 답변이 있으면,
        if not response_queue.empty():
            response = response_queue.get()
            run_flag = True
            break
        # 안정적인 구동을 위한 딜레이 타임 설정
        time.sleep(0.01)

    # 응답이 3.5초 이내에 오지 않으면
    if run_flag == False:
        # 생각 끝남버튼 출력함수 호출
        response = timeover()

    return{
        'statusCode':200,
        'body': json.dumps(response),
        'headers': {
            'Access-Control-Allow-Origin': '*',
        }
    }

# 답변/사진 요청 및 응답 확인 함수
def responseOpenAI(request,response_queue,filename):
    # 사용자다 버튼을 클릭하여 답변 완성 여부를 다시 봤을 시
    if '생각 다 끝났나요?' in request["userRequest"]["utterance"]:
        # 텍스트 파일 열기
        with open(filename) as f:
            last_update = f.read()
        # 텍스트 파일 내 저장된 정보가 있을 경우
        if len(last_update.split())>1:
            kind = last_update.split()[0]  
            if kind == "img":
                bot_res, prompt = last_update.split()[1],last_update.split()[2]
                response_queue.put(imageResponseFormat(bot_res,prompt))
            else:
                bot_res = last_update[4:]
                response_queue.put(textResponseFormat(bot_res))
            dbReset(filename)

    # 이미지 생성을 요청한 경우
    elif '/img' in request["userRequest"]["utterance"]:
        dbReset(filename)
        prompt = request["userRequest"]["utterance"].replace("/img", "")
        bot_res = getImageURLFromDALLE(prompt)
        response_queue.put(imageResponseFormat(bot_res,prompt))
        save_log = "img"+ " " + str(bot_res) + " " + str(prompt)
        with open(filename, 'w') as f:
            f.write(save_log)

    # ChatGPT 답변을 요청한 경우
    elif '/ask' in request["userRequest"]["utterance"]:
        dbReset(filename)
        prompt = request["userRequest"]["utterance"].replace("/ask", "")
        bot_res = getTextFromGPT(prompt)
        response_queue.put(textResponseFormat(bot_res))

        save_log = "ask"+ " " + str(bot_res)
        with open(filename, 'w') as f:
            f.write(save_log)
            
    #아무 답변 요청이 없는 채팅일 경우
    else:
        # 기본 response 값
        base_response = {'version': '2.0', 'template': {'outputs': [], 'quickReplies': []}}
        response_queue.put(base_response)

- 사람의 채팅이 발생하면, lambda_handler호출

- 질문과 응답(또는 이미지)을 담아둘 파일 생성

- Python의 큐(Queue)와 스레딩(threading) 모듈을 사용하여 비동기적으로 작업을 처리

response_queue = q.Queue()
request_respond = threading.Thread(target=responseOpenAI, args=(kakaorequest, response_queue,filename))
request_respond.start()

- q를 생성, responseOpenAI함수를 비동기로 호출. 호출을 마치지 않았어도 아래 코드로 이동

 

- 답변이 3.5초 이내에 돌아 왔으면, q에 저장된 응답을 꺼내어서 응답한다

- 답변이 3.5초 이내에 안오면, timeover()를 통해서 "생각이 다 끝남?"버튼 출력

 

- responseOpenAI는 파일과 q를 받아서

- "생각이 다 끝남?"버튼 클릭이면, 파일에 저장된 응답 결과를 응답해준다

이미지의 경우: img + 이미지주소 + 사용자요청채팅 구조

ChatGPT응답의 경우: ask + 응답 구조

- 문서에 저장된 결과를 카카오챗봇 채팅 구조로 응답해준다

 

- 이미지 요청('/img')이면,

- getImageURLFromDALLE함수를 호출하여, 그림을 받고, 받은 그림 결과는 q에 넣어준다

- 파일에 저장한다

 

- gpt물음 ('/ask')이면,

- getTextFromGPT함수를 호출하여, Chatgpt에게 응답을 받고, 받은 응답을 q에 넣어준다

- 파일에 저장한다

 

- "생각이 다 끝남?" 버튼이 클릭되면, 위에서 저장된 파일의 결과를 채팅으로 돌려주고,

- 해당 내용이 사용되었으니, dbReset(filename)으로 파일을 비운다

def dbReset(filename):
    with open(filename, 'w') as f:
        f.write("")

 

3. AWS lambda + api gateway 셋팅(24시간 응답 서버 구성)

- lambda 셋팅 전, 카카오 챗봇이 작동되는 구조

(1) 사용자가 카카오톡 방에 질문 '/ask 상담가능?' 입력

(2) 카카오api서버가 json형태의 메세지 발송(위에서 챗봇 설정에서 </>스킬 메뉴에서 어디로 보낼지 설정해줌)

여기를 나의 aws api gateway-lambda로 설정해주면 됨

요기서 URL에 입력된 주소로 채팅방에 입력되는 메세지가 전달됨

(3) api gateway - lambda로 들어온 메세지를 코드의 "lambda_handler"가 호출

(4) 코드에 있는 내용을 기반으로 gpt 또는 dalle2를 호출

(5) gpt, dalle2가 보내온 응답을, 카카오챗봇 json구조로 바꿔서, response

(6) 해당 response내용을 채팅방에 챗봇이 출력(이미지 출력 또는 gpt응답 출력)

 

 

- aws lambda로 들어가서, 함수 생성 클릭

- 함수이름, 런타임, 아키텍처 설정 후 함수 생성

 

 

- 함수 코드 작성

- 배포 Deploy 클릭

 

 

- 구성>환경변수>편집 들어가서

- OPENAI키 셋팅

 

 

- lambda에 파이썬 패키지 설치가 필요

- pip install openai를 할수 없으니...

- 내 로컬 venv폴더로 이동: cd venv/lib/python3.10/site-packages/

- 해당 위치에 있는 모든 파일/폴더 zip으로 압축(m1등 실리콘은 추가 조치가 필요함)

- lambda>계층>계층생성 들어가서 zip파일 업로드(나의 venv내용으로 lambda구성하는 개념)

 

 

 

- 생성된 lambda 밑에 layers 클릭

- add a layer 클릭

- 방금 생성한 계층을 선택해서 추가해주기

 

 

- lambda기본 제한시간 3초에서 1분으로 변경하기

- 구성>일반구성>편집 눌러서 제한시간 1분으로 변경

 

 

- lambda가 완성이 되었으니,

- 카카오톡에서 보내온 채팅메세지를 받아서 lambda에게 전달할 api gateway설정하자

- aws api gateway 클릭>HTTP API 구축 클릭

- 통합추가 클릭>Lambda선택>내가 만든 Lambda선택> 다음 클릭

 

 

- POST로 바꾸고, 다음

 

- 그다음 그대로 다음 클릭

- 생성도 그대로 다음

- API Gateway생성 완료

 

 

- 다시 Lambda로 돌아오면

- Gateway가 잘 연결된 것을 확인할 수 있다

 

 

- API Gateway 클릭

- 카카오톡 채팅방이 사용자의 입력을 보낼 api gateway 주소를 확인할 수 있다.

- 해당 주소를 복사한다

 

 

4. 카카오 챗봇에 24시간 응답해주는 lambda서비스 연결해주기

- 사용자입력>카카오api>aws api gateway>lambda>openai(gpt,dalle)로 사용자의 입력이 전달

- gpt/dalle응답>lambda가 카카오 챗봇데이터 구조로 리턴> api gateway가 카카오api에게 전달 > 채팅방에 이미지 또는 응답 보이기

- api gateway를 카카오챗봇 api와 연결한다.

 

- URL에 위에서 생성하고, 복사해 놓은 AWS API Gateway 주소를 넣어준다

 

 

 

그러면 AI 챗봇이 완성된다

이렇게 할루시네이션이 담긴 응답을 받을수 있다

 

(lambda 코드: 도서 "진짜 챗GPT API 활용법" ch04 코드 참고)