← Все материалы

Гайд

startCompletion вернул ID вместо текста. Это ошибка или нормальная работа?

Почему startCompletion возвращает completionId, где приходит ответ модели и как организовать ожидание, результат и ошибку в Чатиуме.

Обновлено

Серверные модули и окно приложения соединены в рабочую цепочку

Вы вызвали startCompletion, дождались await и получили идентификатор вместо текста. Для Чатиума это нормальная работа: метод запускает асинхронную задачу, а ответ приходит позже в зарегистрированный обработчик. Ошибка начинается, когда приложение принимает факт запуска за готовый результат.

В Чатиуме можно описать этот процесс обычным языком: сохранить исходник, показать ожидание и обновить карточку после завершения. Встроенный помощник создаст нужные серверные обработчики и интерфейс состояний.

Промпт

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

Два разных события

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

Приложение хранит собственную запись запуска: кому она принадлежит, какой исходник использован, какая модель выбрана и какое состояние сейчас. Идентификатор комплишена связывает эту запись с событиями платформы.

Минимальный вызов

ctx передаёт серверная среда Чатиума, sourceText — проверенный текст формы. done и failed — заранее зарегистрированные маршруты app.function. В обработчиках проверяется источник вызова: caller.type === 'plugin' && caller.appSlug === 'start'.

TypeScript
import { startCompletion } from '@start/sdk'

const { completionId } = await startCompletion(ctx, {
  model: 'deepseek/deepseek-v4.1-flash-1017',
  system: 'Сократи текст до трёх предложений, сохранив факты.',
  messages: [{ role: 'user', content: [{ type: 'text', text: sourceText }] }],
  onCompletionCompleted: done,
  onCompletionFailed: failed,
})

Эти строки показывают запуск. Они не сохраняют ответ и не рисуют интерфейс. Текст результата нужно извлечь из подходящих блоков сообщений, пришедших в onCompletionCompleted, и записать в связанную карточку. onCompletionFailed обрабатывает ошибку выполнения.

Как связать результат с нужной записью

Кроме completionId, SDK позволяет передать context, который возвращается в колбеках. Там удобно передавать минимальные идентификаторы запуска и версии исходника. Не нужно помещать в него всю базу или данные, которые не требуются обработчику.

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

Состояние Что видит пользователь
Подготовлено Исходник сохранён
Ожидание Задача выполняется
Готово Есть результат конкретного запуска
Ошибка Понятно, что не получилось и доступен следующий шаг

Что происходит при повторной попытке

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

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

Почему await не решает всю асинхронность

await startCompletion(...) ждёт завершения самого вызова запуска, а не превращает SDK в синхронный генератор текста. Если после него сразу показать «готово», пользователь получит ложное состояние. Если пытаться прочитать поле text из возвращённого объекта, его там не окажется.

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

Частые вопросы

Где находится ответ модели?

В данных обработчика onCompletionCompleted, в сообщениях и их блоках содержимого. Не каждый блок обязательно текстовый: формат зависит от задачи и модели.

Нужно ли показывать completionId пользователю?

Обычно достаточно понятного статуса и результата. Идентификатор полезен приложению и диагностике, но не обязан быть главным элементом интерфейса.

Как учитывать стоимость и ошибки?

Для расходов есть отдельное событие onCompletionBilled; оно не заменяет результат. Подробнее — в стоимости генерации и повторных запросах.