Skip to main content
Glama

Document Conversion API

Преобразует документы PDF/Office в структурированный JSON и обратно, в обоих направлениях. Кандидат NEXUS #6 -- ручная сборка, не создан через FORGE, следует тому же паттерну ручных Cloud Run ассетов, что и кандидат #3 (agent-verification-api) и кандидат #4 (url-metadata-api).

  • POST /extract-pdf-to-json -- текст, таблицы (постранично), количество страниц, метаданные. $0,02/вызов.

  • POST /extract-docx-to-json -- абзацы (с уровнями заголовков/стилями), таблицы, метаданные. $0,01/вызов.

  • POST /extract-xlsx-to-json -- сетки ячеек по каждому листу, с ограничением по строкам/столбцам. $0,01/вызов.

  • POST /generate-pdf-from-json -- структурированные блоки (заголовок/абзац/таблица) -> PDF-байты. $0,02/вызов.

  • POST /generate-docx-from-json -- структурированные блоки -> .docx-байты. $0,01/вызов.

  • MCP-инструменты на /mcp, дублирующие все 5 эндпоинтов -- сейчас бесплатно, см. «Известные ограничения».

  • GET /health, GET /.well-known/agent-card.json, GET /openapi.json (содержит x-payment-info).

Все 5 эндпоинтов принимают файловые байты в кодировке base64 напрямую в теле запроса -- никаких URL для загрузки. Это осознанная граница области ответственности: она полностью убирает SSRF из поверхности риска этого ассета (в отличие от url-metadata-api/agent-verification-api, которые оба загружают предоставленные вызывающей стороной URL и требуют защиты от SSRF).

Почему именно эти библиотеки и почему нет внешних источников данных

Чистый OSS, зрелые, без LLM-вызовов, ноль внешних сетевых запросов во время выполнения (полностью локальные вычисления): pdfplumber (извлечение PDF, обёртка над pdfminer.six), python-docx (Word), openpyxl (Excel), reportlab (генерация PDF). В этом ассете намеренно нет ни одного именованного стороннего источника данных -- чтобы избежать риска галлюцинаций в стиле BuyWhere (skills/asset-lifecycle): здесь нечему быть неправильным извне, весь продукт заключается в том, чтобы «запустить известную библиотеку над байтами, которые прислал вызывающий». Без numpy/scipy (позволяет избежать известного сбоя Cloud Run Buildpacks -- нет компилятора Fortran для сборки scipy из исходников, см. skills/infra-deploy-ops; этим библиотекам они в любом случае не нужны).

Related MCP server: docxtpl MCP Server

Почему $0,01–$0,02 (против $0,35 у agent-verification-api и $0,01 у url-metadata-api)

Здесь нет платных сторонних API (в отличие от WHOIS в live-entity-verification/agent-verification-api) -- только стоимость CPU/памяти, поэтому этот ассет находится в той же нижней ценовой категории, что и $0,01 за одиночный запрос у url-metadata-api, а не где-то рядом с $0,35 у agent-verification-api. Операции с PDF (extract-pdf-to-json, generate-pdf-from-json) оценены на ступень выше ($0,02), чем docx/xlsx ($0,01): pdfplumber/reportlab выполняют заметно больше работы за вызов (разбор макета на уровне страниц / рендеринг PDF), чем python-docx/openpyxl (прямой XML-разбор zip-архива). Ожидается, что этот ассет будет самым предсказуемым из 3 ручных кандидатов, а не главным источником дохода -- конвертация документов — это частая потребность агентов с низкой вариативностью, а не дифференцированная/редкая возможность, -- поэтому ценообразование отдаёт приоритет стабильному использованию с низким порогом входа, а не марже с каждого вызова.

