이 글은 2편으로 나뉘어 게재하며, 1편에서 이어지는 글 입니다.
지난 1편에서 우리는 프롬프트의 조합으로 GPT에 문맥을 전달하여, 그 문맥 내에서 답변을 제한 시키는 방법을 배웠습니다. 그와 동시에 프롬프트 길이의 한계점이 있지만 결국 해결방법이 있고, 그 것을 2편에서 소개하기로 했죠.
과연, 두번째 비밀인 그 해결 방법이라는 것이 무엇 일까요?
혹시 1편만 보고 답을 찾으신 분이 계실까요?
이제 그 답을 찾는 여정을 함께 시작해 봅시다.
글 작성 2023.08 현재: 16k(16000), 32k(32000) 까지 가능해 졌습니다. 그러나 토큰이 많을 수록 비용이 매우 높은 문제가 있어, 여전히 이 글은 유용 할 것입니다.
우리는 매 질문마다 컨텍스트를 붙여야 하므로, 가장 저렴한 GPT 3.5 를 사용 하기로 했습니다. 3.5 를 사용해도 4.0 대비 답변 품질은 큰 차이가 없었습니다.
GPT 모델의 토큰 수의 제한과 비용 문제로 가이드 전체가 아니라, 질문을 통해 관련 있는 정보만 뽑아내어 컨텍스트를 만드는 것이 이 챗봇의 핵심 입니다.
이것을 어떻게 구현할 것인지 필요한 기술을 추려봤는데요.
이렇게 크게 2가지가 필요했습니다.
하나씩 자세히 파보기로 합니다.
일단, Word Embedding을 통해 가이드의 모든 내용을 숫자로 벡터화 해두고, 질문도 숫자로 벡터화 하여 벡터값을 기준으로 유사도 검색을 해보기로 합니다.
유사도 알고리즘은 그냥 많이 쓰는 코싸인 유사도
를 사용하고요.
OpenAI
가 파인튜닝
도 제공하는데 Embedding
도 방법을 제시 할 것 같았습니다.parquet
포맷을 채택 합니다.이런 작업에 최적화된 라이브러리는 pandas
니까, 파이썬으로 백엔드 API를 만듭니다.
챗봇의 창을 제공하는 웹 스택은 이것 저것 재기 귀찮으니 그냥 next.js
를 쓰고 tailwind
만 살짝 붙입니다.
"idx","subject","body"
1799,"아임웹 시작 안내서","<h4>코딩을 몰라도, 디자인을 몰라도 멋진 웹사이트를 만들고 싶다면?</h4><p>아임웹 가이드를 활용해 방문자의 관심을 끌 수 있는 멋진 웹사이트, 쇼핑몰을 만들어 보세요. 업무용 웹사이트부터 개인을 위한 포트폴리오, 단체 및 동호회를 위한 커뮤니티 사이트까지 아임웹을 사용하면 쉽게 시작할 수 있습니다.</p><p><a href=""https://imweb.me/faq"
위 처럼 생긴 데이터에서 body
의 내용을 embedding
으로 벡터화 하면,
idx,processed_text,vectors
1799,"코딩을 몰라도...","[-0.010545337,0.005581888,0.017877342,-0.044961132,0.00041083142,0.006155697,-0.022735594,-0.009372216,0.015263323,0.0066306833,-0.0119288545,0.0021709113,-0.0005076617,-0.0016815795,-0.017533056,0.014638509,0.023028873,0.005671147,-0.016449194,-0.018948453,-0.007140736,0.0097547555,0.013082848,-0.0025853289,-0.009920523,0.0135163935,0.0091299405,-0.024877815,-0.015939143,0.006372...]"
처럼 숫자 배열 형태의 벡터값이 추출 됩니다.
압축은 내용상 텍스트 길이를 줄이는 것이지만, 이 글에서는 연관 내용을 컨텍스트로 뽑기 위한 기술로 통칭 합니다.
가이드의 여러 글 중에, 글 하나의 내용만으로도 너무 길다면, 전체 문단을 컨텍스트로 넣는 것 보다 비슷한 연관성을 가진 여러 문단에서 가져오는 것이 더 정확한 답변에 도움이 될 수 있죠.
따라서, 우리의 경우 임베딩을 할때 문단 단위로 잘라서 분리 시키는 것이 큰 도움이 됐습니다. 문단 분리는 이 글의 범위를 넘지만 한번 연구해 보세요!
OpenAI
의 토큰 제한이4092
라는 것은, 프롬프트 입력과 출력이 모두 포함된 총 토큰수 입니다. 결국 답변으로 돌아올1000
토큰 정도의 여유는 두기 위해, 결국 질문을 포함한 총 컨텍스트는3000
토큰 정도로 압축 해야 합니다.
그런데, 텍스트의 토큰이 몇개인지 어떻게 계산 할까요?
OpenAI
가 사용하는 토크나이저는 오픈소스로 공개 되어 있습니다.
GPT3 엔코더
라고 하는 것으로 이 글의 목적은 토크나이저 소개가 아니므로, 어떤 라이브러리를 쓰면 되는지만 파악합시다.
OpenAI
에서는 테스트 해볼 수 있는 Playground 도 제공 됩니다.
또한 npm 모듈로 제공하는 토크나이저도 있습니다.
위 모듈로 어떤 문자열의 토큰 갯수를 GPT API와 같은 방식으로 얻을 수 있습니다.
모듈 코드도 비교적 간단하며, 여러 언어로 제공된 포팅 버전들이 각 언어별로 존재 합니다.
python
과 pandas
를 쓸거고, Rest API
만 제공하면 되니까, 제일 간단한 flask 를 씁니다.
csv
→ 벡터화
-> parquet
변환 하고, 판다스로 불러오고 준비 합니다.
embdeddings API
로 벡터값을 얻고cosine 유사도
를 계산!next.js
으로 npx create-next-app
으로 뚝딱tailwind
로 대충 쓱싹nextjs
가 프록싱 할수 있게 백엔드 코드도 살짝 붙입니다.그런데, 답변의 응답이 답답 합니다.
뭐 하나 물어보면, 답이 오는데 까지 15초 정도 걸립니다. 15초 후에 팍! 하고 뜨니까 더 답답하네요.
아, ChatGPT가 한글자 한글자 답변을 찍어주는게 일부러 그런게 아니라 GPT의 특징 이었습니다.
그렇다면, 우리도 API가 한토큰 한토큰 주는대로 바로 클라이언트에게 흘리기로 합니다.
이럴 때는 스트리밍 기술이 딱 입니다.
스트리밍을 구현 하자고 Web Socket
을 쓰자니 토이프로젝트에 오버엔지니어링 입니다.
Ajax Pulling 을 쓰자니 프론트엔드 코드가 복잡해 지네요.
뭐 심플한 방법 없을까 고민 해봅니다.
결국, SSE (Server Sent Events)
가 제일 간단해서 결정 합니다.
Response
를 질질 끄는 식으로 조금씩 흘려줄수 있는,이걸로 구현 하기로 합니다.
flask
에서 OPENAI
의 API 자체가 HTTP stream
을 지원하니, yield
를 통해 한 토큰씩 보냅니다.nextjs
의 백엔드에서 axios
로 SSE 이벤트
를 받고, 클라이언트에는 HTTP stream
으로 흘립니다.금방 구현 됩니다.
고민 할 것 없이 백엔드/프론트 둘 다 AWS ECS
로 결정 합니다.
혹자는 next.js
의 vercel
을 추천 하기도 하지만, vercel
은 python 백엔드 지원
이 영 불편 합니다.
ECS로 결정하고 도커로 굽기 시작 합니다.
그런데, 프론트 따로, 백엔드 따로 만들기 보다는 둘을 합쳐 프론트인 next.js 와 백엔드인 flask 가 한 몸처럼 움직이는게 하는 것이 중요 합니다.
서로 SSE
로 이벤트를 주고 받고 하는데, 프론트와 백엔드 스택이 분리되면 CF
와 ALB
같은 추가 Layer
를 통과해야 하므로 SSE
나 Stream
처리에 제한이 있을 수 있기 때문 입니다.
둘을 하나의 ECS task
로 합치고 서로 한 몸으로 만들어서 배포 합니다.
이렇게, 아임웹 챗봇 고객센터가 탄생했습니다.
이렇게 여러분은 답을 찾고, 훌륭한 AI 챗봇을 가지게 됐습니다.
아임웹 모든 엔지니어께는 이 챗봇 소스코드를 비롯하여 제가 만든 모든 코드를 공개 합니다.
글 작성은 23년 초에 되어, 8월 현재는 소소한 변경이 있습니다.
모든 개발에 단 일주일 이면 충분 했습니다.
트렌드를 놓치지 않는 가장 좋은 방법은, 직접 사용해 보는 것입니다.
엔지니어는 직접 만들어보는 것이 최고의 습득 방법임을 다시 한번 알려 드리고 싶네요.
즐겁고, 유익한 나날 되세요.
매튜 드림.
술술 읽히는 글 잘 일었습니다!
혹시 백엔드 언어로 파이썬을 선택하셨다가 고랭으로 변경하신 특별한 이유가 있으실까요??