고품질 문서 파싱 파이프라인 - VLM과 Ground Truth의 결합
복잡한 보험 약관 PDF를 RAG 시스템에 최적화된 마크다운으로 변환하기 위해, PaddleOCR과 GPT-5.4 VLM을 결합한 하이브리드 파싱 아키텍처 설계 및 트러블슈팅 과정을 정리합니다.
RAG(Retrieval-Augmented Generation) 시스템의 성능은 결국 벡터 DB에 꽂아 넣는 '문서의 품질'에서 판가름 납니다.
복잡한 표, 다단 레이아웃, 심지어 화살표가 난무하는 타임라인이 포함된 보험 약관 PDF를 LLM이 완벽하게 이해할 수 있는 Markdown으로 뜯어내는 것은 결코 쉬운 일이 아닙니다.
이 글에서는 단순한 텍스트 추출을 넘어, 시각적 VLM(Vision Language Model)과 OCR의 정답지(Ground Truth)를 결합하여 고품질 파이프라인을 구축한 과정과 트러블슈팅 경험을 공유합니다.
🚨 문제 정의: 왜 문자(text) 파싱만으로는 부족할까?
우리가 파싱해야 할 문서는 일반적인 줄글 형태가 아닙니다. 특히 보험 약관과 같은 실무 문서는 다음과 같은 까다로운 특징을 지닙니다.
- 복잡한 다단 레이아웃: 좌우로 나뉜 단락을 상하 순서로 잘못 읽을 경우 문맥이 완전히 파괴됩니다.
- 다양한 비정형 도표: 텍스트만으로는 의미를 온전히 파악할 수 없는 타임라인(기간, 화살표, 상태 변화) 다이어그램이 존재합니다.
- 숫자 및 고유명사:
1,000원을10,000원으로 잘못 인식하거나 조항 번호(①,가)가 누락되면 법적 및 계약적 의미가 심각하게 훼손됩니다.
초기에는 pdfplumber, PyMuPDF 및 PaddleOCR과 같은 전통적인 파이썬 기반 문서 파싱 라이브러리를 활용하여 문제에 접근했습니다.
하지만 이러한 방식은 명확한 한계를 지니고 있었습니다.
단순 텍스트 추출은 가능했으나, **다단 레이아웃의 읽기 순서(Reading Order)**가 보존되지 않았습니다.
또한 화살표나 표와 같은 시각적 다이어그램의 맥락을 전혀 파악하지 못해 의미 없는 문자의 나열로 변형되는 문제가 발생했습니다.
VLM의 도입과 새로운 난관
기존 라이브러리의 한계를 극복하고자 시각적 추론 능력을 갖춘 VLM(Vision Language Model)을 도입했습니다.
GPT-5.4와 같은 고성능 VLM은 사람과 유사하게 전체적인 페이지 레이아웃을 파악합니다. 또한 복잡한 도표의 시각적 맥락을 정확하게 이해하여 기존의 문제점들을 상당 부분 해결하는 듯 보였습니다.
그러나 VLM에 단순히 문서 이미지를 입력하고 마크다운 변환을 지시했을 때, 또 다른 치명적인 결함이 관찰되었습니다.
전반적인 문서의 레이아웃은 훌륭하게 복원해 냈으나, 본문 내 핵심 숫자를 틀리거나 원본에 존재하지 않는 문장을 임의로 생성해 내는 심각한 환각(Hallucination) 현상이 발생한 것입니다.
기존 문서 파싱 라이브러리의 완벽한 '텍스트 정확도(Spelling)'와 VLM의 '시각적 맥락 해석 능력'을 융합할 방법은 없을까?
🏗️ 아키텍처 설계: 4단계 하이브리드 파이프라인
문제 해결의 핵심은 VLM에게 텍스트에 대한 확실한 정답지(Ground Truth)를 제공하는 것이었습니다.
이를 구현하기 위해 문서 파싱 파이프라인을 4개의 독립적인 단계(Phase)로 분리하여 설계했습니다.
Phase 1 & 2: Preprocessing & Table Detection
PyMuPDF를 통해 PDF 페이지를 고해상도로 렌더링하고 이미지화합니다.Table-Transformer를 활용해 문서 내 표(Table)의 구조를 물리적으로 탐지합니다.
Phase 3: OCR Engine (PaddleOCR)
- 이미지 전체를 픽셀 단위로 정밀하게 스캔하여, 화면에 존재하는 모든 글자와 그 글자의 정확한 좌표(Bounding Box)를 추출합니다.
- Heuristic Sorting 알고리즘: OCR 엔진이 반환하는 비정형 텍스트 박스들을
Y좌표(높이)와X좌표를 기준으로 분석하여 논리적인 읽기 순서(위에서 아래로, 좌에서 우로)에 맞도록 텍스트를 구조적으로 정렬합니다.
Phase 4: GPT Assembler (VLM)
- 앞서 생성된 "문서 이미지"와 "정렬된 OCR 좌푯값 JSON(정답지)"을 동시에
GPT-5.4-mini모델에 주입합니다. - VLM은 이미지를 기반으로 레이아웃(표, 다이어그램)의 시각적 맥락을 파악합니다.
- 문서를 조립(Assemble)할 때는 모델이 임의로 텍스트를 생성하지 않고, 반드시 JSON 정답지에 명시된 텍스트만을 활용하도록 강제합니다.
flowchart LR
A(["📄 PDF"]) --> B["Phase 1\nPyMuPDF"]
B --> C["Phase 2\nTable-Transformer"]
C --> D["Phase 3\nPaddleOCR"]
D --> E{{"Heuristic\nSorting"}}
E --> F[("Ground\nTruth\nJSON")]
subgraph output["Phase 4: GPT Assembler"]
G("GPT-5.4\n시각 맥락 파악")
end
B -.->|"이미지"| G
F -.->|"정답지"| G
G --> H(["📝 Markdown"])
🛠️ 개발 과정 및 트러블슈팅
파이프라인 설계 이후, 실제 보험 약관 데이터를 처리하는 과정에서 다수의 예외 상황에 직면했습니다.
이러한 난관들은 프롬프트 엔지니어링(Prompt Engineering)과 추가적인 로직 개선을 통해 해결했습니다.
1. 비정형 다이어그램(타임라인)의 시멘틱 복원
초기 파이프라인에서는 화살표 도표를 마크다운 기반의 다이어그램 문법인 Mermaid 코드로 출력하도록 지시했습니다.
하지만 RAG의 임베딩 모델은 A --> B와 같은 시각적 코드 구문의 의미론적(Semantic) 문맥을 온전히 이해하지 못하는 한계를 보였습니다.
💡 해결책: 프롬프트를 세밀하게 조정하여, 모델이 도표를 스스로 해석한 뒤 구조화된 요약 리스트 형태로 출력하도록 변경했습니다.
- 전체 기준 기간
- 날짜별 주요 마일스톤
- 화살표 구간별 상태 (ex: 2024.01.15 ~ 2024.05.31 : 일반 보장성 보험료)
아래는 실제 파이프라인에서 사용한 시스템 프롬프트의 핵심 구조입니다.
<system_instruction>
<persona>
당신은 문서를 0.1mm의 오차도 없이 데이터화하는
'고정밀 문서 파싱 스페셜리스트'입니다.
</persona>
<parsing_rules>
<rule>텍스트와 숫자는 원본과 100% 일치해야 하며,
임의의 요약이나 수정을 금지합니다.</rule>
<rule>여러 페이지에 걸친 문장은 절대 누락하거나 요약하지
말고 원문 그대로 이어서 작성하십시오.</rule>
</parsing_rules>
<visual_restoration_logic>
<!-- 타임라인 감지 시 시멘틱 복원 리스트 작성 -->
<!-- [경고] 화살표 선 위에 '명시된' 구간만 출력. -->
<!-- 공백 구간을 임의로 추론하여 채우는 행위 엄금. -->
<output_format>
1. 전체 기준/기간
2. 주요 마일스톤 (날짜 : 이벤트명)
3. 명시된 구간별 상태 (시작일 ~ 종료일 : 상태)
</output_format>
</visual_restoration_logic>
<example_case>
<!-- 도메인 오염 방지: 보험이 아닌 가상의 물류 도표 예시 사용 -->
</example_case>
</system_instruction>
유저 프롬프트에는 OCR이 생성한 ground_truth_json과 원본 이미지를 함께 전달합니다.
{"role": "user", "content": [
{"type": "text",
"text": f"<ground_truth_data>{ground_truth_json}</ground_truth_data>\n"
"첨부된 이미지와 좌푯값을 대조하여 마크다운으로만 출력하세요."},
{"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{base64_img}"}}
]}
2. 프롬프트 오염과 환각 제어
프롬프트 내에 구체적인 '보험 타임라인 예시(Few-shot)'를 제공하자, 도표가 존재하지 않는 일반 텍스트 페이지에서도 모델이 예시와 유사한 타임라인을 강제로 생성해 내는 부작용이 발생했습니다.
💡 해결책: 이 문제를 통제하기 위해 두 가지 엄격한 제약 조건을 적용했습니다.
- 도메인 분리: 프롬프트에 포함된 예시를 보험 도메인이 아닌 '가상의 물류 배송'으로 완전히 치환하여, 본문 데이터와의 간섭을 원천적으로 차단했습니다.
- 공백 추론 금지: 화살표 선 위에 텍스트가 명시된 구간만을 데이터로 추출하도록 제한했습니다. 비어있는 구간을 모델이 임의로 논리적 추론을 통해 채워 넣는 행위를 엄격히 금지했습니다.
3. 페이지 간 컨텍스트 단절 극복
약관의 리스트나 긴 문장이 다음 페이지로 이어질 경우, 모델이 임의로 문장의 순서를 뒤바꾸거나 내용을 축약해 버리는 현상이 관찰되었습니다.
💡 해결책: "Ground Truth의 시각적 상하좌우 읽기 순서를 엄격하게 준수하고, 여러 페이지에 걸친 문장은 어떠한 축약 없이 원문 그대로 연결할 것"이라는 강력한 파싱 규칙을 프롬프트에 명시했습니다.
📄 실제 파싱 결과 예시
아래는 실제 보험 약관 문서 중 타임라인 도표가 포함된 페이지를 파이프라인에 통과시킨 결과입니다.
원본 문서 이미지

