Prompt Template Pattern com LangChain
Como separar a estrutura fixa do prompt das variáveis dinâmicas para criar prompts reutilizáveis, fáceis de manter e mais testáveis.
O problema dos prompts fixos
Quase todo projeto com LLM começa com o prompt escrito direto no código, montado com f-string:
prompt = f"Você é um assistente que responde em estilo {estilo}. Pergunta: {pergunta}"
resposta = llm.invoke(prompt)
Funciona no primeiro dia. Depois aparecem os problemas: o mesmo prompt copiado em vários arquivos, ajustes de tom que exigem mexer no código, nenhuma forma de saber quais variáveis o prompt espera e nenhum aviso quando uma delas falta.
O Prompt Template Pattern resolve isso tratando o prompt como um componente: uma estrutura fixa com lacunas nomeadas, definida em um só lugar e preenchida em tempo de execução.
O que é o padrão
Em vez de uma string pronta, você define um template com placeholders entre chaves, como {style} e {question}. O template sabe quais variáveis precisa receber e só vira mensagem quando elas são preenchidas.
| Benefício | Na prática |
|---|---|
| Reutilização | O mesmo template atende perguntas, estilos e idiomas diferentes |
| Manutenção | Mudou o formato do prompt? Altera em um único lugar |
| Validação | O template conhece as variáveis esperadas e acusa as que faltam |
| Composição | Templates se combinam com modelos e parsers em chains |
| Testabilidade | Dá para testar variações de prompt sem mexer na lógica de negócio |
Seu primeiro template
No LangChain, o template para modelos de chat é o ChatPromptTemplate. Cada mensagem é uma tupla com o papel (system, user) e o texto com as variáveis:
from langchain_core.prompts import ChatPromptTemplate
system = ("system", "Você é um assistente que responde em estilo {style}.")
user = ("user", "{question}")
chat_prompt = ChatPromptTemplate.from_messages([system, user])
print(chat_prompt.input_variables)
# ['question', 'style']
Repare que o template já descobriu sozinho quais variáveis precisa. Para gerar as mensagens finais, preencha os valores:
messages = chat_prompt.format_messages(
style="divertido",
question="Quem foi Alan Turing?",
)
for m in messages:
print(m.type, "->", m.content)
# system -> Você é um assistente que responde em estilo divertido.
# human -> Quem foi Alan Turing?
Dica: a mensagem de system define o comportamento do assistente; a de user carrega o pedido. Manter essa separação deixa claro o que é regra fixa e o que muda a cada chamada.
Montando a chain com LCEL
O padrão fica realmente útil quando o template entra numa chain. Com a LCEL (LangChain Expression Language), você liga os componentes com o operador |, como um pipe do terminal: o template gera as mensagens, o modelo responde e o parser transforma a resposta em texto.
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
llm = init_chat_model(model="gemini-2.5-flash", model_provider="google_genai")
parser = StrOutputParser()
chain = chat_prompt | llm | parser
result = chain.invoke(
{"style": "formal", "question": "O que é uma Chain em IA Generativa?"}
)
print(result)
O init_chat_model cria o modelo a partir do nome e do provedor. Trocar Gemini por outro modelo é mudar dois parâmetros; o template e o resto da chain continuam iguais.
Como toda chain LCEL, ela ganha de graça outros modos de execução:
# Várias entradas de uma vez
respostas = chain.batch([
{"style": "formal", "question": "O que é RAG?"},
{"style": "didático", "question": "O que é um embedding?"},
])
# Resposta em streaming, pedaço por pedaço
for pedaco in chain.stream({"style": "curto", "question": "O que é LCEL?"}):
print(pedaco, end="", flush=True)
Organizando no projeto
No repositório pattern_chain, o padrão está separado em três responsabilidades: os prompts, a montagem da chain e o ponto de entrada.
src/
├── main.py # ponto de entrada
└── prompt_template/
├── __init__.py # exports públicos
├── prompts.py # templates
└── chain.py # montagem da chain
prompts.py guarda os templates, cada um numa função com nome descritivo:
from langchain_core.prompts import ChatPromptTemplate
def create_qa_prompt() -> ChatPromptTemplate:
"""Prompt de perguntas e respostas com as variáveis style e question."""
system = ("system", "you are an assistant that answers questions in a {style} style")
user = ("user", "{question}")
return ChatPromptTemplate.from_messages([system, user])
def create_formatter_prompt() -> ChatPromptTemplate:
"""Prompt para formatar um conteúdo no formato pedido."""
system = ("system", "Format the following content as {format_type}")
user = ("user", "{content}")
return ChatPromptTemplate.from_messages([system, user])
chain.py recebe qualquer template e devolve a chain pronta. O modelo é um parâmetro, então a mesma função serve para qualquer prompt e qualquer provedor:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model
def create_chain(
prompt: ChatPromptTemplate,
model_name: str = "gemini-2.5-flash",
model_provider: str = "google_genai",
):
"""Cria uma chain LCEL com prompt, modelo e parser."""
llm = init_chat_model(model=model_name, model_provider=model_provider)
parser = StrOutputParser()
return prompt | llm | parser
def create_basic_chain():
"""Chain básica usando o prompt de perguntas e respostas."""
from prompt_template.prompts import create_qa_prompt
return create_chain(create_qa_prompt())
main.py só carrega as variáveis de ambiente e executa:
from dotenv import load_dotenv
load_dotenv()
from prompt_template import create_basic_chain
def main():
chain = create_basic_chain()
result = chain.invoke(
{"style": "formal", "question": "What is Chain in Generative AI?"}
)
print(result)
if __name__ == "__main__":
main()
Para rodar, copie o .env.example para .env, preencha a GOOGLE_API_KEY e execute:
poetry install
python src/main.py
Boas práticas
1. Dê nomes claros às variáveis
{user_question} e {context} dizem o que esperam; {x} e {input2} não. O nome da variável é a documentação de quem vai preencher o template.
2. Documente cada template
Uma docstring curta dizendo o propósito e os valores esperados evita que o template seja usado para algo que ele não cobre.
3. Deixe o template validar a entrada
Se faltar uma variável, a chain falha antes de gastar uma chamada ao modelo, com uma mensagem que diz exatamente o que faltou:
chat_prompt.invoke({"style": "formal"})
# KeyError: Input to ChatPromptTemplate is missing variables {'question'}.
# Expected: ['question', 'style'] Received: ['style']
Para regras de negócio (um estilo dentro de uma lista permitida, um texto com tamanho máximo), valide antes de chamar a chain.
4. Fixe valores que não mudam com partial
Quando uma variável tem o mesmo valor em todo um fluxo, preencha-a antes e deixe só o que muda em aberto:
formal_prompt = chat_prompt.partial(style="formal")
print(formal_prompt.input_variables)
# ['question']
5. Use chaves duplas para texto literal
Tudo entre { } vira variável. Se o prompt precisa mostrar um JSON de exemplo, escape as chaves com {{ }}; senão o template vai exigir variáveis que não existem.
6. Mantenha os prompts fora da lógica de negócio
Prompts num módulo próprio (como o prompts.py) podem ser revisados, versionados e testados sem tocar no resto do código.
Resumo e próximos passos
- Um template separa a estrutura fixa do prompt das variáveis que mudam a cada chamada.
- O
ChatPromptTemplatedescobre as variáveis sozinho e acusa as que faltam. - Com LCEL,
prompt | llm | parservira uma chain cominvoke,batchestream. - Separar prompts, chains e ponto de entrada deixa o projeto pronto para crescer.
O passo natural depois disso é encadear vários templates, em que a saída de um alimenta o próximo. É o assunto do tutorial Sequential Chain Pattern na prática.