1. 배경
이번에 팀 프로젝트를 하면서 두 번의 기술 설계 논의를 거쳤다. 하나는 "사용자가 입력한 콘텐츠가 개발 관련 콘텐츠인지 어떻게 판단할 것인가"였고, 다른 하나는 "사용자의 학습 이력을 바탕으로 개인화된 문제를 추천할 수 있을까"였다.
두 논의 모두 결론은 같았다. "이번엔 RAG는 안 쓴다."
재밌는 건, 정작 나는 RAG를 "안 쓰기로" 결정하는 과정에서 RAG가 뭔지 처음 제대로 이해하게 됐다는 거다. 그전까지는 "문서 검색해서 LLM한테 참고시키는 것" 정도로만 알고 있었고, 임베딩은 이번 프로젝트 전까지 잘 알지 못했던 용어였다.
그래서 이 글은 "RAG 개념 정리"라기보다는, RAG를 쓰지 않기로 한 두 번의 논의를 따라가면서 거꾸로 배운 것들의 기록에 가깝다.
2. 첫 번째 논의: 콘텐츠 적합성 검증
우리 서비스는 사용자가 URL이나 텍스트로 콘텐츠를 입력하면, 그게 "개발 학습에 적합한 콘텐츠인지"를 AI가 먼저 판단해야 했다. 적합하면 퀴즈 생성으로 넘어가고, 부적합하면 사용자에게 이유를 안내하고 다시 입력을 유도하는 흐름이다.
처음엔 단순하게 생각했다. AI API를 한 번 호출해서 다음 세 가지를 한꺼번에 받으면 되지 않을까?
- 점수
- 적합/부적합 여부
- 부적합 사유
그런데 팀 논의에서 문제가 하나 지적됐다. AI에게 "부적합 사유 설명"까지 자연어로 생성하게 맡기면, 원문이나 정책에 근거하지 않은 설명을 만들어낼 수 있다는 것이었다. 이른바 할루시네이션이다. 판정 자체는 맞았어도, 그 이유를 설명하는 문장이 부정확하거나 매번 표현이 달라지면 사용자 입장에서는 신뢰하기 어려운 결과가 나올 수 있다.
이 지점에서 팀 내에 "그럼 임베딩 기반으로 접근하면 어떨까"라는 이야기가 나왔다. 나는 이때 처음으로 "임베딩이 대체 뭔데?"부터 찾아봐야 했다.
3. RAG와 임베딩, 이해한 만큼 정리
찾아보니 개념 자체는 생각보다 명료했다.
임베딩(Embedding)은 사람이 사용하는 복잡한 데이터(단어, 문장, 이미지 등)를 컴퓨터가 이해하고 처리하기 쉬운 숫자로 이루어진 벡터로 변환하는 방법이다. 이때 핵심은, 의미가 비슷한 텍스트일수록 벡터 공간에서 서로 가까운 위치에 놓인다는 점이다. 예를 들어 "고양이"와 "강아지"의 벡터는 "고양이"와 "자동차"의 벡터보다 훨씬 가깝게 계산된다. 이걸 이용하면 "이 문서가 기준 문서와 얼마나 의미적으로 유사한가"를 수치로 계산할 수 있다.
RAG(Retrieval-Augmented Generation)는 이 임베딩 기반 유사도 검색(Retrieval)을 이용해서 관련 문서를 먼저 찾아온 다음, 그 문서를 근거로 LLM이 답을 생성(Generation)하게 만드는 구조다. 이름 그대로 "검색으로 증강된 생성"인 셈이다.
이걸 이해하고 나니 우리 상황에 대입이 됐다. 만약 콘텐츠 적합성 검증에 RAG를 적용한다면, "개발 콘텐츠 판단 기준이 되는 정책 문서나 예시 사례들"을 임베딩해서 저장해 두고, 새로운 콘텐츠가 들어올 때마다 그것과 가장 유사한 기준 문서를 검색한 다음, 그 근거를 AI에게 쥐여주고 판단하게 만드는 방식이 될 거다.
4. 근데 왜 임베딩을 안 쓰기로 했나
결론부터 말하면, 우리 팀은 이렇게 결정했다.
- AI API는 자연어로 된 부적합 사유 설명을 생성하지 않는다.
- 대신 점수, 적합/부적합 여부, 부적합 사유 코드(
reject_reason_code) 같은 구조화된 값만 응답하게 한다. - 사용자에게 실제로 보여줄 문구는 이 사유 코드에 맞춰 서버에서 고정 문구로 미리 매핑해둔다.
- 임베딩이나 RAG는 "판단 기준이 될 정책 문서나 예시 corpus가 충분히 쌓였을 때" 다시 검토하기로 미뤄둔다.
이 결정을 이해하고 나서야 RAG를 언제 써야 하는지가 더 선명해졌다. RAG는 "검색할 근거 문서가 이미 존재하고, 그 문서에 기반한 판단이 필요할 때" 쓰는 도구지, 단순히 "AI 판단을 더 정교하게 만들고 싶다"는 이유만으로 도입하는 게 아니었다. 우리는 아직 참고할 정책 문서 corpus 자체가 없었으니, 애초에 RAG를 적용할 재료가 없었던 셈이다.
그리고 "AI가 자연어로 사유를 설명하게 두는 것"과 "임베딩으로 검색해서 근거를 붙이는 것" 사이에는 사실 한 단계가 더 있었다. AI는 의미 판단(적합한지 아닌지)에만 집중하게 하고, 사용자에게 보여줄 표현은 애플리케이션이 결정론적으로(deterministic) 통제한다는 절충안이었다. 이게 할루시네이션 위험도 줄이면서, 임베딩 인프라 없이도 MVP 안에서 검증 가능한 방식이었다.
5. 두 번째 논의: 개인화 추천
두 번째로 RAG를 다시 마주친 건 완전히 다른 기능을 논의할 때였다. "사용자가 이전에 학습한 개념과 새로 입력한 아티클을 연결해서 문제를 만들어주면 어떨까?"라는 제안이었다.
- 이미 맞힌 개념은 출제 빈도를 낮춘다.
- 자주 틀리는 태그(예: Spring Transaction, JVM)는 반복 출제한다.
- 같은 주제의 새 아티클이 들어오면 이전에 학습한 개념과 연결해서 문제를 생성한다.
- "이번 아티클은 이전에 학습한 JPA 영속성 컨텍스트와 연결됩니다" 같은 피드백을 제공한다.
얼핏 들으면 단순한 추천 기능처럼 보였는데, 파고들어 보니 이걸 구현하려면 두 가지가 선행되어야 했다.
- 태그·개념 단위로 사용자의 정답 이력을 구조화해서 쌓아두는 것
- 아티클 간 의미적 유사도를 판단하는 것 — 즉 새 아티클이 과거에 학습한 어떤 아티클과 개념적으로 연결되는지 찾아내는 것
이번엔 내가 먼저 알아챌 수 있었다. "이거 사실상 RAG잖아?" 새 아티클과 의미적으로 비슷한 과거 학습 기록을 검색(Retrieval)해서, 그걸 근거로 "이 개념과 연결됩니다"라는 피드백을 생성(Generation) 해야 하는 구조였다.
하지만 이것도 결국 MVP 범위에서는 제외하기로 결정했다. 4주라는 기간 안에 정답 이력 데이터 모델과 임베딩 검색 인프라를 새로 설계하고 구축하는 건 리스크가 너무 컸고, 검증되지 않은 유사도 판단 로직이 오히려 이상한 문제 연결을 유발할 수도 있었다. 무엇보다 이 기능은 서비스의 핵심 목표인 "북마크만 하고 안 읽는 문제 해결"과는 결이 다른 확장 기능이었다.
6. 두 논의를 겪고 정리한 생각
두 논의를 겪고 나서 내 나름대로 정리한 기준은 이거다.
RAG는 "AI를 더 똑똑하게 만들고 싶다"는 막연한 이유로 붙이는 기능이 아니라, 검색할 근거(문서나 이력 데이터)가 이미 쌓여 있고, 그 근거에 기반한 답변이 꼭 필요할 때 고려하는 아키텍처다.
그리고 임베딩은 그 자체로 목적이 아니라, "의미 유사도를 계산 가능한 숫자로 바꾸는 도구"일 뿐이었다. RAG는 그 도구를 활용한 하나의 패턴이고.
신기했던 건, 두 논의 모두 "RAG를 쓸까 말까"로 시작했는데, 결국 "왜 안 쓰는가"를 설명하는 과정이 오히려 "언제 써야 하는가"를 훨씬 선명하게 알려줬다는 점이다. 개념을 먼저 배우고 적용할 곳을 찾았다면 이렇게 와닿지 않았을 것 같다.
7. 느낀 점
이번에 RAG와 임베딩을 배운 방식이 나한테는 꽤 특이한 경험이었다. 보통 새로운 개념은 "일단 정의부터 외우고, 어디에 쓰는지는 나중에"의 순서로 배웠던 것 같은데, 이번엔 정반대였다. "이걸 왜 안 쓰기로 했는가"라는 실제 의사결정 과정을 따라가다 보니 개념이 자연스럽게 이해됐다.
다음에 진짜로 RAG를 구현해 볼 기회가 생긴다면, 이번에 배운 기준부터 먼저 점검해 볼 것 같다. "지금 이 문제에 검색할 근거 문서나 이력 데이터가 실제로 쌓여 있는가? 그리고 그 근거에 기반한 판단이 정말 필요한가?" 이 질문에 그렇다고 답할 수 있을 때 RAG를 꺼내는 게, 이번 프로젝트에서 얻은 가장 실용적인 교훈이었다.