ModuDoc๋ ์๊ฐ-์ธ์ด ๋ชจ๋ธ(VLM)์ ํ์ฉํด ๋ฌธ์์ ์๊ฐ์ ๋ ์ด์์๊ณผ ๋ ผ๋ฆฌ์ ๊ณ์ธต์ ์ดํดํ๊ณ ๊ตฌ์กฐํํ๋ ๋ฌธ์ ํ์ฑ ๋๊ตฌ์ ๋๋ค.
ํนํ HWP/PDF ๋ฌธ์์ ๋ณต์กํ ํ(Table), ๋ค๋จ ๋ ์ด์์, ๋ค์ค ํ์ด์ง ๋ฌธ๋งฅ ๋จ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ฌ RAG ์์คํ ๊ตฌ์ถ์ ์ํ ์์ฒ ๋ฐ์ดํฐ(JSON/Markdown)๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ๐ ๋ฉํฐ๋ชจ๋ฌ(Image + Text) ํ์ด๋ธ๋ฆฌ๋ ํ์ฑ: VLM์ ์๊ฐ์ ์ธ์ง ๋ฅ๋ ฅ๊ณผ ์๋ณธ ํ ์คํธ ๋ ์ด์ด ์ถ์ถ ๊ธฐ์ ์ ๊ฒฐํฉํ์ฌ ์ ๋ณด ๋๋ฝ์ ์ต์ํํฉ๋๋ค.
- ๐ ๋ค์ค ํ์ด์ง ๋ฌธ๋งฅ ๋ณํฉ (Cross-page Chunking): ํ์ด์ง๊ฐ ๋์ด๊ฐ๋ฉฐ ์๋ฆฌ๋ ํ๋ ๋ฌธ๋จ์ ๋ฌผ๋ฆฌ์ ํ์ด์ง๊ฐ ์๋ ๋ชฉ์ฐจ(Heading) ๊ธฐ์ค์ผ๋ก ์๋ฒฝํ๊ฒ ๋ณํฉํฉ๋๋ค.
- ๐ณ ์๋ฏธ ๊ธฐ๋ฐ RAG ์ฒญํน ์ ๋ต: ์ฌ์ฉ์์ ๋ชฉ์ ์ ๋ฐ๋ผ 3๊ฐ์ง ์ฒญํน ๋ชจ๋(
page(ํ์ด์ง ๊ธฐ๋ฐ)/toc(๋ชฉ์ฐจ ๊ธฐ๋ฐ)/tree(๋ฌธ์์ ํธ๋ฆฌ ๊ตฌ์กฐ))๋ฅผ ์ ์ฐํ๊ฒ ์ง์ํฉ๋๋ค. - ๐ ์๊ฐ ์๋ฃ์ ์ง์ํ: ๋จ์ ํ
์คํธ๋ก๋ ์ ์ ์๋ ๋ณต์กํ ๋ค์ด์ด๊ทธ๋จ์ด๋ ๋์์ VLM์ด ์ง์ ๋ถ์ํ์ฌ ์์ธํ ํ
์คํธ(
description)๋ก ๋ณํํฉ๋๋ค. - ๐ ๊ด๋ฒ์ํ ํฌ๋งท ์ง์: PDF, HWP, HWPX, DOCX, PPTX, XLSX ๋ฐ ํธ๋ฆฌํ Web UI ์ ๊ณต.
opendataloader-bench ๊ธฐ์ค 200์ฌ ๊ฐ์ ๋ฌธ์๋ฅผ ๋์์ผ๋ก ํ๊ฐํ ๊ฒฐ๊ณผ, ํ ๊ตฌ์กฐ ์ธ์(TEDS) ๋ฐ ํค๋ฉ ๊ณ์ธต ๊ตฌ์กฐํ(MHS) ๋ถ๋ฌธ์์ ๊ฐ์ฅ ๋์ ์ฑ๋ฅ์ ๋ฌ์ฑํ์ต๋๋ค.
NID: ์ฝ๊ธฐ ์์ ์ ํ๋ ยท TEDS: ํ ๊ตฌ์กฐ ์ ์ฌ๋ ยท MHS: ํค๋ฉ ๊ณ์ธต ์ ํ๋
| ํ์ (Parser) | ์ ์กฐ์ฌ (Maker) | NID | TEDS | MHS | Overall | ๋น๊ณ |
|---|---|---|---|---|---|---|
| opendataloader-hybrid | Hancom (ํ์ปด) | 0.9355 | 0.9276 | 0.8057 | 0.9034 | |
| ModuDoc (Ours) | Marker-Inc | 0.9212 | 0.9358 | 0.8219 | 0.8993 | ๐ ํ(Table), ํค๋ฉ(Heading) 1์ |
| docling | IBM | 0.8995 | 0.8871 | 0.8019 | 0.8766 | |
| marker | โ | 0.8897 | 0.8076 | 0.7956 | 0.8608 | |
| opendataloader | Hancom (ํ์ปด) | 0.9127 | 0.4942 | 0.7404 | 0.8393 | |
| mineru | โ | 0.8574 | 0.8730 | 0.7430 | 0.8311 | |
| markitdown | Microsoft | 0.8786 | 0.0000 | 0.0000 | 0.5832 |
ModuDoc์ ๋ ํต์ฌ ์ํคํ ์ฒ์ธ **'๋ฉํฐ๋ชจ๋ฌ ํ ์คํธ ๋ ์ด์ด ์ฃผ์ '**๊ณผ **'ํ๋กฌํํธ ์์ง๋์ด๋ง'**์ด ์ฑ๋ฅ์ ๋ฏธ์น๋ ์ํฅ์ ์ ๋ํํ์ต๋๋ค.
| ์ํคํ ์ฒ ๊ตฌ์ฑ | VLM ์ ๋ ฅ (Input) | ํ๋กฌํํธ | TEDS (ํ) | MHS (๊ณ์ธต) | Overall |
|---|---|---|---|---|---|
| Naive baseline | ์ด๋ฏธ์ง๋ง ์ ๊ณต | ๊ธฐ๋ณธ ์ง์์ด | 0.8635 | 0.7740 | 0.8667 |
| + Prompt Engineering | ์ด๋ฏธ์ง๋ง ์ ๊ณต | JSON ๊ตฌ์กฐํ ๊ฐ์ | 0.8705 | 0.8058 | 0.8913 |
| + Text Layer (Ours) | ํ ์คํธ + ์ด๋ฏธ์ง | JSON ๊ตฌ์กฐํ ๊ฐ์ | 0.9358 | 0.8219 | 0.8993 |
๐ก ํต์ฌ ์ธ์ฌ์ดํธ: VLM์ ์ด๋ฏธ์ง์ ์ถ์ถ๋ ํ ์คํธ๋ฅผ ๋์์ ๋ฐ์ด ๋ฃ๋ ๋ฉํฐ๋ชจ๋ฌ ์ฃผ์ ๋ฐฉ์์ ํตํด, ๊ธฐ์กด ํ์๋ค์ด ์คํจํ๋ ๋ณต์กํ ํ ๊ตฌ์กฐ ์ธ์๋ฅ (TEDS)์ 0.8705์์ 0.9358๋ก ๋ํญ ํฅ์์์ผฐ์ต๋๋ค.
ModuDoc๋ ๋ฌธ์๋ฅผ ๋จ์ํ ํ ์คํธ์ ๋์ด์ด ์๋, ๊ฒ์์ ์ต์ ํ๋ **๊ณ์ธต์ ๋ฉํ๋ฐ์ดํฐ(Breadcrumbs)**๋ก ๋ณํํฉ๋๋ค.
split_toc.json: ๋ชฉ์ฐจ(Heading) ๊ธฐ์ค์ผ๋ก ํ์ด์ง ๊ฒฝ๊ณ๋ฅผ ๋ฌด์ํ๊ณ ๋ฌธ๋งฅ์ ๋ณํฉํ ์ฒญํฌ. ๊ฐ ์ฒญํฌ์heading_path(๊ณ์ธต ๊ฒฝ๋ก)๊ฐ ๋ฉํ๋ฐ์ดํฐ๋ก ํฌํจ๋์ด RAG ํํฐ๋ง์ ํ์ฉ ๊ฐ๋ฅํฉ๋๋ค.split_tree.json: ๋ฌธ์์ ๋ ผ๋ฆฌ์ ๊ณ์ธต์ ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ํํํ ์ฒญํฌ. ํค๋ฉ depth ๊ธฐ๋ฐ์ผ๋ก ์น์ ์ ๋ถ๋ฆฌํฉ๋๋ค.
ํค๋ฉ ๊ณ์ธต์ ํ๊ตญ ๊ท์ ๋ฒํธ์ฒด๊ณ(
์ N์ฅ > ์ N์กฐ > โ ํญ > 1. ํธ > ๊ฐ. ๋ชฉ, ์ญ์ง4.1.1)๋ก ๋ฌธ์ ์ ์ญ์์ ์ผ๊ด ๋ณด์ ๋ฉ๋๋ค(heading_pathยทdepth). ๋ณธ๋ฌธ์ ๋ฌปํ ์กฐ๋ฌธ(์ N์กฐ(์ ๋ชฉ))๊ณผ ์ ์ ํญ๋ชฉ(N. "์ฉ์ด"๋ โฆ)์ ํค๋ฉ์ผ๋ก ์น๊ธ๋์ด ์กฐ๋ฌธยท์ฉ์ด ๋จ์ ๊ฒ์์ด ๊ฐ๋ฅํฉ๋๋ค. (CHUNK_NORMALIZE๋ก ์ ์ด)
โ ๏ธ toc/tree๋ VLM ์ด ์ถ์ถํ heading ์ ์์กดํฉ๋๋ค. VLM ๊ตฌ์กฐ ๊ฒฐ๊ณผ(*_structured.json)๊ฐ ์์ผ๋ฉด(VLM ๋ฏธ์ฐ๊ฒฐยท์ ์ฒด ์คํจ ๋ฑ) heading ์ด ์์ดtoc/tree๋ ๋ง๋ค ์ ์๊ณ , ๋ ๋ ํ ์คํธ ๊ธฐ๋ฐpage์ฒญํน๋ง ์ ๊ณต๋ฉ๋๋ค.
| ์ ๋ต | ์ฒญํฌ ๋จ์ | ์ธ์ ์ฐ๋ | heading_path |
|---|---|---|---|
page |
ํ์ด์ง 1์ฅ = 1์ฒญํฌ | ํ์ด์ง ๋ ๋ฆฝ์ฑ์ด ์ค์ํ๊ฑฐ๋, VLM ์์ด ๋น ๋ฅด๊ฒ ์ฒญํนํ ๋ | ์์(์น์
ํค๋ฉ์ด ์์ ๋. ํ์ด์ง ๋์ด๊ฐ ์ฐ์ ํยท๋ณธ๋ฌธ์ ์ง์ ์น์
์ ์์ํ๊ณ _heading_inherited ๋ก ํ์) |
toc |
๋ชฉ์ฐจ(heading) ์น์ = 1์ฒญํฌ (ํ์ด์ง ๊ฒฝ๊ณ ๋ฌด์ยท๋ณํฉ) | ์น์ ๋จ์ ๊ฒ์ยท๋ฌธ๋งฅ ๋ณํฉ์ด ์ค์ํ ์ผ๋ฐ RAG (๊ฐ์ฅ ๋ฒ์ฉ) | ์์ |
tree |
ํค๋ฉ depth ํธ๋ฆฌ์ ๊ณ์ธต ๋ ธ๋ = 1์ฒญํฌ | ์กฐ๋ฌธยทํญ๋ชฉ ๋จ์์ ์ ๋ฐ ๊ณ์ธต ๊ฒ์(๊ท์ ยท๋ฒ๋ นยท๊ธฐ์ ํ์ค ๋ฑ) | ์์(+depth) |
์ ๋ชจ๋ฅด๊ฒ ์ผ๋ฉด
toc์tree๋ฅผ ๋ ๋ค ์์ฑํด RAG ์ธ๋ฑ์ค์์ ๋น๊ตํด๋ณด์ธ์. ํฐ ์ฒญํฌ๋CHUNK_MAX_CHARS(๊ธฐ๋ณธ 4000์)๋ฅผ ๋์ผ๋ฉด element ๊ฒฝ๊ณ์์ ์๋ ๋ถํ ๋ฉ๋๋ค(ํ๋ ํต์งธ ์ ์ง).
๊ฐ ์ฒญํฌ(split_toc.json / split_tree.json)์ ๊ตฌ์กฐ:
{
"chunk_id": "tree_0007",
"chunk_type": "tree",
"depth": 2,
"heading_path": ["์ ํฉํ์ ๋ฑ ์ฌ์ฌ๊ธฐ์ค(์ 5์กฐ ๊ด๋ จ)", "4. ํ์ง๊ฒฝ์์์คํ
"],
"page_range": [42, 43],
"elements": [
{"type": "heading_2", "content": "4. ํ์ง๊ฒฝ์์์คํ
"},
{"type": "text", "content": "..."},
{"type": "table", "content": "<table>...</table>", "caption": "ํ ์ ๋ชฉ"}
]
}heading_path: ๋ฃจํธโํ์ฌ ์น์ ๊น์ง์ ๊ณ์ธต ๊ฒฝ๋ก. RAG ์ฒญํฌ์ ๋ฉํ๋ฐ์ดํฐ ํํฐยท์ปจํ ์คํธ๋ก ์ฌ์ฉdepth: ๊ณ์ธต ๊น์ด(= len(heading_path),tree์ ์ฉ)page_range: ์๋ณธ ํ์ด์ง ๋ฒ์[์์, ๋]elements: ์ฒญํฌ์ ์ํ ์์๋ค(heading_*/text/table/figure/footnote)
git clone https://github.com/Marker-Inc-Korea/ModuDoc.git
cd ModuDoc
pip install -r requirements.txt์์คํ ์์กด์ฑ: PDFยทHWPยทHWPX ๋ ๋ณ๋ ์ค์น ์์ด ๋์ํฉ๋๋ค. DOCX/PPTX/XLSX(๋ฐ DOC/PPT/ODT/RTF)๋ ๋ด๋ถ์ ์ผ๋ก LibreOffice(
soffice)๋ก PDF ๋ณํ ํ ์ฒ๋ฆฌํ๋ฏ๋ก LibreOffice ์ค์น๊ฐ ํ์ํฉ๋๋ค. (Python 3.10+)
HWP/HWPX ๋ ๋๋ง: ๋ฆฌ๋
์ค์์๋ rhwp(Skia ๊ธฐ๋ฐ ๋ค์ดํฐ๋ธ ๋ ๋๋ฌ)๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ์ฌ ๋ณ๋ ๋๊ตฌ ์์ด HWP/HWPX๋ฅผ ํ์ด์ง ์ด๋ฏธ์ง๋ก ๋ ๋ํฉ๋๋ค. ํ ๊ฒน์นจยท์๋ฒ ๋๋ ์ด๋ฏธ์ง ์๋ฆผ์ด ์๊ณ ํ๊ธ ์๋ณธ์ ๊ฐ๊น์ด ํ์ด์ง๋ค์ด์
์ ์ ๊ณตํฉ๋๋ค. ์ ํํ ๊ธ์ ์กฐํ์ ์ํด ํ๊ธ ํฐํธ(ํจ์ด๋กฌ HCR / Noto CJK KR / ๋๋) ์ค์น๋ฅผ ๊ถ์ฅํฉ๋๋ค.
- ๋ฆฌ๋
์ค(x86_64)์์๋ OLE ๊ฐ์ฒด๊น์ง ์์ ๋ ๋ํ๋ ํจ์น ๋น๋ rhwp(
vendor/)๋ฅผ ์ฌ์ฉํฉ๋๋ค โ ChemDraw ๋ฑ ํํ๊ตฌ์กฐ์(WMF), ๋ณด๋์๋ฃ ์๋ฒ ๋๋ ๋นํธ๋งต(StaticDib), ์ด๋ํ ๊ท์ ๋ฌธ์(์์ฝ์ฒ ์ํ์ฒจ๊ฐ๋ฌผ ๊ธฐ์ค๊ท๊ฒฉยท๋ํ๋ฏผ๊ตญ์ฝ์ ๋ฑ)๊น์ง ๋ ๋. ํจ์น ๋ด์ฉยท์ฌ๋น๋ ๋ฐฉ๋ฒ์patches/์ฐธ์กฐ. rhwp๋ฏธ์ค์น ๋๋ ๋ ๋ ์คํจ(์ํธํยท์์ ํ์ผ ๋ฑ) ์ LibreOffice + H2Orestart ๋ก ์๋ ํด๋ฐฑํฉ๋๋ค(์ค์น๋์ด ์์ผ๋ฉด).USE_RHWP=0์ผ๋ก rhwp ๊ฒฝ๋ก๋ฅผ ๋๊ณ ํญ์ LibreOffice ๋ฅผ ์ฐ๊ฒ ํ ์ ์์ต๋๋ค.
(Windows ํ๊ฒฝ์์ ๋ฌด์์ค ๋ณํ์ ์ํด์๋ ํ๊ธ๊ณผ์ปดํจํฐ ํ๊ธ(HWP) ํ๋ก๊ทธ๋จ์ด ์ค์น๋์ด ์์ด์ผ ํฉ๋๋ค.)
๊ธฐ๋ณธ์ ์ผ๋ก ๋ก์ปฌ VLM(vLLM ๋ฑ OpenAI ํธํ ์๋ํฌ์ธํธ)์ ์ฌ์ฉํฉ๋๋ค. ๋ชจ๋ธ์ ๋ก์ปฌ์ ์๋นํ ๋ค, ๊ทธ ์ฃผ์๋ฅผ VLM_BASE_URL๋ก ์ง์ ํ๋ฉด ๋ฉ๋๋ค. (๋ณ๋ API ํค ๋ถํ์)
# 1) VLM ๋ก์ปฌ ์๋น (์: vLLM, OpenAI ํธํ ์๋ํฌ์ธํธ)
vllm serve Qwen/Qwen3-VL-30B-A3B-Instruct --port 8000
# 2) ModuDoc๊ฐ ์ด ์๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ๋๋ก ์ง์
export VLM_BASE_URL="http://localhost:8000/v1"
# (์ธ์ฆ์ด ํ์ํ ์๋ํฌ์ธํธ๋ผ๋ฉด) export VLM_API_KEY="your_key"๐ก
/api/process์modelํ๋ผ๋ฏธํฐ(๊ธฐ๋ณธQwen/Qwen3-VL-30B-A3B-Instruct)๋ ์๋น ์ค์ธ ๋ชจ๋ธ ์ด๋ฆ๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค.๐ก ๋ชจ๋ธ ์ ํ: ๊ธฐ๋ณธ
Qwen3-VL-30B-A3B-Instruct๋ GPU ๋ฉ๋ชจ๋ฆฌ ~60GB(FP16)/~30GB(FP8) ํ์. GPU๊ฐ ์๋ค๋ฉด ๋ ์์ Qwen-VL(์: 7B/8B) ๋๋ ์์ํ ๋ชจ๋ธ์ ์๋นํ๊ณmodel์ ๊ทธ ์ด๋ฆ์ผ๋ก ๋ง์ถ์ธ์ โ OpenAI ํธํ ์๋ํฌ์ธํธ๋ฉด ์ด๋ค VLM ์ด๋ ๋์ํฉ๋๋ค(ํ์ง์ ๋ชจ๋ธ ์ฑ๋ฅ์ ๋น๋ก).
HWP ๋ ๋(rhwp) ๊ด๋ จ ์ ํ ํ๊ฒฝ๋ณ์
| ๋ณ์ | ๊ธฐ๋ณธ | ์ค๋ช |
|---|---|---|
USE_RHWP |
1 |
HWP/HWPX ๋ฅผ rhwp ๋ก ๋ ๋. 0 ์ด๋ฉด LibreOffice ๋ง ์ฌ์ฉ |
RHWP_FONTCONFIG |
(์๋) | ํ๊ธ ํฐํธ๋ง ๋ด์ ์ต์ fonts.conf ๊ฒฝ๋ก. ๋ฏธ์ง์ ์ ์๋ ์์ฑ(์์คํ
ํฐํธ๊ฐ ์์ฒ ๊ฐ๋ฉด ๋ ๋๊ฐ ๋๋ ค์ง๋ ๊ฒ์ ๋ฐฉ์ง) |
RENDER_DPI |
300 |
ํ์ด์ง ๋ ๋ ํด์๋(PDF ํฌํจ ์ ํฌ๋งท). ๋์์๋ก ๋๋ํ ๋ถ์ ํยท์กฐ๋ฐํ ํ๋ฅผ ์ ํํ ๋ถ๋ฆฌํ๋ VLM ํ ํฐ/์๊ฐ์ด ๋์ด๋จ |
VLM_IMG_MAXW |
2464 |
VLM ์
๋ ฅ ์ด๋ฏธ์ง ์ต๋ ํญ(px, 28์ ๋ฐฐ์). RENDER_DPI ์ ์ง โ ์ด ๊ฐ์ด ๋ ๋ ํญ๋ณด๋ค ์์ผ๋ฉด ๋ค์ด์ค์ผ์ผ๋ผ ํ ๋ถ๋ฆฌ ํจ๊ณผ๊ฐ ์ค์ด๋ฆ |
VLM_IMG_MAXW_FALLBACK |
1024 |
๋ฐ๋ณต ํญ์ฃผ/ํ์์์ ์ฌ์๋ ์ ๋ฎ์ถ๋ ํด๋ฐฑ ํญ |
๐ก ํ ๊ตฌ์กฐ ์ ํ๋ โ ๋น์ฉ: ๊ธฐ๋ณธ๊ฐ
300DPI/2464px ๋ ์๋ก ๋ถ์ด ์๋ ํ(์: ์ข์ธก ํ๊ฐํ + ์ฐ์ธก ๋ฑ๊ธํ)๋ฅผ ๊ฐ๊ฐ ๋ณ๋<table>๋ก ๋ถ๋ฆฌํฉ๋๋ค. ์๋ยทํ ํฐ์ ์๋ผ๋ ค๋ฉดRENDER_DPI=200 VLM_IMG_MAXW=1568๋ก ๋ฎ์ถ ์ ์์ต๋๋ค(ํ์ด์ง ์ยท๋ด์ฉ์ ๋์ผํ๋ ๋๋ํ ํ์ ๋ถ๋ฆฌ ์ ํ๋๋ ํ๋ฝ).rhwp ์ฝ์ด๋ ๋ ์ด์์ ์ง๋จ(
LAYOUT_OVERFLOW๋ฑ)์ stderr ๋ก ์ถ๋ ฅํฉ๋๋ค(๊ธฐ๋ฅ์ ๋ฌดํด). ๋ก๊ทธ๊ฐ ๊ฑฐ์ฌ๋ฆฌ๋ฉด ํ๋ก์ธ์ค stderr ๋ฅผ ๋ฆฌ๋ค์ด๋ ํธํ์ธ์.
RAG ์ฒญํน ๊ด๋ จ ์ ํ ํ๊ฒฝ๋ณ์
| ๋ณ์ | ๊ธฐ๋ณธ | ์ค๋ช |
|---|---|---|
CHUNK_MAX_CHARS |
4000 |
์ฒญํฌ ์ต๋ ๊ธธ์ด(์). ์ด๊ณผ ์ element ๊ฒฝ๊ณ์์ ๋ถํ (ํ๋ ํต์งธ ์ ์ง) |
CHUNK_OVERLAP |
0 |
ํฌ๊ธฐ ๋ถํ ๋ก ์๊ธด ํ์์ฒญํฌ ์ฌ์ด ์ค๋ฒ๋ฉ(์). 0=๋นํ์ฑ |
CHUNK_NORMALIZE |
1 |
ํ๊ตญ ๋ฒํธ์ฒด๊ณ(์ N์กฐ > โ > 1. > ๊ฐ., ์ญ์ง 4.1.1)๋ก heading ๊ณ์ธต์ ๋ฌธ์ ์ ์ญ์์ ์ผ๊ด ๋ณด์ + ๋ณธ๋ฌธ์ ๋ฌปํ ์กฐ๋ฌธยท์ ์ ํญ๋ชฉ ์น๊ธ. 0=VLM ์๋ณธ ๋ ๋ฒจ ์ฌ์ฉ |
CHUNK_MERGE_CONTINUED |
1 |
ํ์ด์ง ๋๊น์ผ๋ก ๋ฐ๋ณต๋ ๋จธ๋ฆฌ๊ธ/์ฐ์ heading ์ ๋ณํฉํด ์น์
์ชผ๊ฐ์ง ๋ฐฉ์ง. 0=๋นํ์ฑ |
CHUNK_CLAUSE_HEADING_MAX |
40 |
ํญ/ํธ/๋ชฉ ์ ๋ง์ปค๊ฐ ์ด ๊ธธ์ด(์)๋ฅผ ๋์ผ๋ฉด '์ ๋ชฉ'์ด ์๋๋ผ ๋ณธ๋ฌธ ์ ๋ก ๋ณด๊ณ ๊ฐ๋ฑ(๊ธด ์ ๋ฌธ์ฅ์ด heading_path ๋ฅผ ์ค์ผ์ํค๋ ๊ฒ ๋ฐฉ์ง) |
XLSX(์์ ) ์ฒ๋ฆฌ์ ํ๊ณ
์์ ์ ํยท์ฐจํธยท์ด๋ฏธ์ง๋ฅผ ํจ๊ป ๊ตฌ์กฐํํฉ๋๋ค.
- ํ ๋ฐ์ดํฐ: ๋ชจ๋ ์
(๋ณํฉ์
ํฌํจ)์ HTML
<table>๋ก ์ถ์ถ. ์ํธ๋น ํ๋์ ์น์ (heading_path = [์ํธ๋ช ])์ผ๋ก ์ฒญํน๋ฉ๋๋ค. - ์ฐจํธ(๊ทธ๋ํ): VLM ์ด ๋ด์ฉ์ ์ค๋ช ํ ์คํธ๋ก ๋ณํํฉ๋๋ค.
- ์๋ฒ ๋๋ ์ด๋ฏธ์ง(์ฌ์งยท๋์): VLM ์ด ์ค๋ช ํฉ๋๋ค(๋ก๊ณ ยท์ฅ์์ ์ ์ธ).
ํ๊ณ
.xls(๊ตฌ ๋ฐ์ด๋๋ฆฌ)๋ ์ผ๋ฐ VLM ๊ฒฝ๋ก๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค(๋ํ ์ํธ๋ ๋๋ฆด ์ ์์)..xlsx๊ถ์ฅ.- ์์ ์ ์ ๋ง์ง๋ง์ผ๋ก ๊ณ์ฐยท์ ์ฅ๋ ๊ฐ์ ์ฌ์ฉํฉ๋๋ค.
python app.py์๋ฒ๊ฐ http://localhost:5000์์ ์คํ๋๋ฉฐ, ์ง๊ด์ ์ธ Web UI๋ฅผ ํตํด ์ฆ์ ๋ฌธ์๋ฅผ ์
๋ก๋ํ๊ณ ํ์ฑํ ์ ์์ต๋๋ค.
python app.py ์คํ ํ http://localhost:5000 ์์ ๋ฌธ์๋ฅผ ๋๋๊ทธ&๋๋กญ์ผ๋ก ์
๋ก๋ โ ํ์ยท์ฒญํน ์ ๋ต ์ ํ โ ๊ฒฐ๊ณผ ํ์ธ/๋ค์ด๋ก๋.
1) ํ์ฑ ์์ฒญ โ POST /api/process โ task_id ๋ฐํ
curl -X POST http://localhost:5000/api/process \
-F "files=@sample_document.pdf" \
-F "format=json" \
-F "model=Qwen/Qwen3-VL-30B-A3B-Instruct" \
-F "chunk=toc" -F "chunk=tree" \
-F "concurrency=3"
# โ {"message": "์์
์์๋จ", "task_id": "abc123..."}| ํ๋ผ๋ฏธํฐ | ๊ฐ | ๊ธฐ๋ณธ | ์ค๋ช |
|---|---|---|---|
files |
ํ์ผ(๋ณต์ ๊ฐ๋ฅ) | โ | ์ ๋ก๋ํ ๋ฌธ์(PDF/HWP/HWPX/DOCX/PPTX/XLSX) |
format |
json / markdown / xml |
json |
์ถ๋ ฅ ํ์ |
model |
๋ชจ๋ธ๋ช | Qwen/Qwen3-VL-30B-A3B-Instruct |
์๋น ์ค์ธ ๋ชจ๋ธ๋ช ๊ณผ ์ผ์นํด์ผ |
chunk |
page / toc / tree |
(์์) | ์ฒญํน ์ ๋ต(์ค๋ณต ์ ํ). ์ ๊ฐ์ด๋ ์ฐธ๊ณ |
concurrency |
1~16 |
3 |
๋ฌธ์(ํ์ผ) ๋์ ์ฒ๋ฆฌ ์(์ฌ๋ฌ ํ์ผ ์
๋ก๋ ์). ํ์ด์ง ๋จ์ ๋์์ฑ์ VLM_PAGE_CONCURRENCY(๊ธฐ๋ณธ 16) |
2) ์งํ ์กฐํ โ GET /api/progress/<task_id> (is_done ๊ฐ true ๋ ๋๊น์ง ํด๋ง)
curl http://localhost:5000/api/progress/abc123...
# โ {"progress": 100, "status": "...", "is_done": true, "error": null, ...}3) ๊ฒฐ๊ณผ ๋ค์ด๋ก๋ โ GET /api/download/<task_id> โ ์ ์ฒด ๊ฒฐ๊ณผ ZIP
import os
os.environ["VLM_BASE_URL"] = "http://localhost:8000/v1" # ์๋น ์ค์ธ VLM ์๋ํฌ์ธํธ
from utils import DocumentProcessor
DocumentProcessor.process_and_save(
file_path="sample.pdf",
base_output_dir="./processed",
api_key="local-vllm-noauth-key", # ๋ก์ปฌ no-auth๋ฉด ๋น์ด์์ง ์์ ์๋ฌด ๊ฐ
output_format="json", # "json" | "markdown" | "xml"
model_name="Qwen/Qwen3-VL-30B-A3B-Instruct",
chunk_strategies=["page", "toc", "tree"],
)processed/
โโโ document_name/
โโโ metadata.json # ๋ฌธ์ ๋ฉํ๋ฐ์ดํฐ (+ vlm_pages_total / vlm_failed_pages)
โโโ page_0001.txt # ํ์ด์ง๋ณ ์ถ์ถ ํ
์คํธ
โโโ page_0001.png # ํ์ด์ง๋ณ ์ด๋ฏธ์ง
โโโ page_0001_structured.json # ํ์ด์ง๋ณ ๊ตฌ์กฐํ ๋ฐ์ดํฐ (JSON ๋ชจ๋)
โโโ page_0001_structured.md # ํ์ด์ง๋ณ ๋งํฌ๋ค์ด (Markdown ๋ชจ๋)
โโโ split_page.json # ํ์ด์ง ๋จ์ ์ฒญํฌ
โโโ split_toc.json # ๋ชฉ์ฐจ(Heading) ๊ธฐ์ค ๋ณํฉ ์ฒญํฌ
โโโ split_tree.json # Depth ๊ธฐ๋ฐ ๊ณ์ธตํ ํธ๋ฆฌ ์ฒญํฌ
๋ถ๋ถ ์คํจ ํ์ธ: VLM ๊ตฌ์กฐ์ถ์ถ์ด (์ฌ์๋ ํ์๋) ์คํจํ ํ์ด์ง๋
metadata.json์vlm_failed_pages์ ๊ธฐ๋ก๋ฉ๋๋ค(vlm_pages_total์ ํจ๊ป). ํด๋น ํ์ด์ง๋ ๋ด์ฉ ์ ์ค ์์ด ํ ์คํธ๋ง ํด๋ฐฑ ํฌํจ๋๋ฉฐ, ๋ค๋ฅธ ํ์ด์ง๋ ์ ์ ๊ตฌ์กฐํ๋ฉ๋๋ค.
ModuDoc/
โโโ app.py # Flask ์น API ๋ฐ UI ์๋ฒ
โโโ utils.py # ํต์ฌ ํ์ฑ ๋ก์ง (ํ์ด๋ธ๋ฆฌ๋ ํ
์คํธ ์ถ์ถ + VLM ํ๋กฌํํ
)
โโโ hwp_extract.py # ๋ค์ดํฐ๋ธ HWP/HWPX ํ
์คํธยทํ ์ถ์ถ๊ธฐ (์ธ๋ถ ์์กด์ฑ ์์)
โโโ hwpx_paginate.py # HWPX ํ์ด์ง ๊ฒฝ๊ณ ์ถ์ ์ ํธ
โโโ hwp_figures.py # HWP/HWPX ์๋ฒ ๋๋ ์ด๋ฏธ์ง(์๊ฐ์๋ฃ) ์์น-์ธ์ salvage + VLM ์ค๋ช
โโโ table_validate.py # ํ HTML ๊ฒ์ฆยท์๋ฆฌ + HWP/HWPX ๋ค์ดํฐ๋ธ ํ ์นํยทํ์ด์ง ์ฌ๋ถ๋ฐฐ
โโโ hwp_memo.py # HWP/HWPX ํธ์ง๊ธฐ ๋ฉ๋ชจ(์ฃผ์) ์ถ์ถ
โโโ chunker.py # RAG ์ฒญํนยทํ์ฒ๋ฆฌ (page / toc / tree, ๊ณ์ธต ๊ฒฝ๋ก heading_path)
โโโ hwp_to_pdf.py # Windows COM ๊ธฐ๋ฐ HWP/HWPX ๋ฌด์์ค ๋ณํ๊ธฐ
โโโ templates/ # ์น UI ํ
ํ๋ฆฟ
์ด ํ๋ก์ ํธ๋ MIT License๋ฅผ ๋ฐ๋ฆ ๋๋ค.
