사내 문서를 검색하는 챗봇을 만들었습니다

안녕하세요. 제로백데브 백엔드 개발자 박건우입니다.
지난 글에서 컨플루언스에 흩어진 문서를 통째로 꺼냈습니다. 이번엔 그 문서로 챗봇을 만들었습니다. 슬랙에서 부르면 사내 문서를 뒤져 출처까지 달아 답합니다. 맥미니 한 대에서 계속 돌고 있습니다.

시작할 때는 어려운 쪽이 답을 잘 만드는 일일 거라고 생각했습니다. 아니었습니다. 제일 오래 붙잡은 건 답을 안 하게 만드는 쪽이었습니다. 문서에 없는 걸 물으면 모른다고 하게, 사내 제도를 물었는데 고객사 문서가 걸리면 그건 내놓지 않게 하는 일 말입니다.
문서를 검색되는 형태로 부수기
RAG는 문서를 통째로 LLM에 넣지 않습니다. 조각(청크)으로 쪼개 임베딩해두고, 질문과 가까운 조각만 골라 씁니다. 그래서 어떻게 쪼개느냐가 검색 품질을 거의 다 결정합니다.
백업해둔 424개 파일을 3,382개 청크로 쪼갰는데, 조각마다 앞에 '문서 제목 > 상위 섹션 > 하위 섹션'을 붙였습니다. 떼어낸 조각은 그것만 보면 맥락이 없습니다. "점심은 12시부터입니다" 한 줄만 남으면 무슨 제도 얘긴지 알 수가 없습니다. 지난 편에서 폴더 계층까지 굳이 살려 백업한 게 여기서 쓰였습니다.
"회사 정책"을 물었더니 고객사 문서가 나왔다
사내 위키에는 사내 제도 문서만 있는 게 아닙니다. 진행하는 클라이언트 프로젝트마다 정책서와 설계 문서가 쌓이는데, RAG는 이걸 전부 같은 후보로 봅니다. 그래서 "회사 정책 요약해줘"라고 물으면 어느 클라이언트의 '서비스 정책서'가 딸려 올라옵니다. 검색 입장에선 둘 다 '정책 문서'라 자연스러운 결과인데, 물어본 사람이 원한 답은 아닙니다.
복리후생이나 연차처럼 딱 봐도 사내 얘기인 질문은 회사 문서가 든 곳으로만 범위를 좁혔습니다. 그런데 확신할 문서를 못 찾으면 전체 검색으로 되돌아가는 안전장치를 넣어뒀던 게 문제였습니다. 일부러 빼놓은 클라이언트 문서가 그 길로 고스란히 돌아왔습니다. 되돌아가지 말고, 못 찾으면 못 찾은 대로 두기로 했습니다.
모델은 재보고 골랐습니다
생성 모델은 써보고 정했습니다. 정답이 어느 문서에 있는지 아는 질문 21개로 채점하고, 답이 없는 질문엔 모른다고 하는지도 따로 봤습니다.

로컬 모델이 상용 API와 대등했습니다. 답에 필요한 내용은 검색해서 넣어주니, 모델이 할 일은 주어진 문단을 읽고 정리하는 쪽에 가깝습니다. 반대로 한국어 특화 모델은 이기지 못했고, 오히려 넷 중 유일하게 없는 절차를 지어냈습니다. 그래서 아직 하나로 좁히지 않고, 슬랙에선 GPT와 로컬 두 모델이 같은 문서로 나란히 답하게 두고 지켜보는 중입니다.
모른다고 말하게 만들기

사내 챗봇에서 제일 위험한 건 틀린 답을 자신 있게 하는 겁니다. 그래서 관련 있는 조각이 하나도 없으면 생성 모델을 아예 호출하지 않습니다. 근거 없이 지어낼 기회 자체를 없애는 거고, 덤으로 비용도 안 나갑니다. 답할 때도 확신이 낮으면 낮다고 표시합니다.
답변엔 항상 출처를 달고, 일반 상식 질문은 일부러 거절합니다. 이 봇의 유일한 가치는 "사내 문서에 실제로 그렇게 적혀 있다"는 보증이라, 그 밖의 답은 안 하는 편이 낫다고 봤습니다. 검색과 로컬 생성은 전부 사내 장비 안에서 끝나고, 밖으로는 걸러진 조각 몇 개만 나갑니다.
마치며
모델을 붙여 답이 나오게 하는 데까지는 오래 걸리지 않았습니다. 시간이 든 건 그다음이었습니다. 무엇을 색인에서 뺄지, 어떤 질문에 어떤 문서만 보여줄지, 언제 답을 포기하게 할지. 지난 편에서 "RAG의 8할은 데이터 파이프라인"이라고 썼는데, 두 편을 다 만들고 나니 한 줄 덧붙이고 싶어졌습니다. 나머지 2할도 모델이 아니라 정책이었습니다.