기술

RAG 에 이번달을 물었더니 2년 전 문서를 요약했습니다 — 벡터 검색엔 시간 축이 없습니다

2026.09.2910분 읽기

사내 문서 지식그래프 검색 도구를 운영하고 있습니다. 제안·컨설팅 도메인 고객사의 문서 9천여 건이 들어 있고, 채팅으로 물으면 문서를 찾아 답하는 구조입니다. 오후 4시 28분에 고객사 대표가 메신저 창구로 물었습니다. '이번달 주간보고 내용 요약 좀 해줄래?'

돌아온 답은 2024년 8월, 한 발주처 사업의 주간업무보고 PDF 를 길게 요약한 것이었습니다. 질문한 달과 2년이 떨어져 있었습니다.


답이 스스로 확인 필요라고 적고서 진행했습니다

이 사고의 성질은 답 첫 줄에 그대로 드러나 있었습니다. 모델은 '확인 가능한 자료 기준으로는 2024년', '어느 연도·월인지 확인이 필요하다'고 먼저 적었습니다. 그러고 나서 요약을 이어갔습니다.

에러 로그는 한 줄도 없었습니다. 답은 문장으로 멀쩡했고, 근거 문서도 실재하는 문서였고, 요약 자체도 정확했습니다. 틀린 것은 시기 하나뿐입니다. 이런 종류의 답은 맞는 답과 겉모습이 같아서, 질문한 사람이 날짜를 직접 확인하기 전까지는 아무도 못 잡습니다.


첫 의심은 권한이었고, 틀렸습니다

같은 날 오후에 '주간업무는 글로' 기능을 붙였습니다. 주간업무 문서 한 부를 글 한 편으로 올리고 원본은 첨부로만 두는 방식입니다. 최근 5주치를 올린 직후였고, 그 시각 운영 서버는 사내 출처를 모르는 코드였습니다. 올린 5편이 권한 필터에서 빠져 있었던 것입니다.

8분 뒤에 배포하고 같은 질문을 다시 쳤습니다. 답이 같았습니다. 권한은 원인이 아니었습니다.

여기서 하마터면 그냥 넘어갈 뻔했습니다. 원인으로 보이는 것을 하나 고치면 '됐겠지'가 되기 쉬운데, 이날은 두 번 다시 쳐서 두 번 다 뒤집혔습니다. 배포 뒤에 같은 질문을 다시 치는 습관이 이 글의 절반입니다.

이번달 질문이 2년 전 문서를 답하게 만든 세 가지 원인을 정리한 화이트보드 도식


원인은 세 겹이었습니다

처음엔 검색 문제 하나로 봤습니다. 파보니 서로 다른 층에 원인이 세 개 있었고, 하나만 고쳐서는 아무것도 바뀌지 않는 구조였습니다.

벡터 검색은 뜻이 닮은 조각을 고를 뿐입니다. '이번달'이라는 말을 날짜 범위로 바꾸지 못합니다. 검색은 제목에 '주간보고'가 박힌 조각 수백 개를 성실하게 올렸습니다.

답 프롬프트의 규칙이 '근거가 부족하면 모른다고 답하라' 하나뿐이었습니다. 근거는 부족하지 않았습니다. 주간보고 이야기는 잔뜩 있었고 시기만 달랐습니다.

프롬프트에 오늘 날짜가 없었습니다. 모델이 '이번달'을 풀려고 해도 풀 재료가 없었습니다.

세 번째가 가장 허무한데, 실제로 없었습니다. 상대 시간 표현은 기준일이 있어야 뜻이 생기는 말입니다. 기준일을 안 주고 '이번달'을 이해하기를 기대한 셈입니다.

그리고 첫 번째와 두 번째는 짝입니다. 검색이 시기가 다른 조각을 올려도 답 규칙이 그 자리에서 멈춰 세우면 사고가 안 납니다. 반대로 규칙이 허술해도 검색이 기간을 알면 애초에 옛 조각이 위로 안 옵니다. 두 층이 같이 비어 있어서 그대로 통과했습니다.


주간보고는 검색할 것이 아니라 조회할 것이었습니다

갈림길이 하나 있었습니다. 검색을 고칠 것인가, 전용 도구를 만들 것인가. 결론은 둘 다이고 순서가 있었습니다.

