Structured Output: como forçar JSON na Prompt API
Structured Output: como forçar JSON na Prompt API
Structured Output na Prompt API é o que te permite forçar o Gemini Nano a devolver JSON válido, obedecendo um schema que você definiu via responseConstraint. Disponível desde o Chrome 137, a mecânica é direta: você declara a estrutura, o modelo segue. Sem preâmbulos, sem markdown, sem “Certamente! Aqui está a resposta:”.
TL;DR
responseConstraintforça o Gemini Nano a retornar JSON válido conforme um JSON Schema definido por você- Funciona com boolean, string, number, enum, arrays e objetos aninhados — constraint a nível de decodificação
- Disponível desde Chrome 137, funciona com
prompt()epromptStreaming()— elimina parsing manual
Para mim, é o recurso mais útil da API inteira. Sem ele, você fica parseando texto livre com regex — e quem já tentou extrair dados estruturados de output de LLM sabe o inferno que é.

Por que isso importa
LLMs adoram ser verbosos. Mesmo quando você instrui o modelo a responder “true” ou “false”, ele pode soltar um “Certamente! Com base na análise, a resposta é: true”. Para código que precisa processar esses dados programaticamente, isso quebra tudo.
O responseConstraint resolve o problema na raiz. Em vez de pedir gentilmente que o modelo siga um formato — e torcer para ele obedecer — você impõe a estrutura via JSON Schema. O modelo gera apenas tokens compatíveis com o schema declarado. É constraint decoding, não prompt engineering. A diferença é como pedir versus exigir.
JSON Schema: o básico
Se você já usou TypeScript interfaces ou Zod, o conceito é familiar — declarativo, independente de linguagem:
const schema = {
type: "object",
properties: {
resultado: { type: "string" },
confianca: { type: "number" }
},
required: ["resultado", "confianca"],
additionalProperties: false
};
Tipos suportados
| Tipo JSON Schema | Descrição | Exemplo |
|---|---|---|
boolean | Verdadeiro/falso | Spam/não-spam |
string | Texto livre ou com pattern | Extração, enum |
number | Numérico | Score, rating |
object | Objeto com propriedades | Entidade estruturada |
array | Lista tipada | Tags, categorias |
enum | Valor de lista fixa | Categorias predefinidas |
Na prática
A propriedade é passada como opção nos métodos prompt() e promptStreaming():
const session = await LanguageModel.create({
expectedInputs: [{ type: "text", languages: ["pt"] }],
expectedOutputs: [{ type: "text", languages: ["pt"] }],
});
const resultado = await session.prompt(
"Este texto é uma pergunta? 'Quanto custa o frete?'",
{ responseConstraint: { type: "boolean" } }
);
console.log(JSON.parse(resultado)); // true
O retorno é sempre JSON válido que satisfaz o schema. Sem floreios.
Exemplos práticos
1. Classificação binária
O caso mais simples — forçar true ou false:
const session = await LanguageModel.create();
const isSpam = await session.prompt(
"Classifique se é spam: 'GANHE R$5000 AGORA! Clique aqui!!!'",
{
responseConstraint: { type: "boolean" }
}
);
console.log(JSON.parse(isSpam)); // true
Duas linhas de lógica. Sem parser customizado, sem regex, sem “espero que o modelo responda no formato certo”. Funciona.
2. Extração de entidades
Extrair dados estruturados de texto livre — o tipo de coisa que regex faz mal e LLMs fazem razoavelmente bem:
const schema = {
type: "object",
properties: {
nome: { type: "string" },
email: { type: "string", pattern: "^[^@]+@[^@]+\\.[^@]+$" },
telefone: { type: "string" },
assunto: { type: "string" }
},
required: ["nome", "assunto"],
additionalProperties: false
};
const texto = `
Olá, meu nome é Maria Silva, meu email é [email protected].
Preciso de ajuda com a troca de um produto defeituoso.
`;
const entidades = await session.prompt(
`Extraia as entidades de contato do texto:\n\n${texto}`,
{ responseConstraint: schema }
);
console.log(JSON.parse(entidades));
// { nome: "Maria Silva", email: "[email protected]", assunto: "troca de produto defeituoso" }
3. Classificação multi-label com array
Quando o texto pode pertencer a mais de uma categoria:
const schema = {
type: "object",
properties: {
categorias: {
type: "array",
maxItems: 3,
items: {
type: "string",
enum: ["tecnologia", "esportes", "política", "entretenimento", "ciência"]
}
},
sentimento: {
type: "string",
enum: ["positivo", "negativo", "neutro"]
}
},
required: ["categorias", "sentimento"],
additionalProperties: false
};
const analise = await session.prompt(
"Classifique: 'O novo chip da Apple supera benchmarks com IA on-device'",
{ responseConstraint: schema }
);
console.log(JSON.parse(analise));
// { categorias: ["tecnologia", "ciência"], sentimento: "positivo" }
O maxItems: 3 impede que o modelo retorne a lista inteira (porque ele vai tentar, acredite).
4. Validação de formulário
Usar o LLM para validar coisas que regex não pega — nomes absurdos, emails que são sintaticamente válidos mas claramente falsos, contextos semânticos:
const schema = {
type: "object",
properties: {
valido: { type: "boolean" },
erros: {
type: "array",
items: {
type: "object",
properties: {
campo: { type: "string" },
mensagem: { type: "string" }
},
required: ["campo", "mensagem"]
}
}
},
required: ["valido", "erros"],
additionalProperties: false
};
const dadosFormulario = JSON.stringify({
nome: "J",
email: "nao-eh-email",
cpf: "123.456.789-00"
});
const validacao = await session.prompt(
`Valide os dados deste formulário. Verifique se nome tem ao menos 2 caracteres, email é válido, e CPF tem formato correto:\n\n${dadosFormulario}`,
{ responseConstraint: schema }
);
console.log(JSON.parse(validacao));
// { valido: false, erros: [{ campo: "nome", mensagem: "Nome muito curto" }, { campo: "email", mensagem: "Formato de email inválido" }] }
Não substitui validação backend (obviamente), mas como feedback instantâneo ao usuário enquanto ele preenche o formulário, funciona bem.
Prefix mode: controle extra sobre o início
Para cenários onde você quer forçar o modelo a começar de um jeito específico:
const resultado = await session.prompt([
{
role: "user",
content: "Liste 3 cores primárias em JSON"
},
{
role: "assistant",
content: "```json\n",
prefix: true
}
]);
Força o modelo a continuar a partir do prefixo. Útil quando responseConstraint é pesado demais para schemas triviais.
Opções avançadas
omitResponseConstraintInput
Por padrão, o schema é enviado ao modelo como parte do contexto (consome tokens). Se seu prompt já descreve o formato desejado, pode pular essa injeção:
const resultado = await session.prompt(
`Resuma este feedback num rating de 0 a 5. Retorne { "rating": número }:
A comida estava deliciosa, serviço excelente.`,
{
responseConstraint: schema,
omitResponseConstraintInput: true
}
);
Quando usar: schemas complexos onde o contexto está apertado e você já descreveu tudo no prompt textual.
measureContextUsage com schema
Schemas consomem tokens. Para monitorar:
const session = await LanguageModel.create();
const usage = await session.measureContextUsage(
"Meu prompt aqui",
{ responseConstraint: schemaGrande }
);
console.log(`Tokens usados: ${usage.usedTokens} / ${usage.totalTokens}`);
Limitações e boas práticas
| Aspecto | Recomendação |
|---|---|
| Schemas aninhados demais | Manter simples — profundidade degrada qualidade |
pattern em strings | Funciona, mas regex complexo pode causar respostas vazias |
Arrays sem maxItems | Sempre definir — senão o modelo pode gerar listas enormes |
| Tipos numéricos | Usar minimum/maximum quando possível |
| Enums | Preferir a strings livres para categorização |
| Context window | Schema consome tokens — monitorar |
Na minha experiência, schemas com até 3 níveis de profundidade funcionam bem. Acima disso, a qualidade das respostas começa a cair.
Testando seu schema
O Google disponibiliza o JSON Schema Tester para validar antes de integrar.
Para validação em runtime, use Ajv:
import Ajv from 'ajv';
const ajv = new Ajv();
const validate = ajv.compile(schema);
const resposta = JSON.parse(await session.prompt(meuPrompt, { responseConstraint: schema }));
if (!validate(resposta)) {
console.error('Schema inválido:', validate.errors);
// retry ou fallback
}