Два риска, которые есть у этого ассета, но нет у остальных 3 ручных ассетов

  1. Привязан к CPU, а не к вводу-выводу. Каждый вызов парсера/генератора синхронен (ни в одной из 4 библиотек нет асинхронного API), в отличие от остальных 3 ассетов, которые работают через ввод-вывод с помощью httpx.AsyncClient. Каждый обработчик выгружает фактическую работу через asyncio.to_thread() внутри таймаута asyncio.wait_for() (25 с), чтобы медленный парсинг одного вызывающего не блокировал весь цикл событий / все параллельные запросы. Известное остаточное ограничение: asyncio.wait_for() отменяет ожидающую задачу, но не может завершить базовый поток ОС -- в Python нет API для принудительного завершения работающего потока. Патологический ввод, который зависает внутри pdfminer/openpyxl, будет бесконечно удерживать этот рабочий поток, даже после того как вызывающий получит 504. Смягчено, но не решено: ограниченный семафор (NEXUS_MAX_CONCURRENT_JOBS, по умолчанию 4) ограничивает, сколько таких «утекших» потоков может накопиться одновременно, -- новые запросы получают чистую ошибку 503 вместо неограниченного роста потоков, но уже утёкший поток не освобождается никогда. Полное исправление потребовало бы выноса CPU-нагруженной работы в отдельный завершаемый процесс (ProcessPoolExecutor + жёсткое завершение), а не поток; здесь это не делается, так как несоразмерно для кандидата с испытательным сроком в 7 дней.

  2. Бомба декомпрессии / исчерпание ресурсов. .docx/.xlsx — это zip-архивы: специально созданный файл с небольшим сжатым размером, но огромным несжатым (zip-бомба) -- это реальный вектор DoS-атак, не похожий ни на что, с чем сталкиваются остальные 3 ассета. _check_zip_bomb_safe() проверяет центральный каталог zip (zipfile.infolist(), это дёшево и НЕ распаковывает данные записей) перед передачей байтов в python-docx/openpyxl: отклоняет, если общий несжатый размер превышает 50 МБ или если степень сжатия любой отдельной записи превышает 100x. Каждая загрузка также ограничена 8 МБ необработанных/декодированных байтов до любого разбора (включая PDF, даже несмотря на то, что они не основаны на zip, -- это ограничивает наихудший размер входных данных независимо от формата).

Известные ограничения (намеренно оставлены неисправленными -- CLAUDE.md §3, никаких шлюзов без доказательств необходимости)

  • Вызовы MCP-инструментов не тарифицируются. Тот же паттерн внутрипроцессных вызовов (и по той же причине), что и у родственных ручных ассетов: MCP-инструмент вызывает общую функцию конвертации напрямую, а не через повторный вход по HTTP в ASGI-приложение.

  • Нет ограничения частоты запросов для каждого вызывающего. Нормально для 7-дневного одноразового замера; добавьте, если ассет продолжит жить.

  • Сгенерированные документы не проверяются на точность обратного преобразования за пределами локальных смоук-тестов -- отрисовка таблиц/абзацев в reportlab и сопоставление уровней заголовков в python-docx -- это зрелые, широко используемые пути кода, которые здесь не перепроверяются на каждом возможном Office/PDF-ридере.

  • См. риск «Привязан к CPU» выше по поводу остаточного ограничения с утечкой потоков.

NEXUS_X402_FREE_MODE

Тот же паттерн гейта, что и в similarity-search-api/live-entity-verification (skills/x402-payments) -- по умолчанию false (тарификация с первого дня, без бесплатного окна; у движка нет внешней зависимости валидации, как у движка на основе WHOIS в live-entity-verification, поэтому здесь нет эквивалентного аргумента «уже проверено в проде» для перехода сразу к платной модели, -- он и так тарифицируется, согласно формулировке «умеренный и стабильный» доход из брифа сессии, а не из-за конкретного прецедента). Установите true локально для тестирования без реального цикла с платёжным посредником.

Целевое развёртывание: Cloud Run, а не Railway

Тот же конвейер, что и у кандидатов #3/#4 -- см. skills/infra-deploy-ops. Память увеличена до 1 ГиБ (против жёстко заданных 512 МиБ по умолчанию в общем scripts/deploy_cloud_run.sh) -- это самый требовательный к памяти из 3 ручных кандидатов (библиотеки разбора PDF/Office, транзитивно pypdfium2/Pillow через pdfplumber). Развёртывается напрямую через gcloud run deploy (не через общий скрипт, чтобы не редактировать общую инфраструктуру ради увеличения памяти для одного кандидата):

# 1. First deploy -- PUBLIC_DOMAIN not known yet, every real request 421s until step 2.
gcloud run deploy document-conversion-api \
  --source manual_assets/document-conversion-api \
  --project nexus-505016 --region us-central1 \
  --allow-unauthenticated --min-instances=0 --max-instances=3 --memory=1Gi --quiet \
  --env-vars-file manual_assets/document-conversion-api/env-vars.deploy.yaml

# 2. Grab the printed *.run.app URL, then:
gcloud run services update document-conversion-api --region us-central1 --project nexus-505016 \
    --update-env-vars PUBLIC_DOMAIN=<the-real-domain>

Измерение (кандидат #6, 7-дневное окно)

7-дневное окно с момента первого реального развёртывания. Источник истины: таблицы traffic_events/revenue_events/mcp_call_events (asset_name = 'document-conversion-api'), а не логи Cloud Run. День 7: если реального трафика нет (с учётом фильтрации краулеров), приостановить/удалить сервис Cloud Run, то же правило принятия решений, что и для кандидатов #3/#4.

Related MCP Connectors

Related MCP Servers