SHIFTONE

개정된 지 석 달인데 챗봇은 옛 규정을 답해요 — RAG 지식베이스 문서 최신화, 낡은 문서가 색인에 남는 세 자리

규정은 7월에 개정됐는데 챗봇은 옛 한도를 답해요. 개정 전 버전 · 반려 초안 · 회의록이 같은 색인에 남을 때를 40문항으로 재봤더니, 그냥 추가하면 개정 조항 20문항 중 15문항에서 옛 값이 1위였어요. 시간 감쇠는 반쪽이었고, 답은 정본 · 현행 메타데이터 필터였어요.

낡은 흰색 서류 서랍장 수십 칸에 손글씨 라벨이 붙어 있고 위에는 서류 뭉치가 쌓여 있는 모습 — 무엇이 현행인지 적혀 있지 않은 보관함

7월에 출장 규정이 바뀌었다고 해 볼게요. 국내 숙박비 한도가 12만 원에서 13만 원으로요. 석 달 뒤 사내 챗봇에 물었더니 아직 12만 원이라고 답해요. 근거로 붙은 문서는 개정 전 PDF 예요. 검색이 틀린 게 아니에요. 색인에 그 문서가 아직 있었을 뿐이에요.

올해 7월 국내 블로그에도 비슷한 사고가 올라왔어요. 챗봇이 연차 이월 한도를 10일이라고 답했는데, 근거는 3년 전 반려된 취업규칙 개정안 초안이었대요. 글쓴이의 정리가 정확했어요. 검색은 조금도 틀리지 않았다고요. 저희 골든셋 글과 청킹 글도 검색 품질만 쟀어요. 이번엔 색인 운영을 같은 방식으로 재봤어요.

색인에는 '현행'이라는 개념이 없어요

벡터 검색도 BM25 도 조각이 질문과 얼마나 비슷한지만 봐요. 2023년 규정과 2025년 규정은 숫자 하나만 다르니 점수도 거의 같고, 어느 쪽이 위로 올라올지는 사실상 운이에요. 더 나쁜 건 둘 다 올라왔을 때예요. 2025년 3월의 HoH 벤치마크는 옛 답과 현행 답이 공존하는 질문 9만 6천 개로 LLM 을 재봤는데, 현행 정보 옆에 옛 정보가 한 조각만 끼어도 주요 모델의 정확도가 20% 넘게 떨어졌어요. 정보가 없을 땐 모르겠다고 하던 모델이 옛 정보만 있으면 자신 있게 틀렸어요. 낡은 정보가 정보 없음보다 해롭다는 결론이에요.

낡은 문서가 남는 자리는 세 군데였어요

문서 폴더 출장 규정 v1 (2023) · v2 (2025-07) 연차 규정 개정안 초안 (반려) 경영회의 회의록 (2025-10) 재택 한시 지침 (폐지) 전부 수집기 섹션 단위로 조각 내기 B 같은 자리 덮어쓰기 → 짧아진 문서의 꼬리가 남아요 C 문서 단위 삭제 후 재삽입 색인 [출장 규정] 숙박비 12만 원 · v1 [출장 규정] 숙박비 13만 원 · v2 [회의록] 15만 원 인상안 보류 [초안] 이월 10일 · [폐지] 주 5회 … 남아 있는 조각 전부가 후보예요 질문 국내 출장 숙박비 얼마까지예요? 검색 점수는 비슷함만 재요 D 정본 · 현행 필터 status = current, 정본만 상위 3 → LLM 필터 없음: v1 · v2 · 회의록 동거 → 하나를 고르거나 섞어서 답해요 필터 있음: v2 조각만
  • 개정 전 버전. 같은 제목의 파일을 다시 올리면 새 문서로 들어가고, 옛 조각은 아무도 지우지 않아요.
  • 덮어쓴 뒤의 꼬리. 조각 번호 자리에 덮어쓰면 문서가 짧아질 때 끝 조각이 유령처럼 남아요.
  • 정본이 아닌 문서. 반려된 초안, 보류한 회의록, 폐지된 지침. 폴더째 색인하면 전부 들어가요.

같은 질문 40개로 색인 운영 방식 네 가지를 재봤어요

코퍼스는 청킹 글의 가상 규정 10건을 현행(v2, 2025년 7월 시행)으로 두고, 조항 17개의 숫자가 다른 개정 전 버전(v1, 2023년 1월)과 v2 에서 삭제된 조항 4개를 만들었어요. 여기에 반려 초안(연차 이월 10일 제안, 2025년 11월) · 회의록(숙박비 15만 원 인상안 보류, 2025년 10월) · 폐지 지침(인턴 포함 주 5회 재택, 2021년)을 더했어요. 고객사 데이터가 아니라 이 글을 쓰며 만든 문서예요. 질문은 개정 20 · 불변 16 · 삭제 4, 모두 40개예요. 검색은 청킹 글과 같은 문자 2-gram BM25 이고, LLM 없이 상위 3개 조각만 봤어요.

색인 운영 방식색인 조각옛 값이 1위 (개정 20문항)옛·새 값 동거유령 조항 (삭제 4문항)비정본이 상위 3 (40문항)
A 그냥 추가 (폴더 통째로)1021517411
B 같은 자리에 덮어쓰기5602412
C 문서 단위 삭제 후 재삽입5202015
D C + 정본 · 현행 필터460000

A 는 개정 20문항 중 15문항에서 옛 값이 1위였어요. 저희 구현은 점수가 같으면 먼저 들어온 조각이 이기는데, 옛 문서가 먼저 들어왔으니까요. 실제 벡터 DB 라도 거의 같은 점수에서 누가 이길지는 운이에요. 17문항은 옛 값과 새 값이 같이 올라왔어요. HoH 가 가장 위험하다고 한 조합이에요.

