JavaRush /Курсы /ChatGPT Apps /UX-аналитика workflow: измеряем эффективность шагов

UX-аналитика workflow: измеряем эффективность шагов

ChatGPT Apps
11 уровень , 4 лекция
Открыта

1. Зачем вообще мерить workflow

Если коротко: без аналитики вы живёте в режиме «мне кажется», а не в режиме «я знаю».

В предыдущих лекциях этого модуля мы уже нарезали сценарий на шаги, раздавали роли GPT, виджету и MCP, обсуждали tool‑gating и хранение состояния между шагами. Теперь посмотрим на ту же конструкцию глазами аналитики: работает ли всё это так, как мы задумывали, и где пользователи на самом деле застревают.

В обычном вебе все давно привыкли к воронкам: сколько людей пришло на лендинг, сколько положило товар в корзину, сколько дошло до оплаты. В ChatGPT App всё то же самое, только вместо страниц у вас шаги workflow, а вместо «клика по кнопке Купить» — комбинация реплики пользователя, вызов инструмента и взаимодействие с виджетом.

Когда вы строите сложный сценарий без метрик, вы не видите:

  • на каком шаге люди чаще всего «отваливаются»;
  • где они зависают и читают по минуте (или просто пошли за чаем и не вернулись);
  • какой шаг вообще не даёт пользы и только раздражает;
  • как изменения в промптах или tool gating влияют на поведение.

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

С этого момента workflow — это не только архитектурный объект и не только UX‑квест. Это ещё и измеримая штука с цифрами.

2. Воронка сценария в ChatGPT App

В классическом вебе воронка выглядит линейно: Landing → Product → Cart → Checkout. В ChatGPT App картинка немного веселее: пользователь может «перепрыгнуть» шаг словами, модель может иногда пропустить шаг, а виджет и текст диалога могут рассинхронизироваться.

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

Возьмём наш GiftGenius:

  1. collect_recipient — ChatGPT и виджет собирают базовые данные о получателе (пол, возраст, кем приходится, интересы).
  2. collect_budget — уточняем бюджет и валюту.
  3. suggest_ideas — MCP / агент подбирает идеи и возвращает карточки подарков в виджет.
  4. review_selection — пользователь лайкает / скрывает идеи и выбирает 1–2 фаворита.
  5. checkout — создаётся commerce intent, оформляется заказ.

В виде воронки это можно нарисовать так:

flowchart TD
    A[Начало workflow] --> B["1\. Получатель"]
    B --> C["2\. Бюджет"]
    C --> D["3\. Идеи подарков"]
    D --> E["4\. Выбор подарка"]
    E --> F["5\. Checkout"]

Но важно помнить: пользователь может написать в чат «Давай сразу к оплате» или «Покажи сначала дорогие варианты», и модель решит перепрыгнуть часть шагов. Поэтому аналитика по шагам в ChatGPT App — это не только про UI‑экраны, но и про модельное поведение: какие шаги реально проходят, в каком порядке, и кто инициировал переход — пользователь, виджет или GPT.

3. Базовые метрики по шагам

Начнём с классики продуктовой аналитики и чуть адаптируем её под ChatGPT App.

Для каждого workflow нам нужны хотя бы четыре базовых показателя.

Для удобства давайте сведём их в таблицу:

Метрика Что означает Типичный вопрос
Start rate Сколько пользователей вообще начали сценарий Наш App вообще кому‑то показывается?
Completion rate Сколько пользователей дошли до финала Насколько сценарий доводит до результата?
Conversion per step Доля пользователей, перешедших с шага N на шаг N+1 Где именно у нас «дырявый» шаг?
Drop-off per step Доля пользователей, ушедших на шаге N На каком шаге люди чаще всего сдаются?

К ним почти всегда добавляют метрики усилий:

  • среднее время на шаге (где люди «зависают»);
  • количество взаимодействий на шаге (сколько сообщений / кликов понадобилось);
  • долю шагов, завершившихся ошибкой или потребовавших повторной попытки.

