이 글은 AI 모르는 개발자가 스터디로 사내 챗봇을 구축하는 과정을 정리한 기록이다. 작은 팀에서 제한된 개발 리소스로 챗봇을 만들어가는 과정에 초점을 맞췄다. 참고로, 필자가 초기 설계를 담당했지만 핵심 주요 로직은 팀원과 같이 구현하였다.
대상 독자
- 챗봇을 처음 만들어보는 초보/주니어 개발자
- AI 기반 서비스를 실험해보려는 개발자
- 사내 챗봇 도입을 검토 중인 팀
이미 챗봇을 만들어본 개발자라면 내용이 다소 식상하게 느껴질 수 있다. 처음부터 다 읽을 필요 없이 필요한 부분만 골라 읽고, 개선 방향에 대한 조언을 남겨주면 감사하겠다.
�참고: 빠른 프로토타이핑이 목적이라면 Gemini File Search를 먼저 검토해보길 추천한다. RAG 기반 챗봇을 비교적 쉽게 구축할 수 있다.
사내 위키에는 수년간 쌓인 문서가 많지만, 검색 기능으로 원하는 답을 찾지 못하는 경우가 잦았다. 키워드를 바꿔가며 검색하다 보면 업무 흐름이 끊기기 일쑤였고, 신규 입사자는 "이 문서 어디 있어요?"라는 질문을 반복했다. 결국 "차라리 챗봇을 만들자"는 결론에 도달했다.
첫째, 보안이 최우선이다. 개발 전 보안팀의 사전 검토를 거쳐 AI 모델 사용에 문제가 없음을 확인한 뒤 시작했다. (참고: ChatGPT 를 웹에서 사용할시에는 모델 학습에 활용될 수 있으니 주의가 필요하다.)
둘째, 개발 리소스가 부족했다. 초기 개발에 3명이 총 1M/M을 투자했고, 현재는 격주 금요일 2시간 정도만 활용 중이다. 리소스가 부족한 만큼, 가능한 많은 기능을 AI가 대신하도록 설계했다.
셋째, 타 시스템에 영향을 주면 안 된다. 사내 API를 공격적으로 호출했다가 사내 시스템을 다운시킨 사례를 여러 번 봤기 때문에, 호출 빈도와 패턴 조절에 특히 신경 썼다.
※ 이 글은 회사 기밀은 모두 제외했으며, 보안상 소스 코드 없이 개념과 과정 중심으로 정리했다.
※ 참고로 이 글은 업무 시간이 아닌 주말에 개인 시간을 활용해 작성했다. 업무 시간에는 업무에 집중했음을 밝혀둔다.
이 부분은 시간 관계상 생략한다. 구조 자체는 단순하니, 궁금한 점이 있으면 댓글로 남겨주시길 바란다.
Function Calling(또는 Tool Calling)은 LLM이 외부 시스템과 연동할 수 있게 해주는 기능이다. 모델이 직접 함수를 실행하는 게 아니라, "이 함수를 이 인자로 호출해달라"고 요청하면 애플리케이션이 실행하고 결과를 돌려주는 방식이다.
필자가 이 글에서 상세히 설명할 필요는 없을것 같다. 아래 링크를 참고해서 따로 공부해보길 바란다.
https://platform.openai.com/docs/guides/function-calling
https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview
Spring AI 문서를 참고해서 설명한다.
아주 간단히 요약하면, 흐름은 다음과 같다:
1. 애플리케이션(여기서는 사내 챗봇)이 AI 모델에게 전달
- 사용 가능한 툴 목록
- 챗봇 사용자 질문
2. AI 모델이 판단하고 요청
- 답변을 만들기 위해 어떤 툴이 필요할지 판단
- "어떤 툴을, 어떤 파라미터로 실행해달라"는 요청을 애플리케이션에 전송
3. 애플리케이션이 실제 툴 실행
- 요청에 따라 실제 툴(위키 검색, DB 조회 등)을 실행
4. 툴 실행 결과를 AI 모델에게 전달
5. AI 모델이 최종 답변 생성
- 툴 결과를 참고해 사용자에게 보낼 답변 완성
여기서 중요한 사실은, AI 모델은 직접 툴을 실행하지 않는다는 점이다. 모델은 어디까지나 "툴 호출을 어떻게 할지"와 "툴 결과를 어떻게 사용할지"만 결정한다. 실제 실행은 전부 애플리케이션에서 수행된다.
또 하나 중요한 포인트는 사내 데이터를 모델에 전달했을 때 그 데이터가 모델 학습에 활용되는지 여부다. 이 프로젝트를 시작하기 전에 이 부분을 따로 문의했고, 당시 기준으로는 OpenAI API는 입력 데이터를 모델이 학습에 활용하지 않는다는 답변을 받았다. 그 내용을 바탕으로 보안성 검토에서 '이슈 없음' 결론을 받았고, 이후에야 본격적으로 개발을 시작했다. 새로 프로젝트를 진행하는 경우에도, 이런 조건은 꼭 문서화해서 보안팀과 긴밀하게 협업하는 것이 좋다.
사내 챗봇 예시로 다시 풀어보면 다음과 같다:
1. 사용자가 챗봇에 질문
→ 예: “A 프로젝트 진행상황 알려줘~”
2. 챗봇 API 서버가 AI 모델에 전달
- 사용자 질문 ("A프로젝트 진행상황 알려줘~"
- 사용할수 있는 Tool 목록 (위키 키워드/유사도 문서 검색, 위키 PageId로 본문 상세 조회 등)
3. AI 모델이 Tool 호출 요청
- 질문에 답하기 위해 필요한 정보를 추론
- "위키 검색" Tool 호출 요청 (키워드: "A 프로젝트", "진행 상황")
4. 챗봇 API 서버가 실제 검색 실행
- AI 모델이 넘겨준 키워드로 데이터 소스(위키, 검색엔진 등)에서 문서 조회
5. 검색 결과를 AI 모델에 전달
- 검색한 문서 목록을 요약해서 전달
- 나중에 다시 조회할수 있도록 page-id 같은 식별자 포함
6. AI 모델이 추가 정보 요청
- 문서 목록을 보고 더 자세히 볼 문서 선택
- 해당 PageID로 상세 조회 요청
7. 챗봇 API 서버가 상세 문서 조회
- 요청받은 PageID로 위키 상세 문서 가져오기
8. 조회된 본문을 AI 모델에 전달
- 위키 본문을 그대로 또는 요약해서 전달
9 AI 모델이 최종 답변 생성 --> 수집한 정보를 기반으로 사용자에게 응답
구조만 놓고 보면 생각보다 심플하다.
여기서 한 가지 더 중요한 특징이 있다. AI 모델은 단순히 한 번만 툴을 호출하고 끝내지 않는다. 상황을 보고 스스로 “이 결과만으로는 부족하다”고 판단하면, 추가 Function Calling을 다시 요청할 수 있다.
예를 들어,
- 첫 번째 위키 검색 결과가 너무 적게 나왔거나
- 질문과 동떨어진 문서만 나왔다고 판단되면
모델은
- 검색 키워드를 조금 바꿔서 다시 검색해 보자거나
- 아예 다른 데이터 소스를 조회하는 Tool을 한 번 더 호출해 보자고
스스로 결정해서 두 번째, 세 번째 Function Calling을 이어갈 수 있다.
사람 입장에서 보면,
“검색 → 결과 확인 → 검색어 수정 → 재검색”
이 과정을 모델이 어느 정도 대신 수행해 주는 셈이다.
사내 챗봇이 사내 데이터를 기반으로 답변하려면, 우선 질문과 관련된 문서를 잘 찾는 것이 핵심이다. 만약 챗봇이 참고해야 할 문서가 몇 개 안 된다면, 검색 과정 자체가 필요 없을 수도 있다. 조금 무식한 방식이지만, AI 모델에 API를 호출할 때 시스템 프롬프트에 관련 내용을 전부 집어넣어 보내는 방법도 있다. 하지만 현실은 그렇지 않았다. 사내 위키에는 수만 건의 문서가 쌓여 있고, 챗봇은 이 중에서 적절한 문서를 찾아서 답변해야 한다. 결국 문서를 얼마나 잘 찾느냐가 성능의 절반이라고 생각했다.
필자처럼 '옛날 개발자'라면 자연스럽게 키워드 검색을 먼저 떠올리게 된다. 필자는 처음에 키워드 검색만으로도 충분히 문서를 찾을 수 있을 거라 생각했다. 이유는 간단하다. 사내에서 사용하는 용어들이 꽤 정형화되어 있기 때문이다. 예를 들어 "정책", "배포" 같은 단어는 개발, 기획, 사업 부서 모두 거의 같은 의미로 사용한다. 그래서 "A프로젝트 배포일 알려줘"라는 질문이 들어오면, "A프로젝트", "배포"라는 두 키워드로 검색해서 나온 문서만 잘 찾아서 답변해주면 충분할 거라 생각했다. 실제로 초기 프로토타입에 구현 시 어느정도는 잘 동작했다.
추가로 위키 외에 다른 데이터 소스에서도 답변을 제공하기 위해, OpenSearch에 문서를 모두 저장해서 키워드 검색 API를 구현했다. 위키는 자체 키워드 검색 API를 제공했기 때문에 굳이 따로 색인하지는 않았다.
정리하면:
- 위키에서 제공하는 키워드 검색 API 를 사용
- 위키 외에 키워드 검색을 제공하지 않는 데이터소스는 OpenSearch에 문서를 저장 후 키워드 검색 API를 제공
- AI 모델이 Function Calling 으로 키워드 검색 문서를 요청하면, 스코어가 높은 상위 문서들을 AI모델에 알려주는 방식
생각보다 잘 동작하긴 했지만, 실제로 사용해 보니 일부 중요한 문서를 찾지 못하는 이슈가 계속 발생했다. 이 지점에서 "키워드 검색만으로는 20% 부족하겠다"는 생각을 하게 되었다.
이번에는 키워드 검색 이후 추가로 유사도 검색을 도입한 과정이다. 업계에서는 벡터 검색, 유사도 검색, 시멘틱 검색 등으로 사용하는 것 같다. 솔직히 정확한 표현을 필자가 잘 모르겠다. 이 글에서는 유사도 검색으로 표현하겠다.
예를 들어, 챗봇 사용자가 이렇게 질문한다고 해 보자:
"A개발팀의 주간보고 요약해줘"
위키 문서 제목에 '주간보고'라는 단어가 들어 있거나, 본문 어딘가에 '주간보고'라는 글자가 그대로 들어 있다면 키워드 검색만으로도 충분히 문서를 찾아서 답변해 줄 수 있다. 하지만 제목이나 본문 어디에도 '주간보고'라는 단어가 없다면 어떻게 될까? 이 경우 키워드 검색만으로는 해당 문서를 찾지 못한다. 물론 위키 문서 제목이나 라벨(태그)에 '주간보고'를 추가하는 방식으로 어느 정도 보완할 수 있다. 하지만 현실적으로 모든 문서를 그런 식으로 정리해 두는 것은 쉽지 않다.
주간보고 문서 본문에 다음과 같은 문구가 들어 있다고 가정해보자:
- "이번 주 한 일", "다음 주 할 일"
사용자 질문의 핵심 키워드는 '주간보고'이고, 문서에 "주간보고"라는 키워드는 없지만... 실제 문서에는 '이번 주 한 일', '다음 주 할 일'처럼 "주간보고"라는 용어와 의미적으로 유사한 내용이 들어 있다. "주간보고"와 "이번주한일" 두 용어는 키워드는 서로 다르지만 문맥상 상당히 유사하다. 유사도 검색은 바로 이런 경우에 문서를 찾아낼 수 있다.
필자는 위키 문서의 주요 내용을 한두 문장으로 요약한 뒤, 그 요약문을 임베딩하는 방식을 사용했다.
예를 들어, 주간보고 문서는 다음과 같이 요약했다:
"A개발팀의 11월 1주 주간 업무를 요약한 문서입니다."
이 요약문을 임베딩해서 벡터 DB에 저장하고, 나중에 다시 원문을 찾을 수 있도록 PageId를 메타데이터로 함께 저장했다.
사용자가 "A개발팀의 주간보고 요약해줘"라고 질문하면, 이 질문을 임베딩해서 벡터 DB에 유사도 검색하여 가장 유사도가 높은 문서를 찾아오는 방식이다.
벡터 DB는 OpenSearch를 사용했고, 임베딩 모델은 OpenAI의 text-embedding-3-small을 사용했다.
여기서 한 번 더 나아가면, 보통은 청킹(chunking)을 고민하게 된다. 긴 문서를 여러 조각으로 쪼개서 각각 임베딩하면, 더 세밀한 검색이 가능해지기 때문이다. 하지만 이 프로젝트는 어디까지나 프로토타입을 구축하는 스터디이기도 했고, 개발 가능한 시간,개발리소스가 너무 부족했다. 그래서 "청킹까지 제대로 해보자"는 목표를 세우긴 했지만, 현실적으로는 끝까지 진행하지는 못했다.
개인적으로는 이 청킹 전략 설계가 가장 어려운 주제였다고 느꼈다:
- 어느 기준으로 문서를 나눌 것인지
- 한 조각의 길이는 얼마나 해야 할지
- 섹션 단위로 자를지, 문단 단위로 자를지
고민할 요소가 너무 많았다.
결국, 복잡한 청킹 전략 대신 문서를 한두 문장으로 요약해서 임베딩하는 쪽을 택했다. 다만 이렇게 하다 보면, 요약 과정에서 원문에 있던 중요한 정보가 누락될 수 있다. 그 결과, 실제로는 문서에 필요한 정보가 있는데 요약문에는 그 부분이 빠져 있어서 임베딩 기준으로는 해당 문서를 찾지 못하는 상황이 종종 발생했다. 즉, 요약 기반 임베딩은 구현은 간단하지만, 정밀도가 떨어지고 누락 위험이 크다는 단점이 있었다.
이 부분은 향후 과제로, 언젠가는 제대로 청킹 전략을 설계해 보고 싶은 주제다.
유사도 검색을 도입하면서 문서 검색 품질은 확실히 좋아졌다. 그럼에도 불구하고, 가끔은 엉뚱한 문서가 섞여 들어오는 문제가 남아 있었다. 원래라면 AI 모델이 이런 문서를 걸러내고, 정말 중요한 문서만 골라 답변에 활용하는 게 이상적이다. 하지만 약간 덜 관련된 문서까지 억지로 끌어다가 사용한다거나 문서 내용과 다소 동떨어진 내용을 지어내는 할루시네이션이 여전히 발생했다.
이를 보완하기 위해, 유사도 검색 결과 위에 리랭커 로직을 한 겹 더 씌웠다.
흐름은 대략 다음과 같다:
- 벡터 DB에서 유사도 검색으로 여러 개의 문서를 가져온다
- 이 문서들을 AI 기반 리랭커 모델에 다시 한 번 태운다 --> 실제 질문과 가장 잘 맞는 문서만 상위 몇 개 선별해서, 최종 컨텍스트로 넘긴다
이 과정을 추가하자, 불필요한 문서가 답변에 끼어드는 비율이 감소하였고 할루시네이션도 눈에 띄게 감소
다만 대가도 있었다. 리랭커 로직에서만 약 10초 정도의 추가 딜레이가 발생하면서, 전체 응답 속도가 꽤 느려졌다. 이 문제는 필자의 노력 없이도 자연스럽게 해결되었다. 리랭커에 사용하는 모델을 최신 AI 모델로 교체했고, 그 결과 리랭킹 시간이 약 1초 수준으로 줄어들었다. 이 부분은 뒤에서 응답 속도 개선 섹션에서 다시 설명하겠다.
응답 속도를 개선하기 위해 Function Calling 로직에 병렬 실행을 도입했다. 필자가 사용한 Spring AI 1.0.0 기준으로는, Function Calling로 실행해야 하는 Tool들이 기본적으로 순차 실행되는 구조였다. (최신 버전에서는 개선되었는지 별도 확인이 필요하다.) 그래서 애플리케이션 레벨에서 여러 툴 호출을 가능한 범위 내에서 병렬로 처리하도록 구현했다. 그 결과 체감상 2~3초 정도 응답 시간이 줄었다. 엄청 드라마틱한 변화는 아니지만, 사용자가 기다리는 시간을 조금이라도 줄이는 데는 도움이 되었다.
응답 속도 개선의 또 다른 포인트는 리랭커 모델 교체였다. 처음에는 리랭커로 GPT-5 mini를 사용했다. 이때는 리랭킹 단계에서만 약 10초 정도가 걸렸다. 이후 리랭커 모델을 Claude Haiku 4.5로 교체했더니, 같은 작업이 거의 유사한 품질로 1초 수준으로 줄어들었다. 정확히 어떤 원리로 이렇게까지 단축되었는지는 솔직히 잘 모르겠다. 다만, 모델을 교체하는 것만으로도 응답 시간이 크게 줄 수 있다는 경험을 한 셈이다.
이후 GPT-5.1이 출시되면서 사내 챗봇의 핵심 모델을 GPT-5.1로 전환했다. 이 과정에서 전체 응답 속도가 한 번 더 눈에 띄게 개선되었다. 병렬 프로그래밍을 도입해서 겨우 2~3초 정도만 줄였는데, GPT-5.1로 바꾸자 전체 응답 시간이 체감상 50% 이상 감소했다.
이쯤 되니, 필자가 애써 최적화해서 얻는 개선 속도보다 AI 모델이 발전하는 속도가 훨씬 빠르다는 사실을 인정하게 되었다.
이번 사내 챗봇은 특정 LLM에 종속되지 않도록 설계했다. 초기에는 Claude Sonnet 4로 프로젝트를 시작했지만, 불과 반년 사이에 핵심 모델이 여러 번 바뀌었다.
Claude Sonnet 4 (26년 6월)
→ OpenAI o4 (26년 6월)
→ OpenAI GPT-5 mini (26년 8월)
→ Claude Haiku 4.5 (26년10월)
→ GPT-5.1 (26년 11월)
앞으로도 더 좋은 모델이 계속 나올 것이고, 그때마다 코드를 싹 갈아엎는 일은 피하고 싶었다. 그래서 애플리케이션 레벨에서 모델 호출 로직을 인터페이스/추상화 레이어로 감싸고, 실제로 어떤 모델을 쓰는지는 설정만 바꾸면 되도록 구현했다. 덕분에 핵심 모델을 여러 번 갈아끼웠음에도, 코드 수정량은 생각보다 크지 않았다.
하지만, 새로운 모델로 교체할 때 이전과는 다른 스타일의 답변을 하는 경우가 발생했다. 예를 들어 OpenAI o4에서 GPT-5로 넘어갈 때, o4 모델과는 다르게 GPT-5 모델은 굳이 하지 않아도 되는 에이전트 역할을 수행하려고 해서 혼란이 생겼다. 질문에 대한 답만 해주면 깔끔한 상황인데도, 불필요하게 추가 작업이나 아이디어를 제안하면서 답변하는 식이었다. 마치 오지랖을 부리는 것처럼 행동했다. 이 부분은 프롬프트를 수정하는 방식으로 해결했다. 시스템 프롬프트에 "불필요한 추가 행동 제안은 자제하고, 사용자의 질문에 대한 답변에만 집중할 것"과 같은 지침을 명시했고, 이후에는 이전과 비슷한 톤과 범위로 답변하도록 어느 정도 맞출 수 있었다.
OpenAI의 경우 콘솔에서 Function Calling 관련 로그를 비교적 상세하게 확인할 수 있다:
어떤 툴을 호출하려고 했는지, 어떤 인자를 넘겼는지, 응답이 어떻게 들어왔는지 등등
이런 정보를 눈으로 보면서 디버깅할 수 있어, 개발 초반에 많은 도움을 받았다. 다만, 간헐적으로 로그 출력이 늦게 뜨는 경우가 있어서 "지금 이게 안 불린 건지, 단순히 로그가 늦게 나오는 건지" 헷갈릴 때가 종종 있었다. 또한 최근에는 일주일 동안 로그가 늦게 보이는 경우도 발생하였다. 100% 의존하기엔 무리가 있는 상황이다.
현재 구조를 조금만 손보면, 챗봇 API 서버를 MCP서버 형태로 구성해서 유관 부서에도 도구처럼 제공할 수 있다. 내부 API를 MCP로 감싸서 노출하고 다양한 클라이언트에서 같은 챗봇/툴셋을 재활용하는 방식으로 제공할수도 있겠다. 다만 이 프로젝트는 어디까지나 스터디 목적이라, 실제로 MCP 서버까지 구성해서 배포하는 단계까지는 가지 않았다. "이렇게도 확장할 수 있겠다" 정도의 가능성만 확인한 수준이다.
프롬프트는 가장 중요한 요소였다. 다음과 같은 내용들을 꽤 신경 써서 적어야 했다:
- Tool을 어떤 순서와 기준으로 호출해야 하는지
- 답변 형식은 어떻게 해야 하는지
시스템 프롬프트를 대충 써 두면 모델이 엉뚱한 Tool만 반복해서 호출한다든지 답변이 일관성 없이 들쭉날쭉하다든지 하는 문제가 바로 드러났다. 결국, 프롬프트 설계 = 챗봇의 행동 방침을 정의하는 일이라고 생각하고 여러 번 수정해 나갔다.
이 프로젝트의 핵심 목표는, 여러 데이터 소스에 흩어져 있는 정보를 한 번에 찾아서 답변해 주는 챗봇을 만드는 것이다. 수년 동안 구성원들은 위키뿐만 아니라, 다양한 협업 도구와 사내 인트라넷 시스템에 문서를 여기저기 정리하지 않고 작성해 왔다. 그래서 필요한 정보를 찾으려면 위키에서 한 번 검색해 보고 사내 포털에서 또 검색해 보고 또다른 협업 도구에서도 비슷한 키워드로 다시 검색하고 난리다.
이렇게 같은 검색어를 이곳저곳에 반복해서 치는 일이 생각보다 피로도가 크다. 사내 챗봇은 이런 '검색 피로감'을 줄이기 위해, 여러 데이터 소스를 묶어서 한 번에 검색하고, 그 결과를 하나의 인터페이스로 보여주는 방향으로 설계하고 있다.
Ragas라는 라이브러리를 사용해 품질 테스트를 시도해 봤다. 하지만 생각만큼 간단하지 않았다. 테스트용 질문/정답 데이터를 만드는 것도 만만치 않고 실제 서비스 문서 구조에 맞게 스코어를 해석하는 것도 쉽지 않았다.
일단, 사람이 직접 눈으로 보면서 테스트하는 방식에 많이 의존하게 되었다. 스터디 프로젝트라는 특성상, 별도의 품질 측정 도구를 개발하거나 자동화된 평가 파이프라인을 구축하는 일까지 진행하기에는 리소스가 부족하기도 했다.
API 를 호출하는거와 비교해서 품질/비용 등 비교가 필요해서 Private LLM를 테스트해봤다. 로컬/EC2 환경에서 오픈소스 모델을 올려 테스트해 보았다. 하지만, 응답 속도가 생각보다 많이 느렸다. 사용한 EC2 인스턴스 스펙도 높지 않아, 더 느리게 느껴졌으며 인스턴스 사양을 올리면 그만큼 비용이 크게 증가했다. 이게 모델 자체의 문제인지, 우리가 인프라/튜닝을 잘 못해서 그런 건지는 솔직히 확신이 없다. 다만, 스터디 프로젝트라는 관점에서 봤을 때는 "일단은 OpenAI API를 직접 호출하는 것이 속도·안정성·개발 비용 측면에서 더 효율적이다"라는 결론을 내렸다.
OpenAI, Anthropic 등의 모델은 스트리밍 응답을 지원한다. 챗봇 API 서버에서는 모델에서 스트리밍 형태로 토큰이 들어오는 대로 받아서 클라이언트에는 SSE(Server-Sent Events) 방식으로 다시 전송하는 구조로 구현했다. 양방향 통신이 필요한 것은 아니었기 때문에, 굳이 WebSocket까지 구현할 필요는 없다고 판단했다. SSE만으로도 충분했다. 실제로 사용해 보니, 모든 문장이 완성될 때까지 기다리는 것보다 답변이 점점 채워지는 것을 바로바로 볼 수 있어서 사용자 입장에서는 훨씬 빠르게 답변을 받는 느낌을 줄 수 있었다. "기다리는 5초"와 "스트리밍으로 흘러나오는 5초"는 체감이 완전히 다르다.
마지막으로 API 비용도 무시할 수 없다. Private LLM을 쓰지 않기 때문에 인프라 비용은 거의 들지 않았지만, 그 대신 외부 API 호출 비용이 꾸준히 발생했다. 챗봇 질문 내용과 문서 길이에 따라 조금씩 차이는 있지만, 대략 질문 1건당 10원 ~ 100원 정도의 비용이 나오는 구조였다. 시간이 지날수록 더 저렴하고 더 성능 좋은 모델이 계속 나올 가능성이 크다.
암튼, "챗봇을 많이 쓸수록 비용이 어떻게 늘어나는지"는 항상 염두에 두고 설계해야 한다. 내 돈 쓰는게 아니라 회사 비용을 사용한다면 매우 중요한 내용이다.
스터디라서 시간을 많이 할애하긴 어렵지만, 그래도 챗봇의 답변 품질을 끌어올리기 위해 개선해야 할 포인트가 몇 가지 보인다.
현재는 문서를 한두 문장으로 요약해서 임베딩하는 방식으로 운영하고 있는데, 이 방식은 요약 과정에서 중요한 내용이 누락되면 유사도 검색에서 통째로 빠져버리는 경우가 종종 생긴다. 임베딩 시에 중요 키워드를 메타데이터로 별도 저장하는 방향으로 개선이 필요하다. 게임 회사라서 게임 단위의 질문이 많은데, 예를 들어 게임 이름을 메타데이터에 함께 넣어 두는 것만으로도 검색 품질을 꽤 끌어올릴 수 있을 것이다.
또한, 지금처럼 문서를 통째로 요약해서 임베딩하는 대신, 사용자의 질문과 더 잘 매칭되도록 문서를 나누고(청킹하고) 임베딩할지를 고민해야 한다. 결정할 것이 많아서 꽤 어려운 과제지만, 결국 "어떻게 청킹하느냐"가 검색 품질을 좌우할 것 같다.
마지막으로, 현재는 벡터 검색 따로, 키워드 검색 따로 Tool을 수행하고 있는데, OpenSearch의 하이브리드 검색 기능을 잘 활용하면 더 효율적으로 챗봇 답변을 해줄수 있을것이라 확신한다.
사내 특화 지식은 AI 모델이 전혀 모른다. API 호출 시 입력 데이터가 학습에 활용되지 않기 때문에, 시간이 지나도 사내 지식을 알아서 학습해 주지도 않는다. 예를 들어, A프로젝트의 또 다른 별칭이 "A2프로젝트"라고 가정하자. 사용자가 "A프로젝트 진행 상황 알려줘"라고 물었을 때는 A프로젝트로 검색할수도 있지만, 어떤 사람은 "A2프로젝트 어디까지 됐어?"라고 물을 수도 있다. 이럴 때는 A프로젝트와 A2프로젝트를 동일한 엔티티로 묶어서 관리해 두어야 검색이 제대로 동작한다. 이런 정보는 모델이 알아서 추론하기 어렵기 때문에, 사내에서 사용하는 정형화된 지식을 별도로 관리할 수 있다면 답변 품질이 훨씬 좋아질 것이다. 개인적으로는 그래프 DB가 적합해 보인다.
- 프로젝트 노드(A프로젝트=A2프로젝트, B프로젝트, C프로젝트)
- 사람 노드(기획자, 개발자 등)
- 시스템/서비스 노드
노드와 노드 사이의 관계를 그래프로 저장해 두면, "이 프로젝트와 관련된 사람/시스템/별칭"을 훨씬 쉽게 찾을 수 있다. 나중에는 이 지식 그래프를 기반으로 검색 후보를 확장하거나, 답변에 추가적인 컨텍스트를 붙이는 식의 활용도 가능할 것 같다.
언젠가 이 글에서 적어 둔 향후 과제들을 하나씩 풀어가고, "사내 챗봇 2년 차 회고" 같은 두 번째 글을 쓸 수 있는 날이 오면 좋겠다. 다만 이 프로젝트가 어디까지나 스터디 형태로 진행되고 있고, 개인적인 일정도 바쁘다 보니 그날이 언제가 될지는 솔직히 모르겠다.
여기까지 읽어준 분이 몇이나 될지 모르겠지만, 사내 챗봇을 고민하는 누군가에게 "이 정도면 나도 해볼 수 있겠다"는 용기를 주었다면 그걸로 충분하다. 스터디 수준의 작은 기록이지만, 언젠가 누군가에게 조금이라도 도움이 되길 바란다.
마지막으로 한 가지만 덧붙이자면...AI가 아무리 빠르게 발전하더라도, 결국 사내 문서를 가장 잘 아는 건 사람이다. 챗봇은 그 지식을 더 빨리, 더 편하게 꺼내 쓰게 도와주는 도구일 뿐이다.