FAQ
O responseConstraint garante 100% de JSON válido?
Sim. No modo structured output, o modelo é constrained a nível de decodificação — gera apenas tokens que formam JSON válido conforme o schema. Diferente de pedir “responda em JSON” no prompt.
Quais versões do Chrome suportam isso?
Desde o Chrome 137. Para melhor experiência, use Chrome 148+.
Funciona com promptStreaming()?
Sim. O responseConstraint funciona tanto com prompt() quanto com promptStreaming(). No streaming, chunks são entregues progressivamente, mas o resultado final completo é JSON válido.
O schema consome tokens do context window?
Sim. É convertido internamente em instruções para o modelo. Use omitResponseConstraintInput: true se já instruiu via prompt textual.
Posso usar $ref e definitions no schema?
Não. A implementação atual suporta apenas schemas flat (inline). Sem referências externas ou $ref.
Conclusão
O responseConstraint é o que transforma a Prompt API de “brinquedo de geração de texto” em interface tipada para IA on-device. Com JSON Schema, você ganha dados estruturados, validáveis e previsíveis — rodando local no Gemini Nano, sem custo, sem rede.
É o tipo de feature que deveria ter existido desde o dia um. Mas está aqui agora, funciona, e muda fundamentalmente o que dá pra construir com IA no browser.
Combine com streaming para UX responsiva e multimodal para aplicações completas de IA no browser.
Próximo artigo: IA multimodal no browser: imagens e áudio com Gemini Nano