주간업무 글은 날짜와 절 구조를 이미 갖고 있습니다. 어느 주의 글인지 알고, 진행·계약·제안·영업·경영지원 같은 절로 이미 나뉘어 있습니다. 그런 데이터를 청크로 쪼개 유사도로 다시 찾는 것은 가진 구조를 버리고 다시 추측하는 일입니다. 답이 구조에서 나오는 단위는 검색이 아니라 조회입니다.

그래서 주간업무 전용 도구를 90분에 붙였습니다.

주간업무·주간보고 류 낱말과 요약·조회 낱말이 같이 있으면 라우터 앞에서 잡습니다. 쓰는 법을 묻는 질문은 제외합니다.

질문에서 기간을 읽습니다. 이번주·지난주·이번달·지난달·N월·올해·최근 N주를 날짜 범위로 바꾸고, 기간 표현이 없으면 가장 최근 한 편으로 둡니다.

그 기간의 주간업무 글을 DB 에서 골라 통째로 엔진에 넘깁니다. 검색을 아예 하지 않습니다.

기간에 글이 없으면 LLM 을 부르지 않고 '없다, 올라와 있는 주는 이렇다'로 답합니다.

네 번째가 이 도구에서 가장 마음에 드는 부분입니다. 없는 것을 있는 것처럼 쓸 수 있는 자리를 아예 없앴습니다. 모델을 부르지 않으면 지어낼 수도 없습니다.

글의 날짜는 주 시작일로 통일했습니다. 이미 올라가 있던 5편은 DB 에서 옮겼습니다. 배포 뒤 같은 질문을 치니 이번 달 주간업무 3편을 골라 절별로 한 줄씩 정리해 냈고, 근거 3편이 전부 사내 출처였습니다. 2024년 PDF 는 한 칸도 없었습니다.

같은 질문을 검색으로 처리할 때와 조회로 처리할 때의 경로를 좌우로 비교한 도식


도구로는 못 막는 것이 남습니다

전용 도구는 주간보고만 구합니다. '올해 실적'을 물었는데 2022년 제안서를 요약하는 것도 정확히 같은 병인데, 그건 도구를 하나 더 만든다고 막히지 않습니다. 그래서 공통 층을 세 갈래로 손봤습니다.

첫째, 답 프롬프트입니다. 오늘 날짜를 요일까지 적어 넣었습니다. 규칙도 한 줄 늘렸습니다. 질문의 기간과 근거의 시기가 확실히 다르면 그 기간 자료를 찾지 못했다고만 답하라, 다만 시기를 모르는 근거는 버리지 마라. 뒷문장이 없으면 규칙이 과잉 방어로 뒤집힙니다. 날짜를 못 붙인 문서가 있는 한, 모르는 것을 버리라는 규칙은 멀쩡한 근거까지 같이 버립니다. 근거마다 시기 칸도 같이 붙였습니다.

둘째, 시간 축입니다. 질문에서 기간을 읽는 파서를 따로 두고, 청크 payload 에 `date` 를 넣었습니다. 값을 정하는 순서는 파일명 날짜, 없으면 폴더 이름의 여덟 자리 날짜, 글이면 앱이 준 값입니다. 회의록은 회의한 날, 업무일지는 일한 날, 메모는 만든 날로 각각 다릅니다.

셋째, 백필입니다. 문서 9,077건 중 파일명에서 날짜를 읽은 것이 2,651건, 폴더 이름에서 읽은 것이 6,356건, 둘 다 없어 건너뛴 것이 70건이었습니다. 실제로 쓴 것은 9,003건, 실패 0건입니다. Qdrant 의 `date` 에는 datetime 인덱스를 걸었습니다. 문자열 날짜에도 범위 조건이 적용됩니다. 인덱스를 걸기 전에 '날짜가 빈 것'을 세어 보려다 타임아웃이 났는데, 인덱스 없는 키의 전수 스캔이었습니다.


내리기만으로는 순서가 안 바뀝니다

검색 쪽 처리는 기간 밖 조각을 0.6배로 내리는 방식으로 잡았습니다. 거르지 않고 내리기만 하는 이유는 날짜를 모르는 조각 때문입니다. 날짜를 못 붙인 70건이 검색에서 사라지면 안 됩니다.

