RAG- 정리부터 하고 좀 찾자

정리부터 하고 좀 찾아라

by the게으름

바이브코딩 109

정리부터 하고 좀 찾아라


지난 편에서 이렇게 끝냈다.

RAG는 검색 기술이 아니다.

AI의 세계를 설계하는 첫 번째 단계다.

좋다.

설계가 중요하다는 건 알겠다.


그래서 설계를 하러 갔다.

그리고 또 망했다.

이번엔 다른 방식으로.

찾긴 찾는데, 엉뚱한 걸 가져온다



RAG를 붙이고 나서 한동안은 좋았다.


AI가 파일을 안 보고 말하는 일은 줄었다.

없는 파일을 상상하는 일도 줄었다.

연결된 모듈까지 따라가는 것도 됐다.

그런데 어느 순간부터 이상한 일이 벌어지기 시작했다.

분명 맞는 파일을 가져왔는데,

결과가 내가 원한 방향이 아니다.

예전에는 "야, 파일도 안 보고 말하냐"라고 할 수 있었다.

지금은 그 말도 못 한다.


AI는 파일을 봤다.

연결도 따라갔다.

그런데도 어긋난다.


"엄마, 나 저번에 새로 산 옷 어디 갔어?"


"여기 있잖니."

"아니 그건 까만색이고, 새로 산 거. 그 핑크색."

"이거?"

"아니 그 핑크색 바지 말고, 핑크색 티셔츠."

이쯤 되면 엄마가 폭발한다.

"야 이년아, 옷 정리부터 하고 좀 찾아라."


이게 정확히 AI한테 벌어지는 일이다


AI한테 이렇게 말한다.

"저번에 만든 로그인 관련 코드 찾아줘."

AI가 가져온다.

"이 파일인 것 같습니다."

"아니, 그건 옛날 버전이고. 새로 만든 거."

"이건 어떤가요?"

"아니 그건 테스트 파일이잖아. 본 코드 말이야."

세 번, 네 번 반복되면

사람은 빡치고, AI는 점점 혼란에 빠진다.

그리고 결국 나오는 말.

"아 그냥 내가 찾을게."

이 순간,

RAG를 붙인 의미가 사라진다.


왜 이런 일이 벌어지는가

이유는 단순하다.

옷이 한 서랍에 다 들어가 있기 때문이다.

까만색 코트도,

핑크색 바지도,

핑크색 티셔츠도,

3년 전에 산 잠옷도,

어제 빨래한 속옷도,

전부 같은 서랍에 있다.


이 상태에서 "핑크색 티셔츠"를 찾으라고 하면

당연히 핑크색인 것부터 집어온다.

핑크색 바지가 먼저 손에 잡힌다.


틀린 건 아니다.

핑크색 맞다.

근데 내가 원한 건 그게 아니다.


AI도 똑같다.

모든 파일이 한 곳에 있으면,

AI는 "가장 비슷해 보이는 것"을 골라올 수밖에 없다.

그게 진짜 중요한 건지,


아니면 그냥 비슷한 단어가 많은 건지,

구분할 방법이 없다.

서랍이 하나니까.


엄마의 해결책은 늘 같았다

엄마의 해결책은 기술이 아니었다.

정리였다.

"겉옷은 옷장."

"속옷은 서랍 첫 번째 칸."

"계절 지난 건 이불장 위."


이렇게 나눠놓으면

"엄마 내 핑크색 티셔츠 어디있어?"에

1초 만에 답이 나온다.

"옷장에 걸려 있잖아."

찾는 곳이 정해져 있으니까.

겉옷이면 옷장. 끝.

나머지 서랍은 열어볼 필요도 없다.


그래서 나도 서랍을 나눴다


나는 AI한테도 같은 짓을 했다.

서랍을 세 칸으로 나눴다.

각각 역할이 다르다.

각각 속도가 다르다.

각각 들어가는 옷이 다르다.

그리고 각각 기술적으로 아예 다르게 생겼다.


첫 번째 칸: 매일 입는 옷

첫 번째 칸에는 매일 입는 것만 넣는다.

지금 작업 중인 파일.

방금 수정한 코드.

오늘 적은 메모.

이 칸의 핵심은 빠르다는 것이다.


인터넷이 필요 없다.

외부 서버가 필요 없다.

내 컴퓨터 안에서 바로 동작한다.

이 칸이 하는 일은 단순하다.


내 프로젝트 파일들을 쪼갠다.

각 조각에 "이건 이런 내용이다"라는 꼬리표를 붙인다.

AI가 뭔가를 찾을 때, 그 꼬리표를 보고 "이 조각이 제일 비슷하네"라고 골라온다.


