RAG 파이프라인에서 구현 시 가장 먼저 수행되는 작업이 파싱이다. 그렇기 때문에 파싱이 제대로 이루어지지 않으면 해당 RAG 파이프라인을 사용한 LLM 답변의 품질을 기대하기 어렵다.
파싱은 파일에서 텍스트와 구조를 꺼내 검색에 쓸 수 있는 형태로 만드는 과정이라 할 수 있다. 파일 확장자(한글, 엑셀, pdf 등)에 따라서도 알맞은 파싱이 다를 수 있고, 동일한 파일 확장자여도 파일의 포맷 즉, 해당 파일이 줄글 형태인지, 표, 그림 이미지 등이 삽입된 것인지 등에 따라서도 알맞은 파싱이 다를 수 있다. 하나의 파싱으로 모든 파일 확장자와 포맷을 처리하면 좋겠지만, 실제 구현을 해보니까 무조건 하나로 처리하려고 하기보다는 각 파일 특성에 따라 알맞는 파싱을 적용하는 것이 훨씬 더 나은 파싱 결과물을 얻을 수 있었다. (내가 구현을 잘 못했을 가능성도 존재...)
(pdf 파일 하나만을 파싱할 때도 세부적으로 살펴보면, 이미지형으로된 pdf가 있고, 텍스트나 좌표 등으로 구성된 pdf 파일이 존재한다. 이때 이미지형으로 된 pdf의 경우에는 파싱 이전에 OCR이 필수적으로 선행되어야 한다. 이는 하나의 경우에 불과하고 이만큼 다양한 파일 형식과 포맷이 존재하고 그에 알맞은 파싱을 고려해야 할 필요가 있다.)
어려웠던 부분 - 표 추출 어려움
단순 줄글 형태가 아닌 표 등이 포함된 PDF 텍스트를 추출하면 문서의 읽기 순서가 그대로 복원될 거라고 생각하기 쉽다. 하지만 PDF는 화면에 글자와 도형을 배치하는 형식이라, 추출 도구가 읽기 순서와 표 구조를 항상 정확히 알아내는 것은 아니다. 줄글 위주의 문서라면 단순 추출로도 충분했지만, 표나 다단 레이아웃이 포함된 경우에는 단순하지 않았따.
예를 들어 아래 왼쪽표에서 ‘2분기 영업이익’의 값을 확인하려면 ‘영업이익’ 행과 ‘2분기’ 열이 만나는 칸을 찾아서 알 수 있다. 그런데 단순 텍스트 추출되는 방식으로 파싱하면 . 아래 오른쪽 텍스트처럼 결과가 나오게 되는데 이를 보고 2분기 영업이익의 값을 찾기는 쉽지 않다.
PDF에 보이는 표
| 구분 | 1분기 | 2분기 | 3분기 |
|---|---|---|---|
| 매출 | 120 | 135 | 150 |
| 영업이익 | 18 | 22 | 27 |
단순 추출 결과 예시
구분 1분기 2분기 3분기 매출 120 135 150 영업이익 18 22 27
위와 같이 나온 결과는 특히나 청킹 과정에서 표가 잘리면서 제목과 값이 서로 다른 조각에 들어갈 수도 있다. 또한 다단 문서에서는 문장 순서가 뒤섞이는 문제도 생긴다. 때문에 단순 텍스트 위주의 파일이 아니라면 파싱 후 결과물 확인은 반드시 필요하다.
즉 파서들을 어떤 정보를 복원하는 방식인지로 이해하고 파일 특징에 따라 텍스트 추출, 레이아웃 분석, OCR이나 비전 모델 활용 등의 방법을 적절히 조합하고 결과물을 확인해가면 가장 최적의 파서를 구현하도록 해야 한다.
텍스트 추출
글자와 읽기 순서를 가져온다
pypdf나 pdfminer.six 같은 라이브러리는 PDF에 들어 있는 텍스트를 추출하는 데 쓸 수 있다. 줄글 위주의 파일의 경우 빠르고 간단하게 작업이 가능하다. 다만 표의 행·열 관계나 복잡한 다단 배치까지 항상 복원해 주는 것이 아니여서 해당 라이브러리를 무조건적으로 사용하지 않도록 주의해야 한다.
from pypdf import PdfReader
reader = PdfReader("report.pdf")
for page_number, page in enumerate(reader.pages, start=1):
text = page.extract_text()
print(page_number, text)
레이아웃 분석
제목·문단·표의 위치와 유형을 추정한다
Unstructured 같은 도구는 페이지 레이아웃을 분석해 텍스트를 제목, 문단, 표 등의 요소로 나눌 수 있다. Unstructured의 PDF 파티션에서는 strategy="hi_res"와 infer_table_structure=True를 사용할 수 있다.
vision AI를 기반으로 한 문서 파싱의 경우 정확도와 처리 시간이 문서와 실행 환경에 따라 달라진다.
(또한 Vision Ai를 기반으로 한 파서의 경우 결과물의 정확도는 높아지지만 GPU 자원 필요)
from unstructured.partition.pdf import partition_pdf
elements = partition_pdf(
filename="financial_report.pdf",
strategy="hi_res",
infer_table_structure=True,
)
for element in elements:
if element.category == "Table":
print(element.metadata.text_as_html or element.text)
OCR·비전 모델·문서 파싱 서비스
이미지와 복잡한 배치 포맷의 파일의 경우
스캔본처럼 PDF에 텍스트가 없으면 OCR로 글자를 인식해야 한다.
표, 다단, 그림 설명이 복잡한 문서에는 LlamaParse 같은 문서 파싱 서비스나 비전 모델을 검토할 수 있다.
결과를 Markdown으로 받을 수 있는 도구도 존재.
(다만 위와 같은 도구를 사용한 경우에도 무조건 파싱이 잘 된다고 보장 되지는 않는다...)
| 방식 | 예시 도구 | 장점과 주의점 | 검토할 문서 |
|---|---|---|---|
| 텍스트 추출 | pypdf, pdfminer.six | 간단하고 빠름. 복잡한 표·레이아웃은 결과 확인 필요 | 줄글 위주 PDF |
| 레이아웃 분석 | Unstructured 등 | 요소와 표 구조를 추정. 설정과 문서에 따라 정확도·속도가 달라짐 | 표와 제목이 섞인 문서 |
| OCR·비전·파싱 API | Tesseract, LlamaParse 등 | 스캔과 복잡한 배치에 활용 가능. 인식 오류, 비용, 외부 전송 여부를 검토 | 스캔본, 복잡한 표·다단 PDF |
파싱 단계를 잘 구현해도 다음 단계에서 다시 문제가 생길 수 있다. 예를 들어 표를 일반 문장처럼 자르면 헤더와 숫자가 다른 청크로 갈라질 수 있고, 숫자와 구분 기호가 많은 표는 자연어 질문과 잘 연결되지 않을 수 있다.
이럴 때 고려할 방법 중 한 가지는 검색에 쓸 설명과 답변에 쓸 원본 표를 따로 보관하는 것이다. 이를 semi-structured RAG 방식이라고도 하는데, 표의 제목과 내용을 설명하는 짧은 문장을 검색 인덱스에 넣고, 그 검색 결과에 원본 표의 ID와 문서·페이지 정보를 연결하는 것이다.
검색용 설명
분기별 매출과 영업이익을 정리한 표. 3분기 영업이익은 27이다.
답변용 원본
Markdown·HTML 등으로 보존한 표와 원본 문서 위치. 검색 결과의 ID로 조회한다.
질문과 설명이 맞으면 연결된 원본 표를 가져와 답변에 사용한다. 설명문만으로 숫자를 답하지 않도록 구성할 수 있다.
이 방식은 검색에 유리한 표현과 정확한 원본을 함께 활용하는 방식으로, 다만 이 경우 문서 관리의 어려움이 늘어난다.