포스팅 개요
대규모 언어 모델(Large Language Model) 기반의 RAG 시스템을 개발하면서, 가장 중요한 것은 데이터라고 다시 한번 체감하고 있습니다. 아무리 기존에 DX/AX를 준비했어도 LLM과 RAG 기반의 시스템에 well-fit되는 데이터는 더 다른 느낌인데요.
특히 RAG 시스템 개발과 더 나아가 AI Agent(AI 에이전트) 시스템 개발을 하면 word, pdf, ppt, 한글(hwp) 데이터를 활용하여 내부 업무 효율화 등으로 시스템을 구축하는 경우가 많습니다.
그렇다면 이러한 데이터를 LLM 등이 잘 이해할 수 있도록 잘 파싱(Parsing)하고 관리를 해야겠죠?
업스테이지(Upstage)의 Document parser는 이러한 기존 레거시 형태의 문서 데이터들을 파싱해서 RAG에 활용할 수 있는 데이터 형태로 추출할 수 있도록 기능을 제공합니다.
이번 포스팅은 업스테이지의 document parser를 개인적으로 사용하면서, PoC(Proof-of-Concept) 및 Playground 형태로 파싱 결과를 웹 화면으로 확인할 수 있도록 만든 Python streamlit 페이지를 공유하는 포스팅입니다.
왜 이 서비스를 만들었는지, 어떻게 사용할 수 있는 지 등을 설명하고 코드도 같이 공유해드리겠습니다.
Upstage Console
Empower your business with Upstage Console.
console.upstage.ai
포스팅 본문
이번 포스팅은 업스테이지에서 제공하는 Document parsing을 사용하면서 쉽게 결과를 웹 화면으로 확인할 수 있게 지원해주는 서비스를 공유하는 포스팅인데요. 왜 이 서비스를 만들었는지를 이야기 하기에 앞서, 업스테이지 Document parser에 대해서 간단히 소개하려고 합니다.
Upstage Document parser


Upstage document parser는 위 사진과 같이 문서의 구조를 파악하고, 구조에 해당되는 텍스트, 이미지, 표 형태, 수식 형태를 추출하는 기능을 제공합니다. 업스테이지의 Solar LLM 모델을 비롯하여 주력 기술이자 서비스이고 기술력은 top이라고 생각합니다.
이러한 업스테이지 Document parser는 사용자(그 중, 개발자)들이 사용하기 편하도록 API를 제공해주고 있습니다. 이에, API를 활용할 수 있으신 분들은 간단하고 편리하게 Document parsing을 사용할 수 있습니다.
또한, console에 들어가시면 꽤나 자세하게 example code도 제공해주고 있습니다.


위와 같이 업스테이지에서 제공해주는 chat 기능, reasoning 기능, document parsing, document ocr 별로 Python 코드 등을 확인할 수 있습니다.
Upstage Document parsing playground PoC 서비스 소개
회사에서나 개인적인 스터디에서 업스테이지 Document parser를 사용하면서, 결과가 어떻게 추출되는지 확인할 필요가 있었습니다.
문서 별로 결과가 원하는 의도대로 나왔는지, 좌표 유지는 어떻게 되는 지 등을 확인할 필요가 있었죠.
원래는 지극히 개인적으로만 사용하다가, 혹시나 필요하신 분들이 있으실까 싶어서 공유를 위해서 글을 작성해봅니다.
본 서비스를 이용하기 위해서는 다음과 같은 5가지 과정이 필요합니다.
1. Upstage console에 가입해서 Key 받기
가장 먼저, 업스테이지 콘솔에 회원가입해서 Key를 부여 받는 것입니다. 또한, 최초 회원가입 시, $10 크레딧을 제공합니다.
그리고 Document parser의 경우 현재 글을 쓰는 시점 2025년 10월 초 기준으로 페이지당 0.01$의 과금이 부여가 됩니다.


콘솔 페이지에 들어가면 위와 같이 API 키를 확인할 수 있습니다. 회원가입을 하면 동시에 부여가 됩니다.
이 API Key를 복사하셔서, 3번의 .env 파일에 키를 넣어주시면 됩니다.
2. git clone 받기
그 다음은 제가 제공해드린 코드를 clone합니다. 제 코드 url은 다음과 같습니다.
- https://github.com/lsjsj92/upstage-document-parser-playground
GitHub - lsjsj92/upstage-document-parser-playground: Upstage document parser playground(w/ Python streamlit)
Upstage document parser playground(w/ Python streamlit) - lsjsj92/upstage-document-parser-playground
github.com

해당 github 페이지에서 code를 누르면 https나 zip을 통해 받을 수 있는데요.
그 중 https를 선택하면 git clone 명령어를 통해 간단하게 clone할 수 있습니다.
3. .env 파일 준비

