2

Domando o Indomável - 4 formas de controlar o formato das mensagens geradas por IA e facilitar integrações

Adicionar funcionalidades com IA nas nossas aplicações é uma tarefa muito simples e muito difícil ao mesmo tempo.

É simples porque integrar qualquer treco a um modelo LLM é fácil demais hoje em dia: Basta criar uma conta em um provedor (OpenAI, Antropic, Google), escolher qual modelo usar (Fable 5, GPT 6, sei lá, tem tantos), programar uma integração simples com a API deles e voilá, tá feito.

Integrar é muito simples.

Agora conseguir tirar da IA o que tu precisa pra a aplicação funcionar, é outra história.

Qualquer modelo tem uma natureza aleatória indomável, e é praticamente impossível controlar O QUE ela vai escrever, mas podemos ao menos tentar controlar COMO ela vai escrever.

O Problema

Supondo que você é um 👨‍💻 dev 👩‍💻, é provável que queira integrar uma IA no seu sistema para performar ações junto ao usuário (famosos Agentes).

O caso mais clássico disso é aceitar uma solicitação do usuário em texto livre e utilizar uma IA para interpretar esse input e padronizar as ações que precisam ser executadas no sistema.

Dou um exemplo: Em um app de agenda, você pode permitir que o usuário escreva mensagens como ‘quero ver minha agenda da semana’ ou ‘marque uma reunião para 12h amanhã com fulaninho’ e utilizar uma IA para extrair (do texto livre) quais ações devem ser performadas pelo sistema (dentre um grupo de ações pré-definidos).

E aqui nós podemos cair em uma armadilha, pois geralmente a primeira ideia de padronização de mensagens envolve pedir para a IA codificar as solicitações com palavras chave e estruturas rudimentares, mais ou menos assim:

  • Para consultar o banco de dados, escreva CONSULTA < sql > FIM_CONSULTA
  • Para executar uma das funções disponíveis, escreva CALL nome_da_funcao(parametros)

Mas vai por mim, a última coisa que você quer se ver fazendo é escaneando uma resposta gerada por IA com Regex em busca de palavras chaves pra entender o que danado ela está querendo fazer.

Um JSON quentinho e um café, por favor (prompt engineering)

Pra evitar isso, uma técnica muito comum é solicitar ao modelo que gere respostas em um formato realmente padrão, normalmente JSON. É uma abordagem puramente de Prompt Engineering, a gente orienta o modelo a gerar respostas em um formato JSON seguindo o schema passado.

Exemplo de Prompt:

Você é um agente auxiliar bla bla bla ...

Para executar uma das funções disponíveis, escreva a ação em formato JSON 
contendo os campos 'funcao' com o nome da função
e 'parametros' com os parâmetros que a função vai receber

Exemplos:

{
	funcao: 'consultar_agenda_usuario',
	parametros: ['João Pedro']
}

{
	funcao: 'desmarcar_reuniao',
	parametros: [0123]
}

Funções disponíveis:

- consultar_agenda_usuario(nome): Consulta a agenda do usuário pra o dia de hoje
- marcar_reuniao(data, titulo, convidados): Marca uma reunião
- desmarcar_reuniao(id): Desmarca uma reunião

Se o modelo obedecer, nossa aplicação consegue facilmente identificar quais ações ele deseja performar.

Mas orientação não é garantia - o modelo ainda pode falhar, gerar JSONs fora do schema ou sequer gerar um JSON.

Um código que se baseia somente nessa abordagem normalmente vai estar cheio de retries e fluxos de exceção contra diversos tipos de falha do modelo e pode acabar ficando bem complexo.

Dando um empurrãozinho (Prefix Filling)

Uma forma de dar um empurrãozinho no modelo para a direção certa é com o Prefix Filling.

Nele, além da mensagem do usuário/sistema, nos também damos o trecho inicial da resposta do modelo.

Ou seja, o LLM não vai gerar uma resposta totalmente do zero, mas sim continuar a partir do trecho inicial que nós fornecemos.

No exemplo abaixo, nós queremos que o modelo gere uma tabela HTML, então é fornecido como ‘prefixo’ o início das tags do cabeçalho:

from openai import OpenAI

client = OpenAI(
    api_key="<your api key>",
    base_url="https://api.deepseek.com/beta",
)