'장애인전용보험전환특약'의 보험료 유형 변화를 나타내는 복잡한 시간축 도표가 포함된 페이지입니다.
파이프라인 출력 결과 (Markdown)
# 장애인전용보험전환특약
- 장애인전용보험전환특약 전환일 : 202X년 6월 1일
### [시각 도표 요약: 장애인전용보험전환특약 전환 및 취소 시점]
- **전체 기준/기간:** 202X.1.1 ~ 202X.12.31
- **주요 마일스톤:**
- 202X.1.15 : '전환대상계약' 계약일
- 202X.6.1 : 장애인전용보험 전환특약 전환일
- **명시된 구간별 상태:**
- 202X.1.15 ~ 202X.6.1 : 일반 보장성보험료
- 202X.6.1 ~ 202X.12.1 : 장애인전용 보장성보험료
- 202X.1.1 ~ 202X.12.31 : 202X년 과세기간
[사례1] 202X년 12월 1일 전환을 취소한 경우
### [시각 도표 요약: 사례1 전환 취소 시점]
- **전체 기준/기간:** 202X.1.1 ~ 202X.12.31
- **주요 마일스톤:**
- 202X.1.15 : '전환대상계약' 계약일
- 202X.6.1 : 장애인전용보험 전환특약 전환일
- 202X.12.1 : 전환 취소일
- **명시된 구간별 상태:**
- 202X.12.1 ~ 202X.12.31 : 일반 보장성보험료
- 202X.1.1 ~ 202X.12.31 : 202X년 과세기간
[사례2] 202X+1년 6월 1일 전환을 취소한 경우
### [시각 도표 요약: 사례2 전환 취소 시점]
- **전체 기준/기간:** 202X.1.1 ~ 202X+1.12.31
- **주요 마일스톤:**
- 202X.1.15 : '전환대상계약' 계약일
- 202X.6.1 : 장애인전용보험 전환특약 전환일
- 202X.12.31 : 전환 시점 경계
- 202X+1.6.1 : 전환 취소일
- **명시된 구간별 상태:**
- 202X.1.15 ~ 202X.6.1 : 일반 보장성보험료
- 202X.6.1 ~ 202X.12.31 : 장애인전용 보장성보험료
- 202X.12.31 ~ 202X+1.6.1 : 장애인전용 보장성보험료
- 202X+1.6.1 ~ 202X+1.12.31 : 일반 보장성보험료
- 202X.1.1 ~ 202X.12.31 : 202X년 과세기간
- 202X.12.31 ~ 202X+1.12.31 : 202X+1년 과세기간
화살표 도표를 단순 이미지로만 처리했다면 의미 있는 텍스트를 전혀 추출하지 못했을 것입니다.
파이프라인은 어떤 구간에 어떤 보험료 유형이 적용되는지를 RAG 모델이 이해할 수 있는 시멘틱 데이터로 정확하게 변환해 냈습니다.
🚀 마무리
본 아키텍처의 핵심은 각 모델의 장점을 극대화하고, 단점은 상호 보완을 통해 완벽히 통제하는 구조를 설계하는 것에 있습니다.
- OCR (Ground Truth): 문맥 파악 능력은 부족하나, 텍스트의 철자 정확도를
100%보장합니다. - VLM (GPT): 텍스트의 정밀한 추출에는 취약하나, 전체적인
레이아웃과도표의 맥락을 높은 수준으로 파악합니다.
이 두 가지 기술을 병합함으로써 파이프라인의 완성도를 극적으로 향상시킬 수 있었습니다.
향후에는 수백 페이지 단위의 대형 문서를 처리할 때 발생하는 연산 속도 병목을 해결하는 데 집중할 예정입니다.
이를 위해 텍스트 밀집도에 따라 OCR 단계를 유동적으로 생략하는 하이브리드 패스(Bypass) 구조나, 병렬 프로세싱을 도입하여 파싱 엔진을 더욱 고도화할 계획입니다.