Structured Output: porque às vezes “responda em JSON” não é suficiente

Pedir para um modelo “responder só em JSON” dentro do próprio texto do prompt funciona, até não funcionar. E quando falha, costuma falhar de um jeito específico: uma vírgula sobrando, um texto explicativo antes do JSON, uma propriedade que deveria ser número vindo como string. Nada disso quebra a leitura humana. Quebra o parser.

O problema que “responda em JSON” não resolve sozinho

Pedir formato dentro da instrução em linguagem natural depende do modelo seguir a instrução token a token, sem garantia estrutural nenhuma. O modelo pode obedecer na quase totalidade das vezes e ainda assim, ocasionalmente, incluir um texto explicativo antes das chaves, ou fechar um array sem a vírgula esperada. Para um humano lendo, isso é irrelevante. Para um JsonSerializer.Deserialize, é uma exceção.

Isso não é falha do modelo tentando enganar ninguém. É consequência de gerar texto token por token sem nenhuma restrição estrutural imposta durante a geração.

O que muda com suporte nativo a Structured Output

A alternativa é usar suporte nativo do provedor para Structured Output, que restringe a própria geração ou validação da resposta contra um JSON Schema definido, em vez de confiar apenas na instrução em linguagem natural. A OpenAI oferece isso via Structured Outputs, com um modo estrito que garante que a resposta valida contra o schema fornecido antes mesmo de chegar de volta para a aplicação. A Anthropic também oferece suporte nativo a saída estruturada na API do Claude, validando a resposta contra um JSON Schema diretamente, sem depender de instrução textual solta no prompt.

Um JSON Schema típico usado nesse tipo de configuração:

{
  "type": "object",
  "properties": {
    "nome": { "type": "string" },
    "prioridade": { "type": "string", "enum": ["baixa", "media", "alta"] },
    "tags": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["nome", "prioridade"],
  "additionalProperties": false
}

A diferença central é que esse schema não é só documentação de intenção. Ele participa ativamente de como a resposta é gerada ou validada, dependendo do provedor.

Structured Output não é a mesma coisa que Tool Use, mas nasceu perto dele

Antes de existir suporte dedicado a Structured Output, uma prática comum era simular esse comportamento usando tool use (function calling): declarar uma “ferramenta” cujo único propósito era ter um schema de entrada bem definido, forçar o modelo a chamar essa ferramenta, e usar os argumentos recebidos como a saída estruturada desejada, mesmo sem nenhuma ferramenta sendo de fato executada.

Essa técnica ainda funciona e continua sendo usada em alguns cenários, mas suporte nativo a Structured Output resolve o mesmo problema de forma mais direta, sem precisar fingir que existe uma ferramenta que nunca vai rodar.

Onde isso aparece em C#: Microsoft.Extensions.AI

No ecossistema .NET, Microsoft.Extensions.AI oferece uma abstração de IChatClient que permite pedir uma resposta já desserializada para um tipo específico, gerando o JSON Schema a partir da definição da classe automaticamente, em vez de escrever o schema manualmente:

public class Tarefa
{
    public string Nome { get; set; } = "";
    public string Prioridade { get; set; } = "";
    public List<string> Tags { get; set; } = [];
}

var resposta = await chatClient.GetResponseAsync<Tarefa>(
    "Crie uma tarefa de prioridade alta para revisar o deploy de sexta");

A biblioteca cuida de gerar o schema, repassar para o provedor configurado por trás do IChatClient, e desserializar o resultado, com o schema derivado diretamente do tipo Tarefa.

Por que isso importa em integração real com código

Assim que a saída de um modelo alimenta código que vai processar aquele dado automaticamente (salvar num banco, decidir um fluxo, chamar outra API com os valores extraídos), depender de instrução textual solta para garantir formato é um ponto de falha desnecessário. Suporte nativo a Structured Output transforma “espero que o modelo tenha formatado certo” numa garantia estrutural verificável antes mesmo da resposta ser processada pelo restante do sistema.