anydoc에 주목해야 하는 이유
대규모 언어 모델에 문서를 제공할 때, 포맷 변환 품질은 RAG 결과에 직접적인 영향을 미칩니다. Word의 테이블 정렬 오류, PPT의 계층 구조 손실, PDF의 단락 분리 — 이러한 포맷 문제는 벡터 검색의 정확도를 크게 저하시킵니다. 지금까지 우리는 주요 Office 포맷을 어떻게든 커버하기 위해 pandoc, python-docx, LibreOffice 등 여러 도구를 조합해야 했으며, 유지보수 비용이 높고 출력도 일관성이 없었습니다.
anydoc은 Firecrawl 팀이 Rust로 처음부터 개발한 문서 변환 라이브러리입니다. 한 번의 API 호출로 14가지 포맷을 변환할 수 있으며, 중앙 변환 시간은 단 4.4밀리초입니다. 이미 GitHub에서 12,000개 이상의 스타를 획득했으며, RAG/LLM 도구 체인에서 문서 전처리의 새로운 표준이 되고 있습니다.
지원되는 14가지 포맷
anydoc은 모든 오픈소스 변환 도구 중에서 가장 넓은 포맷 커버리지를 제공합니다:
| 포맷 카테고리 | 지원되는 확장자 |
|---|---|
| Word 문서 | .doc, .docx, .docm |
| PowerPoint 프레젠테이션 | .ppt, .pps, .pot, .pptx, .pptm, .ppsx, .ppsm |
| Excel 스프레드시트 | .xls, .xlsx, .xlsm, .xlsb |
| OpenDocument | .odt, .ods, .odp |
| 리치 텍스트 | .rtf |
| 전자책 | .epub |
| 데이터 파일 | .csv |
.pdf |
.doc(이전 바이너리 형식)와 .docx(새 XML 형식)가 모두 지원된다는 점에 주목하세요. 이는 동종의 도구에서 매우 드문 경우입니다. 대부분의 도구는 .docx만 지원하며, 이전 .doc 파일에는 대응하지 못합니다.
성능 테스트: 4.4ms의 비밀
anydoc의 공식 벤치마크는 100개의 실제 문서를 사용하여 7개의 도구(자신 포함)를 14가지 포맷 모두에서 비교했습니다:
| 도구 | 지원 포맷 | 중앙값 시간 | 품질 점수 | 완전성 | 구조 | 포맷팅 | 깔끔함 |
|---|---|---|---|---|---|---|---|
| anydoc | 14/14 | 4.4ms | 81 | 87 | 79 | 78 | 81 |
| libreoffice | 12/14 | 1129.5ms | 40 | 59 | 42 | 40 | 24 |
| unstructured | 8/14 | 572.9ms | 63 | 76 | 59 | 51 | 63 |
| markitdown | 6/14 | 134.8ms | 65 | 78 | 66 | 60 | 52 |
| pandoc | 5/14 | 102.1ms | 56 | 74 | 57 | 56 | 38 |
| docling | 4/14 | 513.6ms | 57 | 60 | 60 | 57 | 51 |
| mammoth | 1/14 | 52.5ms | 70 | 84 | 71 | 75 | 51 |
주요 발견:
- 압도적인 속도: anydoc의 4.4ms는 pandoc(102.1ms)의 1/23, LibreOffice(1129.5ms)의 1/257 시간입니다
- 최고 품질: 평가된 모든 포맷에서 anydoc이 최고 점수를 획득
- 가장 완전한 커버리지: 14가지 포맷 모두를 지원하는 유일한 도구
- Rust의 이점: 순수 Rust 구현, ML 모델 의존 없음, 외부 서비스 호출 없음
품질 점수는 Claude Sonnet 5가 LLM 심사자로서 2개의 출력을 블라인드 비교(LibreOffice로 렌더링된 첫 6페이지 이미지를 기준)하여 결정했습니다. 각 도구는 각 비교 쌍에 대해 2회 평가되어 위치 편향을 제거했습니다 — 총 482회의 평가.
포맷별 비교: anydoc이 전면적으로 리드
다음 데이터는 동일한 포맷에서의 도구 직접 비교를 보여줍니다(최대 점수 100):
| 포맷 | anydoc | libreoffice | unstructured | markitdown | pandoc | docling | mammoth |
|---|---|---|---|---|---|---|---|
| .doc | 87 | 57 | 67 | - | - | - | - |
| .docx | 88 | 56 | 53 | 71 | 68 | 71 | 70 |
| .pptx | 74 | 24 | - | 66 | - | 52 | - |
| .xlsx | 72 | 30 | 66 | 55 | - | 47 | - |
| .rtf | 88 | 53 | 46 | - | 45 | - | - |
| .odt | 80 | 51 | 68 | - | 60 | - | - |
| .epub | 77 | - | 72 | 72 | 52 | - | - |
mammoth는 .docx에서 70점을 획득했는데, 언뜻 좋아 보입니다 — 하지만 이는 단 하나의 포맷에 대한 결과일 뿐입니다. anydoc의 81점은 14가지 포맷 모두의 평균이며, 전혀 다른 의미를 가집니다.
설치 및 빠른 시작
anydoc은 Rust 코어와 Node.js/Python/WebAssembly 바인딩, 그리고 즉시 사용 가능한 CLI를 제공합니다.
CLI (설치 불필요)
# npx로 직접 실행, 처음에는 프리컴파일된 바이너리가 자동 다운로드됩니다
npx @firecrawl/anydoc report.docx # stdout에 출력
npx @firecrawl/anydoc slides.pptx -o slides.md # 파일에 출력
npx @firecrawl/anydoc - --format csv < data.csv # stdin에서 읽기
Python 통합
pip install firecrawl-anydoc
import anydoc
# 파일 경로에서 변환
markdown = anydoc.to_markdown("report.docx")
print(markdown)
# 바이트에서 변환 (포맷 자동 감지)
with open("report.docx", "rb") as f:
data = f.read()
markdown = anydoc.to_markdown_bytes(data)
# 포맷 지정 (CSV 등 magic bytes가 없는 형식에 필요)
markdown = anydoc.to_markdown_bytes(csv_data, "csv")
# 문서 모델 가져오기 (포함된 이미지 등 리소스 포함)
document = anydoc.to_document(data)
Node.js 통합
npm install @firecrawl/anydoc
import { toMarkdown, toMarkdownBytes, toDocument } from '@firecrawl/anydoc';
// 파일 경로에서
const md = await toMarkdown('report.docx');
// 바이트에서
const mdFromBytes = await toMarkdownBytes(bytes);
// 문서 모델 가져오기 (포함된 리소스 포함)
const doc = await toDocument(bytes);
Rust 네이티브 사용
cargo add anydoc
// 파일 경로에서
let markdown = anydoc::to_markdown("report.docx")?;
// 바이트에서 (포맷 자동 감지)
let markdown = anydoc::to_markdown_bytes(&bytes, None)?;
// 포맷 지정
let markdown = anydoc::to_markdown_bytes(&bytes, anydoc::Format::Csv)?;
LangChain / LlamaIndex와의 실전 조합
RAG 파이프라인에서 anydoc은 일반적으로 문서 로드 전처리 단계로 사용되며, 다양한 포맷을 균일하게 Markdown으로 변환한 다음 청크 분할 및 벡터화를 수행합니다.
LangChain과의 조합
import anydoc
from langchain.text_splitter import MarkdownTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
# 1. anydoc으로 통합 Markdown 변환
def load_documents(file_paths):
docs = []
for path in file_paths:
md = anydoc.to_markdown(path)
docs.append({"content": md, "source": path})
return docs
# 2. Markdown 청크 분할
splitter = MarkdownTextSplitter(chunk_size=1000, chunk_overlap=100)
raw_docs = load_documents(["report.docx", "slides.pptx", "data.xlsx"])
chunks = []
for doc in raw_docs:
chunks.extend(splitter.split_text(doc["content"]))
# 3. 벡터화 및 저장
embeddings = OpenAIEmbeddings()
db = FAISS.from_texts(chunks, embeddings)
db.save_local("./faiss_index")
LlamaIndex와의 조합
import anydoc
from llama_index.core import Document, VectorStoreIndex
# anydoc 변환
file_paths = ["contract.docx", "presentation.pptx", "budget.xlsx"]
documents = []
for path in file_paths:
md = anydoc.to_markdown(path)
documents.append(Document(text=md, metadata={"source": path}))
# LlamaIndex 인덱스 생성
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("계약의 위약 조항은 무엇입니까?")
print(response)
RAG 시나리오의 모범 사례
1. 포함 리소스에는 to_document() 사용
문서에 이미지가 포함되어 있을 때, to_document()가 반환하는 문서 모델은 이미지의 바이트 데이터와 미디어 타입을 유지합니다. 다음 선택지가 있습니다:
- 이미지의 alt 텍스트를 텍스트 콘텐츠로 추출
- 이미지를 별도로 멀티모달 모델(GPT-4V 등)에 전송하여 OCR 처리
- 이미지의 URL 참조 유지 (이미지에 외부 링크가 있는 경우)
2. 포맷 자동 감지 활용
anydoc은 파일 확장자가 아닌 파일 내용의 magic bytes를 통해 포맷을 감지합니다. 즉, 파일이 잘못 이름지어져 있어도(예: .pdf가 실제로는 .docx), anydoc은 올바르게 식별하여 변환합니다.
# 포맷 감지
fmt = anydoc.format_from_bytes(data) # Format 열거형 반환
print(fmt) # Format.DOCX
3. 배치 처리 시 GIL 주의
Python 바인딩은 GIL을 해제하므로, 여러 스레드를 사용한 배치 변환은 상호 차단되지 않습니다:
import concurrent.futures
import anydoc
def convert(path):
return anydoc.to_markdown(path)
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
results = list(executor.map(convert, file_paths))
4. 스캔 PDF 처리
anydoc에는 pdf-inspector가 통합되어 있어, PDF가 텍스트 기반인지 스캔인지 자동 감지합니다. 텍스트 기반 PDF는 직접 추출됩니다. 스캔 PDF에는 Firecrawl의 Parse API(OCR 기능 포함)와의 조합을 권장합니다.
제한 사항 및 주의 사항
- 스캔 PDF 능력 제한적: anydoc 자체는 OCR을 수행하지 않습니다. 스캔 PDF에는 외부 OCR 서비스가 필요합니다. Firecrawl의 Parse API로 이를 보완할 수 있습니다.
- 복잡한 레이아웃은 정확하지 않을 수 있음: 다단 레이아웃이나 이미지와 텍스트가 복잡하게 혼재된 문서에서는 변환 결과가 전문적인 레이아웃 도구만큼 정확하지 않을 수 있습니다. anydoc의 목표는 LLM이 읽을 수 있는 구조화된 텍스트이며, 픽셀 단위의 재현이 아닙니다.
- 이전 .doc 포맷: 지원되지만, .doc(OLE2 바이너리 형식)의 파싱 복잡성은 .docx보다 높으며, 변환 품질이 약간 낮을 수 있습니다.
- ML 강화 없음: anydoc은 의도적으로 ML 모델에 의존하지 않습니다. 이는 docling이나 marker처럼 딥러닝을 사용하여 레이아웃 분석을 수행하지 않음을 의미합니다. 장점은 속도가 빠르고 GPU 의존이 없다는 것입니다. 단점은 복잡한 레이아웃의 이해력이 제한적이라는 것입니다.
- 브라우저 WASM 버전: WebAssembly 버전은 브라우저 내에서 로컬로 실행되며, 파일은 사용자의 디바이스를 떠나지 않습니다. 프라이버시에 민감한 시나리오에 적합하지만, 큰 파일의 변환은 브라우저의 메모리 제한을 받을 수 있습니다.
총평
anydoc은 매우 실용적인 문제를 해결합니다: RAG/LLM 파이프라인에서, 경량이고 빠르며 모든 포맷을 커버하는 도구로, 이전에는 4, 5개의 라이브러리를 조합해야 했던 문서 변환 작업을 대체합니다.
장점: - 14가지 포맷 완전 커버, 오픈소스 도구 중 유일 - 4.4ms 중앙 변환 속도, 2위보다 23배 빠름 - 품질 점수에서 전면적으로 리드, 출력 일관성이 높음 - 순수 Rust 구현, 외부 의존 없음, GPU 불필요 - Python/Node.js/Rust/WASM 4가지 바인딩 제공
적합한 사용자: - RAG 파이프라인을 구축하고 업로드된 다양한 포맷의 문서를 처리해야 하는 개발자 - 통일된 문서 출력 포맷이 필요한 팀 - 변환 속도가 요구되는 실시간 시나리오 - 여러 변환 도구 의존성을 유지하고 싶지 않은 개발자
GitHub: firecrawl/anydoc (12,000+ 스타)
자주 묻는 질문 (FAQ)
Q1: anydoc과 pandoc의 차이점은 무엇입니까? pandoc은 범용 문서 포맷 변환 도구로, 매우 많은 입출력 포맷을 지원하지만 각 포맷의 변환 깊이는 제한적입니다. anydoc은 Office 문서를 LLM에 적합한 Markdown으로 변환하는 데 특화되어 있으며, 커버하는 14가지 포맷에서 더 높은 변환 품질, 23배 빠른 속도를 제공하며, RAG 시나리오에 최적화되어 있습니다.
Q2: anydoc에는 GPU나 외부 서비스가 필요합니까? 아니요. anydoc은 순수 Rust 구현으로, ML 모델 의존이 없고 외부 API 호출도 없습니다. 로컬에서 실행되며, 1개 파일의 중앙 변환 시간은 4.4ms입니다.
Q3: anydoc은 스캔 PDF를 처리할 수 있습니까? anydoc에는 pdf-inspector가 통합되어 텍스트 기반 PDF를 처리할 수 있습니다. 스캔 PDF(이미지 기반)에는 Firecrawl Parse API(OCR 기능 포함)와의 조합을 권장합니다.
Q4: anydoc의 출력 포맷은 어떤 것입니까? GitHub Flavored Markdown(GFM)으로 통일되어 출력되며, 제목 계층, 테이블, 리스트, 코드 블록, 링크 등의 구조가 포함됩니다. 14가지 포맷 모두 출력 스타일이 일관됩니다.
Q5: anydoc은 브라우저에서 실행할 수 있습니까? 네. anydoc은 WebAssembly 버전(@firecrawl/anydoc-wasm)을 제공합니다. 파일은 브라우저 내에서 로컬로 변환되며, 서버에 업로드되지 않으므로 프라이버시에 민감한 시나리오에 적합합니다.