HiAPI
Блог

Руководство

Как использовать gpt-6.1-terra через HiAPI API: curl, Python и рабочий запрос

Oct 31, 2026
文章封面图:淡紫色与墨黑色两大平面色块以一条细紫色硬边相接,构图平静而对称。

Что на самом деле нужно для одного запроса к gpt-6.1-terra

Каждый вызов gpt-6.1-terra в HiAPI сводится к четырём вещам: URL, заголовок авторизации, идентификатор модели и тело запроса. Если сделать это правильно, всё остальное в статье — лишь обвязка.

Эндпоинт

Все вызовы моделей выполняются через один базовый URL в HiAPI. Переключать хост для каждой модели не нужно — именно название модели в теле запроса определяет маршрутизацию. Направьте клиент на базовый URL HiAPI и сохраните его в переменной, чтобы менять в одном месте.

Заголовок авторизации

Авторизация передаётся как bearer-токен в заголовке Authorization. Этот токен — API-ключ, выданный в вашем аккаунте HiAPI. Никогда не встраивайте его в исходный файл; читайте из переменной окружения (например, HIAPI_API_KEY), чтобы один и тот же код работал локально, в CI и в продакшене.

Идентификатор модели

В теле запроса модель нужно указать явно: "model": "gpt-6.1-terra". Строка чувствительна к регистру и должна совпадать точно. Неверное или обрезанное имя модели — одна из самых частых причин отклонения запроса, поэтому держите его как константу в коде, а не как строку, набранную на скорую руку.

Поля тела запроса

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

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

Рабочий curl-запрос, разобранный построчно

Ниже полный пример вызова — экспортируйте ключ и вставляйте прямо в терминал. Он использует базовый URL HiAPI и минимальное JSON-тело.

curl -sS -X POST "$HIAPI_BASE_URL/v1/chat/completions" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-6.1-terra",
    "messages": [
      {"role": "user", "content": "Кратко опиши круговорот воды в трёх предложениях."}
    ]
  }'

Что делает каждый флаг

-X POST задаёт HTTP-метод. Вызовы моделей — это POST, поскольку они несут тело запроса.

-sS заставляет curl скрывать прогресс, но показывать реальные ошибки. Без -S неудачный запрос может выглядеть просто тишиной.

-H "Authorization: Bearer $HIAPI_API_KEY" добавляет ваши учётные данные. Префикс Bearer и следующий за ним одиночный пробел — часть схемы авторизации; пропуск любого из них приводит к ошибке авторизации, которая выглядит никак не связанной с заголовком.

-H "Content-Type: application/json" сообщает серверу, что тело — JSON. Некоторые шлюзы терпят отсутствие content type, но полагаться на это не стоит.

-d '...' — это тело запроса. Одинарные кавычки не дают shell подставлять значения в JSON. Если хотите использовать файл, замените -d '...' на -d @payload.json и держите JSON в файле, который можно линтить и диффить.

Две привычки, экономящие время

Во-первых, при сбое добавляйте -i, чтобы напечатать строку статуса и заголовки ответа. Ответ с кодом 200, но странным телом читается совсем иначе, чем 401 или 422, и эту разницу хочется видеть сразу. Во-вторых, прогоняйте вывод через JSON-форматтер (curl ... | python -m json.tool), чтобы вложенные поля ответа были читаемы. Оба флага безвредны, и при настройке вызова вы будете пользоваться ими часто.

Перенос вызова в Python

Python-версия должна сохранять точную семантику curl-вызова: тот же URL, те же заголовки, то же тело, тот же разбор. Использование стандартной библиотеки оставляет число зависимостей нулевым и делает перенос очевидным.

import json
import os
import urllib.request

HIAPI_BASE_URL = os.environ["HIAPI_BASE_URL"].rstrip("/")
HIAPI_API_KEY = os.environ["HIAPI_API_KEY"]