В контексте LLM‑сценариев добавляются ещё более специфичные вещи, вроде точности выбора инструмента моделью или доли «галлюцинаторных» ответов на конкретном шаге, но это уже продвинутая история, до неё мы ещё дойдём в финальных модулях.

Для commerce‑сценариев поверх шагов накручиваются бизнес‑метрики:

  • конверсия в оплату от старта workflow;
  • конверсия в оплату от конкретного шага (например, от «подбор идей»);
  • средний чек;
  • доля отмен / возвратов.

Важно: все эти цифры живут не сами по себе, а между ними есть причинно‑следственная история. Шаг с большим drop‑off не всегда плохой: возможно, он фильтрует неподходящих пользователей и дальше идут только те, кому сценарий реально полезен. Поэтому аналитика — это не просто «считать проценты», а уметь рассказывать историю по данным.

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

4. Как выглядят события аналитики

Прежде чем писать код, нужно договориться о формате «события» (event), которое мы будем слать из виджета и бэкенда.

Обычно событие аналитики содержит:

  • кто: идентификатор пользователя или хотя бы сессии;
  • что за workflow и какая его версия;
  • какой шаг;
  • что произошло (тип события);
  • было ли успешно, сколько заняло времени;
  • чуть‑чуть метаданных (локаль, девайс и т.п.).

Упрощённую схему событий для workflow можно описать так:

export type WorkflowEventType =
  | "workflow_started"
  | "workflow_finished"
  | "step_started"
  | "step_completed"
  | "step_failed";

export interface WorkflowAnalyticsEvent {
  eventId: string;              // uuid
  timestamp: string;            // ISO-строка
  userId?: string;              // если можно деанонимизировать
  conversationId?: string;      // id диалога ChatGPT (если доступен)
  workflowId: string;           // наш внутренний идентификатор
  workflowType: "gift_selection";
  workflowVersion: string;      // например, "1.2.0" или "1.2.0-A"
  stepName?: string;            // collect_budget, suggest_ideas и т.д.
  eventType: WorkflowEventType;
  toolName?: string;            // если связано с tool-call
  success?: boolean;
  errorCode?: string | null;
  durationMs?: number;
  metadata?: Record<string, unknown>;
}

Несколько нюансов:

Во‑первых, workflowVersion очень важен, если вы собираетесь делать A/B‑тесты: без него вы никогда не узнаете, какой именно вариант сценария даёт лучшие цифры.

Во‑вторых, conversationId или другой correlation ID позволяет связать события: шаги в виджете, tool‑вызовы в MCP и текстовый диалог. В последующих модулях мы ещё будем говорить про трассировку и наблюдаемость, но привычка с самого начала думать о «сквозных» идентификаторах очень полезна.

В‑третьих, не нужно тащить в событие всё подряд: полные тексты сообщений, e‑mail, адреса и прочую PII лучше избегать или жёстко анонимизировать — об этом ещё поговорим ближе к концу.

5. Инструментация в виджете (Next.js + Apps SDK)

Теперь самое интересное: как сделать так, чтобы наш GiftGenius‑виджет сам тихо репортил шаги, когда пользователь проходит сценарий.

Предположим, в предыдущих лекциях вы уже сделали что‑то вроде:

// components/GiftWizard.tsx
type StepId = "recipient" | "budget" | "ideas" | "review" | "checkout";

export function GiftWizard() {
  const [currentStep, setCurrentStep] = useState<StepId>("recipient");
  const [workflowId] = useState(() => crypto.randomUUID());

  // ... здесь отрисовка разных шагов
}

Добавим небольшой «слой аналитики» в виде хука.

Хук useWorkflowAnalytics

Сделаем обёртку, которая знает про workflowId, workflowVersion и умеет отправлять событие в наш Next.js API‑роут /api/workflow-analytics.

