0%

요즘 유행하는 ChatGPT로 슬랙봇 만들기 2편

LLM을 사용하여 사내가이드 슬랙봇 만드는 방법을 정리합니다.


시작하며

1편 뒤에 바로 2편을 쓴다고 다짐했었지만 게으름과 귀찮음에 사로잡혀 이제서야 글을 써본다. 마음속의 내 모습은 갓생 사는 직장인인데, 현생에서는 그냥 일에 치이고 스트레스가 통제되지 않는 망나니에 가깝다. 언제나 스스로에게 실망하지만 포기하지 않고 끝까지 글을 써본다. 경험들을 하나씩 저장해두면 나중에 큰 자산이 될 것이라고 일단 믿어본다.

설계도면

이번에 만든 슬랙봇 설계도는 다음과 같다.

  1. 일배치로 원천 데이터를 업데이트 한다.
  2. 원천 데이터가 업데이트 된다면 Vector DB도 업데이트 한다.
  3. Vector DB에는 문서 요약본과 문장(청크)이 구성되어 있다.
  4. 유사도 분석은 문장 단위로 진행된다.
  5. 질문에 가장 유사한 문장이 속한 문서 요약본을 GPT에 전달한다.
  6. 질문과 문서 요약본을 보내 정리된 답을 받은 후 슬랙봇을 통해 전달한다.
  7. 슬랙봇은 @ 태그를 하거나 특정 채널에 글이 올라오면 쓰레드로 답변을 달아준다.

준비물

슬랙봇을 만들기 위해서는 여러가지 준비물이 필요하다.

  • VectorDB를 만들 원천 데이터
  • Slack 앱
  • OpenAi 토큰
  • 응답을 반환해줄 서버
  • 약간의 돈 (또는 회사의 지원)

개발 순서

1. 원천 데이터를 수집 및 가공하기

우리 회사에서는 주요한 가이드를 모두 컨플루언스에 저장하고 관리하고 있었다. 따라서 개인 계정에서 API token을 하나 발급받아서 크롤링을 하면 된다. 크롤링은 빠르게 python의 requests 라이브러리를 사용하여 개발할 수 있다.

나의 경우 먼저 가져오려는 space 이름으로 space id를 가져온 후 해당 스페이스에 있는 모든 page 데이터를 가져왔다. page 데이터의 경우 사용자가 추가하거나 업데이트 할 수 있기에 일배치로 실행하였다.

1
2
3
4
5
# space 이름으로 아이디 가져오기
url = "https://{your-domain}/wiki/api/v2/spaces"

# 특정 space id에 있는 모든 페이지 데이터 가져오기
url = "https://{your-domain}/wiki/api/v2/spaces/{id}/pages"

API 관련된 자세한 설명은 컨플루언스 공식 페이지에서 확인할 수 있다. token의 경우 요청을 날릴 때 HTTPBasicAuth를 사용하여 랩핑한 후 같이 보냈다. 아래 코드는 공식 API 가이드에 있는 예제 코드이다. 공식 페이지에 언어별로 잘 정리되어 있으니 참고하기 바란다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
from requests.auth import HTTPBasicAuth

auth = HTTPBasicAuth("email@example.com", "<api_token>")

headers = {
"Accept": "application/json"
}

response = requests.request(
"GET",
url,
headers=headers,
auth=auth
)

2. Vector DB 생성하기

원천 데이터가 준비 완료되었다면 Vector DB를 생성한다. Vector DB를 생성하는 구조는 다음과 같다.

  1. 원천 데이터 가져오기
  2. 요약본 만들기
  3. 임베딩하기
  4. S3에 저장하기

요약본을 생성하는 것은 랭체인(langchain)의 TokenTextSplitter를 사용하여 Document에 맞게 청크를 생성한 후 각각의 청크를 요약하고 이를 합치는 구조로 만들었다. 특히 load_summarize_chain를 사용할 때 chain_type을 map_reduce로 쓰지 않았는데 이는 테스트해보니 너무 많은 부분이 요약되었기 때문이다. 이 요약본의 목적은 이후 GPT에게 답변을 생성할 때 보내지는 데이터이다.

그 다음 UnstructuredHTMLLoader를 사용하여 HTML 구조의 데이터를 문장 별로 쪼개 train set을 만들었다. 이 문장들을 임베딩하여 이후 유사도 분석에서 사용한다. 임베딩의 경우 OpenAI의 임베더를 사용하였으며, 모델은 text-embedding-3-large를 사용하였다. OpenAI의 임베더의 경우 한번에 처리할 수 있는 문자의 길이가 8192이기 때문에 한 문장이 이를 넘는다면 쪼개서 작업해야 한다.

이렇게 하여 생성된 Vector DB의 컬럼은 다음과 같다.

1
["id", "version", "url", "title", "train", "summary", "content"]

idversion을 추가한 이유는 업데이트 로직을 위함이다. 사용자가 해당 컨플루언스 페이지를 업데이트하면 API로 데이터를 가져올 때 Version 매트릭이 +1 된다. 이를 이용하여 Vector DB도 변동값이 있는 페이지만 다시 생성하는 것이다. 이렇게 작업한 이유는 간단하다. 임베딩에는 돈이 들어간다. 이미 임베딩이 된 페이지를 다시 진행할 필요는 없기 때문이다.

