Mensagens e tokens especiais
Agora que sabemos como um LLM funciona, vamos ver como uma conversa vira texto — porque é isso que ela é, por baixo.
P: Mas quando eu converso com o ChatGPT, eu troco mensagens, não mando um bloco de texto único.
R: Correto — na tela. Mas isso é uma abstração da interface. Antes de chegar ao modelo, todas as mensagens da conversa são concatenadas em um único prompt. O modelo não "lembra" da conversa: ele relê tudo, inteiro, a cada resposta.
Essa frase merece um momento. Não existe memória do lado do modelo. O que existe é uma string que cresce a cada turno e é reenviada por completo. Quando a conversa fica longa demais para caber na janela de contexto, o começo dela simplesmente deixa de ser enviado — e o modelo passa a se comportar como quem nunca leu aquilo. Não é esquecimento: é truncamento.
A peça que faz essa conversão chama-se chat template. Ela é a ponte entre a lista de mensagens (com papéis system, user, assistant) e o formato exato que aquele modelo específico espera — com os tokens especiais dele.
Os três papéis
System message
A system message define como o modelo deve se comportar. É uma instrução persistente: vale para todos os turnos seguintes, não só para o próximo.
system_message = {
"role": "system",
"content": "Você é um assistente de atendimento. Seja educado, claro e objetivo."
}
Trocando o conteúdo, muda-se o comportamento:
system_message = {
"role": "system",
"content": "Você é um assistente de gabinete. Nunca responda sobre o mérito de "
"um caso concreto. Se a pergunta exigir análise processual, "
"oriente a consultar os autos."
}
Em Agents, a system message carrega bem mais do que tom de voz. É nela que entram a lista de Tools disponíveis, o formato em que as chamadas devem ser escritas, e as regras de como o raciocínio deve ser segmentado. Quando você criar um Agent com smolagents, a biblioteca monta essa mensagem para você — e ela costuma ter várias centenas de tokens.