// lib/useWorkflowAnalytics.ts
import { useCallback } from "react";
import type { WorkflowAnalyticsEvent, WorkflowEventType } from "./types";

const WORKFLOW_VERSION = "1.0.0";

export function useWorkflowAnalytics(
  workflowId: string,
  workflowType: WorkflowAnalyticsEvent["workflowType"] = "gift_selection"
) {
  const sendEvent = useCallback(
    async (payload: Omit<WorkflowAnalyticsEvent, "eventId" | "timestamp" | "workflowType" | "workflowVersion" | "workflowId">) => {
      const event: WorkflowAnalyticsEvent = {
        eventId: crypto.randomUUID(),
        timestamp: new Date().toISOString(),
        workflowId,
        workflowType,
        workflowVersion: WORKFLOW_VERSION,
        ...payload,
      };

      // простая отправка в API; в проде можно добавить буфер/дебаунс
      await fetch("/api/workflow-analytics", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(event),
      });
    },
    [workflowId, workflowType]
  );

  const trackStepEvent = useCallback(
    async (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => {
      await sendEvent({ stepName, eventType, ...extra });
    },
    [sendEvent]
  );

  return { sendEvent, trackStepEvent };
}

Здесь важно, что хук никак не зависит от конкретного UI шага. Он просто знает, что такое stepName и eventType. Конкретные компоненты будут говорить ему: «вот я начал шаг», «вот я его закончил» и так далее.

Отправляем workflow_started и workflow_finished

В компоненте GiftWizard можно в момент монтирования и размонтирования регистрировать начало и завершение сценария:

// components/GiftWizard.tsx
export function GiftWizard() {
  const [currentStep, setCurrentStep] = useState<StepId>("recipient");
  const [workflowId] = useState(() => crypto.randomUUID());
  const { sendEvent } = useWorkflowAnalytics(workflowId);

  useEffect(() => {
    void sendEvent({ eventType: "workflow_started" });
    return () => {
      void sendEvent({ eventType: "workflow_finished" });
    };
  }, [sendEvent]);

  // ...
}

Конечно, завершение по размонтированию — грубое приближение: пользователь может просто свернуть чат или уйти в другой диалог. Но даже такая грубая метрика уже даёт ощущение того, сколько сценариев вообще «дошли куда‑то».

Отслеживаем события по шагам

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

interface StepProps {
  stepId: StepId;
  onNext: () => void;
  trackStepEvent: (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => Promise<void>;
}

function StepRecipient({ stepId, onNext, trackStepEvent }: StepProps) {
  useEffect(() => {
    void trackStepEvent(stepId, "step_started");
  }, [stepId, trackStepEvent]);

  const handleSubmit = async () => {
    // ... валидация, сохранение в widgetState
    await trackStepEvent(stepId, "step_completed");
    onNext();
  };

  return (
    <div>
      {/* поля формы получателя */}
      <button onClick={handleSubmit}>Дальше</button>
    </div>
  );
}

В GiftWizard передаём trackStepEvent:

export function GiftWizard() {
  // ...
  const { trackStepEvent } = useWorkflowAnalytics(workflowId);

  const goToNext = () => {
    setCurrentStep((prev) => NEXT_STEP[prev]);
  };

  if (currentStep === "recipient") {
    return (
      <StepRecipient
        stepId="recipient"
        onNext={goToNext}
        trackStepEvent={trackStepEvent}
      />
    );
  }

  // остальные шаги...
}

Аналогично, в шагах, где есть потенциальные ошибки (например, запрос в внешний API в suggest_ideas), можно при неудаче послать "step_failed" с errorCode, а при успешной подгрузке вариантов — "step_completed".

Так мы получаем:

