# Работа с PDF

> Отправляйте PDF-документы любой модели на RouterAI.

RouterAI поддерживает обработку PDF через API `/api/v1/chat/completions`. PDF можно отправлять как **прямые URL** или **данные в формате base64** в массиве сообщений через тип контента file. Эта функция работает с **любой** моделью на RouterAI.

**Поддержка URL**: Отправляйте публично доступные PDF напрямую без скачивания или кодирования
**Поддержка Base64**: Требуется для локальных файлов или приватных документов, которые недоступны публично

PDF также работают в чате для интерактивного тестирования.

<Info>
  Когда модель изначально поддерживает ввод файлов, PDF передается напрямую в
  модель. Когда модель не поддерживает ввод файлов изначально, RouterAI
  разберет файл и передаст результаты разбора в запрошенную модель.
</Info>

<Tip>
  Вы можете отправлять как PDF, так и другие типы файлов в одном запросе.
</Tip>

## Конфигурация плагина

Для настройки обработки PDF используйте параметр `plugins` в вашем запросе. RouterAI предоставляет несколько движков обработки PDF с различными возможностями и ценами:

```json
{
  "plugins": [
    {
      "id": "file-parser",
      "pdf": {
        "engine": "cloudflare-ai"
      }
    }
  ]
}
```

Доступные движки:

- `cloudflare-ai` — для текстовых PDF (бесплатно)
- `mistral-ocr` — для отсканированных документов и PDF с изображениями
- `native` — для моделей с нативной поддержкой файлов

<Info>
  Значение `"pdf-text"` устарело и автоматически интерпретируется как
  `"cloudflare-ai"`. Существующие запросы с `"pdf-text"` продолжат работать.
</Info>

## Цены

RouterAI предоставляет несколько движков обработки PDF:

1. `mistral-ocr`: лучше всего подходит для отсканированных документов или PDF с изображениями (~450 ₽ за 1 000 страниц).
2. `cloudflare-ai`: конвертирует PDF в markdown через Cloudflare Workers AI (бесплатно, без изображений).
3. `native`: доступен только для моделей, которые поддерживают ввод файлов нативно (оплачивается как входные токены).

Если вы явно не указываете движок, RouterAI по умолчанию сначала использует нативные возможности обработки файлов модели, а если они недоступны — `cloudflare-ai`.

## Ограничения OCR на изображения

Когда движок `mistral-ocr` извлекает изображения из PDF, RouterAI запрашивает у Mistral не более **8 изображений на PDF** и передаёт не более 8 изображений в нижестоящую модель. Избыточные изображения отбрасываются, при этом весь распознанный текст сохраняется в полном объёме.

Это ограничение существует потому, что лимиты на количество изображений в одном запросе значительно различаются у разных провайдеров — некоторые отклоняют запросы с более чем 8 изображениями, а длинные PDF, которые выдают по одному изображению на страницу, часто упираются в лимит контекста. Ограничение в 8 изображений удерживает запросы в рамках лимитов всех поддерживаемых провайдеров.

Если ваша нижестоящая модель вообще не принимает изображения на вход, OCR-извлечённые изображения полностью отбрасываются, а в модель передаётся только распознанный текст.

## Использование URL PDF

Для публично доступных PDF вы можете отправить URL напрямую без необходимости скачивания и кодирования файла:

```bash
curl https://routerai.ru/api/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ROUTERAI_API_KEY" \
  -d '{
    "model": "anthropic/claude-sonnet-4",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "Каковы основные моменты в этом документе?"
          },
          {
            "type": "file",
            "file": {
              "filename": "document.pdf",
              "file_data": "https://bitcoin.org/bitcoin.pdf"
            }
          }
        ]
      }
    ],
    "plugins": [
      {
        "id": "file-parser",
        "pdf": {
          "engine": "mistral-ocr"
        }
      }
    ]
  }'
```

<Info>
  URL PDF работают со всеми движками обработки. Для Mistral OCR URL передается напрямую в сервис. Для других движков RouterAI загружает PDF и обрабатывает его внутренне.
