Асинхронные задачи

7.1 Создание задачи

Клиент создаёт задачу, отправляя запрос ответа, который хочет выполнить, обёрнутый в конверт задачи. Рекомендуется заголовок Idempotency-Key (раздел 9.2).

POST /lmsp/v1/tasks
Authorization: Bearer <token>
Idempotency-Key: 01J8M7C4Y3N5P8R2T6V9X1Z4BH
{
  "type": "response",
  "input": {
    "model": "qwen3:14b-q4_k_m",
    "input": [
      {
        "type": "message",
        "role": "user",
        "content": [
          {
            "type": "input_text",
            "text": "Write a long report."
          }
        ]
      }
    ]
  },
  "on_restart": "fail"
}
Поле Обязательное Значение
type Да response — единственный тип задачи в версии 1.0.
input Да Запрос ответа, точно такой же, как для POST /lmsp/v1/responses. Его поле stream игнорируется.
on_restart Нет fail (по умолчанию), retry или resume. См. раздел 7.5.

Сервис ДОЛЖЕН сохранить задачу до того, как ответит. Затем он возвращает:

HTTP/1.1 202 Accepted
{
  "id": "task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC",
  "object": "task",
  "type": "response",
  "status": "queued",
  "created_at": 1794200100,
  "updated_at": 1794200100,
  "response_id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW",
  "events_url": "/lmsp/v1/tasks/task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC/events",
  "on_restart": "fail",
  "response": null,
  "error": null
}
Поле задачи Значение
id Идентификатор задачи, например task_....
object Всегда "task".
type response.
status Текущее состояние (раздел 7.2).
created_at, updated_at Время в секундах с начала эпохи Unix.
response_id Идентификатор ответа, который создаёт задача. Присваивается при создании задачи.
events_url Путь к потоку событий. Присутствует, когда возможность event_resume равна true.
on_restart Политика перезапуска задачи.
response Объект ответа, когда status равен succeeded, иначе null.
error Объект проблемы (раздел 9.3), когда status равен failed, иначе null.

GET /lmsp/v1/tasks/{task_id} возвращает объект задачи. Клиенты, которые не используют потоки событий, опрашивают этот эндпоинт. Неизвестный идентификатор даёт 404 с кодом task_not_found. Завершённая задача выглядит так:

{
  "id": "task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC",
  "object": "task",
  "type": "response",
  "status": "succeeded",
  "created_at": 1794200100,
  "updated_at": 1794200162,
  "response_id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW",
  "events_url": "/lmsp/v1/tasks/task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC/events",
  "on_restart": "fail",
  "response": {
    "id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW",
    "object": "response",
    "created_at": 1794200100,
    "status": "completed",
    "model": "qwen3:14b-q4_k_m",
    "output": [
      {
        "id": "msg_01J8M8R6A2C5E8G1J4M7P0S3VY",
        "type": "message",
        "role": "assistant",
        "content": [
          {
            "type": "output_text",
            "text": "Local model services make on-device inference a shared, discoverable utility..."
          }
        ]
      }
    ],
    "usage": {
      "input_tokens": 12,
      "output_tokens": 1480,
      "total_tokens": 1492
    },
    "error": null,
    "incomplete_details": null
  },
  "error": null
}

7.2 Конечный автомат

queued --> loading --> running --> succeeded
  |           |           |
  |           |           +--> failed
  |           |           +--> cancelling --> cancelled
  |           +--------------> failed
  +--------------------------> cancelled
Состояние Значение Следующие состояния
queued Принята и сохранена, ожидает запуска. loading, cancelled, failed
loading Модель загружается. running, cancelling, failed
running Модель генерирует. succeeded, failed, cancelling
cancelling Отмена запрошена, но бэкенд ещё не остановился. cancelled
succeeded Конечное. Ответ готов. нет
failed Конечное. нет
cancelled Конечное. нет

Конечные состояния — succeeded, failed и cancelled. Сервис НЕ ДОЛЖЕН переводить задачу из конечного состояния в другое. Задача, ответ которой заканчивается статусом incomplete, например по лимиту токенов, считается succeeded.

Каждый переход атомарен, поэтому задача никогда не бывает одновременно отменённой и завершённой. Побеждает тот переход, который зафиксирован первым.

7.3 Отмена

POST /lmsp/v1/tasks/{task_id}/cancel

Сервис возвращает 200 OK и текущий объект задачи. Задача queued сразу становится cancelled. Задача loading или running становится cancelling, пока бэкенд не остановится, а затем cancelled.

Сервис ДОЛЖЕН обеспечивать идемпотентность отмены. Отмена задачи, которая уже cancelling или cancelled, возвращает текущую задачу и не порождает новой ошибки.

Отмена задачи, которая уже succeeded или failed, получает ответ 409 с кодом task_not_cancelable. Неизвестный идентификатор даёт 404 с кодом task_not_found.

7.4 Потоки событий

GET /lmsp/v1/tasks/{task_id}/events
Accept: text/event-stream

Этот эндпоинт необязателен. Он доступен, когда возможность event_resume равна true.

  • События — это события ответа из раздела 6.3 для ответа, который создаёт задача. Нумерация и возобновление следуют разделу 6.4.
  • Поток начинается с response.created, которое записывается при приёме задачи. Он заканчивается одним последним событием, соответствующим исходу: response.completed, response.incomplete, response.failed или response.cancelled.
  • Клиент, подключившийся поздно, сначала получает пропущенные события, затем живые. После последнего события сервис закрывает поток.
  • События хранятся в течение срока хранения (раздел 11.2). Более старые события дают 410 Gone с event_history_expired.

7.5 Поведение при перезапуске

on_restart определяет, что происходит с незавершённой задачей при перезапуске сервиса:

Значение Поведение
fail По умолчанию. Сервис помечает незавершённую задачу как failed с кодом ошибки server_restarted.
retry Сервис запускает задачу заново с начала.
resume Сервис продолжает со снимка состояния. Это допускается, только если бэкенд явно поддерживает возобновляемые снимки. В противном случае запрос на создание отклоняется с 400 и кодом unsupported_capability.

Задача, которая находится в состоянии cancelling при перезапуске сервиса, завершает отмену и становится cancelled независимо от своей политики.

Значение retry не рекомендуется использовать по умолчанию, потому что задача могла уже запустить инструменты или вызвать внешние побочные эффекты. Клиент, выбравший retry, ДОЛЖЕН сочетать его с Idempotency-Key и сделать вызовы своих инструментов идемпотентными.