def call_terra(prompt: str, model: str = "gpt-6.1-terra") -> dict:
    body = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
    }
    req = urllib.request.Request(
        f"{HIAPI_BASE_URL}/v1/chat/completions",
        data=json.dumps(body).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {HIAPI_API_KEY}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=120) as resp:
        return json.loads(resp.read().decode("utf-8"))

result = call_terra("Кратко опиши круговорот воды в трёх предложениях.")
print(json.dumps(result, indent=2))

Замечания о переносе

Заголовки строятся один раз внутри объекта запроса и соответствуют двум флагам -H. Тело — Python-dict, сериализуемый через json.dumps, что программно эквивалентно curl-телу в одинарных кавычках — и заодно устраняет проблемы с кавычками в Windows shell.

Функция принимает модель как параметр со значением по умолчанию, а не повторяет литерал повсюду. Так у имени модели единственный источник истины, и при этом его можно осознанно переопределить.

Обработка ошибок здесь намеренно опущена, чтобы нормальный путь оставался читаемым. В реальном коде оберните вызов urlopen и перехватывайте HTTP-исключения, читая .read() из объекта ошибки, чтобы увидеть сообщение сервера. Таймаут задан явно; бесконечное блокирование в итоге подвесит worker.

Если вы предпочитаете httpx или requests, соответствие один к одному: requests.post(url, headers=..., json=body, timeout=120). Параметр json= сериализует dict за вас. Семантика не меняется — в этом и суть: curl-вызов — это контракт, а Python лишь его вторая форма представления.

Выбор между одиночным вызовом и асинхронной задачей

Ждать подходит не для каждого запроса. Решение зависит от длительности работы и от того, может ли вызывающая сторона позволить себе блокировку.

Когда подходит одиночный вызов

Короткие интерактивные запросы — суммаризация, классификация, небольшая переработка текста — должны быть обычными синхронными вызовами. Ответ приходит встроенным, вы его разбираете и возвращаете. Это простейший путь в коде и самый простой для рассуждений в тестах. Если пользователь ждёт в UI, синхронность — ожидаемая форма.

Когда стоит рассматривать задачу

Когда работа идёт долго или порождает много артефактов, а обрыв соединения означает дорогостоящую переделку, вызов следует рассматривать как асинхронный. В таких случаях вы отправляете запрос, получаете идентификатор задачи и опрашиваете, пока она не достигнет терминального состояния. Сама отправка возвращается быстро, поэтому ваш worker или HTTP-обработчик не занят весь процесс.

Практическое правило: если вы ловите себя на том, что задаёте аномально длинный клиентский таймаут, чтобы «вызов прошёл», это сигнал перейти к задачам. Таймаут — не механизм планирования.

Сделайте переключение дешёвым

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

Будьте честны со своей нагрузкой, а не угадывайте поведение платформы. Если ваши запросы обычно завершаются за несколько секунд, оставайтесь синхронными и пропустите сложность. Асинхронный путь ценен для действительно медленных запросов, а не для всех.

Опрос завершения без долбёжки API

Цикл опроса имеет три обязанности: ждать достаточно долго, чтобы быть вежливым, в конце концов остановиться и ясно представить терминальное состояние. Вот компактная версия.

import time

def poll_until_done(fetch_status, job_id, interval=3.0, timeout=600.0):
    deadline = time.monotonic() + timeout
    while True:
        status = fetch_status(job_id)
        state = status.get("status")
        if state in ("succeeded", "failed", "cancelled"):
            return status
        if time.monotonic() > deadline:
            raise TimeoutError(f"job {job_id} not finished after {timeout}s")
        time.sleep(interval)

Почему такая структура

fetch_status внедряется, поэтому один и тот же цикл работает и при опросе через HTTP, и при опросе внутренней очереди. Это же делает тривиальным тестирование с фейковым объектом, который сначала дважды возвращает running, а затем succeeded.

Проверка состояния — явный белый список терминальных состояний, а не проверка одной строки "done". Терминальные состояния разные — успех, ошибка, отмена — и вы хотите остановиться на любом из них, а не крутиться до таймаута, потому что искали только одно.

