Contextualizando o tema e objetivos
Introdução
Neste vídeo, vamos comentar sobre a engenharia de prompts (instruções): o que é e algumas boas práticas, com conselhos práticos para criar bons prompts (instruções). Antes, queremos problematizar a situação.
Problematizando com exemplos práticos
Problematização
Vamos imaginar que estamos interagindo com o Claude Code ou mesmo com o ChatGPT e decidimos que queremos programar, que queremos recodificar uma API (interface de programação de aplicações), um endpoint (rota de API) para listar tarefas do projeto no qual estamos trabalhando. Então, pedimos à nossa LLM (Large Language Model, Modelo de Linguagem Grande), ao ChatGPT ou ao Claude: "Crie uma API (interface de programação de aplicações) para listar tarefas para nós."
Depois de um tempo, obtemos a resposta. Ao revisar, temos um código em Python usando Flask, que é o framework (estrutura de suporte) HTTP do Python, com a lista de tarefas totalmente hardcoded (inserida diretamente no código). Além disso, há uma rota definida como /tarefas, quando a convenção do nosso projeto seria, em inglês, /tasks. Estranho, certo?
Para ilustrar essa primeira situação, segue um exemplo simples de como esse retorno poderia aparecer:
from flask import Flask, jsonify
app = Flask(name)
tarefas = ["comprar leite", "estudar", "malhar"]
@app.route("/tarefas")
def listar():
return jsonify(tarefas)
app.run()
Note como a lista está fixa no código e a rota segue o padrão em português (/tarefas), destoando do esperado no projeto (/tasks). Esse tipo de desalinhamento é comum quando o prompt inicial é demasiado genérico.
Agora, suponhamos que, em outra sessão, em vez de simplesmente dizer "Crie uma API (interface de programação de aplicações) para listar tarefas", sejamos mais específicos e digamos: "Crie uma API (interface de programação de aplicações) para listar as tarefas em TypeScript, com o input (entrada) x — o x pode ser substituído por qualquer estrutura de dados desejada — devolvendo também o input (entrada) y", sem detalhar aqui, mas presumindo que seja uma estrutura de dados pensada previamente, com validações e consultando o banco de dados. A partir dessa interação com uma LLM, com o Claude Code ou com o ChatGPT, obtemos como resultado um código em TypeScript, com validações, que respeita o payload (corpo da requisição) — isto é, o input (entrada) que tínhamos em mente — e cuja saída esperada realiza as validações de forma correta.
Um exemplo de retorno mais alinhado com o pedido, incorporando validações e uma consulta a dados, poderia ser:
import { z } from "zod";
import express from "express";
const app = express();
const QuerySchema = z.object({
status: z.enum(["pendente", "concluida"]).optional(),
limit: z.coerce.number().min(1).max(100).default(20),
});
type Tarefa = { id: string; titulo: string; status: "pendente" | "concluida" };
app.get("/tarefas", (req, res) => {
const parsed = QuerySchema.safeParse(req.query);
if (!parsed.success) {
return res.status(400).json({ error: parsed.error.flatten() });
}
const { status, limit } = parsed.data;
const tarefas: Tarefa[] = buscarTarefas({ status, limit });
res.json({ data: tarefas, total: tarefas.length });
});
Perceba como esse exemplo já traz: tipagem explícita (TypeScript), validação de entrada com Zod, tratamento de erro com status 400 quando a query é inválida e um esboço de integração com dados via buscarTarefas. Tudo isso emerge de um prompt mais específico e contextualizado.
Qual é a diferença entre essas duas situações? Basicamente, isso exemplifica a aplicação de técnicas de engenharia de prompts (instruções).
Definindo a engenharia de prompts
Definição de engenharia de prompts
Se fôssemos definir formalmente o que é engenharia de prompts (instruções), diríamos que é o processo de projetar e refinar as instruções que enviamos em nossas interações com modelos de linguagem.
Como acabamos de ver, para realizar a mesma tarefa — a mesma intenção — podemos obter resultados totalmente diferentes, dependendo de como estruturamos nossos prompts (instruções) e do contexto fornecido. Uma característica da engenharia de prompts (instruções) é que se trata de uma disciplina extremamente iterativa. Há bastante tentativa e erro quando trabalhamos e fazemos a engenharia do nosso prompt (instrução).
É provável que a primeira versão do prompt (instrução) que criamos não seja a mais adequada — isso normalmente ocorre. Na primeira versão da interação com a LLM, muitas vezes não obtemos a resposta desejada. Mas tentamos novamente, refinamos, adicionamos contexto e, gradualmente, vamos nos aproximando do que desejamos.
Esse é um conselho: se não obtivermos um bom resultado em uma interação com a LLM, talvez o problema esteja no nosso prompt (instrução). Vale a pena insistir, refinar, pegar o prompt (instrução) inicial, adicionar mais contexto, ou aplicar algumas técnicas que vamos comentar agora, e testar novamente para observar o resultado.
Detalhando os componentes essenciais
Componentes de um bom prompt
Com relação aos componentes de um bom prompt (instrução), ele costuma ter algumas seções que o tornam eficaz.
-
Contexto: Se fornecermos contexto no prompt (instrução), maiores são as probabilidades de que a LLM acerte. No exemplo em que pedimos "Crie uma API (interface de programação de aplicações) para listar tarefas", a partir do momento em que informamos que queríamos TypeScript e validações, o resultado obtido foi muito melhor. Portanto, o contexto é um componente muito importante ao criar nossos prompts (instruções).
-
Instrução clara: Outro ponto é a instrução clara.
É altamente recomendável que, em cada interação com uma LLM (Modelo de Linguagem Grande), deixemos claro qual é o objetivo e qual é a tarefa que deve ser realizada pela IA, de forma não ambígua e, de preferência, apenas uma tarefa por interação. Quanto mais específicos formos, melhores serão os resultados.
Como exemplo, estamos construindo um prompt (instrução). Suponhamos que queiramos usar a IA para revisar um pull request (solicitação de alteração), isto é, o código de uma pessoa colega. Partindo dessa suposição, já definimos o contexto: anteriormente indicamos que a LLM (Modelo de Linguagem Grande) deve atuar como revisora de código. A instrução clara seria: queremos que revise a função a seguir, a função do código da pessoa colega, e aponte problemas de desempenho e legibilidade. Dizer apenas “revisar a função” é ambíguo; ao acrescentarmos mais contexto — isto é, “apontar problemas de desempenho e legibilidade” — ficamos mais específicos, o que é positivo do ponto de vista do prompt (instrução).
Para tornar esse exemplo concreto, considere que a função a ser revisada é:
def get_active_users(users):
result = []
for u in users:
if u.active == True:
result.append(u)
return result
Outro ponto importante a incluir em nossos prompts (instruções) é a questão dos exemplos, o que muitas pessoas chamam de few-shot (poucos exemplos). Imaginemos que mentoramos uma pessoa e pedimos que faça algo sem fornecer exemplos de como fazer. Ela precisará inferir muitas coisas — “adivinhar”, por assim dizer — e, dependendo de como formulamos o pedido, talvez não consiga inferir corretamente. A partir do momento em que fornecemos exemplos de como executar a tarefa e de como chegar ao resultado desejado, aumentamos a probabilidade de que essa pessoa entregue o que esperamos. Com as LLMs (Modelos de Linguagem Grande) é a mesma lógica: se oferecermos exemplos, é mais provável que sigam o que esperamos. No nosso caso hipotético de revisão de código, se adicionarmos exemplos de frases que a LLM (Modelo de Linguagem Grande) poderia usar durante a avaliação, melhor. Assim, a LLM (Modelo de Linguagem Grande) terá um caminho mais claro a seguir e irá pela direção que desejamos.
Para guiar o tom da revisão com exemplos (few-shot), podemos incluir algo como:
- "Aqui dá pra simplificar usando..."
- "Isso pode ficar mais performático se..."
Outro componente importante são as restrições. Assim como dizemos à LLM (Modelo de Linguagem Grande) o que deve fazer, convém também deixar claro o que não deve fazer. Declarar os casos negativos muitas vezes é até mais importante do que os positivos. Neste caso, não queremos, por exemplo, que, durante a revisão, a IA reescreva a função, mude a linguagem de programação ou faça qualquer outra coisa além de revisar. Queremos que se mantenha focada nesse aspecto. A LLM (Modelo de Linguagem Grande) tende, às vezes, a realizar ações adicionais ou coisas que não pedimos; por isso, é útil especificar esses casos negativos — as restrições.
Aplicando isso ao nosso exemplo de revisão, podemos explicitar:
- Restrições: Não reescreva a função inteira. Não sugira trocar de linguagem ou framework. Foque só na função dada.
Outro componente extremamente importante é o formato de saída. Se quisermos uma saída específica, precisamos tornar isso explícito para a LLM (Modelo de Linguagem Grande). Se quisermos que a avaliação seja apresentada de determinada maneira, temos de explicitar qual é essa forma. Isso é extremamente importante; o output (formato de saída) talvez seja um dos componentes mais cruciais que podemos incluir em nossos prompts (instruções). Por mais potente que seja o modelo, o formato de saída costuma ser um ponto de falha — “falha”, entre aspas, porque o modelo não consegue acertar se não tiver o contexto. Fornecer o contexto sobre o formato de saída é muito útil nas interações. No exemplo de prompt (instrução) que estamos construindo, colocamos como última parte o formato de saída: no processo de avaliação, a saída gerada pela LLM (Modelo de Linguagem Grande) será uma lista com 3 marcadores (3 itens), cada qual contendo um problema e uma sugestão de correção. Estamos indicando exatamente a estrutura de cada item para evitar erros.
Especificando o formato de saída no nosso caso:
- Formato de saída: Lista com no máximo 3 bullets, cada um com o problema e a sugestão de correção.
Apresentando outras técnicas complementares
Existem também outras técnicas comumente utilizadas. As que mencionamos até aqui são as mais importantes, mas vale pontuar brevemente outras:
-
Role prompting (definição de papel): quando pedimos à LLM (Modelo de Linguagem Grande) que assuma um papel específico, é mais provável que a resposta seja precisa. No nosso caso, já fizemos role prompting (definição de papel): solicitamos que a LLM (Modelo de Linguagem Grande) atue como uma pessoa desenvolvedora sênior especializada em Python. Ao assumir o papel, o modelo passa a considerar várias dimensões implícitas: necessidade de código escalável, legível, que funcione bem sob pressão, entre outras. Há toda uma semântica que podemos incorporar ao prompt (instrução) simplesmente ao pedir que a LLM (Modelo de Linguagem Grande) adote um papel.
-
Chain of thought (cadeia de raciocínio): essa técnica ganhou validação após a grande expansão do uso de LLMs (Modelos de Linguagem Grande) em 2022 e 2023. Ela consiste em solicitar que a LLM (Modelo de Linguagem Grande) raciocine “escrevendo” antes de responder. Se fizermos um pedido e dissermos algo como “Pense passo a passo; ao resolver o problema, vá explicando passo a passo como resolveria e, ao final, dê a resposta”, aumentamos a probabilidade de um bom resultado ao final do processo. Esse comportamento é natural também em seres humanos: ao resolvermos um problema de matemática complexo em uma única linha, corremos o risco de ignorar partes importantes da equação. Já ao separar o problema em etapas — resolver uma parte, depois outra, e então combinar as duas em uma seção específica — aumentamos a clareza do processo e a chance de um resultado melhor. Com as LLMs (Modelos de Linguagem Grande) ocorre o mesmo. A questão do chain of thought (cadeia de raciocínio) tornou-se tão relevante que, a partir de 2024, muitos modelos passaram a incorporá-lo de forma natural. A maioria dos modelos com os quais interagimos hoje são modelos de thinking (raciocínio). É possível notar isso ao fazer um pedido no chat (bate-papo): o modelo de thinking (raciocínio) do chat (bate-papo) leva um pouco mais de tempo, “fica pensando”. Nos bastidores, está realizando o chain of thought (cadeia de raciocínio), emitindo tokens (unidades de texto), gerando tokens (unidades de texto) para raciocinar sobre a resposta.
-
Decomposição de tarefas: é útil dividir tarefas em etapas menores. Isso vale para nós, humanos, e também para interações com IA. Se temos um projeto muito ambicioso — por exemplo, uma tese a ser entregue na faculdade — e definimos apenas “resolver a tese” como problema, fica difícil de digerir. Ao dividir em partes menores (introdução, revisão bibliográfica, conclusão etc.), o trabalho se torna mais viável, e sabemos exatamente quais passos seguir para resolver o problema maior. Com LLMs (Modelos de Linguagem Grande) é igual: se a tarefa for muito complexa, é recomendável pedir que a LLM (Modelo de Linguagem Grande) a divida em partes menores e avance passo a passo. Possivelmente, ao executar cada subtarefa, chegará ao resultado final da tarefa maior. Naturalmente, ao longo do fluxo de desenvolvimento, veremos que muitas vezes pediremos explicitamente à LLM (Modelo de Linguagem Grande) que planeje o processo de desenvolvimento, escreva a lista de tarefas que precisam ser feitas e siga essa lista de to-dos (tarefas a fazer) para resolver um problema. Veremos isso mais adiante; o importante é saber que, ao decompor a tarefa em etapas menores, tanto nós, como pessoas, quanto a LLM (Modelo de Linguagem Grande) teremos mais facilidade para resolver problemas complexos.
Avaliando a relevância e otimizações
Sobre a engenharia de prompts (instruções), há quem diga que “morreu”. Na prática, ela continua muito ativa e brilha em várias situações específicas. Suponhamos que exista um fluxo de trabalho repetitivo, como extrair informações diariamente de 10.000 PDFs. Se criarmos um prompt (instrução) bem otimizado, iterado e refinado, é mais provável obtermos um bom resultado e até economizarmos dinheiro do que com um prompt (instrução) pouco refinado, pois conseguiremos reduzir tokens (unidades de texto) quando necessário e torná-lo mais conciso sem perda de qualidade.
Ao trabalhar com modelos menores, é interessante dedicar atenção à engenharia de prompts (instruções), porque esses modelos são mais sensíveis a variações do prompt (instrução). Em fluxos ágeis de desenvolvimento, haverá situações em que usaremos modelos mais antigos ou menos potentes, e, ao interagir com eles, convém ter ainda mais cuidado com o prompt (instrução).
Em qualquer interação mais séria com uma LLM (Modelo de Linguagem Grande) — escrever um e-mail, produzir código ou realizar algo que envolva algum tipo de risco — é recomendável adotar uma postura profissional e aplicar técnicas de engenharia de prompts (instruções). Isso tende a melhorar os resultados, mesmo utilizando um modelo potente e de última geração. Nessas situações mais críticas ou profissionais, vale ter um enfoque mais rigoroso e ser mais estrito em relação aos nossos prompts (instruções) do que costumamos ser. Aplicar essa forma de pensar costuma gerar resultados melhores nas interações com LLMs (Modelos de Linguagem Grande).