본문 바로가기

AI·LLM 엔지니어링

RAG 전처리의 진짜 시작점, 문서 구조 분석 - 한컴 데이터 로더로 보고서 PDF를 직접 돌려봤습니다

반응형

한컴으로부터 제품 이용 및 원고료를 지원받아 작성했습니다.

들어가며

RAG(Retrieval Augmented Generation, 검색 증강 생성)를 직접 구축해 보면, 검색이 안 맞는 순간의 원인이 대부분 임베딩이나 검색 알고리즘이 아니라 그 앞단에 있다는 걸 알게 됩니다. 문서를 어떻게 잘라 넣었는지, 각 조각에 어떤 메타데이터가 붙었는지에서 품질이 갈립니다. 표 한가운데가 잘려 절반만 청크로 들어가거나, 제목과 본문이 뒤섞여 한 덩어리가 되면 아무리 좋은 리트리버를 붙여도 엉뚱한 답이 나옵니다.

그래서 저는 RAG 파이프라인에서 가장 저평가된 단계가 전처리, 그중에서도 문서 구조 분석이라고 생각합니다.

이번 글은 문서 구조가 청킹과 메타데이터, 검색 품질을 어떻게 결정하는지 정리하는 개념 가이드이면서, 동시에 한컴 데이터 로더(Hancom Data Loader)로 제가 직접 만든 보고서 PDF를 API로 변환해 결과를 뜯어본 실사용 후기입니다. 남이 준 샘플이 아니라, 구조를 알고 있는 문서를 직접 넣어봤습니다.

핵심 요약

  • RAG 검색 품질은 리트리버 이전에 청킹 단위와 메타데이터에서 대부분 결정됩니다.
  • 좋은 청킹은 문장 수가 아니라 문서의 구조(제목·본문·표·목록) 경계를 따라야 합니다.
  • 한컴 데이터 로더는 문서를 요소 단위로 인식해 구조화 JSON(aijson)으로 변환해 줍니다.
  • 직접 만든 2쪽 보고서를 데이터 로더 API로 변환하니 30개 요소로 분해됐고, 제목(DocTitle 1.00)·표(1.00)·목록(0.99)이 구분돼 나왔습니다.
  • 표 3개가 모두 인식됐고, 제가 병합해 둔 셀은 <td rowspan=2>로 보존됐습니다. 다만 소제목 분류가 흔들리고 저신뢰 요소도 있어, 그대로 쓰기보단 검수 한 겹이 필요했습니다.

RAG 전처리에서 문서 구조 분석이 청킹과 검색 품질을 좌우하는 이유

RAG 전처리에서 청킹을 흔히 "500 토큰씩, 50 토큰 겹쳐서" 같은 고정 길이로 처리합니다. 빠르지만, 이 방식은 문서를 글자 흐름으로만 보고 의미 단위를 무시합니다. 그 결과 이런 문제가 생깁니다.

  • 제목과 본문이 한 청크에 섞이거나, 반대로 제목만 따로 떨어져 맥락을 잃습니다.
  • 표가 행 중간에서 잘려, 헤더(항목명)와 값이 분리됩니다. 검색에서 표의 숫자만 걸리고 그게 무슨 값인지 모르게 됩니다.
  • 목록의 상·하위 항목 관계가 사라져, "1-1)"이 어느 "1)"에 속하는지 알 수 없습니다.

▲ 개념도: 고정 길이 청킹 vs 문서 구조 기반 청킹

핵심은 청킹의 단위가 "글자 수"가 아니라 "문서의 구조"여야 한다는 점입니다. 제목은 섹션의 경계이고, 표는 그 자체로 하나의 의미 블록이며, 목록은 계층을 가진 묶음입니다. 이 구조 정보를 미리 뽑아두면, 청킹 전략을 "구조 경계 기준"으로 바꿀 수 있고 각 청크에 "이건 표다", "이건 3장의 하위 항목이다" 같은 메타데이터를 붙일 수 있습니다. 문서 구조 분석은 바로 이 구조 정보를 자동으로 추출하는 단계입니다.

한컴 데이터 로더는 어떤 도구인가