Дедлайн использует time.monotonic(), чтобы корректировки системных часов случайно не удлиняли и не укорачивали ожидание.

Выбор интервала опроса

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

Таймаут не опционален

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

Скачивание и хранение выходных данных

Если ответ содержит ссылки на артефакты — URL, идентификаторы файлов или ключи хранилища — считайте их эфемерными. Они могут истечь, быть ограничены вашим аккаунтом и не пережить повторную попытку. Долговечная копия — та, которую вы сохранили сами.

Схема «получить и сохранить»

Когда задача завершается, прочитайте ссылку на артефакт из терминального состояния, скачайте один раз и запишите в место, которое контролируете: локальный путь для скриптов, бакет объектного хранилища для всего, что близко к продакшену. Записывайте идентификатор задачи вместе с сохранённым файлом, чтобы можно было проследить артефакт до породившего его запроса.

def save_output(fetch_bytes, artifact_url, dest_path):
    data = fetch_bytes(artifact_url)
    with open(dest_path, "wb") as f:
        f.write(data)
    return dest_path

Практики против тихой потери данных

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

Также сохраняйте сырой ответ финального вызова статуса. JSON хранить дёшево, и когда кто-то спросит, почему файл выглядит неправильно, документ состояния с именем модели, отметкой времени и id задачи — самый быстрый способ восстановить ход событий.

Наконец, разделите получение и обработку. Сначала скачайте, сохраните, затем преобразуйте. Если вы парсите по мере скачивания, кратковременная сетевая ошибка выглядит как баг форматирования, и вы потратите полдня на решение не той проблемы.

Отладка типовых сбоев

Большинство сбоев попадает в четыре категории. У каждой есть быстрый изолирующий тест.

Ошибки авторизации

Симптом: 401 или 403 ещё до того, как что-либо в теле запроса станет важным. Проверьте, что ключ действительно загружен — печатайте, задана ли переменная окружения, а не её значение. Проверьте префикс Bearer и пробел после него. Убедитесь, что имя заголовка написано правильно и прокси его не срезает. Если один и тот же ключ работает в curl, но не в вашем приложении, значит приложение строит заголовок иначе; сравните буквальный dict заголовков с флагами curl.

Некорректный формат тела запроса

Симптом: 400 или 422 с сообщением о теле. Валидируйте JSON перед отправкой — json.dumps сериализует почти что угодно, включая dict, который вы не собирались строить. Проверьте висячие запятые, скопированные из конфигурационных файлов, значения, переданные списком вместо строки, и отсутствующие обязательные поля. Сократите до минимального тела из первого раздела и добавляйте поля по одному, пока не сломается.

Неверное имя модели

Симптом: отказ, похожий на ошибку запроса или сообщение о ненайденной модели. Строка модели должна быть ровно gpt-6.1-terra. Частые причины: устаревшее значение по умолчанию в хелпере, переменная окружения с другим значением или опечатка из-за повторного ввода вместо копирования. Держите её как константу модуля и грепайте дубликаты.

Таймаут опроса

Симптом: ваш цикл завершается по таймауту, а задача всё ещё жива. Сначала убедитесь, что опрашиваемый идентификатор задачи — тот самый, что вы отправили; распечатать оба при отправке — простейшая проверка. Затем посмотрите на список терминальных состояний: состояние, которого вы не ожидали, заставит цикл крутиться вечно. Наконец, проверьте, соответствуют ли ваш интервал и дедлайн реальной длительности задачи; дедлайн, настроенный под короткие запросы, всегда сработает на длинных. При таймауте логируйте последнее состояние — так вы узнаете, в каком состоянии была задача, когда вы сдались.

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

Похожие статьи

Все статьи →

Начните работать с постоянными артефактами ИИ

Получите API-ключ и вызывайте модели изображений, видео и аудио через один API на основе tasks.

Читать документацию API