  • понятный список событий: когда шаги стартуют и заканчиваются;
  • возможность посчитать время шага: разница между "step_started" и "step_completed";
  • видимость того, какие шаги чаще всего завершаются "step_failed".

6. Инструментация на backend / MCP

Клиентская аналитика — это хорошо, но виджет живёт в довольно хрупком мире: браузер пользователя, ифрейм, ограничения песочницы и всё, что с этим связано. Поэтому параллельно стоит логировать события на серверной стороне — в MCP‑инструментах или backend‑API вашего App.

Например, у вас есть инструмент suggest_gifts, который реально делает тяжёлую работу: ходит в product feed, применяет фильтры и возвращает подарки. Внутри такого инструмента вы можете логировать и бизнес‑логику, и аналитические события.

Условный обработчик MCP‑tool на TypeScript может выглядеть так:

// mcp/tools/suggestGifts.ts
import type { SuggestGiftsArgs } from "../schemas";
import { logWorkflowEvent } from "../analytics/log";

export async function handleSuggestGifts(args: SuggestGiftsArgs, context: { workflowId: string; stepName: string }) {
  const startedAt = Date.now();

  try {
    // ... основная логика подбора идей

    await logWorkflowEvent({
      workflowId: context.workflowId,
      workflowType: "gift_selection",
      workflowVersion: "1.0.0",
      stepName: context.stepName,
      eventType: "step_completed",
      toolName: "suggest_gifts",
      success: true,
      durationMs: Date.now() - startedAt,
    });

    return {
      content: [{ type: "text", text: "Нашёл 5 идей подарков." }],
      _meta: {
        // сырые данные для виджета
      },
    };
  } catch (e) {
    await logWorkflowEvent({
      workflowId: context.workflowId,
      workflowType: "gift_selection",
      workflowVersion: "1.0.0",
      stepName: context.stepName,
      eventType: "step_failed",
      toolName: "suggest_gifts",
      success: false,
      errorCode: "SUGGEST_FAILED",
      durationMs: Date.now() - startedAt,
    });
    throw e;
  }
}

А logWorkflowEvent может писать в ту же таблицу/хранилище, куда складываются события с фронта, просто с пометкой "source": "backend".

Почему серверная аналитика надёжнее

Во‑первых, tool‑вызов либо случился, либо нет — это чёткий факт, а не эвристика «пользователь вроде нажал кнопку».

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

В‑третьих, так вы видите различие между UX‑проблемой (пользователь не доходит до шага, где вызывается инструмент) и технической проблемой (до шага доходят, но инструмент часто падает).

7. Как читать данные: ищем узкие места

Предположим, вы уже реализовали отправку событий "workflow_started", "step_started", "step_completed" и "step_failed" из виджета и MCP, и в хранилище накопилось достаточно данных. Представим, что вы уже собрали некоторое количество данных по GiftGenius и получили сводную статистику по шагам. В таблице ниже приведены условные цифры по 1000 запущенным workflow:

Шаг Старт шага Завершён шаг Drop‑off на шаге Среднее время (сек)
recipient 1000 950 5% 12
budget 950 700 26% 35
ideas 700 680 3% 8
review 680 500 26% 40
checkout 500 420 16% 20

На что здесь стоит смотреть:

Во‑первых, шаг budget — очевидное узкое место. Высокий drop‑off (26%) и заметно выше среднее время. Возможно, вы спрашиваете слишком много про валюты/налоги, формулировки непонятны, или у людей просто нет уверенности в бюджете. Это хороший кандидат на упрощение шага, разбиение его на два подшага или изменение формулировок вопросов.

Во‑вторых, review тоже даёт сильный отвал. Возможно, UI карточек подарков перегружен, или пользователю непонятно, что значит «лайкнуть» подарок. Может быть, модель возвращает слишком много вариантов, и виджет кажется бесконечным списком. Тут уже стоит смотреть не только на цифры, но и на скриншоты / записи сессий (если вы их ведёте), или хотя бы вручную пройти сценарий как пользователь.

В‑третьих, checkout теряет 16% — для commerce‑сценария это много денег. Но здесь надо понять, где они теряются: на оформлении заказа, на ошибках платёжного провайдера, на том, что пользователь просто передумал. Это уже не чистый UX‑вопрос, а совокупность UX + бизнес‑ограничений.

Важно уметь различать проблемы UI и модели.