B 는 옛 값 문제를 없애지만 삭제된 조항 4문항 전부에서 유령 조각이 1위였어요. 조항이 하나 줄어 다섯째 자리를 덮어쓸 새 조각이 없었거든요. C 는 유령까지 없애는데, 비정본이 상위 3 에 든 문항이 11에서 15로 늘었어요. 옛 버전이 빠진 자리를 초안과 회의록이 채운 거예요. D 는 전부 0 이었어요. 지운 게 아니라 정본이고 현행인 조각만 후보로 삼았을 뿐이에요.

시간 감쇠는 반쪽짜리였어요

먼저 떠오르는 처방은 새 문서에 가산점이에요. A 색인 그대로 1년마다 점수를 10~30% 깎아 봤어요.

1년당 감쇠율옛 값이 1위옛·새 값 동거유령 조항비정본이 상위 3
0 (감쇠 없음)1517411
10%01448
20%11149
30%15210

옛 값 1위는 0~1로 떨어지는데, 동거 5문항과 유령 2개가 남고 비정본은 줄지 않아요. 노트북 교체 주기를 물으면 감쇠 30% 에서 1위가 '2년으로 줄이는 안이 나왔으나 보류'라는 회의록이에요. 최신이 정본은 아니었어요. HoH 도 시간 가중 랭킹이 옛 문서 적중을 88% 에서 50% 로 줄이는 대신 관련 문서 적중을 17% 잃었다고 적었어요.

그래서 '현행'을 점수가 아니라 메타데이터에 적기로 했어요

결정은 조각마다 정본 여부 · 상태 · 시행일을 적고 검색 필터로 거르는 거예요. 점수는 비슷함을 재는 도구지 현행을 재는 도구가 아니거든요.

doc_id: policy-travel
version: "2025-07"
doc_class: 정본          # 정본 · 참고 · 기록(회의록) · 초안
status: current          # current · superseded · repealed · rejected
effective_from: 2025-07-01
effective_to: null
supersedes: policy-travel@2023-01
content_hash: sha256:9f3a…
chunk_idx: 3

기본 검색에 status = current AND doc_class = 정본 이 들어가고, 이력 질문은 시행일 범위로 따로 걸러요. 옛 버전은 지우지 않고 상태만 바꿔요.

def ingest(doc, chunks):
    old = index.find(doc_id=doc.id, status="current")
    for c in chunks:
        c.meta.update(doc_class=doc.doc_class, status="current",
                      effective_from=doc.effective_from,
                      content_hash=sha256(c.text))
    index.upsert(chunks)                       # 새 버전을 전부 넣고
    index.update(old, status="superseded",     # 옛 버전은 지우지 않고
                 effective_to=doc.effective_from)   # 상태와 종료일만 바꾼다

그래서 꼬리 조각 문제가 구조적으로 사라져요. 옛 버전 조각 전체의 상태가 한 번에 바뀌니 새 버전의 조각 수는 상관없어요. 바뀐 조각만 다시 임베딩하는 건 해시 비교로 해요. 수정 시각은 다시 저장만 해도 바뀌지만 해시는 내용이 바뀔 때만 바뀌니까요.

AS-ISTO-BE
현행 판단검색 점수 (비슷함) 가 정함status · doc_class 메타데이터가 정함
개정본 입수파일 추가 또는 자리 덮어쓰기새 버전 삽입 + 옛 버전 superseded
초안 · 회의록폴더에 있으면 색인doc_class 로 분리, 기본 검색에서 제외
변경 감지수정 시각조각 해시

바로 믿으면 안 되는 세 가지

  • 가상 코퍼스에 BM25, 40문항이에요. 임베딩과 리랭커는 재지 않았어요. HoH 에서는 그쪽도 옛 조각을 50% 넘게 가져왔지만, 저희가 잰 건 아니에요.
  • '옛 값 1위 15문항'은 동점 규칙의 산물이에요. 숫자보다 '누가 이길지 색인이 정하지 않는다'는 구조가 결론이에요.
  • 메타데이터는 저절로 채워지지 않아요. 무엇이 정본이고 언제부터 시행인지는 문서 소유자가 정하는 일이에요.

검색은 틀리지 않았다는 말로 돌아와서

처음 장면의 챗봇은 색인에 남은 것 중 가장 비슷한 조각을 잘 가져왔어요. 문제는 색인에 무엇이 남아 있는지를 아무도 재지 않았다는 거예요. 저희가 쓰는 RAG 지표에는 시간 축이 없거든요. 그래서 골든셋에 옛 값과 새 값이 둘 다 있을 때 새 값을 가져오는지, 삭제된 조항을 물으면 없다고 답하는지를 더 넣기로 했어요. 권한 필터 글의 선필터 원칙을 '현행'에 옮겨 적은 규칙이라, 다음 개정 때 또 다듬게 될 거예요.

자주 묻는 질문

옛 버전을 그냥 지우면 안 되나요? 현행 질문만 받는다면 돼요. 다만 이력 질문에 못 답하고 과거 답변의 근거도 추적할 수 없어요. 색인 포함과 보관은 따로 정하는 편이 안전해요.

벡터 DB 의 메타데이터 필터가 있으면 끝인가요? status 를 갱신하는 수집기가 없으면 필터는 아무것도 거르지 않아요. 그리고 검색 뒤가 아니라 앞에 걸어야 해요. 뒤에 걸면 상위 3 이 전부 걸러져 빈 답이 나와요.

문서가 바뀐 걸 어떻게 알아채요? 수정 시각보다 내용 해시가 믿을 만해요. 다만 해시는 삭제를 못 잡으니, 원본 폴더와 색인을 주기적으로 대조해 사라진 문서를 repealed 로 돌리는 절차가 따로 필요해요.

Sources