한컴 데이터 로더는 한컴이 만든 문서 구조 분석 솔루션입니다. HWP·HWPX·PDF 같은 문서를 넣으면 제목, 본문, 표, 목록, 그림 같은 구성 요소를 인식해 구조화된 JSON으로 변환해 줍니다. 변환 결과물은 aijson이라는 형식으로, 요소별 텍스트뿐 아니라 카테고리·좌표·페이지 정보까지 담고 있어 RAG 파이프라인의 청킹·메타데이터 구성에 바로 넣을 수 있습니다.

한컴 설명에 따르면 외부 네트워크 연결 없이 내부망에서도 운용할 수 있다고 합니다. 계약서나 사내 보고서처럼 외부로 내보내기 어려운 문서를 다루는 환경에서는 이 온프레미스 문서 처리 특성이 현실적으로 중요한 선택 기준이 됩니다.

(제품 상세와 데모는 공식 페이지에서 확인할 수 있습니다: 한컴 데이터 로더 공식 페이지 )

한컴 데이터 로더로 직접 테스트, 내가 만든 보고서를 API로 변환

테스트 문서는 사내 "RAG 파일럿 운영 보고서" 2쪽짜리 PDF로, 일부러 구조를 다양하게 넣었습니다. 문서 제목, 문서정보 표, 병합 셀이 있는 지표 표, 1)·1-1) 형태의 중첩 목록, 주의(경고) 박스, 이슈 표, 페이지 번호까지 포함했습니다.

처리는 한컴 SDK의 마이페이지에서 API Key를 발급받아, 데이터 로더 REST API로 진행했습니다. 흐름은 문서 그대로입니다: 변환 요청(convert, webhook_url은 필수 파라미터입니다) → 상태 조회(status)로 PROCESSING을 몇 번 폴링 → 완료(DONE) 후 결과(download)로 aijson을 받았습니다. 과금은 페이지 단위(1페이지 10크레딧)라 2쪽이면 20크레딧이 잡힙니다. 크레딧은 변환이 성공한 경우에만 차감됩니다(공식 가이드 명시 사항). 실제로 손상된 PDF와 암호를 건 PDF를 넣어 확인해 보니, 요청 단계에서 거부되면서 크레딧이 전혀 차감되지 않았습니다.

▲ 실제 처리 결과 - 내가 만든 보고서 PDF의 구조 인식 (bbox 오버레이, 2쪽)

 

변환 결과(aijson)의 상단 메타데이터와 요소 개수는 이렇게 나왔습니다.

  • engine: PDF_AI_DL, format: PDF, numOfPages: 2, status: COMPLETED
  • ocrMode: AUTO가 아니라 FORCE로 잡혀, 디지털 PDF인데도 OCR(Optical Character Recognition, 광학 문자 인식)이 강제 적용됐습니다(텍스트는 정확했지만 참고할 점).
  • elements: 30개. 요소마다 id(예: p0o0)·category{label,type}·level·confidence·content{html,markdown,text}·bbox·pageIndex가 붙습니다.

카테고리 분포는 ListText 13, ParaText 5, Table 3, RegionTitle 2, PageHeader·PageNumber 각 2, DocTitle·OtherText 각 1, 그리고 정체불명의 None 1개였습니다. 평균 신뢰도는 0.907이었습니다.

▲ API 변환 결과 요약(실측) - 카테고리 분포 · 신뢰도 · 병합셀 보존

 

가장 확인하고 싶었던 표부터. 3개의 표가 모두 Table로 잡혔고, 특히 제가 세로로 병합해 둔 셀("이용", "검색 품질")이 실제로 content.html에서 <td rowspan=2>이용</td> 형태로 복원됐습니다. 표 제목은 별도 캡션 요소(TableName가 아닌 ListText로 잡히긴 했지만)로 분리돼, 표 본문과 따로 다룰 수 있었습니다. 제목(DocTitle)은 신뢰도 1.00, 본문 표도 1.00으로 안정적이었습니다.

이 구조를 RAG 전처리로 - 청킹과 메타데이터

