Перевод текстов через API: пайплайн для больших объёмов

Перевести один абзац просто. Перевести десять тысяч строк без двойных списаний и с предсказуемой ценой — отдельная задача.

4 мин · · Обновлено · автоматизация · перевод

Модель под задачу, а не по умолчанию

Перевод — не самая творческая задача, поэтому гнать весь объём через самую дорогую модель бессмысленно. Для прямого перевода технических и маркетинговых текстов стоит начать с дешёвых glm-5.3-flash ($0.04/$0.04 за 1M токенов) и gpt-5.6-luna ($0.10/$0.10). Для художественных текстов или текстов с игрой слов, где важно сохранить тон, попробуйте claude-sonnet-5 ($0.30/$0.30) — проверьте на своих текстах, держит ли он стиль на длинных фрагментах лучше. Не обязательно выбирать одну модель на весь пайплайн: можно прогнать первый проход дешёвой моделью, а сложные абзацы — те, что редактор пометил — пересчитать через claude-opus-5-5.

Оценка стоимости до запуска

Перед тем как гнать десять тысяч строк, полезно посчитать токены бесплатно:

bash
curl https://api.zukko.pro/v1/messages/count_tokens \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"текст для перевода..."}]}'

Запрос ничего не стоит и не идёт к модели, так что можно прогнать через него весь корпус заранее и посчитать примерный бюджет по цене модели из GET /v1/models/info — там же лимиты.

Пакетная обработка без двойного списания

Сетевые обрывы на большом объёме — обычное дело: клиент падает по таймауту, вы не знаете, дошёл ли перевод строки до биллинга, и повторяете запрос. Чтобы не заплатить дважды за одну и ту же строку, добавляйте заголовок Idempotency-Key с уникальным значением на каждую строку (например хэш от текста + номер строки):

bash
import hashlib, requests

def translate_line(text, key):
    idem = hashlib.sha256(f"{key}:{text}".encode()).hexdigest()
    resp = requests.post(
        "https://api.zukko.pro/v1/chat/completions",
        headers={
            "Authorization": "Bearer fetch_ваш_ключ",
            "Idempotency-Key": idem,
        },
        json={
            "model": "glm-5.3-flash",
            "messages": [
                {"role": "system", "content": "Переведи на английский, сохрани форматирование."},
                {"role": "user", "content": text},
            ],
        },
        timeout=30,
    )
    return resp.json()

Повтор запроса с тем же Idempotency-Key и тем же телом в течение 24 часов вернёт тот же ответ без нового списания. А если тело вдруг изменится при том же ключе — это ошибка 422 idempotency_conflict, сразу видно, что где-то разъехалась логика ретраев.

Кэш для повторяющихся фрагментов

В большом корпусе часто попадаются одинаковые куски — шаблонные фразы, повторяющиеся блоки интерфейса, юридические формулировки. Для них имеет смысл включить кэш ответов заголовком X-Zukko-Cache: true (temperature: 0 тоже включает кэш, но только у моделей, которые принимают этот параметр; Claude-модели — claude-sonnet-5, claude-opus-5-5 — могут отклонить temperature, отличную от 1, с ошибкой 400, поэтому им его не передавайте): тогда одинаковый запрос от одного пользователя кэшируется на срок до 7 суток для чата, повтор возвращается мгновенно и бесплатно. Срабатывает это только при полном совпадении тела запроса — тот же фрагмент с тем же системным промптом и параметрами; похожие, но не идентичные куски в кэш не попадают. В ответе это видно по заголовку X-Zukko-Cache: hit — удобно логировать долю кэш-хитов у себя, потому что в /v1/usage такие попадания не пишутся.

Батчинг нескольких строк в один запрос

Слать по одному короткому предложению за запрос дорого по накладным расходам: каждый вызов платит временем на обвязку ответа независимо от длины текста. Практичнее объединять пачку строк — 20-50 в зависимости от длины — в один запрос с нумерованным списком и просить вернуть перевод в том же порядке с теми же номерами. Вызовов на тот же объём текста становится меньше, а Idempotency-Key в этом случае вешается на пачку целиком, а не на каждую строку по отдельности.

Лимиты и что делать при 429

Собственного rate limit у шлюза нет. 429 от Zukko означает лимит расхода конкретного ключа: key_daily_limit_reached (сбрасывается в 00:00 UTC) или key_monthly_limit_reached, если на ключе в кабинете задан дневной или месячный лимит. Прочие 429 при параллельном прогоне приходят от провайдера модели; правильная реакция на них — не долбить запросами дальше, а сделать паузу с экспоненциальной задержкой и уменьшить параллелизм воркеров. Если пайплайн запущен ночью без присмотра, лимит на ключ — это ещё и страховка от того, что баг в цикле retry сожжёт весь баланс за час.

Резервная модель при отказе основной

Если основная модель для перевода недоступна (503 provider_unavailable или 503 model_disabled), скрипт должен уметь переключиться на резервную без остановки всего пайплайна. Обычно это соседняя модель того же уровня: gpt-5.6-luna как запасной вариант для glm-5.3-flash, claude-opus-5-5 как запасной для claude-sonnet-5. Разумно держать список фолбэков в конфиге, а не в коде, чтобы менять порядок без деплоя.

bash
FALLBACK = {
    "glm-5.3-flash": "gpt-5.6-luna",
    "claude-sonnet-5": "claude-opus-5-5",
}

def translate_with_fallback(text, model="glm-5.3-flash"):
    try:
        return translate_line(text, model)
    except requests.HTTPError as e:
        if e.response.status_code == 503 and model in FALLBACK:
            return translate_line(text, FALLBACK[model])
        raise

Проверка качества выборочным обратным переводом

На большом объёме нет смысла вычитывать каждую строку вручную. Практичнее взять случайную выборку — например каждую сотую строку — и прогнать перевод обратно на исходный язык той же моделью. Если обратный перевод сильно расходится по смыслу с оригиналом, строка уходит в очередь на ручную проверку. Это не ловит стилистические огрехи, но надёжно ловит смысловые потери и случаи, когда модель домыслила отсутствующий в оригинале кусок.

Проверка результата и отслеживание расходов

По ходу прогона полезно смотреть заголовок X-Zukko-Cost-Micros в каждом ответе (он есть у обычных, не потоковых ответов) и складывать в лог — так видно среднюю цену строки перевода и можно поймать аномалию, если какая-то модель вдруг начала генерировать втрое больше токенов, чем обычно. Полную историю по завершении (общую на весь аккаунт, все ключи вместе) можно свести через GET /v1/usage с курсорной пагинацией — сверить фактический расход с тем, что насчитал count_tokens заранее.

zukko

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

за Claude Code & ChatGPT

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