400페이지짜리 법률 PDF의 RAG 환각 문제를 LlamaParse와 시맨틱 검색을 버리고 엄격한 메타데이터 필터링으로 해결했습니다.
I fixed RAG hallucinations on a 400-page Legal PDF by ditching LlamaParse and Semantic Search for strict metadata filtering.
핵심 요약
법률 문서의 구조적 특성을 활용해 정규식 파싱과 메타데이터 필터링을 도입하여 RAG 시스템의 환각을 제거한 사례입니다.
- 구조적 파싱 — 정규식을 활용해 각주를 제거하고 조항 단위로 청킹함
- 메타데이터 필터링 — Pinecone 메타데이터 필터를 적용해 검색 정확도 향상
- 멱등성 레이어 — SHA-256 해싱으로 중복 처리 방지 및 효율적 동기화 구현
- 성능 최적화 — 하드웨어 제약 상황에서 결정론적 접근으로 환각 제거
다들 안녕, 법률/금융 문서 파싱용 Agentic RAG 시스템 만들다가 아키텍처 개선한 거 있어서 공유 좀 하려고.
문제점: 인도 헌법(400페이지 넘음)을 인덱싱하려고 했는데, 처음엔 LlamaParse를 썼거든. 근데 이 문서에는 완전히 꽝이었어. 페이지들을 624개의 거대한 덩어리로 뭉쳐버리고, 조항(Article) 경계도 다 놓치고, 각주까지 전부 다 긁어오더라고. 사용자가 "제19조가 뭐야?"라고 물어보면, 검색기가 그냥 숫자 "19"가 의미적으로 비슷하다는 이유만으로 200페이지에 있는 아무 상관 없는 수정안 각주를 가져오는 식이었음. 당연히 LLM은 쓰레기 같은 문맥을 바탕으로 헛소리를 지껄였지.
해결책: 비싼 LLM 파서 갖다 버리고, 그냥 PyMuPDF로 갈아탄 다음에 아주 특화된 인제션 파이프라인을 짰어:
- 커스텀 정규식 파싱:
______각주 라인을 기준으로 페이지 텍스트를 바로 잘라버림. 하단부는 그냥 버렸어. 각주는 하나도 안 들어감. - 조항 단위 청킹: 부모 청크를 나누려고 쓰던
RecursiveCharacterTextSplitter는 갖다 버렸어. 오직 조항 정규식 경계로만 문서를 나눴지. 이렇게 하니까 정확한 부모/자식 청크가 3,248개 나오더라. - 메타데이터 주입: 정규식으로 조항 번호를 뽑아서 Pinecone에 올리기 전에 청크 메타데이터에 박아버림 (
{"article_number": "19"}). - 스마트 라우팅: 내 LangGraph 라우터가 질문이 특정 조항을 묻는 건지 감지해. 맞으면
article_number를 검색기로 넘겨. 그럼 검색기는 Pinecone 메타데이터 필터({"article_number": {"$eq": "19"}})를 빡세게 걸어서 일반 벡터 검색을 아예 건너뛰어 버림.
결과 (환각 테스트): 여러 복잡한 질문으로 테스트해 봤는데, 시스템이 아주 완벽하게 작동함 (제3자 LLM 평가기로 검증 완료):
- 테스트 1 (제31C조 & Kesavananda Bharati 판례): 31C조 텍스트를 정확히 가져옴. 판례는 제공된 텍스트에 없다고 솔직하게 말함. 헛소리 안 함 (첨부).