  • Если пользователи часто возвращаются к предыдущему шагу и меняют ответы — это сигнал про непонятный или плохо сформулированный вопрос.
  • Если шаг быстро завершается, но tool‑вызов на нём часто падает — это проблема backend/MCP.
  • Если шаг длится долго и при этом нет ни ошибок, ни возвратов, возможно, пользователь просто читает лонгрид текста, который ему не очень нужен.

8. Эксперименты и A/B‑тесты workflow

Цифры сами по себе ничего не улучшают. Чтобы от аналитики был толк, нужно уметь ставить эксперименты: менять шаги и сравнивать, стало ли лучше.

В контексте ChatGPT App типичный эксперимент — это сравнение двух версий шага или последовательности шагов:

  • длинный wizard с несколькими простыми экранами против одной сложной формы;
  • разные формулировки вопросов;
  • разный порядок шагов (например, бюджет спросить раньше или позже);
  • разные стратегии tool gating (на первом шаге меньше tools, на втором — больше).

Хорошая привычка — фиксировать версию сценария в workflowVersion и добавлять туда идентификатор эксперимента, например "1.3.0-A" и "1.3.0-B".

Простейший A/B‑сплит в виджете

Разумеется, в бою вы захотите стабильный assignment на уровне пользователя или сессии (через бэкенд), но для учебного примера достаточно случайного выбора варианта.

// lib/useWorkflowVariant.ts
import { useMemo } from "react";

export type WorkflowVariant = "A" | "B";

export function useWorkflowVariant(): WorkflowVariant {
  return useMemo(() => {
    return Math.random() < 0.5 ? "A" : "B";
  }, []);
}

В GiftWizard определяем вариант и передаём его в аналитику:

export function GiftWizard() {
  const [workflowId] = useState(() => crypto.randomUUID());
  const variant = useWorkflowVariant();
  const { sendEvent, trackStepEvent } = useWorkflowAnalytics(
    workflowId,
    "gift_selection"
  );

  useEffect(() => {
    void sendEvent({
      eventType: "workflow_started",
      metadata: { variant },
    });
  }, [sendEvent, variant]);

  // дальше можно менять тексты/структуру шагов в зависимости от variant
}

На сервере можно заменить жёсткое "1.0.0" на что‑то вроде "1.1.0-A" и "1.1.0-B" или просто логировать metadata.variant и уже в аналитике группировать по нему.

Главный смысл A/B‑теста: заранее выбрать целевую метрику. Например: «мы хотим поднять completion rate сценария с 42% до 50%» или «уменьшить время на шаге budget на 20%». Без целевой метрики любая реструктуризация workflow будет похожа на ремонт по принципу «переставили шкаф, вроде стало красивее».

9. Приватность и этика данных

Раньше мы уже вскользь упоминали, что в metadata и события аналитики лучше не тащить PII в лоб. Пока мы обсуждаем метрики, легко увлечься и начать логировать всё подряд. Но надо помнить, что вы работаете внутри ChatGPT, а пользователь вполне может разумно ожидать, что его личные сообщения не улетают во внешнюю аналитику в сыром виде.

Несколько простых правил, которых стоит придерживаться уже сейчас, до модулей про безопасность и Store:

