Суммаризация длинных документов: map-reduce или 1M контекста

Map-reduce нужен, когда документ не влезает в контекст или нужна параллельность, а не ради экономии. Разбираем оба подхода с кодом и расчётом стоимости.

6 мин · · разработка · автоматизация

Суммаризация длинных документов — частая задача: годовой отчёт, пакет договоров, выгрузка переписки, толстый PDF с исследованием. Есть два подхода: нарезать текст на части и суммировать их по отдельности (map-reduce) или отправить документ целиком в модель с большим контекстом. Ниже — когда какой выбирать, рабочий код и расчёт стоимости по ценам Zukko.

Главное про выбор

Распространённый миф — «map-reduce дешевле». На одной и той же модели это не так: в map-reduce модель всё равно читает весь документ (каждый кусок по разу), а сверху добавляются инструкции к каждому куску, промежуточные резюме на выходе и их повторное чтение на шаге reduce. Токенов получается больше, а не меньше.

Map-reduce выбирают по другим причинам:

  • Документ не влезает в контекст модели. У glm-5.3 и glm-5.3-flash контекст 128K токенов; документ на 300K туда целиком не поместится.
  • Параллельность. Куски можно обрабатывать одновременно, и итоговое время зависит от размера куска, а не всего документа.
  • Резюме по разделам. Если нужен отчёт «по главам» или ссылки на конкретные части, промежуточные резюме полезны сами по себе.

Отправка целиком выигрывает, когда документ помещается в контекст выбранной модели: модель видит связи между разделами (ссылка из приложения на пункт договора, вывод в конце, опирающийся на таблицу в начале), а код проще. У claude-sonnet-5, claude-opus-5-5, gpt-5.6-sol и gpt-5.6-luna контекст 1M токенов.

Сколько токенов в документе

Условно: сотня страниц деловой прозы — это десятки тысяч токенов, а текст на русском обычно занимает больше токенов, чем тот же объём на английском. Для точной цифры по Claude-моделям есть эндпоинт подсчёта токенов — см. документацию count_tokens; для остальных ориентируйтесь на usage в ответе первого запроса.

Код: оба подхода и автоматический выбор

Нужен pip install openai и ключ fetch_... в OPENAI_API_KEY.

bash
import os
from concurrent.futures import ThreadPoolExecutor

from openai import OpenAI

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["OPENAI_API_KEY"],
)

