anydoc: конвертація 14 форматів документів у Markdown за мілісекунди
Розбираємо anydoc від Firecrawl на Rust: як швидко він перетворює docx, pptx, xlsx і PDF на Markdown на свіжому тестовому наборі, що ламається і як ним користуватися.
Firecrawl виклав у open source конвертер документів anydoc, і цифри в нього вражають. Прогнати docx через LibreOffice, перш ніж віддати моделі, може тривати понад секунду на файл, і це без урахування ручних кроків. anydoc опускає медіану до 4,4 мілісекунди на 14 поширених форматах і завжди видає той самий варіант Markdown у стилі GitHub.
Що це таке
Це не черговий конвертер із серії «і так зійде». Увесь пайплайн переписано на чистому Rust. Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV і PDF розкладаються в одну спільну модель документа, а результат пише один серіалізатор. Тому старий .doc 2003 року і .pptx, вивантажений учора, виглядають однаково: рівні заголовків, об'єднані клітинки, виноски та списки завдань поводяться ідентично. Усе працює локально, не звертається до зовнішніх сервісів і не використовує моделей машинного навчання.
Прийнято вважати, що для розбору документів потрібен ланцюжок склеєних інструментів або похід у хмару. Але така швидкість і якість досяжні на власній машині. anydoc уже працює всередині ендпоінта Parse у самого Firecrawl, де зайвий крок OCR потрібен лише сканованим PDF. Текстові PDF та офісні файли обробляються локально за один прохід.
Перевіряємо заяви на свіжому корпусі
Бенчмарк розробника відтворити неможливо. У README сказано, що його 100 файлів не можна поширювати і в репозиторії їх немає. Тому зібрали новий тестовий набір: по 40 файлів на формат, кожен менший за 25 МБ.
Вийшло 206 файлів, з них успішно сконвертувалися 194.
Головна обіцянка — медіана менше 5 мс — справджується лише для csv і xlsx. Медіана для docx становила 6,4 мс, для pptx і pdf — по 22. Найповільніший pptx забрав 346 мс: це був великий файл у вибірці, де траплялися екземпляри понад двісті мегабайтів. Цифра 5 мс — медіана по всьому змішаному корпусу, і її тягнуть донизу порядкові формати на кшталт csv.
На практиці тисяча файлів docx обробляється приблизно за 6 секунд, а тисяча файлів PowerPoint — понад двадцять. І те, і те швидко, але планувати варто за повільнішими цифрами.
Вісім із дванадцяти збоїв сталися не з вини інструмента. Ці вісім файлів повернули malformed, тож кожен перевірили командою file. Два файли .docx виявилися один data, другий JSON data, а два .xlsx і чотири .pptx — просто data. Справжніх офісних документів серед них не було. anydoc визначає вміст за форматом, а не за розширенням, тож ошукати його не вдалося. Якщо прибрати вісім підробок, реальна частка успіху — 194 із 198, тобто 98%.
Тут є й незапланований бонус. Проженіть його бібліотекою документів, зберіть усе, що повернуло malformed, і отримаєте готовий список пошкоджених або неправильно підписаних файлів. Створювали його не для цього, але працює.
Чому це важливо для LLM-пайплайнів
Коли віддаєш моделі договір, перша проблема — зрозуміти, у якому він насправді форматі. Розширення каже .docx, а всередині може бути змінений ZIP. CSV не має сигнатури, його доводиться оголошувати вручну. Зміниш інструмент — зміняться й правила виводу: таблиці інколи перетворюються на списки, виноски зникають, а відступи вкладених списків роз'їжджаються.
Для порівняння: медіана в LibreOffice перевищує 1100 мілісекунд, і всіх 14 форматів він не покриває. unstructured, markitdown і pandoc дають раду кожен лише частині діапазону, а їхні оцінки якості не піднімаються вище приблизно 65. На тій самій пачці зі 100 реальних документів anydoc отримав 81 бал загалом: 87 за повноту, 79 за структуру, 78 за форматування і 81 за чистоту. І це приблизно в 250 разів швидше.
Це не лише лабораторна цифра. 500 файлів docx обробляються за 1,7 секунди — досить швидко, щоб вбудувати конвертер просто в цикл агента. Раніше довелося б будувати асинхронну чергу й чекати колбеків. Тепер навіть синхронні виклики здаються миттєвими.
Старі файли .xls та .odp з неправильними іменами все одно розпізнавалися за заголовком файлу. CSV потрібна явна підказка: без сигнатури інструмент інакше його пропускає. Такі дрібниці краще знати до того, як будувати на ньому щось своє.
Як це влаштовано всередині
Кожен файл починається з визначення вмісту: PDF — за заголовком, RTF — за групами, ZIP-контейнери — за mimetype. Після розпізнавання окремий парсер заповнює єдину модель Document із блоками, inline-елементами, таблицями, виносками та вбудованими ресурсами. Завершує роботу єдиний серіалізатор GitHub-Flavored Markdown.
Саме тому об'єднані клітинки таблиць, якорі заголовків, закреслення, блоки коду й нотатки доповідача підкоряються тим самим правилам. Зображення та об'єкти перетворюються на Markdown з alt-текстом, а сирі байти лишаються в моделі, щоб їх можна було дістати пізніше.
PDF проходять через pdf-inspector, який береться лише за файли з текстовим шаром. Чистий скан повертає Unsupported, а не вдає, що його прочитано, а зашифрований файл дає зрозумілу помилку. Така чесність у будь-якому разі краща за «конвертацію в міру сил».
Оскільки це чистий Rust, немає накладних витрат інтерпретатора Python і тимчасових файлів на диску. Біндинг для Node використовує пул потоків libuv, тож event loop не блокується, а біндинг для Python відпускає GIL. Так і виходить медіана 4,4 мс.
Хтось назве парсер на Rust оверінжинірингом. Інші пакують його в навичку агента, щоб Claude чи Cursor перетворювали файл на Markdown, щойно він з'явиться. В обох таборів є свої аргументи.
Як користуватися
Найпряміший шлях — командний рядок:
npx @firecrawl/anydoc report.docx
Вивід іде в термінал. Додайте -o slides.md, щоб записати файл. Щоб прочитати CSV зі stdin:
npx @firecrawl/anydoc - --format csv < data.csv
У Node:
import { toMarkdown, toMarkdownBytes } from '@firecrawl/anydoc';
const md = await toMarkdown('report.docx');
const mdFromBytes = await toMarkdownBytes(bytes);
const mdCsv = await toMarkdownBytes(bytes, 'csv');
Версія для Python схожа:
import anydoc
md = anydoc.to_markdown("report.docx")
md_bytes = anydoc.to_markdown_bytes(data)
md_csv = anydoc.to_markdown_bytes(data, "csv")
Є й збірка для браузера, @firecrawl/anydoc-wasm, тож файли взагалі не залишають машину. Для Rust виконайте cargo add anydoc.
З агентами ще простіше. Після npx skills add firecrawl/anydoc тулчейн сам викликає конвертер, щойно з'являється документ.
Очікуйте чистий Markdown зі збереженими таблицями, списками й заголовками. У складних документах інколи губиться оформлення граничних випадків — це компроміс парсера, а не падіння.
Обмеження, які варто врахувати
Проєкт поки на стадії 0.1.x. Сильно пошкоджені файли або завеликі вбудовані об'єкти теоретично можуть спричинити ResourceLimit. У продакшені обробляйте повернуті помилки за категоріями, а зашифровані й непідтримувані файли логуйте окремо, щоб один поганий файл не поклав увесь пайплайн.
Якщо конвертацію можна зробити локально, обійдіться без походу в хмару, а скани залиште ендпоінтам з OCR. Швидкість і якість уже достатньо добрі, тож лишається питання, як це впишеться у ваш власний пайплайн. Проєкт живе на github.com/firecrawl/anydoc.