이 꼬리표를 만드는 방식이 TF-IDF라는 건데,

이름은 어려워 보여도 원리는 간단하다.

"이 단어가 이 문서에서 얼마나 중요한가"를 숫자로 매기는 것이다.

"the"같은 단어는 어디에나 있으니까 점수가 낮고,

"login"같은 단어는 특정 파일에만 있으니까 점수가 높다.

그 점수가 꼬리표가 된다.

전부 내 컴퓨터 안에서 계산되고, JSON 파일 몇 개로 저장된다.


.vibe/cache/

├── memory_index.v1.json ← 꼬리표 저장소

├── memory_hashvec_index.v1.json ← 빠른 비교용 인덱스

└── memory_embeddings_index.v1.json


와이파이 꺼져도 된다.

API 비용도 0원이다.

매일 입는 옷은 손 닿는 곳에 있어야 하니까.

AI한테 이렇게 시키면 된다:

"내 프로젝트 파일들을 스캔해서 로컬 검색 인덱스를 만들어줘.
TF-IDF 기반으로, JSON 파일로 저장하고,
외부 API 없이 내 컴퓨터에서만 동작하게 해줘.
파일이 바뀌면 자동으로 인덱스도 업데이트되게 하고."


이 정도면 AI가 구조를 잡아준다.

물론 세부 조정은 필요하지만, 뼈대는 이걸로 나온다.


두 번째 칸: 외출복

첫 번째 칸에서 못 찾았다.

혹은 찾긴 했는데 확신이 안 선다.

그러면 두 번째 칸을 연다.

여기에는 좀 더 격식 있는 것들이 들어간다.

첫 번째 칸과 결정적으로 다른 게 하나 있다.

검색 방식이 아예 다르다.


첫 번째 칸은 "이 단어가 들어있는 파일"을 찾는다.

두 번째 칸은 "이 말과 뜻이 비슷한 파일"을 찾는다.


예를 들어 "로그인 기능"을 검색하면,

첫 번째 칸은 "로그인"이라는 글자가 있는 파일을 가져온다.

두 번째 칸은 "사용자 인증", "세션 관리", "비밀번호 확인"처럼

로그인이라는 단어가 없어도

의미가 가까운 파일까지 같이 가져온다.

이게 벡터 검색이다.


글자가 아니라 의미로 찾는 것.

어떻게 가능하냐.


임베딩이라는 기술을 쓴다.

문장을 숫자 배열로 바꾸는 건데,

비슷한 의미의 문장은 비슷한 숫자 배열이 된다.

"로그인 기능"과 "사용자 인증"은

사람이 보면 비슷한 말이다.

임베딩을 거치면 숫자도 비슷해진다.

그래서 단어가 달라도 찾을 수 있다.

이 숫자 배열들을 모아두는 곳이 벡터 데이터베이스다.

나는 ChromaDB라는 걸 쓴다.

이건 내 컴퓨터에서 돌릴 수도 있고, 서버에 띄울 수도 있다.

AI한테 이렇게 시키면 된다:

"벡터 데이터베이스를 하나 세팅해줘. ChromaDB 쓸 거야.
내 프로젝트 문서들을 임베딩해서 저장하고,
의미 기반으로 검색할 수 있게 만들어줘.
임베딩 모델은 로컬에서 돌릴 수 있는 걸로 해줘.
인터넷 안 될 때도 동작해야 하니까."


핵심 차이를 다시 말하면 이거다.

첫 번째 칸: 글자로 찾는다. 빠르고 가볍다.

두 번째 칸: 의미로 찾는다. 느리지만 정확하다.


사람으로 치면,

머릿속으로 안 되니까 노트를 뒤지는 단계다.

예전에 정리해둔 것, 비슷한 주제로 묶어둔 폴더.

거기서 찾으면 좀 더 확실하다.


세 번째 칸: 금고에 넣어둔 정장

세 번째 칸은 아무 때나 안 연다.

여기에는 공식 문서만 들어간다.

확정된 스펙.

최종 결정 사항.

절대 바뀌면 안 되는 기준.


여기서 나온 답은 다른 칸보다 무겁다.

첫 번째 칸이 "이거 아닐까요?"라면,

세 번째 칸은 "이겁니다."다.


이 칸은 첫 번째, 두 번째 칸과 또 다르게 생겼다.

두 가지 검색을 동시에 돌린다.

최종 점수 = 0.6 × 의미 유사도 + 0.4 × 키워드 일치도

"의미가 비슷한 것"과 "단어가 정확히 일치하는 것"을

섞어서 점수를 매긴다.


