03-01-01 — Roles: system, user, assistant — e por que importam
TL;DR
Todo LLM de chat estrutura a conversa em três roles: system (instruções persistentes do desenvolvedor), user (input do humano) e assistant (resposta do modelo). O system prompt é sua alavanca mais poderosa — define personalidade, restrições e formato da saída antes de o usuário digitar uma única palavra.
O modelo de mensagens
Quando você chama a API do GPT-4, do Claude ou do Gemini, não está enviando um texto único. Está enviando um array de mensagens, cada uma com um role. Pense nisso como uma peça de teatro: o system define o palco, o user fala a fala, o assistant responde.
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "Você é um assistente jurídico especializado em direito empresarial brasileiro. Responda sempre de forma objetiva, cite artigos de lei quando relevante, e recuse pedidos fora do escopo jurídico."
},
{
"role": "user",
"content": "O que é uma sociedade limitada?"
}
]
)
print(response.choices[0].message.content)
O que cada role faz
system — a personalidade persistente
O system prompt é executado antes de qualquer mensagem do usuário. É aqui que você define:
- Persona: quem o modelo é ("você é um assistente sênior de DevOps")
- Restrições: o que ele não deve fazer ("nunca revele dados pessoais")
- Formato: como responder ("sempre use bullet points", "responda em JSON")
- Contexto de negócio: informações que todo usuário vai precisar
appsettings.json — configuração que precede a execução. Mude o system prompt e você tem uma aplicação completamente diferente, sem alterar uma linha de código de negócio.
user — o input
Mensagens com role user representam o que o humano (ou seu sistema) está enviando. Podem conter texto, imagens (em modelos multimodais), ou até resultados de tool calls. É a entrada de dados da sua pipeline.
assistant — o output que virou contexto
Cada resposta do modelo tem role assistant. Em conversas multi-turn, você inclui as respostas anteriores no array de mensagens para que o modelo "lembre" do que disse. Sem isso, cada chamada é stateless — o modelo começa do zero.
O poder do system prompt: exemplo comparativo
O mesmo input do usuário, dois system prompts diferentes. Resultado radicalmente diferente:
import openai
user_input = "Explique machine learning em 3 linhas."
# System prompt 1: sem restrições
response_1 = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Você é um assistente útil."},
{"role": "user", "content": user_input}
]
)
# System prompt 2: audiência específica
response_2 = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "Você fala com CEOs de empresas de tecnologia .NET. Use analogias de arquitetura de software. Sem jargão acadêmico. Máximo 3 linhas."
},
{"role": "user", "content": user_input}
]
)
# Resposta 1: genérica, acadêmica
# Resposta 2: "Machine learning é como um sistema que aprende regras de negócio
# automaticamente a partir de dados históricos, em vez de você codificá-las.
# É o oposto de um if/else: o modelo descobre os padrões, você só fornece os exemplos."
Multi-turn: memória dentro da janela
LLMs são stateless por natureza. A "memória" de uma conversa é uma ilusão que você constrói injetando o histórico no array de mensagens:
messages = [
{"role": "system", "content": "Você é um assistente de suporte técnico da Impar."}
]
# Simula uma conversa de 3 turnos
turns = [
"Qual é o prazo de SLA para incidentes críticos?",
"E para incidentes de alta prioridade?",
"Me dá um resumo do que você disse."
]
for user_input in turns:
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="gpt-4o",
messages=messages # envia TODO o histórico
)
assistant_reply = response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_reply})
print(f"User: {user_input}")
print(f"Assistant: {assistant_reply}\n")
Diferença entre providers: OpenAI vs Anthropic
A OpenAI usa os três roles (system, user, assistant). A Anthropic (Claude) separa o system do array de mensagens — ele vai num parâmetro dedicado:
import anthropic
client = anthropic.Anthropic()
# No Claude, system é parâmetro separado, não uma mensagem
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system="Você é um assistente de arquitetura de software .NET.", # parâmetro top-level
messages=[
{"role": "user", "content": "Quando usar microserviços vs monolito?"}
]
)
print(response.content[0].text)
AzureOpenAI em vez de OpenAI no SDK Python, ou configure via variáveis de ambiente AZURE_OPENAI_ENDPOINT e AZURE_OPENAI_API_KEY.
Boas práticas para system prompts
- Seja específico: "responda em português brasileiro" é melhor que "responda no idioma do usuário"
- Defina o que não fazer: restrições negativas são tão importantes quanto positivas
- Coloque exemplos: um exemplo vale mil palavras de instrução
- Versione seus prompts: trate system prompts como código — commits, diffs, testes
- Teste com adversários: tente quebrar seu próprio prompt antes que o usuário faça isso
Como isso se conecta
- → 03-01-02 Context window management: o multi-turn que você acabou de ver consome tokens rapidamente — o próximo tópico explica como gerenciar isso
- → 03-03-01 Prompt injection: usuários mal-intencionados tentam sobrescrever seu system prompt — precisa entender roles para defender
- → 03-02-03 Structured outputs: system prompts são onde você instrui o modelo a responder em JSON