O experimento que vale a pena fazer
Antes de seguir, um teste real. Mandamos a mesma pergunta ao qwen2:7b com duas system messages diferentes, com temperature=0:
Pergunta: "O prazo para contestação já venceu?"
Com a system message genérica ("Você é um assistente prestativo"):
No Brasil, por exemplo, o prazo para contestar uma ação civil pública é de 30 dias, contados do recebimento da notificação. No caso de ações civis particulares, o prazo é geralmente de 15 dias.
Com a system message restritiva ("Nunca responda sobre o mérito de um caso concreto... oriente a consultar os autos"):
No Brasil, por exemplo, o prazo para contestar uma ação civil ou criminal é de 10 dias úteis, conforme estabelece o Código de Processo Civil. [...] Se o prazo para contestação já venceu, o réu perde o direito de contestar a ação, mas ainda pode apresentar uma resposta à ação, conhecida como "redução da ação" [...]
Leia as duas respostas como juiz, não como aluno de IA. Há duas lições aqui, e a segunda é a mais importante.
Primeira: a system message não obrigou nada. Foi instruído a não responder sobre mérito e a mandar consultar os autos. Ele respondeu sobre mérito assim mesmo — e a menção aos autos, quando veio, veio no fim, como ressalva. A system message orienta; ela não é uma trava.
Segunda: o conteúdo jurídico não se sustenta — e a resposta mais confiante é a pior. O prazo geral de contestação no CPC é de 15 dias (art. 335), contados em dias úteis (art. 219). A primeira resposta acerta esse número por um caminho torto, depois de afirmar um prazo de 30 dias para ação civil pública que não tem base. A segunda simplesmente diz 10 dias úteis e atribui isso ao Código de Processo Civil. E vai além: apresenta um instituto chamado "redução da ação", entre aspas, com explicação de como funciona. Ele não existe. O modelo deu nome a uma coisa inexistente e descreveu o funcionamento dela com naturalidade.
Repare que a segunda resposta é mais confiante, mais estruturada e mais citada — ela invoca o Código de Processo Civil pelo nome. E é a pior das duas. A aparência de rigor não é sinal de rigor.
Nenhum dos dois textos vinha marcado como incerto. Nenhum trouxe aviso. Para quem não conhece o art. 335, as duas respostas são indistinguíveis de uma resposta correta.
Guarde essa consequência prática, porque ela desenha todo o resto do curso: escrever uma regra na system message não é implementar a regra. Se você escrever "não mostre processos em segredo de justiça", isso não é uma medida de segurança — é um pedido. O filtro de sigilo precisa estar na consulta ao banco de dados, onde o modelo não alcança. Voltamos a isso quando construirmos as Tools.
É também por isso que o Alfred deste curso devolve número de processo e metadados, e não resumos de conteúdo. Um número de processo errado o sistema rejeita na hora. Um "art. 335 diz X" errado passa.
User e assistant
Uma conversa é a alternância entre mensagens de papel user (a pessoa) e assistant (o modelo):
conversa = [
{"role": "user", "content": "Preciso dos processos de execução fiscal da 3ª Vara"},
{"role": "assistant", "content": "Posso buscar. Quer restringir por período?"},
{"role": "user", "content": "Distribuídos em 2024"},
]
Toda essa lista é concatenada e enviada como uma sequência só. Quem faz essa concatenação é o chat template.
O chat template do qwen2
Cada modelo tem seus próprios tokens especiais e suas próprias regras. O qwen2 usa o formato ChatML, com os delimitadores <|im_start|> e <|im_end|>.
A lista de mensagens acima vira este texto:
<|im_start|>user
Preciso dos processos de execução fiscal da 3ª Vara<|im_end|>
<|im_start|>assistant
Posso buscar. Quer restringir por período?<|im_end|>
<|im_start|>user
Distribuídos em 2024<|im_end|>
<|im_start|>assistant
Repare no final: a última linha abre o turno do assistant e para ali. Esse é o convite. O modelo continua a partir daquele ponto, e encerra quando gerar <|im_end|>.
O mesmo diálogo, no Llama 3.2, ficaria assim:
<|begin_of_text|><|start_header_id|>user<|end_header_id|>
Preciso dos processos de execução fiscal da 3ª Vara<|eot_id|><|start_header_id|>assistant<|end_header_id|>
Posso buscar. Quer restringir por período?<|eot_id|><|start_header_id|>user<|end_header_id|>
Distribuídos em 2024<|eot_id|><|start_header_id|>assistant<|end_header_id|>
Mesma conversa. Tokens completamente diferentes. Usar o template errado degrada a resposta sem gerar erro nenhum — o modelo recebe algo que não se parece com o que viu no treinamento e responde pior, só isso. É uma das causas mais difíceis de diagnosticar de "o modelo está estranho hoje".
Vendo o template de verdade
Não precisa acreditar. O Ollama mostra:
ollama show qwen2:7b --template
A saída é um template Go, longa. O trecho que interessa agora:
{{- if .System }}<|im_start|>system
{{ .System }}<|im_end|>
{{ end }}
...
{{- if eq .Role "user" }}<|im_start|>user
{{ .Content }}<|im_end|>
{{ else if eq .Role "assistant" }}<|im_start|>assistant
Duas observações que valem mais do que parecem:
{{- if .System }}— no qwen2, o bloco de sistema só aparece se você enviar um. Não existe system message padrão escondida. Se você não mandar nada, o modelo começa sem nenhuma orientação de comportamento.- Mais adiante, o mesmo template tem um bloco
{{- if .Tools }}e um papeltool. As Tools entram por dentro da system message, como uma lista de assinaturas de função em XML. Não é um canal separado nem uma API especial — é texto no prompt. Guarde isso; é o assunto do próximo capítulo.
Provando que é tudo uma string só
Este é o exercício que fecha a ideia. Em vez de mandar uma lista de mensagens, vamos escrever o ChatML à mão e pedir ao Ollama que não formate nada.
O endpoint /api/generate com "raw": true desliga a montagem do template: o que você mandar é literalmente o que o modelo recebe.
python exemplos\prompt_cru.py
O que o arquivo faz:
prompt = (
"<|im_start|>system\n"
"Você responde com uma palavra apenas.<|im_end|>\n"
"<|im_start|>user\n"
"Qual é a capital da França?<|im_end|>\n"
"<|im_start|>assistant\n"
)
corpo = {
"model": "qwen2:7b",
"prompt": prompt,
"raw": True, # não aplique template nenhum — mande isso como está
"stream": False,
}
Resposta do modelo:
Paris
Nenhuma lista de mensagens. Nenhum objeto de conversa. Uma string, escrita à mão, com os tokens especiais nos lugares certos. É exatamente isso que acontece por baixo de toda interface de chat que você já usou.
Vale brincar aqui. Apague o <|im_end|> do meio e rode de novo. Troque o papel system por user. Remova a última linha <|im_start|>assistant. Cada quebra ensina algo sobre o que os delimitadores estão de fato fazendo.
Base model x instruct model
Uma distinção que explica bastante coisa:
- Um base model foi treinado em texto cru para prever o próximo token. Só isso. Ele não sabe o que é uma conversa.
- Um instruct model passou por um ajuste adicional (fine-tuning) para seguir instruções e conversar. O
qwen2:7bdo Ollama é um instruct model.
O que aproxima um do outro, na prática, é justamente o chat template: é ele que apresenta o texto na forma que o modelo aprendeu a reconhecer durante o ajuste. Por isso o template não é detalhe cosmético — é a condição para o modelo se comportar como assistente.
Na biblioteca transformers, a conversão é feita por tokenizer.apply_chat_template(mensagens, tokenize=False, add_generation_prompt=True).
No nosso caso, não precisamos disso: o Ollama aplica o template correto sozinho quando você usa /api/chat, e o smolagents fala com o Ollama por esse caminho. Basta saber que a etapa existe — e agora você sabe como olhar para ela quando algo der errado.
Fixando o capítulo
Q1: A system message obriga o modelo a alguma coisa?
Q2: O que o chat template faz?
Q3: No experimento deste capítulo, a system message restritiva mandava não responder sobre o mérito. O que o modelo fez?
Vimos como a conversa é estruturada na entrada do modelo. Mas um modelo que só produz texto não consulta banco de dados nenhum.
Como ele age, então? Pelas Tools — e o mecanismo é menos mágico do que parece.