안녕하세요. 자바파커입니다.
PDF를 AI에 넣기 전에 Markdown으로 바꾸면 문제가 해결될까요?
본문만 있는 문서라면 꽤 잘 동작합니다. 하지만 계약서의 표, 설비 도면의 주석, 보고서의 차트, 여러 단으로 배치된 논문에서는 이야기가 달라집니다. 글자는 남아도 무엇이 무엇에 속했는지가 사라질 수 있습니다.
좋은 디지털화는 문서를 텍스트로 바꾸는 작업이 아닙니다. 의미와 출처를 다시 찾을 수 있게 만드는 작업입니다.
이 글에서는 Markdown을 버리지 않으면서도 정보 손실을 줄이는 하이브리드 문서 구조, 이를 만들어 내는 파이프라인, 그리고 RAG에서 구조를 훼손하지 않고 청킹하는 방법을 정리합니다.
하이브리드 Markdown은 표준 포맷이 아니다
먼저 용어부터 분명히 하겠습니다. 여기서 말하는 하이브리드 Markdown은 CommonMark나 MDX처럼 정해진 문법 표준이 아닙니다.
이 글에서는 다음 설계 방식을 편의상 하이브리드 Markdown이라고 부릅니다.
사람이 읽고 LLM에 전달할 본문은 Markdown으로 만들고, Markdown이 표현하기 어려운 좌표·계층·관계·신뢰도는 JSON과 원본 자산으로 함께 보존한다.
Markdown은 훌륭한 읽기용 표현입니다. 제목, 목록, 코드, 단순한 표처럼 언어 모델이 이해하기 좋은 구조를 적은 토큰으로 전달합니다. 그러나 문서의 모든 정보를 손실 없이 표현하는 저장 형식은 아닙니다.
따라서 원본에 가까운 기준 데이터는 Markdown이 아니라 다음 두 가지가 되어야 합니다.
- 변환 전 원본 파일
- 원본의 요소와 관계를 보존한 구조화 문서 모델
Markdown, HTML, 청크, 임베딩은 모두 이 기준 데이터에서 다시 만들 수 있는 파생물로 취급합니다.
Markdown으로만 변환할 때 사라지는 것
문서 변환에서 가장 위험한 오류는 글자가 빠지는 오류만이 아닙니다. 값은 정확하게 추출됐지만 엉뚱한 항목과 연결되는 관계 손실이 더 찾기 어렵습니다.
| 원본 정보 | 단순 Markdown 변환에서 생기는 문제 | 보존해야 할 것 |
|---|---|---|
| 다단 편집 | 두 열의 문장이 섞임 | 읽기 순서 |
| 병합 셀 표 | 상위 헤더와 하위 열의 관계가 평탄화됨 | rowspan·colspan·셀 좌표 |
| 그림과 캡션 | 캡션이 그림에서 분리됨 | figure–caption 관계 |
| 각주와 미주 | 어느 문장을 설명하는지 사라짐 | 참조 관계 |
| 페이지 위치 | 답의 근거 위치를 보여줄 수 없음 | 페이지·bounding box |
| OCR 결과 | 잘못 읽은 글자도 확정값처럼 저장됨 | confidence·추출 방식 |
| 반복 머리말 | 검색 청크마다 같은 문장이 들어감 | content layer·header/footer 구분 |
Docling 문서에도 이 차이가 명시되어 있습니다. 내부 JSON 모델은 병합 셀 정보를 보존하지만 Markdown 표에는 셀 병합 문법이 없어 평탄화됩니다. 병합 구조가 중요하면 HTML 또는 JSON을 사용하라고 권장합니다.
즉 PDF → Markdown → 폐기 파이프라인은 되돌릴 수 없습니다. 나중에 잘못된 답을 발견해도 어느 페이지의 어떤 영역에서 오류가 생겼는지 추적하기 어렵습니다.
권장 구조 — Markdown은 본문, JSON은 지도
하나의 문서를 파일 하나로 끝내지 않고 다음과 같은 문서 패키지로 관리합니다.
document/
├── source.pdf
├── document.md
├── document.json
├── assets/
│ ├── figures/
│ │ └── figure-003.png
│ ├── tables/
│ │ └── table-007.html
│ └── pages/
│ └── page-012.png
└── chunks/
└── chunks.jsonl각 파일의 역할은 다릅니다.
document.md — 읽기와 전달
- 제목 계층과 읽기 순서를 복원합니다.
- 단순 표는 Markdown 표로 표현합니다.
- 병합 셀이 중요한 표는 HTML 또는 별도 자산을 참조합니다.
- 그림은 안정적인 ID를 가진 링크로 연결합니다.
- LLM 프롬프트와 사람이 검토하는 화면에 사용합니다.
document.json — 구조와 provenance
{
"id": "table-007",
"type": "table",
"parent": "section-2.1",
"page": 12,
"bbox": [84, 316, 934, 842],
"caption": "표 3. 분기별 장애 건수",
"confidence": 0.96,
"source": "source.pdf",
"content_hash": "sha256:..."
}block_id, 부모·자식 관계, 페이지, 좌표, OCR 신뢰도, 원문 범위와 같은 정보는 Markdown 본문에 모두 노출하지 않고 JSON에 둡니다. 검색 결과가 나오면 이 ID를 통해 원본 페이지를 열고 근거 영역을 강조할 수 있습니다.
assets/ — 텍스트로 환원되지 않는 정보
차트, 도면, 서명, 수식, 복잡한 표는 원본 이미지를 보존합니다. 필요하면 VLM이 만든 설명이나 표의 CSV 표현을 추가하되, 생성된 설명이 원본 자산을 대체하게 해서는 안 됩니다.
비정형 문서를 만드는 전체 파이프라인
모든 파일을 OCR에 넣는 방식보다 문서 유형에 따라 경로를 나누는 편이 정확합니다.
PDF·스캔·DOCX·PPTX·XLSX
↓
문서 유형 판별
↓
텍스트 추출 / OCR / 레이아웃 분석
↓
제목·문단·표·그림·수식 식별
↓
통합 Document Model
↓
Markdown + JSON + Assets
↓
구조 기반 청킹1. 원본 유형을 먼저 판별한다
- Born-digital PDF: 내장 텍스트와 글자 좌표를 우선 사용합니다.
- 스캔 PDF·이미지: OCR과 레이아웃 모델을 사용합니다.
- DOCX·PPTX: 가능하면 네이티브 XML 구조를 직접 읽습니다.
- XLSX: 셀 값뿐 아니라 수식, 병합, 시트와 표 범위를 보존합니다.
- 도면·차트: 전체 문서가 아니라 필요한 영역만 VLM으로 보강합니다.
원본에 이미 구조가 있는데 이미지를 만든 뒤 OCR을 수행하면 정확도와 비용을 동시에 잃습니다.
2. 글자보다 요소를 추출한다
추출 결과의 최소 단위는 문자열이 아니라 Title, Paragraph, ListItem, Table, Picture, Formula 같은 문서 요소여야 합니다. 각 요소에는 안정적인 ID와 provenance를 붙입니다.
Azure Document Intelligence의 Layout 모델도 Markdown 본문 외에 페이지, 문단, 단어, 표, 그림, 섹션과 bounding region을 JSON으로 제공합니다. Markdown은 그 분석 결과를 표현하는 출력 중 하나입니다.
3. Markdown과 구조 데이터를 동시에 만든다
Docling을 사용하면 하나의 DoclingDocument에서 Markdown과 JSON을 각각 저장할 수 있습니다.
from pathlib import Path
from docling.document_converter import DocumentConverter
result = DocumentConverter().convert("source.pdf")
doc = result.document
doc.save_as_markdown(Path("document.md"))
doc.save_as_json(
Path("document.json"),
artifacts_dir=Path("assets"),
)이 구조의 장점은 Markdown 직렬화 규칙을 바꾸거나 청킹 전략을 교체해도 PDF를 다시 분석하지 않아도 된다는 점입니다.
청킹은 Markdown 문자열이 아니라 문서 모델에서 시작한다
가장 흔한 구현은 Markdown을 만든 뒤 500자 또는 500토큰마다 자르는 방식입니다. 간단하지만 표의 행, 목록, 캡션과 본문 관계를 무시합니다.
좋은 청킹은 다음 순서를 따릅니다.
- 제목과 문서 요소를 기준으로 의미 경계를 결정합니다.
- 임베딩 모델의 토큰 제한을 넘는 요소만 나눕니다.
- 같은 제목 경로를 가진 작은 이웃 청크만 합칩니다.
- 각 청크에 원본 요소 ID와 위치를 남깁니다.
Docling의 HybridChunker라는 이름은 여기에서 나옵니다. 문서 계층 기반 청킹 위에 토큰 인지형 분할·병합을 추가하는 방식입니다.
import json
from pathlib import Path
from docling.chunking import HybridChunker
chunker = HybridChunker()
with Path("chunks/chunks.jsonl").open("w", encoding="utf-8") as fp:
for index, chunk in enumerate(chunker.chunk(dl_doc=doc)):
record = {
"chunk_id": f"chunk-{index:04d}",
"text": chunk.text,
"embedding_text": chunker.contextualize(chunk),
"metadata": chunk.meta.export_json_dict(),
}
fp.write(json.dumps(record, ensure_ascii=False) + "\n")contextualize() 결과에는 제목과 캡션 같은 문맥이 보강됩니다. 임베딩 모델에는 이 보강된 문자열을 넣고, 사용자에게 보여줄 본문과 원본 위치는 별도 필드로 유지하는 편이 좋습니다.
요소별 청킹 노하우
제목과 문단
새 제목이 시작되면 기존 청크를 닫습니다. 단, 한두 문장뿐인 작은 하위 절은 같은 제목 경로 안에서만 합칩니다. 다른 절의 남는 공간을 채우기 위해 합치면 검색 결과에 서로 다른 주제가 섞입니다.
청크를 임베딩할 때 다음처럼 짧은 제목 경로를 앞에 붙이면 단독으로 검색돼도 문맥을 잃지 않습니다.
장애 보고서 > 3. 원인 분석 > 3.2 데이터베이스
커넥션 풀의 대기 시간이 30초를 초과했다.표
- 가능하면 표 전체를 하나의 청크로 유지합니다.
- 너무 크면 행 경계에서 나눕니다.
- 나뉜 모든 청크에 표 제목과 열 헤더를 반복합니다.
- 병합 셀이 의미를 결정한다면 Markdown 표보다 HTML 또는 구조화 JSON을 사용합니다.
- 숫자 질의가 중요하면 표의 원본 구조와 함께 행 단위 텍스트 표현도 별도로 색인합니다.
그림과 차트
그림 이미지, 캡션, 그림을 직접 언급하는 앞뒤 문단을 하나의 논리 그룹으로 묶습니다. VLM이 생성한 설명에는 generated_description: true 같은 표시를 남겨 OCR 또는 원문과 구분합니다.
목록·코드·수식
목록 항목, 코드 블록, 수식은 중간에서 자르지 않는 것이 기본입니다. 하나의 요소가 토큰 제한보다 크면 줄 경계를 보존하는 전용 규칙을 사용합니다.
페이지 경계와 overlap
페이지 번호는 반드시 보존해야 하지만 페이지가 바뀐다고 무조건 청크를 나눌 필요는 없습니다. 하나의 문단이 다음 페이지로 이어질 수 있기 때문입니다.
overlap도 모든 청크에 일괄 적용하지 않습니다. 구조 경계를 지킨 청크는 이미 문맥이 완결되어 있습니다. overlap은 긴 서술 문단을 토큰 제한 때문에 강제로 나눌 때만 적용하는 편이 중복 검색을 줄입니다.
고정된 청크 크기보다 평가 세트가 먼저다
500토큰이 정답인가요?라는 질문에는 보편적인 답이 없습니다. 임베딩 모델, 문서 종류, 질문의 범위가 다르기 때문입니다.
최소한 다음 유형의 질문을 문서별로 만들어 비교해야 합니다.
- 한 문단 안에서 답할 수 있는 질문
- 제목과 본문을 함께 봐야 하는 질문
- 표의 여러 행을 비교하는 질문
- 그림과 캡션을 함께 봐야 하는 질문
- 서로 다른 페이지의 조항을 연결하는 질문
- 답의 페이지와 위치를 제시해야 하는 질문
청킹 설정을 바꿀 때는 검색 적중률뿐 아니라 다음 지표도 봅니다.
grounding coverage
= 원본 위치를 가진 청크 수 / 전체 청크 수그리고 답이 맞더라도 잘못된 표의 열이나 다른 그림의 숫자를 근거로 삼았다면 실패로 처리해야 합니다. 비정형 문서에서는 값의 정확성과 관계의 정확성을 따로 평가해야 합니다.
언제 Markdown만으로 충분한가
모든 프로젝트에 복잡한 문서 모델이 필요한 것은 아닙니다.
Markdown만으로 충분한 경우
- 원래부터 Markdown 또는 HTML로 작성된 문서
- 제목과 문단 중심의 기술 문서
- 단순한 표만 포함한 문서
- 답의 원본 좌표를 제시할 필요가 없는 내부 검색
하이브리드 구조가 필요한 경우
- 계약서·규정·감사 보고서처럼 출처 추적이 중요한 문서
- 병합 셀과 다중 헤더가 많은 표
- 차트·도면·수식이 핵심인 문서
- 스캔 품질이 일정하지 않은 대량 문서
- 추출 결과를 사람이 다시 검증해야 하는 업무
- 동일 문서로 검색, 정보 추출, 요약을 모두 수행하는 시스템
이미지와 구조가 중요한 문서라면 RAG가 무엇인지보다 먼저 문서를 어떤 형태로 보존할 것인지를 결정해야 합니다. 설비 문서를 지식 그래프로 바꾸는 사례는 P&ID를 지식 그래프로 만드는 과정에서도 확인할 수 있습니다.
실무 체크리스트
- 원본 파일을 변환 결과와 함께 보존했는가?
- 각 요소에 안정적인 ID가 있는가?
- 페이지와 bounding box로 원본 위치를 찾을 수 있는가?
- 제목·본문·표·그림·캡션 관계가 남아 있는가?
- OCR 신뢰도와 생성형 설명을 원문과 구분했는가?
- 병합 셀을 Markdown 표로 억지로 평탄화하지 않았는가?
- 청크가 원본 요소 ID를 참조하는가?
- 큰 표를 나눌 때 헤더를 반복하는가?
- 임베딩 모델과 같은 tokenizer로 크기를 계산하는가?
- 검색 결과에서 원본 페이지를 바로 열 수 있는가?
FAQ
하이브리드 Markdown은 별도의 파일 확장자인가요?
아닙니다. 이 글에서는 Markdown, JSON, 원본 자산을 함께 관리하는 설계 방식을 뜻합니다. .hmd 같은 새로운 표준 파일을 제안하는 것이 아닙니다.
JSON만 저장하면 Markdown은 필요 없지 않나요?
JSON은 구조 보존에는 좋지만 사람이 읽고 LLM에 전달하기에는 장황합니다. Markdown은 검토, 프롬프트 구성, 버전 비교에 유리합니다. 두 표현의 역할이 다릅니다.
페이지마다 청크를 나누면 출처 관리가 쉬워지지 않나요?
출처 관리는 쉬워지지만 문단과 표가 페이지를 넘어갈 때 의미가 끊깁니다. 페이지는 청크의 메타데이터로 보존하고, 실제 경계는 문서 요소와 제목 구조를 우선하는 것이 좋습니다.
HybridChunker를 쓰면 모든 문서에서 좋은 결과가 나오나요?
아닙니다. 레이아웃 분석 단계에서 제목이나 표를 잘못 분류하면 구조 기반 청킹도 그 오류를 이어받습니다. 문서 유형별 평가 세트와 원본 위치 검증이 필요합니다.
마무리
Markdown은 AI 문서 파이프라인에서 여전히 좋은 중간 표현입니다. 문제는 Markdown을 유일한 원본으로 사용할 때 생깁니다.
원본 파일과 구조화 문서 모델을 보존하고, Markdown을 읽기용 투영으로 만들고, 청크마다 원본 요소와 위치를 연결하십시오. 그러면 파서와 임베딩 모델을 바꿔도 문서를 다시 분석할 필요가 없고, AI의 답을 원본에서 검증할 수 있습니다.
디지털화의 품질은 얼마나 많은 글자를 남겼는지가 아니라, 의미와 출처로 얼마나 정확히 돌아갈 수 있는지로 판단해야 합니다.