git clone하면 .env.tmp 파일이 있습니다.
이 파일의 내용에서 Upstage API key를 넣어주면 되는데요. 1번 과정에서 복사한 API Key를 넣어줍니다.
그리고 파일 이름을 .env.tmp에서 .env로 바꿔주면 됩니다. .env 파일의 위치는 .env.tmp가 위치한 그대로 넣어주시면 됩니다.
4. 라이브러리 설치하기

git clone을 하시면 requirements.txt 파일이 있습니다. 해당 파일을 활용해서 pip install을 진행해주시면 됩니다.
또한, Python은 3.10 이상을 권장합니다.
5. 백엔드 및 프론트 실행하기
이제 거의 다 왔습니다. 백엔드와 프론트를 각각 실행해주면 되는데요.
먼저, 백엔드부터 실행하면 됩니다. 백엔드는 아래와 같은 명령어로 실행하면 됩니다.
python -m uvicorn backend.main:app --reload

위 사진과 같이 fastapi 서버가 정상적으로 실행되면 문제가 없는 것입니다.
또한, backend를 실행하면 storage라는 디렉토리가 만들어지는데요. parsing한 결과를 프로젝트 폴더의 storage라는 경로에 저장하도록 합니다.
다음은 프론트 페이지를 띄웁니다. 아래와 같은 명령어로 실행하면 됩니다. Python streamlit 기반의 페이지입니다.
streamlit run frontend/app.py

프론트 명령어를 실행했을 때 위와 같이 나온다면 정상적으로 실행된 것입니다.
업스테이지 Playground 기능 설명
자, 이제 하나씩 기능을 살펴봅시다.
1. 메인 페이지 - 파일 업로드 및 파싱 수행

가장 먼저 메인페이지입니다. 메인 페이지에는 3개의 메뉴를 확인할 수 있습니다.
가장 기본으로 파일을 업로드하고 파싱한 결과를 저장하는 파일 업로드 페이지가 있습니다.
파싱된 문서 리스트 메뉴와 문서 상세 뷰어 메뉴는 파싱된 결과를 볼 수 있는 페이지라고 볼 수 있습니다.


