# Интеграция Shopping Assistant по API

Shopping Assistant - это AI-помощник для покупателей. Он ведет диалог, уточняет потребность, помогает выбрать подходящие товары из каталога и возвращает результат в структурированном формате для отображения в клиентском интерфейсе.

Этот вариант интеграции подходит, когда сайт, приложение, backend или другой клиентский канал самостоятельно реализует UI/UX чата и напрямую вызывает API Gravity:

POST https://shopping-assistant-api.gravityfield.ai/shopping/generate
Content-Type: application/json

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


# Что нужно реализовать на стороне клиента

  1. Создать чат-интерфейс: поле ввода, список сообщений, состояние загрузки, обработку ошибок и повтор запроса.
  2. Генерировать стабильный threadId на одну чат-сессию. Все запросы одного диалога должны отправляться с тем же threadId.
  3. Передавать стабильный resourceId - это uid пользователя Gravity Field. Удобный способ получить его заранее: POST /user.
  4. В messages передавать текущее сообщение пользователя. Историю диалога повторно отправлять не нужно: контекст сохраняется на стороне Shopping Assistant по threadId и resourceId.
  5. Рендерить блоки из response.contents:
    • message как текстовое сообщение ассистента;
    • products как список товарных карточек;
    • button как действие, например переход в поддержку.
  6. Рендерить response.ui.suggests как быстрые ответы. При нажатии быстрый ответ отправляется как обычное новое сообщение пользователя.
  7. Для товарных карточек использовать sku как основной идентификатор товара. Открытие PDP, добавление в корзину, получение изображения и актуальной цены остаются на стороне клиентского канала или клиентского каталога.

# Типовой поток

  1. Пользователь открывает чат, клиентская сторона создает threadId.
  2. Если у клиентской стороны еще нет uid Gravity Field, она получает или создает пользователя через POST /user и использует user.uid как resourceId.
  3. Пользователь отправляет сообщение.
  4. Клиентская сторона вызывает /shopping/generate с текущим сообщением пользователя в messages.
  5. API возвращает структурированный ответ и сохраняет ход диалога в memory по threadId и resourceId.
  6. Клиентская сторона отображает ответ и сохраняет его в локальной истории интерфейса.
  7. На следующем ходе клиентская сторона снова отправляет только новое сообщение пользователя с тем же threadId и resourceId.

Для обычной интеграции используйте роль user.


# Контракт запроса

type ShoppingGenerateRequest = {
  threadId: string;
  resourceId: string;
  trafficType?: "real" | "test";
  messages: Array<{
    role: "user";
    content: string;
  }>;
  runtimeContext: {
    tenant_id: string;
    ctx?: {
      type?: string;
      data?: unknown;
      lng?: string | null;
      location?: string | null;
    };
  };
};

# Поля запроса

Поле Обязательное Назначение
threadId Да ID диалога. Должен оставаться одинаковым для всех сообщений одной чат-сессии.
resourceId Да uid пользователя Gravity Field. Его можно получить через POST /user.
trafficType Нет real для боевого трафика или test для тестовых сценариев.
messages Да Текущее сообщение пользователя. Обычно передается массив с одним элементом { "role": "user", "content": "..." }.
runtimeContext.tenant_id Да ID секции Gravity Field.
runtimeContext.ctx Нет Контекст текущего экрана или страницы.

# Как заполнять ctx

ctx использует ту же модель контекста, что и API V2. Основные типы: HOMEPAGE, SEARCH, PRODUCT, CATEGORY, CART, OTHER.

Если чат открыт на карточке товара, передайте SKU товара в контексте:

{
  "runtimeContext": {
    "tenant_id": "670ccaae56afcafaf808c146",
    "ctx": {
      "type": "PRODUCT",
      "data": ["1066109"]
    }
  }
}

# Минимальный пример

# Запрос

curl --request POST \
  --url 'https://shopping-assistant-api.gravityfield.ai/shopping/generate' \
  --header 'Content-Type: application/json' \
  --data '{
    "threadId": "thread-8f2b1c",
    "resourceId": "665f0a000000000000000001",
    "trafficType": "test",
    "messages": [
      {
        "role": "user",
        "content": "Помогите выбрать корм для взрослой кошки"
      }
    ],
    "runtimeContext": {
      "tenant_id": "670ccaae56afcafaf808c146"
    }
  }'

# Ответ

{
  "threadId": "thread-8f2b1c",
  "resourceId": "665f0a000000000000000001",
  "response": {
    "contents": [
      {
        "type": "message",
        "value": "Подберу корм, но сначала уточню пару деталей. Кошка стерилизована и есть ли особенности по здоровью?"
      }
    ],
    "ui": {
      "suggests": [
        "Стерилизована, без особенностей",
        "Не стерилизована, чувствительное пищеварение",
        "Пожилая кошка, нужен мягкий корм"
      ]
    }
  }
}

# Пример ответа с товарами

{
  "threadId": "thread-8f2b1c",
  "resourceId": "665f0a000000000000000001",
  "response": {
    "contents": [
      {
        "type": "message",
        "value": "Вот несколько подходящих вариантов. Смотрите на возраст, стерилизацию и чувствительность пищеварения."
      },
      {
        "type": "products",
        "value": [
          {
            "sku": "1066109",
            "name": "Сухой корм для взрослых кошек",
            "price": 1299,
            "features": "Для взрослых кошек; повседневный рацион",
            "is_stm": false,
            "brand": "Demo Brand",
            "url": "https://example.com/product/1066109",
            "image_url": "https://example.com/product/1066109.jpg"
          },
          {
            "sku": "1023814",
            "name": "Корм для стерилизованных кошек",
            "price": 1599,
            "features": "Для стерилизованных кошек; контроль веса",
            "is_stm": true
          }
        ]
      },
      {
        "type": "message",
        "value": "Если кошка стерилизована, лучше начать со второго варианта. Если нет, подойдет первый."
      }
    ],
    "ui": {
      "suggests": [
        "Показать варианты дешевле",
        "Нужен влажный корм",
        "У кошки чувствительное пищеварение"
      ]
    }
  }
}

# Контракт ответа

type ShoppingGenerateResponse = {
  threadId: string;
  resourceId: string;
  response: {
    contents: Array<
      | { type: "message"; value: string }
      | { type: "products"; value: Product[] }
      | { type: "button"; value: string; action: string }
    >;
    ui?: {
      suggests: string[];
    };
  };
};

type Product = {
  sku?: string;
  name: string;
  price?: number | null;
  features?: string;
  is_stm?: boolean;
  brand?: string;
  url?: string;
  image_url?: string;
};

Для кнопок клиентская сторона должна обработать action. Например, open_support_chat можно связать с открытием клиентского чата поддержки.

Для товарных карточек считайте sku основным идентификатором товара. Поля price, brand, url, image_url и другие данные могут использоваться для быстрого отображения карточки, но актуализация цены, изображения, наличия, открытие PDP и добавление в корзину остаются на стороне клиентского канала или клиентского каталога.


# Ошибки и повтор запроса

HTTP-статус Причина Что делать клиенту
400 Некорректный JSON или тело запроса не соответствует контракту. Проверить payload перед повторной отправкой.
500 Техническая ошибка генерации ответа. Показать ошибку и предложить повторить запрос.
502 Ответ ассистента не прошел структурную валидацию. Показать сообщение вроде "Не удалось получить ответ, попробуйте еще раз".

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