</Info>

## Использование PDF в формате Base64

Для локальных PDF-файлов или когда вам нужно отправить содержимое PDF напрямую, вы можете закодировать файл в base64:

### Подготовка base64 в Linux

```bash
# Кодируем PDF в base64 без переносов строк и сохраняем в файл
base64 -w 0 path/to/your/document.pdf > document.b64
```

<Warning>
  Не подставляйте base64 PDF прямо в `curl -d '{...}'` через переменную
  (`-d '{"file_data":"'"$DATA_URL"'"}'`). У реальных PDF base64-строка занимает
  сотни килобайт и упирается в лимит длины аргументов командной строки
  (`ARG_MAX`), из-за чего bash выдаёт `/usr/bin/curl: Слишком длинный список
  аргументов`. Вместо этого собирайте тело запроса через `jq` (читает base64
  из файла, а не из аргумента) и передавайте JSON в curl через stdin
  (`--data-binary @-`). Примеры ниже используют именно этот подход — он работает
  с PDF любого размера. Для них нужна утилита `jq`.
</Warning>

### Пример запроса с base64

```bash
# Кодируем PDF в файл (см. выше)
base64 -w 0 path/to/your/document.pdf > document.b64

# Собираем тело запроса через jq и передаём в curl через stdin.
# --rawfile читает base64 из файла, поэтому ARG_MAX не превышается.
jq -n --rawfile pdf document.b64 '{
  model: "google/gemma-3-27b-it",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "Каковы основные моменты в этом документе?" },
        {
          type: "file",
          file: {
            filename: "document.pdf",
            file_data: ("data:application/pdf;base64," + ($pdf | gsub("\\s"; "")))
          }
        }
      ]
    }
  ],
  plugins: [
    { id: "file-parser", pdf: { engine: "cloudflare-ai" } }
  ]
}' | curl https://routerai.ru/api/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ROUTERAI_API_KEY" \
  --data-binary @-
```

<Info>
  Разбор PDF будет работать, даже если плагин не указан явно. По умолчанию используется движок, оптимальный для выбранной модели.
</Info>

## Пропуск затрат на разбор

Когда вы отправляете PDF в API, ответ может включать аннотации файлов в сообщении ассистента. Эти аннотации содержат структурированную информацию о разобранном PDF-документе. Отправляя эти аннотации обратно в последующих запросах, вы можете избежать повторного разбора того же PDF-документа, что экономит время обработки и затраты.

Вот как повторно использовать аннотации файлов:

### Первый запрос с PDF

```bash
# Кодируем PDF в файл
base64 -w 0 path/to/your/document.pdf > document.b64

# Первый запрос (тело собираем через jq, отправляем через stdin)
RESPONSE=$(jq -n --rawfile pdf document.b64 '{
  model: "google/gemma-3-27b-it",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "Каковы основные моменты в этом документе?" },
        {
          type: "file",
          file: {
            filename: "document.pdf",
            file_data: ("data:application/pdf;base64," + ($pdf | gsub("\\s"; "")))
          }
        }
      ]
    }
  ]
}' | curl -s https://routerai.ru/api/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ROUTERAI_API_KEY" \
  --data-binary @-)

# Сохраняем аннотации и ответ ассистента в файлы (используем их в следующем запросе)
echo "$RESPONSE" | jq '.choices[0].message.annotations' > annotations.json
echo "$RESPONSE" | jq -r '.choices[0].message.content'     > assistant.txt
```

### Последующий запрос с аннотациями

```bash
# Последующий запрос. PDF отправляется повторно ВМЕСТЕ с аннотациями: RouterAI
# сверяет SHA256 присланных байт с hash в аннотации и переиспользует уже
# разобранное содержимое, не вызывая движок разбора заново.
jq -n \
  --rawfile pdf document.b64 \
  --rawfile assistant assistant.txt \
  --slurpfile annotations annotations.json '{
  model: "google/gemma-3-27b-it",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "Каковы основные моменты в этом документе?" },
        {
          type: "file",
          file: {
            filename: "document.pdf",
            file_data: ("data:application/pdf;base64," + ($pdf | gsub("\\s"; "")))
          }
        }
      ]
    },
    {
      role: "assistant",
      content: ($assistant | rtrimstr("\n")),
      annotations: $annotations[0]
    },
    {
      role: "user",
      content: "Можете ли вы подробнее рассказать о втором пункте?"
    }
  ]
}' | curl https://routerai.ru/api/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ROUTERAI_API_KEY" \
  --data-binary @-
```

