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 .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

주요 발견:

  1. 압도적인 속도: anydoc의 4.4ms는 pandoc(102.1ms)의 1/23, LibreOffice(1129.5ms)의 1/257 시간입니다
  2. 최고 품질: 평가된 모든 포맷에서 anydoc이 최고 점수를 획득
  3. 가장 완전한 커버리지: 14가지 포맷 모두를 지원하는 유일한 도구
  4. 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 (설치 불필요)

BASH
# npx로 직접 실행, 처음에는 프리컴파일된 바이너리가 자동 다운로드됩니다
npx @firecrawl/anydoc report.docx               # stdout에 출력
npx @firecrawl/anydoc slides.pptx -o slides.md  # 파일에 출력
npx @firecrawl/anydoc - --format csv < data.csv # stdin에서 읽기

Python 통합

BASH
pip install firecrawl-anydoc
PYTHON
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 통합

BASH
npm install @firecrawl/anydoc
JAVASCRIPT
import { toMarkdown, toMarkdownBytes, toDocument } from '@firecrawl/anydoc';

// 파일 경로에서
const md = await toMarkdown('report.docx');

// 바이트에서
const mdFromBytes = await toMarkdownBytes(bytes);

// 문서 모델 가져오기 (포함된 리소스 포함)
const doc = await toDocument(bytes);

Rust 네이티브 사용

BASH
cargo add anydoc
RUST
// 파일 경로에서
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과의 조합

PYTHON
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와의 조합

PYTHON
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은 올바르게 식별하여 변환합니다.

PYTHON
# 포맷 감지
fmt = anydoc.format_from_bytes(data)  # Format 열거형 반환
print(fmt)  # Format.DOCX

3. 배치 처리 시 GIL 주의

Python 바인딩은 GIL을 해제하므로, 여러 스레드를 사용한 배치 변환은 상호 차단되지 않습니다:

PYTHON
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 기능 포함)와의 조합을 권장합니다.

제한 사항 및 주의 사항

  1. 스캔 PDF 능력 제한적: anydoc 자체는 OCR을 수행하지 않습니다. 스캔 PDF에는 외부 OCR 서비스가 필요합니다. Firecrawl의 Parse API로 이를 보완할 수 있습니다.
  2. 복잡한 레이아웃은 정확하지 않을 수 있음: 다단 레이아웃이나 이미지와 텍스트가 복잡하게 혼재된 문서에서는 변환 결과가 전문적인 레이아웃 도구만큼 정확하지 않을 수 있습니다. anydoc의 목표는 LLM이 읽을 수 있는 구조화된 텍스트이며, 픽셀 단위의 재현이 아닙니다.
  3. 이전 .doc 포맷: 지원되지만, .doc(OLE2 바이너리 형식)의 파싱 복잡성은 .docx보다 높으며, 변환 품질이 약간 낮을 수 있습니다.
  4. ML 강화 없음: anydoc은 의도적으로 ML 모델에 의존하지 않습니다. 이는 docling이나 marker처럼 딥러닝을 사용하여 레이아웃 분석을 수행하지 않음을 의미합니다. 장점은 속도가 빠르고 GPU 의존이 없다는 것입니다. 단점은 복잡한 레이아웃의 이해력이 제한적이라는 것입니다.
  5. 브라우저 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)을 제공합니다. 파일은 브라우저 내에서 로컬로 변환되며, 서버에 업로드되지 않으므로 프라이버시에 민감한 시나리오에 적합합니다.