이걸 하이브리드 검색이라고 한다.

왜 섞느냐.

의미만으로 찾으면 엉뚱한 게 올라올 수 있다.

키워드만으로 찾으면 정확한 의미를 놓칠 수 있다.

둘을 6:4로 섞으면 꽤 정확하다.


이 비율을 찾는 데 시간이 좀 걸렸다.

이걸 돌리려면 제대로 된 데이터베이스가 필요하다.

나는 PostgreSQL에 pgvector라는 확장을 붙여서 쓴다.

이건 첫 번째 칸의 JSON 파일과는 급이 다르다.


무겁지만, 그만큼 정확하다.

금고라서.

AI한테 이렇게 시키면 된다:

"PostgreSQL에 pgvector 확장을 설치하고
벡터 검색이 가능한 테이블을 만들어줘.
임베딩 컬럼과 텍스트 검색 인덱스를 둘 다 만들고,
검색할 때 벡터 유사도 60%, 텍스트 매칭 40% 비율로
합산 점수를 내는 하이브리드 검색을 구현해줘."


이 말을 AI한테 던지면

테이블 구조, 인덱스, 검색 쿼리까지 만들어준다.

물론 PostgreSQL이 뭔지 모르겠다면

AI한테 "PostgreSQL 설치부터 해줘"라고 하면 된다.

그것도 해준다.


세 칸의 차이를 한눈에 보면 이렇다


화면 캡처 2026-02-24 020629.png
화면 캡처 2026-02-24 021916.png

위에서 아래로 갈수록 느려지지만 정확해진다.

아래에서 위로 갈수록 빨라지지만 대충이다.

그래서 항상 위에서 시작한다.

아침에 양말 꺼내려고 금고 비밀번호를 누를 필요는 없으니까.


꼭 세 칸 다 만들어야 하나?

아니다.

처음부터 세 칸을 다 만들 필요 없다.

첫 번째 칸만으로도 시작할 수 있다.

JSON 파일 몇 개면 되고,

인터넷도 필요 없고,

데이터베이스 설치도 필요 없다.

AI한테 "내 프로젝트 파일 스캔해서 검색 인덱스 만들어줘"

이 한 마디면 첫 번째 칸은 완성된다.


그다음 프로젝트가 커지면

두 번째 칸을 추가한다.

의미 기반 검색이 필요할 때.

그리고 공식 스펙 문서가 쌓이기 시작하면

세 번째 칸을 올린다.


확정된 답이 필요할 때.

이게 점진적 검색이다.

필요할 때 한 칸씩 추가하면 된다.

처음부터 금고를 만들 필요는 없다.


왜 첫 번째 칸은 인터넷이 필요 없어야 하는가

여기서 한 가지 더.

첫 번째 칸이 오프라인에서 도는 건 편의가 아니다.

설계 원칙이다.

바이브코딩을 하는 사람은

항상 인터넷이 좋은 환경에 있지 않다.

카페에서 와이파이가 끊길 수도 있고,

비행기 안에서 작업할 수도 있고,

그냥 서버가 느린 날도 있다.

그때 AI가 내 프로젝트를 아예 못 보면?

그건 도구가 아니라 짐이다.

실제로 내가 만든 시스템에서는

5개 검색 방식 중 4개가 인터넷 없이 돌아간다.

인터넷이 필요한 건 가장 정밀한 외부 API 검색 하나뿐이다.

내 프로젝트의 검색이

다른 회사의 서버 상태에 좌우되면,

그건 내가 통제할 수 있는 시스템이 아니다.


하지만 칸을 나누면 새로운 문제가 생긴다


칸이 세 개라는 건 좋다.

하지만 현실에서는 이런 일이 벌어진다.

같은 내용이 세 칸에 다 들어가 있다.


첫 번째 칸에서는 "로그인은 이메일 방식입니다."

두 번째 칸에서는 "로그인은 소셜 로그인으로 변경 검토 중."

세 번째 칸에서는 "로그인은 이메일 + 소셜 모두 지원. 확정."


AI가 세 개를 다 찾아왔다.

이 중에 뭘 믿어야 하는가?

이건 검색의 문제가 아니다.

신뢰의 문제다.

같은 정보라도

어디서 나왔느냐에 따라 무게가 달라야 한다.


스펙 문서에서 나온 답과

3일 전 메모에서 나온 답이

같은 무게일 수는 없다.

이걸 정하지 않으면

칸을 세 개로 나눈 의미가 없다.

세 칸에서 각각 다른 답을 가져왔는데

AI가 아무거나 골라온다면,

한 서랍에 다 넣은 것과 뭐가 다른가.