url의 경우에는 슬랙봇이 참고한 컨플루언스 URL을 같이 첨부해서 주기 때문이다. 이렇게 기획한 이유는 할루시네이션 이슈도 있고 더 자세한 정보를 알고 싶은 사용자에게 더 많은 데이터를 주기 위함도 있다.

참고로 요약 요청 시 보내는 프롬프트의 경우 매우 간단하게 설정하였다.

1
2
3
4
5
너는 긴 문서를 정리하는 요약 도우미야. 
내용 무엇도 제외하지 말고 최대한 요약해줘.
그리고 다음 규칙을 무조건 지켜야해.
규칙0. 글에 예외사항이나 Q&A가 있는 경우에는 무조건 가져와줘
규칙1. 글에 시간, 증명자료, 지원여부 관련 설명이 있다면 무조건 가져와줘.

이렇게 만들어진 Vector DB는 S3에 parquet 파일로 저장해둔다.

3. 유사도 분석 로직 개발하기

이제 요리를 위한 재료 준비는 모두 끝났다. 조리 방법만 잘 정하면 된다. Vector DB를 다룰 수 있는 다양한 라이브러리들이 존재하지만 나는 FAISS를 사용했다. 특히 ChromaDB와 FAISS 중 무엇을 쓸지 고민했었는데 FAISS를 사용한 이유로는 다음과 같다.

  • 인덱싱이 빠르다.
  • 코드를 유연하게 개발할 수 있다.
  • CPU 모드를 지원한다.
  • 확장성이 좋다.

나는 이 분야의 전문가가 아니라서 최대한 고수준 API나 기능들을 제공해주는 라이브러리를 쓰고 싶었는데 ChromaDB는 너무 무겁고 생각보다 사용성이 편리하지 않았다. 특히 지금처럼 간단한 인덱싱 작업을 할꺼라면 차라리 FAISS로 개발하는게 훨씬 편한 것 같았다. 이렇게 사용할 라이브러리는 대충 정해졌다. 빠르게 개발을 시작한다.

유사도 측정 알고리즘은 간단하게 내적을 이용하였다. 문장이 길지 않거니와 질문에 대한 정확한 답을 가져오는게 가장 우선시되었기 때문이다. 자세한 내용은 해당 문서에서 확인할 수 있다.

1
index = faiss.IndexIDMap(faiss.IndexFlatIP(encoded_data.shape[1]))

4. 응답을 생성할 백엔드 서버 구축하기

RAG 아키텍처에 맞춰서 질문과 함께 요청이 들어오면 다음과 같이 진행한다.

  1. 질문을 임베딩하고 메모리에 올라가 있는 Vector DB에서 유사도 분석을 진행한다.
  2. 유사도가 가장 높은 20개의 요약본을 GPT에게 보내 최대 3개까지 질문에 대한 답이 있는 문서 ID를 달라고 요청한다.
  3. GPT가 뽑아준 문서 ID의 원본을 다시 GPT에게 보내 사용자에게 반환될 응답을 생성한다.
  4. 슬랙 API로 응답을 보낸다.

이는 간단한 fastapi 앱으로 개발하였다. 백엔드 서버의 경우 데이터팀이 구축 및 관리하고 있는 인프라 클러스터가 있어서 그 위에 살짝 올려두었으며, 매일 Vector DB가 업데이트 되면 다시 parquet 파일을 읽도록 설정해두었다. 이렇게 하면 Vector DB 사이즈가 작은 경우에는 따로 복잡한 미들웨어를 추가할 필요 없이 사용이 가능하다.

5. 슬랙 앱 생성하기

마지막으로 직접적으로 사용자에게 제공할 인터페이스를 만든다. 슬랙 앱을 사용하는 방법은 slack 홈페이지에도 잘 나와있다.

슬랙의 앱 디렉토리로 가서 새로 앱을 생성한 후 설정에 들어가서 몇 가지 조작만 해주면 된다.
나의 경우 Event Subscription 기능을 사용하여 특정 이벤트가 발생한 경우 백엔드 서버로 요청이 올 수 있도록 설정하였다.
Event Subscription 기능에 대해서는 공식 문서에서 자세히 확인할 수 있다.

개발 후기

처음 시작했을 때는 큰 기대 없이 호기심을 해결할 겸 공부할 겸 그렇게 겸사겸사 시작했었다. 하지만 생각보다 좋은 성능을 보여줬고, 사내에서도 반응이 꽤나 좋았다. 가장 잊어버리기 쉬운 와이파이 비밀번호나 프린터 사용법, 근무 규칙 등등의 질문들을 잘 답변해주었고 때로는 특정 업무의 담당자가 누구인지도 잘 설명해주었다. 아직 부족한 부분들도 보이지만 프로토 타입치고는 성능이 좋았다. 다음에는 시간되면 해당 슬랙봇의 성능을 높이기 위한 시도들도 해봐야겠다.