<Info>
  Когда вы включаете аннотации файлов из предыдущего ответа в последующие запросы, RouterAI будет использовать эту предварительно разобранную информацию вместо повторного разбора PDF, что экономит время обработки и затраты. Это особенно полезно для больших документов или при использовании движка `mistral-ocr`, который влечет дополнительные расходы.
</Info>

## Схема file-аннотаций

Когда RouterAI разбирает PDF, ответ включает file-аннотации в сообщении ассистента. JSON Schema:

```json
{
  "$schema": "https://json-schema.org/draft-07/schema",
  "$defs": {
    "FileAnnotation": {
      "type": "object",
      "required": ["type", "file"],
      "properties": {
        "type": { "const": "file" },
        "file": {
          "type": "object",
          "required": ["hash", "content"],
          "properties": {
            "hash":    { "type": "string", "description": "Уникальный SHA256 hash разобранного файла" },
            "name":    { "type": "string", "description": "Исходное имя файла (опционально)" },
            "content": {
              "type": "array",
              "items": { "$ref": "#/$defs/ContentPart" }
            }
          }
        }
      }
    },
    "ContentPart": {
      "oneOf": [
        {
          "type": "object",
          "required": ["type", "text"],
          "properties": {
            "type": { "const": "text" },
            "text": { "type": "string" }
          }
        },
        {
          "type": "object",
          "required": ["type", "image_url"],
          "properties": {
            "type": { "const": "image_url" },
            "image_url": {
              "type": "object",
              "required": ["url"],
              "properties": {
                "url": {
                  "type": "string",
                  "description": "Data URL вида data:image/png;base64,..."
                }
              }
            }
          }
        }
      ]
    }
  }
}
```

Массив `content` содержит распознанное содержимое PDF — текстовые блоки и изображения (как base64 data URL). Поле `hash` уникально идентифицирует разобранный файл и используется для пропуска повторного разбора, если вы включаете аннотацию в последующие запросы.

## Формат ответа

API вернет ответ в следующем формате:

```json
{
  "id": "gen-1234567890",
  "provider": "DeepInfra",
  "model": "google/gemma-3-27b-it",
  "object": "chat.completion",
  "created": 1234567890,
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "Документ обсуждает...",
        "annotations": [
          {
            "type": "file",
            "file": {
              "hash": "abc123...",
              "name": "document.pdf",
              "content": [
                { "type": "text", "text": "Распознанное содержимое..." },
                { "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }
              ]
            }
          }
        ]
      }
    }
  ],
  "usage": {
    "prompt_tokens": 1000,
    "completion_tokens": 100,
    "total_tokens": 1100
  }
}
```

## Ошибки с разобранными аннотациями

Если RouterAI успешно разобрал PDF, но провайдер инференса затем вернул ошибку, аннотации всё равно возвращаются в поле `error.metadata.file_annotations` ответа с ошибкой. Структура совпадает с `FileAnnotation` из success-ответа, поэтому вы можете передать тот же массив обратно в RouterAI при повторной попытке и пропустить повторный разбор.

Это применимо к движкам `mistral-ocr` и `cloudflare-ai`, которые разбирают PDF до отправки в модель. Движок `native` не выдаёт аннотации, поскольку файл передаётся напрямую в модель.

```json
{
  "error": "Provider returned an error",
  "metadata": {
    "file_annotations": [
      {
        "type": "file",
        "file": {
          "hash": "abc123...",
          "name": "document.pdf",
          "content": [
            { "type": "text", "text": "Распознанное содержимое..." }
          ]
        }
      }
    ]
  }
}
```