채팅창에 한 줄이 들어온다.
"그 타임아웃 원인, 정리된 거 있어?"
몇 초 뒤 AI PM이 답한다. 결제가 왜 멈췄는지, 언제 자주 터졌는지, 팀이 어떻게 대응하기로 했는지를 조목조목 짚고, 맨 끝에 참고한 문서 이름까지 붙여서.
이상한 일이다. 이 AI는 우리 회사에 입사한 적도, 우리 결제 모듈을 들여다본 적도 없다. 그런데 어떻게 "우리 프로젝트"를 아는 것처럼 답할까. 똑똑해서가 아니다. 질문이 들어올 때마다 관련 문서를 찾아 답변 재료로 끼워 넣는 구조가 뒤에서 돌기 때문이다. 이 구조의 이름이 RAG(retrieval-augmented generation, 검색 증강 생성)다. AI가 자기 머리로 외운 지식 대신, 그때그때 외부 문서를 찾아 참고해서 답하는 방식이다.
갓 입사한 똑똑한 컨설턴트를 떠올려 보자. 경영·재무·마케팅 이론은 빠삭하다. 그런데 "우리 회사 연차는 며칠까지 이월되죠?"라고 물으면 답을 못 한다. 머리가 나빠서가 아니다. 이 회사 규정집을 아직 안 읽어서다.
똑똑한 신입은 여기서 지어내지 않는다. "잠깐만요, 규정집 찾아볼게요" 하고 인사팀 폴더에서 해당 조항을 펼친 뒤, 그 페이지를 짚으며 답한다. RAG가 하는 일이 딱 이 세 동작이다. 질문이 오면 관련 문서를 찾고, 그 문서를 책상에 펼쳐 질문과 함께 모델에게 건네고, 모델은 펼쳐진 자료를 읽어 답을 쓴다. 뒤에서는 이 셋을 각각 검색(retrieval)·주입(augmentation)·생성(generation)이라 부른다. 용어는 몰라도 된다. 찾고, 펼치고, 쓴다. 이게 전부다.
거대언어모델(LLM, 방대한 글로 훈련된 AI)은 학습 시점까지 인터넷에 쌓인 글을 통째로 익힌다. 그래서 일반 상식, 코딩 문법, 글쓰기 요령은 잘 안다. 컨설턴트의 머릿속 이론에 해당한다. 이걸 '모델 머릿속 지식'이라 부르자.
문제는 어제 쓴 우리 팀 기획서, 지난주 회의에서 정한 규칙, 우리 회사만 쓰는 용어다. 애초에 학습 데이터에 없었다. 없는 걸 알 수는 없다. 더 고약한 건 모를 때도 아는 척을 한다는 점이다. 그럴듯하지만 틀린 답을 자신 있게 내놓는 것, 이를 환각(hallucination)이라 한다.
그래서 지식을 두 자리로 나눠 둬야 한다. 하나는 방금 말한 모델 머릿속 지식이다. 한번 굳으면 그중 하나만 콕 집어 고치기 어렵다. 다른 하나는 모델 바깥, 책상 위에 올려 두는 문서다. 창고의 문서를 갈아 끼우면 내용이 즉시 바뀐다. 우리 프로젝트 문서는 여기 들어간다.
'우리 프로젝트를 모르는 문제'를 푸는 길도 여기서 갈린다. 모델을 우리 데이터로 다시 학습시켜 머릿속 지식 자체를 고쳐 넣는 길이 하나다(파인튜닝). 돈과 시간이 많이 들고, 문서가 하루에도 몇 번씩 바뀌는 프로젝트엔 안 맞는다. 오늘 바뀐 일정을 반영하자고 매번 모델을 다시 구울 수는 없다. 다른 길이 RAG다. 모델은 그대로 두고 책상 위 문서만 갈아 끼운다. 창고에 문서를 넣어 두면 질문이 올 때마다 꺼내 쓴다.
정리하면 모델은 '생각하는 법'을 맡고, 문서는 '우리만의 사실'을 맡는다. 신입이 이론과 규정집을 함께 쓰듯이.
신입이 규정집을 찾으려면 규정집이 폴더에 정리돼 있어야 한다. RAG도 질문을 받기 전에 찾을 거리를 갖춰 둔다.
먼저 문서를 조각낸다. 기획서 한 편이 수십 페이지다. 통째로는 찾기도, 모델에 넣기도 버겁다. 그래서 작은 토막, 청크(chunk, 문서를 잘라 놓은 한 조각)로 나눈다. 그래야 질문과 맞닿은 토막만 집어내고, 모델이 한 번에 읽을 수 있는 분량 안에 담긴다.
관건은 어디서 자르냐다. 규정집 한 페이지가 "위 세 가지 이유로 일정을—"에서 찢기고 다음 조각이 "—2주 연기한다"로 시작하면, 두 조각 다 혼자서는 뜻이 안 통한다.
자르는 자리만 바꿔도 답이 어떻게 달라질까. 약한 방식은 글자 수 500자에서 기계적으로 끊는다. 문장 한가운데가 잘려 토막이 문맥을 잃는다. 강한 방식은 문단·소제목 경계에서 자르고, 인접한 토막끼리 한두 문장을 겹쳐 둔다. 겹친 부분이 끊긴 맥락을 이어 줘, 토막 하나만 읽어도 말이 선다.
자른 다음엔 찾기 좋게 정리한다. 어떤 단어가 어느 조각에 있는지 적어 둔 목록(색인)을 만들고, 각 조각의 뜻을 숫자 열로 바꾼 것(벡터)도 함께 저장한다. 왜 두 벌로 저장하는지는 곧 검색 단계에서 드러난다.
준비가 끝났다. 맨 앞의 그 장면으로 돌아가자. 사용자가 방금까지 결제 모듈 얘기를 하다가 묻는다.
"그 타임아웃 원인, 정리된 거 있어?"
타임아웃은 바깥 시스템에 요청을 보내고 정해 둔 시간까지 답이 안 오면 실패로 처리하는 것을 말한다. 결제가 그렇게 자꾸 실패했다는 얘기다. 이 한 줄이 네 걸음을 거쳐 출처 붙은 답으로 돌아온다.
"그 타임아웃"만 보면 뭘 찾을지 모른다. '그'가 가리키는 건 방금까지 오간 대화에 있다. 그래서 검색어를 세울 때 두 가지를 함께 본다. 방금 들어온 질문과, 직전까지의 대화다.
여기선 직전 화제가 결제 모듈이었으니, "그 타임아웃"을 "결제 API 타임아웃"으로 풀어 검색어를 세운다. (API는 프로그램끼리 데이터를 주고받는 규칙이다.) 신입이 "그거 언제까지죠?"를 방금 전 화제에 비춰 알아듣는 것과 같다.
'그'가 무엇인지 놓치면 어떻게 될까. 약한 방식은 "그 타임아웃"을 맥락 없이 그대로 검색해 엉뚱한 '그것'을 건진다. 강한 방식은 직전 대화의 핵심 이름(결제 모듈)을 검색어에 보태 정확히 좁힌다. 다만 지난 대화에 너무 기대면 이미 끝난 화제로 검색이 끌려가니, 최근 맥락을 가볍게 보태는 선에서 멈춘다.
검색어가 섰으면 문서 창고에서 관련 조각을 뽑는다. 뽑는 길이 둘이다. 둘 다 신입이 규정집을 찾는 방식에 그대로 대응한다.
하나는 단어로 찾기다. 신입이 '제14조'나 '결제 API 타임아웃'처럼 정확한 말로 폴더를 뒤지는 식이다. 기술 용어로 키워드 매칭(BM25가 대표적이다)이라 한다. 빠르고 정확하지만, 겹치는 단어가 적으면 약하다. 문서가 "페이먼트 요청 지연"처럼 다른 말로만 적혀 있으면, "결제 API 타임아웃"으로 검색했을 때 겹치는 단어가 적어 순위가 뒤로 밀리기 쉽다.
다른 하나는 뜻으로 찾기다. 신입이 "연차 이월, 그 내용"처럼 단어가 아니라 의미로 기억해 찾는 식이다. 앞서 저장해 둔 숫자 열(벡터)이 여기 쓰인다. 질문의 뜻과 가까운 조각을 고르니, "결제 API 타임아웃"과 "페이먼트 요청 지연"을 단어가 달라도 이어 준다. 기술 용어로 의미 검색(dense retrieval)이다. 뜻으로 찾는 이 방식이 단어로만 찾는 것보다 관련 문서를 더 잘 집어낸다는 건 2020년 DPR(Dense Passage Retrieval) 연구로 널리 알려졌다.
그럼 뜻으로 찾기만 쓰면 될까. 아니다. "ERR_4092" 같은 정확한 오류 코드를 찾을 땐 단어로 찾기가 낫다. 뜻으로 찾기는 "비슷한 느낌의 오류"를 끌어올 수 있는데, 정작 필요한 건 그 코드가 콕 박힌 문서 한 건이다. 그래서 실무에선 둘을 함께 돌린다. 이걸 하이브리드 검색이라 한다. 단어로 찾기가 "ERR_4092"가 그대로 든 문서를 잡고, 뜻으로 찾기가 표현이 다른 "페이먼트 요청 지연" 문서를 건진다. 둘을 합치면 하나만 썼을 때 놓쳤을 문서까지 후보에 오른다.
검색 방식의 작은 차이가 어떤 결과로 이어질까. 약한 방식은 뜻으로만 찾아 "ERR_4092"를 물어도 번호가 다른 "비슷한" 오류를 위로 올린다. 강한 방식은 단어와 뜻을 함께 돌려, 코드가 정확히 든 문서와 뜻이 가까운 문서를 모두 후보에 세운다.
검색이 끝나자 창고에서 세 조각이 후보로 올라왔다(실제론 더 많지만 셋만 보자).
조각 1 — [결제 모듈 이슈 히스토리] "결제 API 타임아웃(ERR_4092): 외부 결제사 응답이 늦을 때 재시도 없이 바로 실패로 떨어지는 문제. 대응으로 재시도 로직을 넣고 응답 대기시간을 늘리기로 함."
조각 2 — [결제 관련 회의록] "페이먼트 요청 지연은 사용자가 몰리는 시간대에 특히 잦았음. 알림 임계치를 조정해 먼저 감지하기로 함."
조각 3 — [결제 모듈 배포 노트] "결제 완료 화면의 버튼 색과 안내 문구를 바꿔 반영."
조각에 낯선 말이 둘 보인다. 재시도 로직은 한 번 실패해도 바로 포기하지 않고 몇 번 더 요청해 보는 장치다. 알림 임계치는 "이 수치를 넘으면 경보를 울려라"의 기준선이다. 사용자가 몰려 지연이 잦아지면, 그 선을 낮춰 더 일찍 알아채겠다는 뜻이다.
여기서부터가 설계의 진짜 승부처다. 올라온 후보를 그대로 쓰는 게 아니다. 검색 엔진은 조각마다 관련도 점수를 매긴다. 질문과 맞닿을수록 높고, 스쳐 지나갈수록 낮다. 이 점수로 고르고 버린다.
조각 1은 "타임아웃"과 "ERR_4092"가 질문에 정면으로 걸려 점수가 높다. 맨 앞에 둔다. 조각 2는 같은 사건을 다른 말로 적었다. 단어는 안 겹쳐도 뜻이 가까워 점수가 받쳐 준다. 원인을 넓혀 주니 뒤에 붙인다. 조각 3은 '결제'라는 단어만 겹쳤을 뿐 타임아웃과는 남남이라 점수가 낮다. 버린다.
이 버리는 판단이 핵심이다. 신입으로 치면, 찾아 온 규정집 페이지 중 질문에 맞는 두 장만 책상에 펼치고 상관없는 한 장은 덮는 일이다. 무관한 자료를 같이 펼쳐 두면 모델이 거기까지 읽고 엉뚱한 소리를 섞는다. 그래서 모델에게 실제로 건네지는 입력은 대략 이렇게 생겼다.
[지시] 아래 참고 자료에만 근거해 답하라. 자료에 없는 내용은 지어내지 말고 모른다고 답하라. [참고 자료] 1) 결제 API 타임아웃(ERR_4092): 외부 결제사 응답이 늦을 때 재시도 없이 바로 실패로 떨어지는 문제. 대응 = 재시도 로직 추가 + 응답 대기시간 상향. 2) 페이먼트 요청 지연은 사용자가 몰리는 시간대에 특히 잦았음. 알림 임계치 조정으로 먼저 감지하기로 함. [질문] 결제 API 타임아웃 원인, 정리된 거 있어?
마지막으로 모델이 참고 자료와 질문을 함께 읽고 답을 쓴다. 원칙은 하나다. 머릿속 일반 지식이 아니라 방금 책상에 펼친 우리 문서를 근거로 삼게 한다. 신입이 머릿속 일반론을 읊는 대신 펼쳐 둔 규정집을 짚으며 "여기 보면요" 하는 것과 같다.
그래서 잘 만든 RAG는 답 끝에 어떤 문서를 봤는지 붙인다. 우리 장면의 답은 이렇게 돌아온다.
"결제 API 타임아웃(ERR_4092)은 외부 결제사 응답이 늦을 때 재시도 없이 바로 실패로 떨어지는 게 원인으로 정리돼 있습니다. 사용자가 몰리는 시간대에 특히 잦았고요. 대응으로는 재시도 로직을 넣고 응답 대기시간을 늘리며, 알림 임계치를 조정해 먼저 감지하기로 돼 있습니다. (참고: 결제 모듈 이슈 히스토리, 결제 관련 회의록)"
출처가 붙었으니 사용자는 믿기 전에 원문을 열어 볼 수 있다. 맨 앞의 "정리된 거 있어?"가 여기 와서야 근거 있는 답이 됐다.
문서 창고를 아무리 잘 채워도, 검색 결과가 부실하거나 아예 비는 순간이 온다. 그리고 잘 설계한 시스템은 바로 이 순간을 스스로 자라는 기회로 쓴다. 폴백(fallback, 정상 경로가 막혔을 때의 대비 동작)이 그 장치다.
먼저, 시스템은 언제 "근거가 없다"고 판단할까. 앞서 매긴 관련도 점수를 다시 쓴다. 올라온 조각 중 가장 높은 점수마저 기준선 아래면, 쓸 만한 근거가 없다고 본다. 억지로 긁어모으지 않는다.
이때 가장 위험한 건, 모델이 우리 문서에 답이 없으니 머릿속 일반 지식으로 빈칸을 메우는 것이다. 신입이 규정집에서 못 찾자 다른 회사 관행을 "우리도 그럴 거예요"라며 지어내는 상황이다. 당장은 그럴듯하다. 그 말을 믿은 고객이 잘못된 결정을 내리면 책임은 회사로 돌아온다.
좋은 폴백은 지어내는 대신 솔직히 말한다. "관련 자료를 찾지 못했습니다. 문서를 올려 주시면 다음부터 반영하겠습니다." 이 한마디가 두 가지 일을 동시에 한다. 하나, 모르는 걸 모른다고 말할 줄 안다는 걸 보여 줘 신뢰를 쌓는다. 둘, 창고의 어디가 비었는지 사용자에게 알려 준다. 그 안내를 따라 올라온 문서는 다음 질문의 검색 대상이 된다. 그래서 폴백은 실패 메시지가 아니라 창고를 두껍게 키우는 선순환의 첫 바퀴다.
그렇다면 이 원리를 실제 제품은 어떻게 구현하고 있을까. 지금까지 '문서 창고'라 부른 것을, 온틀에선 '지식 온틀'이라는 기능이 맡는다.
프로젝트마다 전용 지식 온틀이 생긴다. 문서를 올리면 앞서 본 대로 조각나 저장된다. AI PM에게 물으면, 그 순간 해당 프로젝트의 지식 온틀 안에서만 찾아 답에 반영한다. 다시 그 결제 타임아웃 장면으로 돌아가 보자. 사용자가 "그 타임아웃 원인 정리된 거 있어?"라고 치면, 온틀은 그 프로젝트 창고에서 결제 이슈 히스토리와 회의록을 건지고, 배포 노트처럼 단어만 겹친 조각은 걷어 낸 뒤, 출처를 붙여 답한다. 사용자 눈엔 그냥 '우리 프로젝트를 아는 AI PM'이지만, 뒤에선 그 네 걸음이 질문마다 돈다.
그래서 온틀이 공들이는 데는 '문서 많이 넣기'가 아니라 '맞는 문서 골라내기'다. 문서가 쌓인다고 정확도가 저절로 오르지 않는다. 오히려 무관하거나 낡은 자료가 함께 검색돼 섞이면 오답이 늘 수 있다(관련 실험). 조각 3처럼 단어만 겹친 자료를 제때 걷어 내는 일이 그래서 중요하다.
폴백도 제품에 박혀 있다. 근거가 없으면 지어내는 대신 못 찾았다고 밝히고 문서 보강을 청한다. 모델을 다시 학습시키지 않고 문서만 올리면 참고할 재료가 느는 것, 책상 위 문서로 돌아가는 RAG의 이점 그대로다.
우리 프로젝트의 맥락을 기억하는 AI PM이 필요하다면, 지금 온틀에서 시작해 보자. https://ontle.kr
문서가 몇 개 안 되고 짧으면 그래도 된다. 하지만 프로젝트 문서가 쌓이면 모델이 한 번에 읽을 수 있는 분량을 금방 넘는다. 게다가 매번 전부 붙이면 질문과 무관한 자료까지 들어가 오답을 부른다. RAG는 그때그때 관련 토막만 골라 넣어, 문서가 아무리 늘어도 질문에 맞는 자료만 추려 쓴다.
목적이 다르다. 파인튜닝은 말투나 답변 형식처럼 '몸에 밴 습관'을 바꿀 때 쓴다. RAG는 자주 바뀌는 '사실'을 대 줄 때 쓴다. 둘을 함께 쓰기도 한다. 다만 날마다 바뀌는 프로젝트 지식을 파인튜닝으로 욱여넣으려 하면 유지가 고달프다. 그런 지식은 문서 창고에 두는 편이 낫다.
찾는 과정이 더해지니 순수 생성만 할 때보다는 품이 든다. 다만 창고를 통째로 훑는 게 아니라, 미리 만들어 둔 색인과 벡터로 후보를 좁힌 뒤 소수의 토막만 모델에 넘기기 때문에 체감 지연은 크지 않다. 오히려 긴 문서를 통째로 밀어 넣는 쪽이 읽을 양이 많아 더 느려질 수 있다.