Генерация ответа
Отправьте новое сообщение пользователя и отобразите блоки response.contents. Для пошагового запуска используйте API-интеграцию; для параметров доступа и идентификаторов — обзор API.
POST /shopping/generate
POST https://shopping-assistant-api.gravityfield.ai/shopping/generate
Content-Type: application/json
Endpoint принимает новое сообщение пользователя и возвращает текстовые блоки, товарные карточки, кнопки действий и быстрые ответы. Готовый UI чата API не возвращает.
Для всех сообщений одного диалога используйте одинаковые threadId и resourceId. В resourceId передавайте user.uid Gravity Field или значение user.custom, если ваша интеграция использует внешний ID. Для гостевой сессии зарегистрируйте тот же resourceId и добавляйте заголовок Authorization: Bearer gst_..., включая debug-режим. Получение токена и общие ошибки описаны на странице Гостевые токены. Примеры ниже без заголовка авторизации относятся к прежнему режиму.
Историю диалога повторно отправлять не нужно: Shopping Assistant сохраняет контекст по этим идентификаторам.
В гостевом режиме диалог и память ассистента принадлежат текущей сессии. После новой регистрации с тем же resourceId используйте новый threadId: токен другой сессии не откроет прежний разговор. Порядок действий описан в разделе восстановления сессии.
Для обычной интеграции используйте роль user.
Контракт запроса
type ShoppingTextContentPart = {
type: "text";
text: string;
};
type ShoppingImageContentPart = {
type: "image";
source:
| {
type: "url";
url: string;
}
| {
type: "base64";
mediaType: "image/jpeg" | "image/png" | "image/webp";
data: string;
};
};
type ShoppingGenerateRequest = {
threadId: string;
resourceId: string;
trafficType?: "real" | "test" | "debug";
messages: Array<{
role: "user";
content:
| string
| Array<ShoppingTextContentPart | ShoppingImageContentPart>;
}>;
runtimeContext: {
tenant_id: string;
ctx?: {
type?: string;
data?: unknown;
lng?: string | null;
location?: string | null;
attributes?: {
storeId?: string;
[key: string]: unknown;
};
};
};
};
Поля запроса
Как передать изображение
Чтобы отправить изображение, передайте в content массив блоков. В одном пользовательском сообщении поддерживается только одно изображение. Вместе с ним можно передать текстовый блок с пояснением:
{
"threadId": "thread-8f2b1c",
"resourceId": "demo-user",
"trafficType": "test",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Найдите похожий диван в более светлом цвете"
},
{
"type": "image",
"source": {
"type": "base64",
"mediaType": "image/jpeg",
"data": "/9j/4AAQSkZJRgABAQ..."
}
}
]
}
],
"runtimeContext": {
"tenant_id": "demo-section"
}
}
В поле data передавайте только Base64-содержимое файла, без префикса data:image/jpeg;base64,. Значение mediaType должно соответствовать фактическому формату файла.
Изображение также можно передать по URL. Замените блок image в примере выше:
{
"type": "image",
"source": {
"type": "url",
"url": "https://cdn.example.com/user-photo.webp"
}
}
Текст необязателен: для запроса только по изображению передайте в content массив с одним блоком image.
Ограничения для изображения
После проверки Shopping Assistant учитывает EXIF-ориентацию, уменьшает изображение до размера не более 2048 × 2048 пикселей без увеличения и преобразует его в JPEG для обработки.
Как заполнять ctx
runtimeContext.ctx использует модель контекста API V2. Передавайте актуальный контекст, согласованный с /visit и /choose. Поддерживаемые типы и формат data описаны в разделе Контекст API V2.
Если чат открыт на карточке товара, передайте SKU товара в контексте:
{
"runtimeContext": {
"tenant_id": "demo-section",
"ctx": {
"type": "PRODUCT",
"data": ["1066109"]
}
}
}
Если покупатель выбрал магазин, передавайте его storeId в runtimeContext.ctx.attributes.storeId вместе с каждым запросом /shopping/generate, в том числе после перехода на другую страницу:
{
"runtimeContext": {
"tenant_id": "demo-section",
"ctx": {
"type": "PRODUCT",
"data": ["1066109"],
"attributes": {
"storeId": "kzn-014"
}
}
}
}
Используйте точный storeId из справочника магазинов. Если магазин не выбран, не передавайте поле с пустой строкой; ассистент уточнит точку по сообщению пользователя или задаст вопрос. ctx.lng обозначает зону обслуживания для фида и поиска и не заменяет storeId. Передача storeId сама по себе не включает проверку наличия: для неё требуется подключить API наличия товаров в магазинах и согласовать обработку этого поля в коннекторе.
Режим отладки
Используйте trafficType: "debug", чтобы проверить вызов endpoint-а и отображение ответа в клиентском интерфейсе без обращения к LLM и расхода токенов. В этом режиме Shopping Assistant возвращает одну и ту же заглушку независимо от текста сообщения и настроек секции.
Запрос по-прежнему должен соответствовать контракту: передайте непустые threadId, resourceId, runtimeContext.tenant_id и хотя бы одно сообщение пользователя.
curl --request POST \
--url 'https://shopping-assistant-api.gravityfield.ai/shopping/generate' \
--header 'Content-Type: application/json' \
--data '{
"threadId": "debug-thread-1",
"resourceId": "debug-user-1",
"trafficType": "debug",
"messages": [
{
"role": "user",
"content": "Проверка интеграции"
}
],
"runtimeContext": {
"tenant_id": "demo-section"
}
}'
Ответ содержит текст, два тестовых товара, три быстрых ответа и тестовое tracking-событие:
{
"threadId": "debug-thread-1",
"resourceId": "debug-user-1",
"assistantMessageId": "shopping:assistant:8a21...",
"context": null,
"response": {
"contents": [
{
"type": "message",
"value": "Debug response"
},
{
"type": "products",
"title": "Debug products",
"shelf_id": "debug-products",
"source_query": "debug",
"value": [
{
"sku": "debug-product-1",
"name": "Debug product 1",
"brand": "Debug brand",
"price": 100
},
{
"sku": "debug-product-2",
"name": "Debug product 2",
"brand": "Debug brand",
"price": 200
}
]
}
],
"ui": {
"suggests": [
"Debug suggestion 1",
"Debug suggestion 2",
"Debug suggestion 3"
]
}
},
"responseLatencyMs": 100,
"events": [
{
"type": "visible_impression",
"url": "https://runtime.test/widget"
}
]
}
Значения responseLatencyMs и events в этом ответе являются частью заглушки: они не отражают фактическую задержку и не фиксируют аналитику. Debug-запрос не запускает генерацию и обработку изображений, не сохраняет сообщения и не появляется в истории. Не отправляйте оценку такого ответа через /shopping/feedback.
После проверки интеграции замените debug на test или real.
Минимальный пример
Запрос
curl --request POST \
--url 'https://shopping-assistant-api.gravityfield.ai/shopping/generate' \
--header 'Content-Type: application/json' \
--data '{
"threadId": "thread-8f2b1c",
"resourceId": "demo-user",
"trafficType": "test",
"messages": [
{
"role": "user",
"content": "Помогите выбрать ноутбук для учёбы"
}
],
"runtimeContext": {
"tenant_id": "demo-section"
}
}'
Ответ
{
"threadId": "thread-8f2b1c",
"resourceId": "demo-user",
"assistantMessageId": "shopping:assistant:8a21...",
"context": {
"intent": "product-selection",
"scenario": { "id": "laptop-selection", "version": 1 },
"mode": "clarify"
},
"response": {
"contents": [
{
"type": "message",
"value": "Какой у вас бюджет и нужна ли большая диагональ экрана?"
}
],
"ui": {
"suggests": [
"До 60 000 ₽, компактный",
"До 80 000 ₽, большой экран",
"Помогите определиться с бюджетом"
]
}
}
}
Пример ответа с товарами
ID и версия сценария в примерах условные: фактические значения зависят от опубликованных плейбуков секции и выбранного сценария текущего ответа.
{
"threadId": "thread-8f2b1c",
"resourceId": "demo-user",
"context": {
"intent": "product-selection",
"scenario": { "id": "laptop-selection", "version": 1 },
"mode": "answer"
},
"response": {
"contents": [
{
"type": "message",
"value": "Вот два варианта для учёбы: компактный ноутбук и модель с большим экраном."
},
{
"type": "products",
"title": "Ноутбуки для учёбы",
"categories": [
"Электроника",
"Ноутбуки"
],
"value": [
{
"sku": "1066109",
"name": "Ноутбук Demo 14",
"price": 59990,
"features": "14 дюймов; 16 ГБ RAM; SSD 512 ГБ",
"is_stm": false,
"brand": "Demo Brand",
"url": "https://example.com/product/1066109",
"image_url": "https://example.com/product/1066109.jpg",
"events": [
{
"type": "visible_impression",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?...",
"https://evs-02.gravityfield.ai/v2/engagement?..."
]
},
{
"type": "click",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?..."
]
}
]
},
{
"sku": "1023814",
"name": "Ноутбук Demo 16",
"price": 74990,
"features": "16 дюймов; 16 ГБ RAM; SSD 512 ГБ",
"is_stm": true
}
]
},
{
"type": "message",
"value": "Первый вариант удобнее носить на занятия. Второй подойдёт, если важнее большой экран."
}
],
"ui": {
"suggests": [
"Показать варианты дешевле",
"Нужен ноутбук легче",
"Сравнить эти модели"
]
}
},
"events": [
{
"type": "WRIMP",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?...",
"https://evs-02.gravityfield.ai/v2/engagement?..."
]
}
]
}
Контракт ответа
type ShoppingResponseContext = {
intent: string;
scenario: { id: string; version: number } | null;
mode: "answer" | "clarify" | "handoff" | "refuse";
};
type ShoppingGenerateResponse = {
threadId: string;
resourceId: string;
assistantMessageId?: string;
messageFormat?: "plain_text" | "markdown";
context: ShoppingResponseContext | null;
response: {
contents: Array<
| { type: "message"; value: string }
| { type: "products"; value: Product[]; title?: string; categories?: string[]; shelf_id?: string; source_query?: string }
| { type: "button"; value: string; action: string; url?: string }
>;
ui?: {
suggests: string[];
};
};
responseLatencyMs?: number;
events?: EngagementEvent[];
};
type Product = {
sku?: string;
category_id?: string;
name: string;
price?: number | null;
features?: string;
is_stm?: boolean;
brand?: string;
url?: string;
image_url?: string;
events?: EngagementEvent[];
};
type EngagementEvent =
| { type: string; urls: string[] }
| { type: string; url: string };
Поле assistantMessageId содержит ID созданного ответа ассистента. Сохраните его вместе с сообщением в UI: этот ID нужен для отправки оценки через /shopping/feedback.
messageFormat задаёт формат текста. Для plain_text отображайте текст без интерпретации HTML. Для markdown используйте Markdown-рендерер с безопасной обработкой ссылок и HTML; если поле отсутствует, уточните формат подключённой секции. responseLatencyMs — длительность ответа сервиса, а не время отображения на устройстве покупателя.
Кнопка перехода к оператору приходит в массиве response.contents в следующем формате:
{
"type": "button",
"value": "Позвать оператора",
"action": "open_support_chat"
}
Поле value содержит текст кнопки, а action: "open_support_chat" обозначает переход к оператору. Shopping Assistant API только возвращает идентификатор действия. Сам переход выполняется клиентским каналом после нажатия кнопки.
Поле title в блоке products содержит готовый заголовок для этой группы рекомендаций. Если поле пришло, отобразите его над товарными карточками. Если title отсутствует, блок можно отобразить без заголовка.
Поле categories в блоке products содержит путь категории товарного блока: от общей категории к более конкретной. Например, ["Диваны и кресла", "Диваны"]. Поле опционально и появляется в ответе после согласования с командой сопровождения Gravity Field.
Для товарных карточек считайте sku основным идентификатором товара. Поля price, brand, url, image_url и другие данные могут использоваться для быстрого отображения карточки, но актуализация цены, изображения, наличия, открытие PDP и добавление в корзину остаются на стороне клиентского канала или клиентского каталога.
category_id — необязательный идентификатор категории товара. Поле categories у блока products содержит путь категории и не заменяет этот идентификатор. Для кнопки с action: "follow_url" поле url содержит HTTP(S)-адрес перехода; open_support_chat передаётся обработчику действий приложения.
Контекст сценария и интерфейс
Поле context находится на верхнем уровне рядом с response. Оно описывает решение для текущего ответа и позволяет адаптировать собственный UI под задачу покупателя. Например, для сценария подбора ноутбука можно показывать элементы интерфейса для выбора диагонали и бюджета. Такие элементы реализует клиентское приложение.
ID и текущую версию активного сценария можно посмотреть в личном кабинете: Настройки ассистента → Сценарии. Рядом есть кнопка копирования ID и справка Использование в интерфейсе. ID и версия также показаны в сохранённом плейбуке. Названия сценариев и компоненты вашего UI задаются на стороне приложения.
При обработке ответа соблюдайте следующие правила:
- Меняйте UI после получения готового ответа. Отдельного раннего события с выбранным сценарием нет.
- При
handoffиrefuseотдавайте приоритет передаче обращения или отказу, скрывая элементы подбора по сценарию. - Для неизвестного ID,
scenario: null,context: nullили отсутствующего поля используйте обычный чат и продолжайте отображатьresponse.contentsи быстрые ответы. - Используйте контекст текущего ответа. Следующее сообщение может сохранить сценарий при другом
intentили получитьscenario: null; не переносите прежний сценарий автоматически. - Связывайте ответ с
assistantMessageId. Повтор старого запроса не должен переключать UI, который уже показывает более новый ответ.
scenario: null означает, что для текущего ответа плейбук не выбран. context: null означает, что полных корректных метаданных нет: например, для старых сообщений или фиксированного debug-ответа. В старой версии сервера поле может отсутствовать — обрабатывайте это так же, как null.
Контекст сохраняется вместе с конкретным сообщением: повтор запроса и история диалога возвращают его исходный сценарий и версию, даже если плейбук затем обновили или архивировали.
Типы событий EngagementEvent
EngagementEvent содержит готовые tracking URL для учета показов и кликов по рекомендациям. Поле type определяет, при каком действии нужно вызвать эти URL. Само получение ответа API не означает, что пользователь увидел подборку или товар.
Корневой events[] может содержать события рекомендательного блока и ссылки оценки ответа. Массив events[] внутри товара в блоке products относится к конкретной карточке.
Обрабатывайте только события, которые пришли в ответе, и сохраняйте регистр type: WRIMP и visible_impression — разные значения. Поле оставлено строковым в контракте, поскольку набор событий зависит от рекомендательной кампании.
При наступлении нужного действия найдите все события с соответствующим type на нужном уровне. Для каждого события выполните GET-запрос по каждому адресу из urls[]; если вместо массива пришло поле url, вызовите этот единственный адрес. Используйте URL как есть: параметры трекинга уже включены в него. Tracking URL фиксирует событие; для перехода на страницу товара используйте Product.url.
Отправляйте показ и видимый показ один раз для одного фактического показа блока или карточки, без повторов при каждой перерисовке. click отправляйте при каждом клике пользователя. Ошибка tracking-запроса не должна блокировать интерфейс.
Ссылки like и dislike не являются событиями показа. Не отправляйте весь массив при рендеринге. См. Оценка ответа.
Пример обработки events[] и события воронки диалога описаны в разделе Трекинг событий.
Ошибки и повтор запроса
Для повтора того же сообщения сохраняйте messages[].id, threadId, resourceId и исходные данные запроса. Без стабильного ID повтор после потери ответа может создать второе сообщение.
Повтор возвращает context исходного ответа. Если после него уже получен более новый ответ, не применяйте старый контекст к текущему UI.
Показывайте индикатор загрузки до получения ответа, не создавайте новый threadId при повторе того же пользовательского сообщения и защищайтесь от двойной отправки одинакового сообщения при нестабильной сети.