해당 페이지에서 파일을 업로드하면 업스테이지의 Document parsing을 수행할 수 있습니다.
저는 링크드인(linkedin)에서 리뷰했던 논문인 Overhearing LLM Agent 논문 pdf 파일을 업로드 했습니다.( https://www.linkedin.com/posts/lsjsj92_overhearing-llm-agents-a-survey-taxonomy-activity-7382032494960943104-BO3i?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAlabLcBJq1_NeHvpHXjctBSzkXZFsby3rY )
Overhearing LLM Agents: A Survey, Taxonomy, and Roadmap | soojin lee
Overhearing(엿듣는) Agent 우리가 사용하는 대부분의 AI는 대화형(Conversational) Agent입니다. 이는 사용자가 명확한 질문이나, 명령을 해야만 작동합니다. 예를 들어, 회의 중 궁금한 것이 생기면 “헤이
kr.linkedin.com


파일을 업로드하면 위와 같이 업로드 성공이라고 메세지가 나오면서 Upstage의 Document parsing을 수행합니다.
이때, Upstage에서 제공해주는 API를 활용해서 파싱을 수행합니다. API를 호출하는 코드는 Upstage console에서 제공해주는 코드를 바탕으로 작업하였습니다. 원하시는 기능이 있으다면 코드 레벨에서 수정하셔도 될 것 같습니다!
파싱은 HTML, Markdown 형태로 나오도록 수행하게 됩니다. 또한, 이미지, 테이블 형태도 추출이 가능합니다(업스테이지에서 기본으로 제공해주는 기능입니다.).
만약, 파일이 업로드 된다면 아래와 같은 순서로 코드가 동작이 됩니다.
1. 파일 업로드가 되면 확장자 점검하고 내부 파일 구조를 잡고 저장
2. Upstage API를 호출하여 파싱 수행
async def parse_document_with_hybrid_extraction(self, file_path: Path, extract_images: bool = True) -> ParsedDocument:
"""
Upstage API를 사용하여 문서를 파싱합니다.
단일 API 호출로 전체 문서를 처리:
- 모든 페이지를 한 번에 파싱
- OCR 자동 적용 (force mode)
- 이미지 추출 (table, figure, chart, equation)
Args:
file_path: 파싱할 파일 경로
extract_images: 이미지 Base64 인코딩 추출 여부
Returns:
ParsedDocument: 파싱된 문서 객체 (모든 페이지 포함)
"""
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.post(self.base_url, headers=headers, files=files, data=data)
response.raise_for_status()
result = response.json()
parsed_data = self._parse_response(result)
3. 파싱 결과를 수신 받고 처리. 이때 이미지도 처리하도록 함
def get_image_mime_type_from_base64(base64_string: str) -> Optional[str]:
"""
Base64 인코딩된 이미지 문자열로부터 MIME 타입을 판별합니다.
매직 넘버(파일 시그니처)를 분석하여 실제 이미지 형식을 확인합니다.
Args:
base64_string: Base64로 인코딩된 이미지 데이터 문자열
Returns:
Optional[str]: 이미지 MIME 타입 (image/png, image/jpeg 등)
판별 실패 시 None 반환
"""
try:
# Decode the base64 string to get the image header bytes
decoded_data = base64.b64decode(base64_string[:20]) # First 20 chars are enough
4. 페이지별로 그룹화 및 관련 텍스트 생성
def _analyze_and_enhance_elements(self, elements: List[DocumentElement]) -> List[DocumentElement]:
"""
기존 파싱된 요소들을 분석하여 복합 구조를 감지하고 개선된 요소로 변환
"""
if not elements:
return elements
enhanced_elements = []
processed_element_ids = set()
# 페이지별로 그룹화하여 처리
pages = {}
for elem in elements:
page = elem.page
if page not in pages:
pages[page] = []
pages[page].append(elem)
for page_num, page_elements in pages.items():
page_enhanced = self._process_page_elements(page_elements, processed_element_ids)
enhanced_elements.extend(page_enhanced)
return enhanced_elements
5. HTML 및 마크다운(Markdown) 생성
def _convert_elements_to_markdown(self, elements: list[DocumentElement]) -> str:
"""문서 요소들을 논리적 순서에 따라 Markdown 문자열로 변환"""
if not elements:
return ""
sorted_elements = sorted(elements, key=lambda e: (e.page, e.coordinates[0].y if e.coordinates else 0))
markdown_parts = []
for elem in sorted_elements:
if (hasattr(elem, '_ocr_enhanced') and elem._ocr_enhanced) or elem.category == 'composite_table':
if elem.content and elem.content.markdown:
markdown_parts.append(elem.content.markdown)
elif elem.content and elem.content.text:
markdown_parts.append(elem.content.text)
else:
html_content = elem.content.html
if html_content:
markdown_content = self.markdown_converter.handle(html_content).strip()
elem.content.markdown = markdown_content
markdown_parts.append(markdown_content)
return "\n\n".join(part for part in markdown_parts if part)
6. 각종 통계 등 저장하고 결과 저장
2. 파싱된 문서 리스트 페이지
해당 페이지는 큰 기능은 없습니다. 이 페이지에서는 파싱된 문서 리스트를 전부 보여줍니다.

파싱된 문서 리스트를 선택해서 확인할 수 있습니다.
3. 문서 상세 뷰어
해당 페이지에서는 파싱된 결과를 상세하게 확인할 수 있는 페이지입니다.
HTML로 변환한 페이지 뷰와, 통합 뷰, bounding box 좌표 등을 확인할 수 있습니다.

3개의 메뉴가 있는데요. 문서 레이아웃은 문서의 HTML 레이아웃 등을 볼 수 있습니다.
바운딩 박스 시각화는 좌표 기반해서 Document parsing의 좌표를 확인할 수 있고 요소 상세 정보는 상세한 요소 별로 확인할 수 있는 기능을 제공합니다.
먼저, 방금 파싱한 논문의 문서 레이아웃을 페이지를 확인하면 다음과 같습니다.



좌표 기반으로 문서 레이아웃을 구성할 수 있고(페이지 렌더링상 좀 깨집니다 ㅠ) HTML 랜더링으로도 확인할 수 있습니다.
위 사진의 오른쪽을 보면 표(Table) 결과를 확인할 수 있는데요. 결과가 잘 나온 것을 확인할 수 있습니다.


또한, 바운딩 박스와 요소 상세 정보도 추가로 확인할 수 있습니다.
이건 그냥 제가 필요해서 만든 기능이긴한데, 다른 분들에게는 유용하지 않을 수도 있을 것 같습니다.
마무리
이번 포스팅은 업스테이지(Upstage) document parsing 결과를 확인할 수 있는 playground PoC 서비스 페이지를 만들고 공유한 후기를 작성한 포스팅입니다.
해당 코드도 공유해놨으니 참고 부탁드립니다.
'인공지능(AI) > LLM&RAG' 카테고리의 다른 글
| CRAG: RAG의 검색 실패와 환각(Hallucination)을 해결하는 Corrective RAG 논문 리뷰 (1) | 2025.11.23 |
|---|---|
| 윈도우(windows)에서 PostgreSQL pgvector 설치 및 사용하기 (0) | 2025.11.19 |
| ChatGPT GPT-5 프롬프트 가이드 정리 및 프롬프트 템플릿 예제(example) 공유 (0) | 2025.10.12 |
| AI 기반의 개인화된 교육과 맞춤형 학습 경험(Google: Towards an AI-Augmented Textbook) (1) | 2025.10.07 |
| Group Query Attention(GQA)란? LLM 추론 속도 향상을 위한 노력 (0) | 2025.09.06 |