  • Во‑первых, не логируйте полные тексты сообщений пользователя. Вместо них можно сохранять длину сообщения, тип ответа (число, «да/нет», выбор из списка), или анонимизированные признаки вроде «ответ пустой / неполный / изменён».
  • Во‑вторых, не логируйте явно идентифицирующую информацию (PII), если она вам не нужна для бизнес‑логики: e‑mail, телефоны, адреса, полные имена. Если без этого нельзя, храните их в другом, защищённом контуре и жёстко ограничивайте доступ.
  • В‑третьих, аккуратно относитесь к контексту диалога. Если вы сохраняете conversationId, убедитесь, что в аналитике вы не пытаетесь «склеивать» отдельные диалоги в супер‑профили без уважительной причины и правовой базы.
  • В‑четвёртых, обратите внимание на политики OpenAI и требования Store (мы подробно дойдём до этого в модулях про публикацию и безопасность), где явно говорится о том, какие данные можно выносить из ChatGPT, а какие — нет. На этапе проектирования аналитики полезно сразу заложить анонимизацию и минимизацию данных, чтобы потом не переписывать полсистемы.

И, наконец, помните, что UX‑аналитика — это не тотальная слежка и точно не surveillance в духе Big Brother. Цель — улучшать сценарии и уменьшать фрустрацию пользователя, а не строить Big Brother‑панель «кто в 2:37 ночи не дошёл до checkout».

10. Типичные ошибки в UX-аналитике workflow

Ошибка №1: «Мы ещё не в проде, метрики потом».
Очень часто разработчики начинают думать про аналитику в тот момент, когда App уже используется реальными людьми. В результате события вводятся «задним числом», данные получаются рваными, а сравнить старую и новую версии сценария почти невозможно. Минимальную воронку ("workflow_started", "step_started", "step_completed", "workflow_finished") лучше заложить сразу, пока код ещё относительно прост.

Ошибка №2: Логировать только успехи и игнорировать ошибки.
Иногда в логах есть только "step_completed", а вот "step_failed" никто не пишет, потому что «ну оно же не должно падать». В результате вы видите, что до какого‑то шага доходит мало людей, но не понимаете, это они сами ушли или их выкинуло ошибками. Всегда логируйте и успешное завершение шага, и неуспешное, с хотя бы грубым errorCode.

Ошибка №3: Полное отсутствие привязки к версии workflow.
Вы меняете тексты, порядок шагов, вводите tool gating, а в событиях всё время фигурирует workflowVersion: "1.0.0". Через месяц вы смотрите на графики и не можете понять, что было до изменений, а что после. Фиксация версии сценария и, при необходимости, варианта A/B — обязательный элемент аналитики.

Ошибка №4: Слишком детальная аналитика без повода.
Противоположная крайность — сразу строить «идеальную» схему событий на 50 полей, логировать каждый клик по пикселю и каждое перепечатывание символа. Во‑первых, это может нарушать приватность. Во‑вторых, такие данные сложно анализировать, и вы утонете в шуме. Лучше начать с небольшого набора событий и метрик, которые реально отвечают на конкретные продуктовые вопросы, а потом развивать систему по мере необходимости.

Ошибка №5: Несогласованность имён шагов и сценариев.
Иногда в коде шаг называется budget, в аналитике — collect_budget, а в отчёте — «шаг с вопросом про деньги». Через пару недель никто не помнит, что есть что. На этапе проектирования workflow полезно договориться о стабильных идентификаторах шагов (stepName) и использовать их и в UI, и в логах, и в отчётах.

Ошибка №6: Наличие метрик, которыми никто не пользуется.
Самая грустная история: вы аккуратно собрали массу данных, настроили отправку событий из виджета и MCP, но никто потом не открывает дашборды и не принимает решений. Аналитика ради аналитики не нужна; всегда задавайте себе вопрос: «Какое решение я смогу принять на основе этой метрики?». Если ответа нет — метрика вам пока не нужна.

1
Задача
ChatGPT Apps, 11 уровень, 4 лекция
Недоступна
Мини-воронка из 3 шагов с временем шага и step_failed
Мини-воронка из 3 шагов с временем шага и step_failed
1
Опрос
Workflow, 11 уровень, 4 лекция
Недоступен
Workflow
Workflow и постепенное раскрытие инструментов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