1. Por que medir o workflow
Em resumo: sem análise você vive no modo “acho”, e não no modo “eu sei”.
Nas aulas anteriores deste módulo, já dividimos o cenário em etapas, atribuímos papéis ao GPT, ao widget e ao MCP, discutimos tool‑gating e o armazenamento de estado entre as etapas. Agora, veremos a mesma construção pelo olhar da análise: será que tudo funciona como planejado e onde os usuários realmente ficam travados.
Na web tradicional, todos já estão acostumados a funis: quantas pessoas chegaram à landing page, quantas colocaram o produto no carrinho, quantas chegaram ao pagamento. No ChatGPT App é a mesma coisa, só que em vez de páginas você tem etapas do workflow, e em vez de “clique no botão Comprar” — uma combinação de fala do usuário, chamada de ferramenta e interação com o widget.
Quando você constrói um cenário complexo sem métricas, você não vê:
- em qual etapa as pessoas mais “desistem”;
- onde elas ficam paradas e leem por um minuto (ou simplesmente foram pegar um café e não voltaram);
- qual etapa não traz benefício nenhum e só irrita;
- como mudanças em prompts ou tool gating afetam o comportamento.
O objetivo da análise por etapas é simples: aprender a aumentar a parcela de cenários concluídos, reduzir o tempo até o resultado e diminuir a quantidade de erros e de chamados ao suporte.
A partir de agora, o workflow não é apenas um objeto arquitetural e não só uma missão de UX. É também algo mensurável, com números.
2. O funil do cenário no ChatGPT App
Na web clássica, o funil é linear: Landing → Product → Cart → Checkout. No ChatGPT App o desenho é um pouco mais animado: o usuário pode “pular” uma etapa com palavras, o modelo às vezes pode ignorar uma etapa, e o widget e o texto do diálogo podem ficar dessincronizados.
Ainda assim, a ideia básica é a mesma: temos uma sequência de etapas, e em cada uma delas parte dos usuários segue adiante e parte — não.
Vamos pegar o nosso GiftGenius:
- collect_recipient — o ChatGPT e o widget coletam dados básicos sobre o destinatário (sexo, idade, relação, interesses).
- collect_budget — ajustamos o orçamento e a moeda.
- suggest_ideas — o MCP/agente seleciona ideias e retorna cards de presentes no widget.
- review_selection — o usuário curte/oculta ideias e escolhe 1–2 favoritas.
- checkout — cria‑se um commerce intent e o pedido é finalizado.
Em forma de funil, dá para desenhar assim:
flowchart TD
A[Início do workflow] --> B["1\. Destinatário"]
B --> C["2\. Orçamento"]
C --> D["3\. Ideias de presente"]
D --> E["4\. Escolha do presente"]
E --> F["5\. Checkout"]
Mas é importante lembrar: o usuário pode escrever no chat “Vamos direto para o pagamento” ou “Mostre primeiro as opções caras”, e o modelo pode decidir pular parte das etapas. Portanto, a análise por etapas no ChatGPT App não é só sobre telas de UI, mas também sobre o comportamento do modelo: quais etapas são realmente percorridas, em que ordem e quem iniciou a transição — o usuário, o widget ou o GPT.
3. Métricas básicas por etapa
Vamos começar com o básico da análise de produto e adaptá-lo um pouco para o ChatGPT App.
Para cada workflow, precisamos de pelo menos quatro indicadores básicos.
Para conveniência, vamos reuni-los em uma tabela:
| Métrica | O que significa | Pergunta típica |
|---|---|---|
| Start rate | Quantos usuários iniciaram o cenário | Nosso App está sendo mostrado para alguém? |
| Completion rate | Quantos usuários chegaram ao final | Até que ponto o cenário leva ao resultado? |
| Conversion per step | Proporção de usuários que passaram da etapa N para N+1 | Em qual etapa exatamente temos um “vazamento”? |
| Drop-off per step | Proporção de usuários que abandonaram na etapa N | Em qual etapa as pessoas desistem com mais frequência? |
Quase sempre adicionamos métricas de esforço:
- tempo médio por etapa (onde as pessoas “travam”);
- quantidade de interações na etapa (quantas mensagens/cliques foram necessários);
- parcela de etapas que terminaram com erro ou exigiram nova tentativa.
No contexto de cenários com LLM, entram coisas ainda mais específicas, como a precisão na escolha da ferramenta pelo modelo ou a parcela de respostas “alucinatórias” em uma etapa específica, mas isso é assunto avançado, chegaremos lá nos módulos finais.
Para cenários de comércio, sobre as etapas adicionam‑se métricas de negócio:
- conversão em pagamento a partir do início do workflow;
- conversão em pagamento a partir de uma etapa específica (por exemplo, de “seleção de ideias”);
- ticket médio;
- parcela de cancelamentos/devoluções.
Importante: todos esses números não existem isoladamente; há causalidade entre eles. Uma etapa com grande drop‑off nem sempre é ruim: talvez ela filtre usuários não adequados e só seguem adiante aqueles para quem o cenário é realmente útil. Portanto, análise não é apenas “contar porcentagens”, mas saber contar uma história com dados.
Para que todas essas porcentagens e funis possam ser calculados, precisamos de eventos brutos: quem, quando e qual etapa passou (ou não passou). Na próxima seção, vamos combinar um formato para esses eventos.
4. Como são os eventos de análise
Antes de escrever código, precisamos combinar o formato do “evento” que enviaremos do widget e do backend.
Normalmente, um evento de análise contém:
- quem: o identificador do usuário ou pelo menos da sessão;
- qual workflow e qual versão dele;
- qual etapa;
- o que aconteceu (tipo do evento);
- se foi bem‑sucedido, quanto tempo levou;
- um pouco de metadados (localidade, dispositivo etc.).
Um esquema simplificado de eventos para workflow pode ser descrito assim:
export type WorkflowEventType =
| "workflow_started"
| "workflow_finished"
| "step_started"
| "step_completed"
| "step_failed";
export interface WorkflowAnalyticsEvent {
eventId: string; // uuid
timestamp: string; // string no formato ISO
userId?: string; // se for possível desanonimizar
conversationId?: string; // id do diálogo do ChatGPT (se disponível)
workflowId: string; // nosso identificador interno
workflowType: "gift_selection";
workflowVersion: string; // por exemplo, "1.2.0" ou "1.2.0-A"
stepName?: string; // collect_budget, suggest_ideas etc.
eventType: WorkflowEventType;
toolName?: string; // se relacionado a tool-call
success?: boolean;
errorCode?: string | null;
durationMs?: number;
metadata?: Record<string, unknown>;
}
Alguns nuances:
Primeiro, workflowVersion é muito importante se você pretende fazer testes A/B: sem ela você nunca saberá qual variante do cenário entrega os melhores números.
Segundo, conversationId ou outro correlation ID permite correlacionar eventos: etapas no widget, chamadas de ferramenta no MCP e o diálogo em texto. Em módulos seguintes ainda falaremos de tracing e observabilidade, mas o hábito de pensar desde o início em identificadores “de ponta a ponta” é muito útil.
Terceiro, não precisa colocar no evento tudo o que aparece: textos completos de mensagens, e‑mail, endereços e outros PII é melhor evitar ou anonimizar rigidamente — falaremos mais sobre isso perto do final.
5. Instrumentação no widget (Next.js + Apps SDK)
Agora, a parte mais interessante: como fazer o nosso widget GiftGenius reportar silenciosamente as etapas enquanto o usuário percorre o cenário.
Vamos supor que, nas aulas anteriores, você tenha feito algo como:
// components/GiftWizard.tsx
type StepId = "recipient" | "budget" | "ideas" | "review" | "checkout";
export function GiftWizard() {
const [currentStep, setCurrentStep] = useState<StepId>("recipient");
const [workflowId] = useState(() => crypto.randomUUID());
// ... renderização das diferentes etapas
}
Vamos adicionar uma pequena “camada de análise” na forma de um hook.
Hook useWorkflowAnalytics
Vamos criar um wrapper que conhece workflowId, workflowVersion e sabe enviar um evento para a nossa rota de API do Next.js /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,
};
// envio simples para a API; em produção você pode adicionar buffer/debounce
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 };
}
O importante aqui é que o hook não depende de uma etapa específica de UI. Ele simplesmente sabe o que é stepName e eventType. Componentes específicos irão dizer a ele: “comecei a etapa”, “terminei a etapa” e assim por diante.
Enviando workflow_started e workflow_finished
No componente GiftWizard, no momento de montagem e desmontagem, podemos registrar o início e a conclusão do cenário:
// 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]);
// ...
}
Claro, encerrar no unmount é uma aproximação grosseira: o usuário pode simplesmente minimizar o chat ou ir para outro diálogo. Mas mesmo essa métrica grosseira já dá uma noção de quantos cenários “chegaram a algum lugar”.
Rastreando eventos por etapa
Agora, vamos fazer com que cada etapa relate a si mesma na análise. Para começar, adicionamos um wrapper simples:
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 () => {
// ... validação, salvar no widgetState
await trackStepEvent(stepId, "step_completed");
onNext();
};
return (
<div>
{/* campos do formulário do destinatário */}
<button onClick={handleSubmit}>Avançar</button>
</div>
);
}
Em GiftWizard, passamos o 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}
/>
);
}
// demais etapas...
}
Da mesma forma, nas etapas onde há possíveis erros (por exemplo, uma chamada a API externa em suggest_ideas), você pode, em caso de falha, enviar "step_failed" com errorCode, e em caso de carregamento bem‑sucedido das opções — "step_completed".
Assim, obtemos:
- uma lista clara de eventos: quando as etapas começam e terminam;
- a possibilidade de calcular o tempo da etapa: a diferença entre "step_started" e "step_completed";
- visibilidade de quais etapas mais frequentemente terminam em "step_failed".
6. Instrumentação no backend / MCP
Análise no cliente é bom, mas o widget vive em um mundo bastante frágil: navegador do usuário, iframe, limitações de sandbox e tudo que vem junto. Por isso, em paralelo, vale registrar eventos no lado do servidor — nas ferramentas MCP ou no backend‑API do seu App.
Por exemplo, você tem a ferramenta suggest_gifts, que realmente faz o trabalho pesado: acessa o product feed, aplica filtros e retorna presentes. Dentro de uma ferramenta assim, você pode registrar tanto a lógica de negócio quanto os eventos de análise.
Um handler hipotético de MCP‑tool em TypeScript pode ser assim:
// 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 {
// ... lógica principal de seleção de ideias
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: "Encontrei 5 ideias de presentes." }],
_meta: {
// dados brutos para o widget
},
};
} 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;
}
}
E o logWorkflowEvent pode escrever na mesma tabela/armazenamento onde ficam os eventos do front, apenas com a marca "source": "backend".
Por que a análise no servidor é mais confiável
Primeiro, a chamada de ferramenta ou aconteceu, ou não — é um fato claro, não uma heurística do tipo “o usuário aparentemente clicou no botão”.
Segundo, no servidor é mais fácil agregar dados: você pode contabilizar quantas vezes cada ferramenta foi chamada, qual o durationMs médio e qual a parcela de chamadas que termina em erro.
Terceiro, assim você vê a diferença entre um problema de UX (o usuário não chega à etapa onde a ferramenta é chamada) e um problema técnico (chegam à etapa, mas a ferramenta falha com frequência).
7. Como ler os dados: encontrando gargalos
Suponha que você já tenha implementado o envio de eventos "workflow_started", "step_started", "step_completed" e "step_failed" do widget e do MCP, e que no armazenamento já existam dados suficientes. Vamos imaginar que você já coletou uma certa quantidade de dados do GiftGenius e obteve estatísticas resumidas por etapa. A tabela abaixo traz números hipotéticos para 1000 workflows iniciados:
| Etapa | Inícios da etapa | Etapa concluída | Drop‑off na etapa | Tempo médio (s) |
|---|---|---|---|---|
| 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 |
O que vale observar aqui:
Primeiro, a etapa budget é um gargalo óbvio. Alto drop‑off (26 %) e tempo médio visivelmente maior. Talvez você esteja perguntando demais sobre moedas/impostos, as formulações não sejam claras, ou as pessoas simplesmente não tenham certeza do orçamento. É um bom candidato para simplificar a etapa, dividi‑la em dois subpassos ou mudar a formulação das perguntas.
Segundo, review também causa bastante abandono. Talvez o UI dos cards de presente esteja sobrecarregado, ou o usuário não entenda o que significa “curtir” um presente. Pode ser que o modelo retorne opções demais e o widget pareça uma lista interminável. Aqui vale olhar não só os números, mas também screenshots/gravações de sessão (se você as faz), ou ao menos percorrer o cenário manualmente como um usuário.
Terceiro, checkout perde 16 % — para um cenário de comércio, isso é muito dinheiro. Mas é preciso entender onde se perde: no fechamento do pedido, em erros do provedor de pagamento, ou porque o usuário simplesmente desistiu. Isso já não é uma questão puramente de UX, mas a soma de UX + restrições de negócio.
É importante saber distinguir problemas de UI e de modelo.
- Se os usuários voltam com frequência à etapa anterior e mudam respostas — é um sinal de pergunta confusa ou mal formulada.
- Se a etapa termina rapidamente, mas a chamada de ferramenta nela falha com frequência — é problema do backend/MCP.
- Se a etapa dura muito e ao mesmo tempo não há erros nem retornos, talvez o usuário esteja apenas lendo um textão que não é muito necessário.
8. Experimentos e testes A/B de workflow
Números, por si só, não melhoram nada. Para que a análise seja útil, é preciso saber conduzir experimentos: mudar etapas e comparar se ficou melhor.
No contexto do ChatGPT App, o experimento típico é comparar duas versões de uma etapa ou da sequência de etapas:
- um wizard longo com várias telas simples versus um formulário único e complexo;
- formulações diferentes das perguntas;
- ordem diferente das etapas (por exemplo, perguntar o orçamento antes ou depois);
- estratégias diferentes de tool gating (menos tools na primeira etapa, mais na segunda).
Um bom hábito é fixar a versão do cenário em workflowVersion e adicionar o identificador do experimento, por exemplo "1.3.0-A" e "1.3.0-B".
Split A/B mais simples no widget
Claro que, em produção, você vai querer um assignment estável no nível do usuário ou da sessão (via backend), mas para um exemplo didático basta escolher a variante aleatoriamente.
// 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";
}, []);
}
No GiftWizard, definimos a variante e a repassamos à análise:
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]);
// depois, você pode mudar textos/estrutura das etapas conforme a variant
}
No servidor, você pode substituir o rígido "1.0.0" por algo como "1.1.0-A" e "1.1.0-B", ou simplesmente registrar metadata.variant e, na análise, agrupar por ele.
O principal sentido de um teste A/B: escolher a métrica objetivo com antecedência. Por exemplo: “queremos aumentar o completion rate do cenário de 42% para 50%” ou “reduzir o tempo na etapa budget em 20%”. Sem uma métrica objetivo, qualquer reestruturação do workflow vai parecer uma reforma do tipo “mudamos o armário de lugar, parece que ficou mais bonito”.
9. Privacidade e ética de dados
Já mencionamos de passagem que, em metadata e nos eventos de análise, é melhor não enviar PII de forma direta. Enquanto discutimos métricas, é fácil se empolgar e começar a registrar tudo. Mas lembre‑se de que você trabalha dentro do ChatGPT e o usuário pode razoavelmente esperar que suas mensagens pessoais não sejam enviadas, em bruto, para uma análise externa.
Algumas regras simples, que valem a pena seguir desde já, antes dos módulos sobre segurança e Store:
- Primeiro, não registre os textos completos das mensagens do usuário. Em vez disso, você pode salvar o comprimento da mensagem, o tipo de resposta (número, “sim/não”, escolha em lista) ou sinais anonimizados como “resposta vazia/incompleta/alterada”.
- Segundo, não registre informações claramente identificáveis (PII) se elas não forem necessárias para a lógica de negócio: e‑mail, telefones, endereços, nomes completos. Se for inevitável, armazene em outro domínio protegido e restrinja rigidamente o acesso.
- Terceiro, trate o contexto do diálogo com cuidado. Se você salva o conversationId, garanta que, na análise, você não tente “juntar” diálogos em super‑perfis sem motivo legítimo e base legal.
- Quarto, atenção às políticas da OpenAI e aos requisitos do Store (vamos tratar disso em detalhes nos módulos sobre publicação e segurança), que explicitam quais dados podem ou não ser levados para fora do ChatGPT. Na fase de projeto da análise, é útil já prever anonimização e minimização de dados para não ter que reescrever meio sistema depois.
E, por fim, lembre‑se de que análise de UX não é vigilância total e certamente não é surveillance ao estilo Big Brother. O objetivo é melhorar cenários e reduzir a frustração do usuário, não criar um painel Big Brother do tipo “quem às 2:37 da madrugada não chegou ao checkout”.
10. Erros comuns na análise de UX de workflow
Erro nº 1: “Ainda não estamos em produção, as métricas ficam para depois”.
Muitas vezes, os desenvolvedores começam a pensar na análise quando o App já está sendo usado por pessoas reais. Como resultado, os eventos são introduzidos “depois do fato”, os dados ficam fragmentados e comparar versões antiga e nova do cenário se torna quase impossível. É melhor planejar um funil mínimo ("workflow_started", "step_started", "step_completed", "workflow_finished") desde o início, enquanto o código ainda é relativamente simples.
Erro nº 2: Logar apenas sucessos e ignorar falhas.
Às vezes, nos logs há apenas "step_completed", e ninguém registra "step_failed" porque “isso não deveria falhar”. Como resultado, você vê que pouca gente chega a certa etapa, mas não entende se saíram por vontade própria ou se foram expulsos por erros. Sempre registre tanto a conclusão bem‑sucedida quanto a malsucedida, com pelo menos um errorCode grosseiro.
Erro nº 3: Ausência total de vínculo à versão do workflow.
Você muda textos, a ordem das etapas, introduz tool gating, e nos eventos a workflowVersion segue sendo "1.0.0". Um mês depois você olha os gráficos e não consegue entender o que foi antes das mudanças e o que foi depois. Fixar a versão do cenário e, se necessário, a variante A/B — é um elemento obrigatório da análise.
Erro nº 4: Análise detalhada demais sem motivo.
O extremo oposto — tentar construir desde o início o “esquema perfeito” de eventos com 50 campos, registrar cada clique em cada pixel e cada caractere digitado. Primeiro, isso pode violar a privacidade. Segundo, esses dados são difíceis de analisar e você vai se afogar em ruído. É melhor começar com um conjunto pequeno de eventos e métricas que realmente respondam a perguntas de produto específicas, e depois evoluir o sistema conforme a necessidade.
Erro nº 5: Inconsistência nos nomes das etapas e dos cenários.
Às vezes, no código a etapa se chama budget, na análise — collect_budget, e no relatório — “etapa com a pergunta sobre dinheiro”. Em poucas semanas, ninguém lembra o que é o quê. Na fase de projeto do workflow, é útil combinar identificadores estáveis das etapas (stepName) e usá‑los tanto no UI quanto nos logs e nos relatórios.
Erro nº 6: Ter métricas que ninguém usa.
A história mais triste: você coletou cuidadosamente um monte de dados, configurou o envio de eventos do widget e do MCP, mas ninguém abre os dashboards nem toma decisões. Análise por análise não serve; sempre se pergunte: “Que decisão eu consigo tomar com base nesta métrica?”. Se não houver resposta — você ainda não precisa dessa métrica.
GO TO FULL VERSION