Uma interface generativa (Generative UI) confiável não pede ao LLM que emita código arbitrário de interface. Ela deixa o modelo escolher uma ferramenta ou intenção de apresentação limitada, valida o dado retornado contra um esquema e mapeia o resultado para componentes testados como gráfico, tabela, métrica, comparação ou ação. O texto continua sendo a explicação; a interface estruturada torna a evidência inspecionável.
Como a saída do modelo vira estado confiável da interface
Renderizador tipado por lista permitida e limite de ação
Esquema do resultado da ferramenta e componentes testados
- 01IntençãoFerramenta ou visual permitido
- 02EsquemaResultado validado
- 03ComponenteRenderização testada
- 04EstadosCarregando · vazio · parcial · erro
Contrato de renderização
Separe a escolha do modelo da execução do componente#
- 01
Interpretar
O modelo identifica o objetivo e escolhe uma ferramenta ou visualização permitida.
- 02
Validar
Um esquema restringe campos, tipos, limites, rótulos e estados vazios.
- 03
Renderizar
Um componente confiável recebe dados validados e cuida da acessibilidade.
- 04
Explicar
O texto declara o que a visualização mostra, a fonte e a incerteza importante.
Contrato reutilizável
Faça cada visualização explicar seu estado e sua origem#
Um envelope pequeno mantém a intenção de apresentação separada dos dados de negócio. O servidor o valida antes de o cliente escolher um componente; nomes desconhecidos, campos inválidos e ações não permitidas falham de forma segura.
O exemplo é deliberadamente genérico. Em produção, versione o esquema e autorize cada ação no servidor, sem confiar nos valores propostos pelo modelo.
Deixe o modelo escolher a intenção, não a interface executável
Este contrato sanitizado reflete um padrão de interface conversacional em produção: valide os dados, mapeie somente variantes conhecidas e autorize efeitos colaterais fora da resposta do modelo.
type ViewSpec =
| { schema_version: "1"; kind: "metric"; label: string; value: number }
| { schema_version: "1"; kind: "comparison"; rows: Array<{ label: string; value: number }> };
type RenderNode = { component: "MetricCard" | "ComparisonTable"; props: object };
function parseView(input: unknown): ViewSpec | null {
if (!input || typeof input !== "object") return null;
const value = input as Record<string, unknown>;
if (value.schema_version !== "1") return null;
if (value.kind === "metric" && typeof value.label === "string" && typeof value.value === "number") {
return value as ViewSpec;
}
if (value.kind === "comparison" && Array.isArray(value.rows)) {
const valid = value.rows.every(row => row && typeof row.label === "string" && typeof row.value === "number");
return valid ? value as ViewSpec : null;
}
return null;
}
const registry = {
metric: (view: Extract<ViewSpec, { kind: "metric" }>): RenderNode =>
({ component: "MetricCard", props: { label: view.label, value: view.value } }),
comparison: (view: Extract<ViewSpec, { kind: "comparison" }>): RenderNode =>
({ component: "ComparisonTable", props: { rows: view.rows } }),
};
export function renderToolResult(input: unknown): RenderNode | { component: "SafeFallback"; props: object } {
const view = parseView(input);
if (!view) return { component: "SafeFallback", props: { reason: "invalid_view_contract" } };
return view.kind === "metric" ? registry.metric(view) : registry.comparison(view);
}
export function authorizeAction(input: { action: string; requires_confirmation: boolean }, confirmed: boolean) {
if (input.requires_confirmation && !confirmed) return { status: "confirmation_required" };
return { status: "authorized_for_server_validation", action: input.action };
}
console.log(JSON.stringify([
renderToolResult({ schema_version: "1", kind: "metric", label: "Conversion", value: 12.4 }),
renderToolResult({ schema_version: "2", kind: "html", value: "<script>" }),
authorizeAction({ action: "publish_report", requires_confirmation: true }, false),
], null, 2));tsc trusted-renderer.ts && node trusted-renderer.jsSaída esperada
[
{
"component": "MetricCard",
"props": {
"label": "Conversion",
"value": 12.4
}
},
{
"component": "SafeFallback",
"props": {
"reason": "invalid_view_contract"
}
},
{
"status": "confirmation_required"
}
]Falha exercitada. O esquema inválido vira SafeFallback; a ação sensível permanece bloqueada até a confirmação.
Limite de produção. Um adaptador pode renderizar esses nós em React, Vue ou interface nativa. O limite de confiança independe da tecnologia adotada.
Escolha da visualização
Escolha a representação pela pergunta, não pela novidade#
Use métrica para um valor importante, tabela para consulta precisa, linha para mudança no tempo, barras para comparar categorias e texto quando a evidência não justificar visualização.
Dê ao modelo um catálogo pequeno com descrições e limites. Se nenhum componente servir, volte para texto ou tabela em vez de gerar um gráfico sem suporte.
Padrão sanitizado de produto
A conversa se torna o ponto de controle da análise#
Em um produto de análise conversacional, a pessoa não pede apenas uma resposta. Ela refina um segmento, compara alternativas, inspeciona evidências e continua a partir do estado atual.
A interface precisa de identificadores estáveis, proveniência e ações reversíveis. Uma continuação como “compare só os dois primeiros” deve transformar dados validados, não reinterpretar uma captura de tela ou um parágrafo.
Estados operacionais
Carregamento, ausência, resultado parcial, desatualização e erro fazem parte do contrato#
A execução de cada ferramenta leva tempo e pode falhar de forma independente. Componentes precisam de estados explícitos e mensagens acessíveis; a conversa precisa de recuperação que preserve a intenção.
Não exiba um gráfico vazio com aparência de certeza quando faltam dados. Mostre a diferença entre ausência de dados, filtro sem resultado, fonte indisponível e falha de ferramenta.
Segurança das ações
Um controle gerado ainda precisa de autorização convencional#
Separe a exploração somente de leitura das ações que alteram estado. Um botão proposto pelo modelo não concede permissão: o servidor precisa verificar identidade, escopo, estado atual e operação exata antes de executá-la.
Use identificadores estáveis de ação, idempotência para novas tentativas, confirmação para mudanças destrutivas ou caras e um resultado visível. Quando faltar autorização ou evidência, preserve a análise e desative a ação em vez de adivinhar.
Acessibilidade
Toda visualização precisa de um caminho não visual#
- Forneça um resumo textual do principal achado.
- Ofereça tabela ou representação equivalente.
- Não codifique sentido apenas pela cor.
- Anuncie carregamento e resultado programaticamente.
- Mantenha o foco previsível após atualizações.
- Teste reorganização do conteúdo (reflow) e ampliação em telas pequenas.
- Exponha fonte, horário, filtros e incerteza.
- Mantenha ações reversíveis ou confirme as destrutivas.
Limite
O que a interface generativa não deve gerar#
PODE
- Selecionar componentes testados
- Adaptar apresentação a dados validados
- Expor evidência progressivamente
- Apoiar análises seguintes
NÃO PODE
- Executar código arbitrário criado pelo modelo
- Tornar confiável um dado sem suporte
- Substituir acessibilidade por gráfico
- Esconder incerteza atrás de acabamento
Fontes primárias
Fontes primárias
- AI SDK: Generative User Interfaces
Resultados de ferramentas mapeados para componentes.
- JSON Schema basics
Restrições para dados estruturados.
- Web Content Accessibility Guidelines 2.2
Alternativas textuais, reorganização do conteúdo, semântica e mensagens de estado.
- OWASP Top 10 for LLM Applications 2025
Agência excessiva, tratamento inseguro de saídas e exposição de informações sensíveis.
Projete além da conversa