CONTEXT = {
    "glm-5.3-flash": 128_000, "glm-5.3": 128_000,
    "gpt-5.6-luna": 1_000_000, "gpt-5.6-sol": 1_000_000,
    "claude-sonnet-5": 1_000_000, "claude-opus-5-5": 1_000_000,

Что здесь важно:

  • summarize отправляет документ целиком, если он с запасом помещается в контекст модели, и переключается на map-reduce только когда не помещается.
  • Куски режутся по абзацам; абзац длиннее лимита останется одним большим куском — для таких текстов добавьте резку по предложениям.
  • Map-шаг идёт параллельно в 5 потоков. Zukko не ограничивает частоту запросов на своей стороне, но у ключа могут стоять дневной и месячный лимиты в долларах: при их исчерпании придёт 429 с кодом key_daily_limit_reached или key_monthly_limit_reached.
  • Если частей очень много и их резюме сами не помещаются в контекст, делайте reduce в несколько уровней: сначала группами по 10–20, потом итог.

Подробнее о клиенте — в руководстве по Python SDK.

Сравнение стоимости

Все модели в Zukko стоят одинаково за вход и выход, поэтому формула простая: (вход + выход) × цена / 1 000 000. Допущения: инструкция — около 50 токенов, резюме куска — около 400, итоговое резюме — около 1500.

Документ на 50 тыс. токенов, одна и та же модель

Map-reduce с кусками по 4000 токенов — 13 кусков. Вход: 50 000 (документ) + 13 × 50 (инструкции) + около 5 250 (чтение промежуточных резюме на шаге reduce) ≈ 56 000. Выход: 13 × 400 + 1 500 = 6 700. Итого ≈ 62 700 токенов.

Целиком: вход ≈ 50 050, выход 1 500. Итого ≈ 51 550 токенов.

МодельMap-reduceЦеликом
glm-5.3-flash ($0.04)62 700 × 0.04 / 1 000 000 ≈ $0.002551 550 × 0.04 / 1 000 000 ≈ $0.0021
claude-sonnet-5 ($0.3)62 700 × 0.3 / 1 000 000 ≈ $0.01951 550 × 0.3 / 1 000 000 ≈ $0.015

На одной модели map-reduce дороже примерно на 11 тыс. токенов — это плата за нарезку.

Документ на 400 тыс. токенов

В glm-5.3-flash (128K) он целиком не влезет, значит варианты такие: map-reduce на дешёвой модели или целиком в модель с 1M контекста.

  • Map-reduce на glm-5.3-flash: 100 кусков. Вход ≈ 400 000 + 100 × 50 + 40 050 ≈ 445 000, выход ≈ 100 × 400 + 1 500 = 41 500. Итого 486 500 × 0.04 / 1 000 000 ≈ $0.019.
  • Целиком в gpt-5.6-luna: (400 000 + 1 500) × 0.1 / 1 000 000 ≈ $0.040.
  • Целиком в claude-sonnet-5: 401 500 × 0.3 / 1 000 000 ≈ $0.12.
  • Map-reduce на claude-sonnet-5: 486 500 × 0.3 / 1 000 000 ≈ $0.15.

Разница в цене здесь идёт от цены модели, а не от метода. Какой результат вам подходит, проверьте на двух-трёх своих документах: сравните, какие факты, цифры и решения попали в резюме каждого варианта. Актуальный список моделей — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете.

Оговорка про повторные вопросы и кэш

Если вы задаёте по одному большому документу много вопросов, каждый запрос заново отправляет весь документ во входе. У вендоров чтение такого повторяющегося префикса из кэша стоит около 10% входной цены, а в Zukko кэшированные токены оплачиваются по обычной входной цене. Десять вопросов по документу на 400 тыс. токенов в gpt-5.6-luna — это 10 × 400 000 × 0.1 / 1 000 000 = $0.40. Выгоднее один раз сделать подробное резюме и задавать вопросы по нему.

Как выбрать: короткая шпаргалка

СитуацияПодход
Документ помещается в контекст выбранной моделиЦеликом
Документ больше контекста моделиMap-reduce или модель с 1M контекста
Нужно резюме по каждому разделуMap-reduce
Важны связи между далёкими частями текстаЦеликом
Много документов, важна скоростьMap-reduce с параллельными запросами

Чтобы запустить код, создайте ключ в кабинете; порядок подключения описан в быстром старте.

Частые вопросы

Теряет ли map-reduce информацию?

Может: связь, которая проходит через границу кусков, модель на шаге map не видит. Помогают перекрытие кусков (последний абзац предыдущего куска в начале следующего) и просьба отмечать в промежуточных резюме незакрытые ссылки. Для договоров и документов с перекрёстными ссылками надёжнее отправка целиком.

Как обрабатывать PDF и DOCX?

Сначала извлеките текст, затем передайте его в summarize:

bash
import pdfplumber
from docx import Document

with pdfplumber.open("report.pdf") as pdf:
    pdf_text = "\n\n".join(page.extract_text() or "" for page in pdf.pages)

docx_text = "\n\n".join(p.text for p in Document("contract.docx").paragraphs)

Для сканов без текстового слоя нужен OCR — extract_text вернёт пустые строки.

Как получить резюме на нужном языке?

Добавьте в промпт строку вроде «Answer in Russian». Качество на вашем языке и в вашей предметной области проверьте на нескольких документах до запуска на весь архив.

Как управлять длиной резюме?

Задавайте длину в промпте: «не больше 300 слов» или «5 пунктов». Параметр max_tokens — это жёсткий обрез ответа, а не регулятор длины: если лимит мал, резюме оборвётся на полуслове.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.