prefixo = """
	<table>
          <thead>
            <tr>
            <th>Nome</th>
            <th>Data de Nascimento</th>
            <th>Total de Gols</th>
"""

response = client.responses.create(
    model="deepseek-v4-pro",
    input=[
        {
            "role": "system",
            "content": (
                "Responda a pergunta do usuário em uma tabela HTML"
            ),
        },
        {
            "role": "user",
            "content": "Faça uma lista dos 10 maiores jogadores de Futebol"
        },
        {
            "role": "assistant",
            "content": prefixo,
            "prefix": True
        },
    ],
)

print("Continuação gerada:\n")
print(response.output_text)

Nesse outro exemplo, tentamos induzir o modelo a gerar um JSON válido

prefixo = """
	{
		'funcao': 
"""

response = client.responses.create(
    model="deepseek-v4-pro",
    input=[
        {
            "role": "system",
            "content": (
                """
                Você é um agente auxiliar bla bla bla ...
                Para executar uma das funções disponíveis, 
                escreva a ação em formato JSON
                """
            ),
        },
        {
            "role": "user",
            "content": "Quais meus compromissos pra hoje?"
        },
        {
            "role": "assistant",
            "content": prefixo,
            "prefix": True
        },
    ],
)

print("Continuação gerada:\n")
print(response.output_text)

E aqui dá pra ser bem criativo- unindo prompt e o trecho certo, conseguimos induzir o modelo a respeitar o padrão desejado.

Dando um empurrãozão (JSON Mode)

Mas garantia de formato mesmo a gente consegue com o JSON Mode, e esse é bem direto.

Alguns modelos de IA mais avançados suportam restrições de gramática, o que significa que eles NÃO CONSEGUEM gerar respostas fora do padrão esperado.

E é bem fácil de usar, na API do seu modelo favorito deve ter um parâmetro ‘json_mode’ ou ‘json_output’ que você pode ligar. É só ativar e pronto, todas as respostas agora serão em formato JSON.

prefixo = """
	{
		'funcao': 
"""

response = client.responses.create(
    model="deepseek-v4-pro",
    input=[
        {
            "role": "system",
            "content": (
                """
                Você é um agente auxiliar bla bla bla ...
                Para executar uma das funções disponíveis, 
                escreva a ação em formato JSON
                """
            ),
        },
        {
            "role": "user",
            "content": "Quais meus compromissos pra hoje?"
        }
    ],
    response_format={
        'type': 'json_object'
    }
)

print("Continuação gerada:\n")
print(response.output_text)

Mas isso ainda não é tudo, a gente consegue garantir que a resposta é um JSON, mas não COMO é esse JSON. Por isso, precisamos ir além.

Agora sim! (Constrained Decoding +)

O JSON Mode que a gente acabou de ver é um caso particular de Constrained Decoding, um conjunto de matemáticas e estatísticas mágicas que forçam o modelo a seguir um padrão de sintaxe.

Algumas implementações - como o Structured Outputs da OpenAI - dão um passo a mais e conseguem garantir não somente sintaxe, mas também que a resposta gerada vai seguir o SCHEMA fornecido.

Ele também é batata de usar, basta verificar na documentação do seu provedor se ele possui suporte a essa funcionalidade e como utilizar, daí é só fornecer o schema e consumir a resposta normalmente.

Aqui o exemplo tirado direto da documentação Oficial da OpenAI (usando schemas definidos pelo Pydantic diretamente no Python):

from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]

response = client.responses.parse(
    model="gpt-5.6",
    input=[
        {"role": "system", "content": "Extract the event information."},
        {
            "role": "user",
            "content": "Alice and Bob are going to a science fair on Friday.",
        },
    ],
    text_format=CalendarEvent,
)

event = response.output_parsed

Essa funcionalidade, embora muito poderosa, é mais rara e geralmente só está disponível em modelos mais avançados (e caros!).


Conclusão

Na minha opinião esses são macetes indispensáveis pra quem quer desenvolver sistemas integrados a LLM modernos. A IA é aleatória, mas nossa aplicação não é! O mínimo de garantia de estrutura é indispensável pra facilitar o desenvolvimento do nosso lado 😉

Referências

Carregando publicação patrocinada...