API

PDF

PDF уходит в chat/completions как type file: публичный URL или data-URI с base64. Работает с любой моделью — либо нативно, либо через разбор файла.

POST https://api.aitunnel.ru/v1/chat/completions, в messages — массив частей. В file_data — публичный URL или data:application/pdf;base64,....

Нативно или через разбор

Если модель умеет файлы нативно (file в modalities.input), PDF передаётся ей напрямую. Иначе AITUNNEL разбирает файл и отдаёт модели текст (и при OCR — картинки).

Несколько PDF — отдельные элементы content. Сколько штук примет запрос, зависит от провайдера и модели. Текст лучше ставить первым, затем файлы. Если PDF должен идти первым — положите его в системный промпт. В одном запросе можно смешать PDF и картинки.

Плагин не обязателен

Разбор сработает и без plugins. Движок по умолчанию — нативный, если модель его умеет, иначе mistral-ocr.

URL

Для публично доступных PDF достаточно ссылки, без загрузки и кодирования.

curl https://api.aitunnel.ru/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -d '{
    "model": "gpt-5.4",
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "text", "text": "Какие основные моменты в этом документе?"},
          {
            "type": "file",
            "file": {
              "filename": "document.pdf",
              "file_data": "https://bitcoin.org/bitcoin.pdf"
            }
          }
        ]
      }
    ]
  }'

Base64

Локальный или закрытый файл — data-URI.

# file_data — data-URI: data:application/pdf;base64,<...>
curl https://api.aitunnel.ru/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -d '{
    "model": "gpt-5.4",
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "text", "text": "Какие основные моменты в этом документе?"},
          {
            "type": "file",
            "file": {
              "filename": "document.pdf",
              "file_data": "data:application/pdf;base64,JVBERi0..."
            }
          }
        ]
      }
    ]
  }'

Движок разбора

Движок задаётся плагином file-parser в plugins:

JSON
{
  "plugins": [
    {
      "id": "file-parser",
      "pdf": {
        "engine": "cloudflare-ai"
      }
    }
  ]
}
curl https://api.aitunnel.ru/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -d '{
    "model": "gpt-5.4",
    "plugins": [
      {
        "id": "file-parser",
        "pdf": { "engine": "mistral-ocr" }
      }
    ],
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "text", "text": "Какие основные моменты в этом документе?"},
          {
            "type": "file",
            "file": {
              "filename": "document.pdf",
              "file_data": "https://bitcoin.org/bitcoin.pdf"
            }
          }
        ]
      }
    ]
  }'

Официальные SDK не знают про plugins

Поле plugins не входит в стандарт OpenAI: в Python передавайте через extra_body, в TypeScript — с @ts-expect-error. На cURL и любых «сырых» HTTP-клиентах ограничений нет.

Сжатие слишком длинного промпта — другой плагин, оптимизация сообщений.

ДвижокКогдаОплата
nativeМодель принимает файлы сама (file во входе каталога)Как обычные токены входа
mistral-ocrСканы, PDF с картинкамиOCR плюс токены модели — всё в usage.cost_rub
cloudflare-aiТекст в PDF, нужен markdownРазбор бесплатный, токены модели — как обычно

Если движок не указать: сначала нативный разбор модели, если его нет — mistral-ocr. У gpt-5.4 во входе есть file, поэтому без плагина пойдёт native. Явно задайте mistral-ocr или cloudflare-ai, если нужен другой движок.

На прямых маршрутах отдельных провайдеров plugins не передаются — остаётся только нативная поддержка файлов у модели.

Какие модели принимают файлы сами — группа chat, поле modalities.input:

cURL
curl https://api.aitunnel.ru/public/aitunnel/models/chat

Картинки из OCR

У mistral-ocr из PDF в модель уходит не больше 8 изображений. Лишние отбрасываются, текст сохраняется целиком. Лимит нужен, потому что у провайдеров разные потолки на число картинок в одном запросе: часть сразу отвечает ошибкой, часть упирается в контекст, если с каждой страницы идёт картинка.

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

Не разбирать PDF повторно

В ответе ассистента могут быть annotations — разобранное содержимое файла. Если отдать их обратно в следующем запросе (вместе с тем же file), PDF не разбирают заново: быстрее и без повторной оплаты OCR.

Python
import base64
import requests

def data_url(path):
    with open(path, "rb") as f:
        return "data:application/pdf;base64," + base64.b64encode(f.read()).decode()

url = "https://api.aitunnel.ru/v1/chat/completions"
headers = {
    "Authorization": "Bearer sk-aitunnel-xxx",
    "Content-Type": "application/json",
}
file_part = {
    "type": "file",
    "file": {"filename": "document.pdf", "file_data": data_url("document.pdf")},
}

first = requests.post(
    url,
    headers=headers,
    json={
        "model": "gpt-5.4",
        "messages": [
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": "Какие основные моменты в этом документе?"},
                    file_part,
                ],
            }
        ],
    },
).json()

msg = first["choices"][0]["message"]
annotations = msg.get("annotations")

follow = requests.post(
    url,
    headers=headers,
    json={
        "model": "gpt-5.4",
        "messages": [
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": "Какие основные моменты в этом документе?"},
                    file_part,
                ],
            },
            {
                "role": "assistant",
                "content": msg["content"],
                "annotations": annotations,
            },
            {"role": "user", "content": "Разверни второй пункт"},
        ],
    },
)
print(follow.json()["choices"][0]["message"]["content"])

Схема аннотации:

TypeScript
type FileAnnotation = {
  type: "file";
  file: {
    hash: string; // идентификатор разобранного файла
    name?: string; // исходное имя
    content: ContentPart[]; // текст и картинки из PDF
  };
};

type ContentPart =
  | { type: "text"; text: string }
  | { type: "image_url"; image_url: { url: string } };

hash стабилен для одного и того же разобранного файла. По нему можно дедуплицировать аннотации из успешного ответа и из ошибки.

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

JSON
{
  "id": "gen-1234567890",
  "model": "gpt-5.4",
  "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,
    "cost_rub": 1.2,
    "balance": 950.5
  }
}

annotations есть, когда PDF разбирали движком mistral-ocr или cloudflare-ai. У native файла в ответе нет: его видела сама модель. В usage — токены, cost_rub и balance после списания.

Ошибки после разбора

Если PDF уже разобрали, но ни один провайдер не смог сгенерировать ответ, в ошибке могут лежать те же аннотации: error.metadata.file_annotations. Их можно сразу отдать в повторном запросе, чтобы не платить за разбор снова. Для native аннотаций нет — файл уходил в модель как есть.

JSON
{
  "error": {
    "code": 502,
    "message": "Провайдер вернул ошибку",
    "metadata": {
      "file_annotations": [
        {
          "type": "file",
          "file": {
            "hash": "abc123...",
            "name": "document.pdf",
            "content": [{ "type": "text", "text": "Разобранный текст..." }]
          }
        }
      ]
    }
  }
}

Коды ошибок — ошибки и отладка.