그렇게 배포하고 실측했더니 순서가 그대로였습니다. 감점은 후보 풀에 들어온 조각에만 적용됩니다. 옛 문서가 상위를 다 차지하고 있으면 새 문서는 애초에 후보에 없어서, 감점을 아무리 세게 걸어도 올라올 조각 자체가 없습니다.

그래서 기간 안 조각만으로 보조 풀을 하나 더 떠서 기존 결과와 합치도록 고쳤습니다. 내리는 것과 올리는 것은 다른 일입니다.

두 번째 배포 뒤 실측입니다. 그 2024년 사업 이름을 넣고 '이번달 진행 상황'을 물으니 2026년 9월 자료를 찾지 못했다고 답하면서 있는 것은 2024년 하반기라고 덧붙였습니다. 6.9초입니다. '이번달 주간보고 요약'에는 9월 글이 위로 올라오고 2024년 PDF 는 상위 6칸 밖으로 밀렸습니다.

기간 밖 조각을 감점만 했을 때와 보조 풀을 추가했을 때의 검색 결과 순위 변화 비교


고치고도 남은 것

기간이 없는 질문에는 아무것도 바뀌지 않습니다. 그냥 '주간보고 요약'이라고만 물으면 2024년 PDF 가 그대로 위에 있습니다. 그게 맞는 동작입니다. 여기서 '최신을 우선하라'까지 가면 옛 사업을 물을 때 망가집니다.

폴더 날짜는 대개 사업 시작일이라, 그 안의 문서가 실제로 쓰인 시기와 다를 수 있습니다. 시기가 근사치인 조각이 6천 건대입니다. 날짜가 있다는 것과 날짜가 맞다는 것은 다릅니다.

전용 도구는 낱말 매칭이라 '지난주에 뭐 했더라' 같은 표현은 라우터 앞에서 못 잡습니다. 라우팅 규칙이 늘수록 규칙끼리 충돌하는 자리도 늘어납니다. 그래서 자체검증 표본으로 라우터 정확도를 잰 다음 걷어낼 계획을 같이 잡아 뒀습니다.

그리고 전용 도구 응답이 35.3초입니다. 3편 분량을 경량 모델로 쓰는 시간인데, 메신저 창구에는 스트리밍이 없어서 그 시간이 그대로 침묵으로 체감됩니다. 답이 맞아도 35초면 안 씁니다. 미리 요약해 저장하면 되는데 아직 안 했습니다.


같은 병을 찾는 점검 항목

RAG 를 운영하면서 이 사고와 같은 자리를 찾아볼 때 쓰는 목록입니다.

답 프롬프트에 오늘 날짜가 요일까지 들어 있는지 확인합니다. 없으면 상대 시간 표현은 모델에게도 뜻이 없습니다.

거절 규칙이 근거 부족만 막고 있는지 봅니다. 근거가 넘치는데 시기·대상이 다른 경우는 따로 적어야 합니다.

거절 규칙에는 '시기를 모르는 근거는 버리지 마라'를 짝으로 붙입니다. 안 붙이면 규칙이 과잉 방어로 뒤집힙니다.

날짜와 구조를 이미 가진 데이터가 검색으로 처리되고 있지 않은지 봅니다. 그건 조회로 옮길 후보입니다.

기간에 해당하는 자료가 없을 때 모델을 부르는지 봅니다. 안 부르는 것이 가장 확실한 거절입니다.

순위 조정을 넣었으면 실제 순서를 다시 잽니다. 후보 풀에 못 든 조각에는 감점이 적용되지 않습니다.


마무리

이 사고는 고장이 아니라 길이 없던 것이었습니다. 회사의 주간업무를 답하는 경로가 애초에 없었고, 없는 길을 지나가려던 질문이 가장 비슷해 보이는 다른 길로 들어갔습니다. 고장이 아니면 에러가 안 나고, 답은 맞는 답과 겉모습이 같습니다.

그래서 이런 종류는 사람이 표본을 모아 다시 읽는 수밖에 없다고 보고 있습니다. 매일 어제 답을 다른 자리에서 다시 심판하는 배치를 돌리기 시작했는데, 이 사고가 그 첫 표본이 됐습니다. 모델이 더 똑똑해지기를 기다릴 일이 아니라, 모델이 이미 '확인이 필요하다'고 적은 그 자리에서 멈추게 하는 일이 남아 있었습니다.

#RAG#벡터검색#시간축#날짜필터#Qdrant#프롬프트설계#도구라우팅