이제 이 결과를 청킹 전략과 메타데이터 구성에 대입해 보겠습니다. 핵심은 "elements를 그대로 청킹 기준으로 삼는다"는 발상입니다.

▲ 개념도: 문서 구조 정보가 청킹과 메타데이터로 이어지는 흐름

제목·소제목 요소를 섹션 경계로 삼아 그 사이의 문단(ParaText)을 한 청크로 묶고, 표(Table)는 통째로 한 청크로 유지합니다. 목록은 level과 1)·1-1) 같은 numbering으로 상·하위를 복원합니다. 각 청크에는 page(pageIndex), type(category.label), heading_path(level+순서), bbox(원문 위치), confidence를 메타데이터로 붙입니다. id가 "p0o0"처럼 페이지와 읽기 순서를 함께 담고 있어, 청크를 다시 이어 붙이거나 앞뒤 문맥을 가져올 때 정렬 키로 그대로 쓸 수 있었습니다.

▲ 실제 elements로 구성한 구조 기반 청킹 + 메타데이터

 

실제로 제 문서의 elements를 위 규칙으로 묶어보면, 표는 헤더와 값이 분리되지 않고 한 덩어리로 남고, 중첩 목록은 부모 항목과 함께 묶입니다. 표 바로 앞의 캡션("표1 …")은 표의 설명 메타로 붙였습니다.

좋았던 점

  • 제목·표·목록·그림이 각각 다른 카테고리로 분리돼 나온 점. 청킹 규칙을 "구조 기준"으로 짜기가 훨씬 쉬워집니다.
  • 병합 셀이 <td rowspan=2>로 보존된 점. 표를 markdown·html로 바로 받아 LLM 컨텍스트에 넣기 좋았습니다.
  • 요소마다 page·bbox·confidence가 붙어, 메타데이터를 따로 만들지 않아도 되는 점. 근거 페이지 표기와 하이라이트에 바로 씁니다.
  • 비동기 API가 문서 그대로(convert→status→download)라 붙이기 쉬웠고, 실패 시 크레딧이 환불되는 과금이라 테스트 부담이 적었습니다.

아쉬웠던 점 / 확인이 필요한 점

  • 소제목 분류가 흔들렸습니다. "1. 개요"는 ListText, "2.2 주요 활용 사례"는 RegionTitle(그것도 신뢰도 0.51)로 잡혀, "제목 계층"과 "목록"을 구분하려면 level과 텍스트 패턴(예: "2.2")을 함께 보는 추가 로직이 필요했습니다.
  • 저신뢰 요소가 있었습니다. 표 캡션 0.60, 위 소제목 0.51, 계획 항목 하나 0.61, 그리고 신뢰도 0.00의 None 요소 1개(작은 노이즈)까지. 이런 구간은 검수 큐로 플래그해 두는 편이 안전했습니다.
  • ocrMode가 FORCE로 잡혀 디지털 PDF에도 OCR이 강제됐습니다. 결과 텍스트는 정확했지만, 텍스트 레이어가 있는 문서라면 불필요한 처리가 될 수 있어 옵션 확인이 필요해 보입니다.
  • 이번엔 2쪽 문서라 빨랐지만, 대용량·다단 편집·복잡한 병합 셀에서의 안정성은 더 큰 문서로 이어서 검증해 볼 계획입니다.

정리

RAG의 검색 품질을 끌어올리려고 리트리버와 임베딩 모델을 바꾸는 데 시간을 많이 쓰지만, 정작 효과가 큰 건 전처리 단계에서 문서 구조를 제대로 살리는 일이었습니다. 이번엔 제가 만든 문서를 데이터 로더 API로 직접 변환해, 제목·표·목록이 요소로 분해되고 병합 셀까지 보존되는 걸 눈으로 확인했습니다. 그대로 청킹 경계와 메타데이터로 쓰되, 소제목 분류와 저신뢰 구간은 검수 한 겹을 두는 구성이 현실적이었습니다. 사내 문서를 내부망에서 다뤄야 하는 팀이라면 온프레미스 문서 처리 조건까지 맞물려 검토해 볼 만합니다.

공식 페이지: 한컴 데이터 로더

반응형