Few-Shot Prompting com LangChain
Como usar exemplos dentro do prompt para mostrar ao modelo o tom, o tamanho e a estrutura das respostas, em vez de tentar descrever tudo isso em regras.
Descrever estilo é difícil
Toda empresa tem um jeito de falar com o cliente. Tente colocar isso num prompt e o resultado vira uma lista de regras:
Responda de forma amigável mas profissional, sem "Prezado" nem "Atenciosamente".
Use no máximo três frases. Comece reconhecendo o problema, sem exagerar.
Termine sempre com um próximo passo concreto e um prazo.
Não use emojis. Trate o cliente por "você"...
Cada regra é uma interpretação: o que é "amigável mas profissional"? Quanto é "sem exagerar"? O modelo acerta algumas, erra outras, e você continua acrescentando regras.
Agora compare com mostrar uma resposta do jeito certo:
Cliente: Fui cobrado duas vezes na fatura deste mês.
Resposta: Poxa, cobrança em dobro não dá! Já pedi o estorno da segunda cobrança
e ele aparece na sua fatura em até 5 dias úteis. Se não aparecer, é só
responder este e-mail.
Um exemplo carrega o tom, o tamanho e a estrutura de uma vez. Esse é o Few-Shot Prompting: dar ao modelo alguns exemplos ("few shots") de entrada e saída, para ele imitar.
Como o padrão funciona
Em modelos de chat, os exemplos entram como se fossem conversas anteriores: uma mensagem do cliente seguida da resposta ideal. Depois deles vem a mensagem real. O modelo vê o padrão e continua no mesmo estilo.
No LangChain, quem monta essa sequência é o FewShotChatMessagePromptTemplate. Ele pode usar sempre os mesmos exemplos ou escolher, a cada mensagem, os mais parecidos com ela.
Escrevendo os exemplos
Os exemplos são dados comuns: uma lista de dicionários com os mesmos campos. No repositório são seis, cobrindo assuntos diferentes:
EXAMPLES: list[dict[str, str]] = [
{
"message": "Fui cobrado duas vezes na fatura deste mês.",
"reply": "Poxa, cobrança em dobro não dá! Já pedi o estorno da segunda "
"cobrança e ele aparece na sua fatura em até 5 dias úteis. "
"Se não aparecer, é só responder este e-mail.",
},
{
"message": "O sistema não carrega desde ontem, fica só a tela branca.",
"reply": "Que chato, vamos resolver isso. Limpa o cache do navegador e "
"tenta de novo? Se a tela branca continuar, me manda um print "
"do console (F12) e eu respondo ainda hoje.",
},
{
"message": "Quero cancelar minha assinatura.",
"reply": "Tudo bem, cancelamento feito sem multa. Você continua com "
"acesso até o fim do período já pago. Se quiser, posso pausar "
"a conta por 3 meses em vez de cancelar.",
},
# + reembolso, webhook e nota fiscal
]
Repare no que se repete em todas as respostas: uma reação curta ao problema, a solução e um próximo passo com prazo, em três frases. É essa consistência que o modelo aprende. Exemplos que se contradizem (um formal, outro informal) ensinam que qualquer estilo serve.
Também é preciso dizer como cada exemplo vira mensagens. Esse é o example_prompt:
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
SYSTEM = (
"You are the support team of Nuvem, a SaaS company. "
"Reply to the customer in Portuguese following exactly the tone, "
"length and structure of the previous answers."
)
# Cada exemplo vira uma mensagem humana e uma resposta da IA
EXAMPLE_PROMPT = ChatPromptTemplate.from_messages(
[("human", "{message}"), ("ai", "{reply}")]
)
Exemplos fixos
O caso mais simples: todos os exemplos vão em toda chamada. O FewShotChatMessagePromptTemplate entra no meio do prompt, entre a instrução de sistema e a mensagem real:
def _wrap(few_shot):
"""System instructions, then the examples, then the real message."""
return ChatPromptTemplate.from_messages(
[("system", SYSTEM), few_shot, ("human", "{message}")]
)
def create_fixed_few_shot_prompt(examples):
few_shot = FewShotChatMessagePromptTemplate(
examples=examples,
example_prompt=EXAMPLE_PROMPT,
)
return _wrap(few_shot)
A chain é a mesma de sempre:
def create_fixed_few_shot_chain(llm=None, examples=EXAMPLES):
llm = llm or _default_llm()
return create_fixed_few_shot_prompt(examples) | llm | StrOutputParser()
chain = create_fixed_few_shot_chain()
chain.invoke({"message": "Paguei o boleto mas a conta ainda aparece bloqueada."})
Com os seis exemplos, o modelo recebe 14 mensagens: a de sistema, os 6 pares e a mensagem real.
Seleção dinâmica
Seis exemplos cabem tranquilamente. Com sessenta, mandar todos em toda chamada fica caro, e a maioria não ajuda: para uma dúvida sobre webhook, o exemplo de reembolso só ocupa espaço.
A solução é um seletor de exemplos. O SemanticSimilarityExampleSelector transforma cada exemplo num vetor (embedding), guarda numa base vetorial e, a cada mensagem, devolve os k exemplos mais parecidos:
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_google_genai import GoogleGenerativeAIEmbeddings
def create_semantic_selector(embeddings=None, examples=EXAMPLES, k=2):
if embeddings is None:
embeddings = GoogleGenerativeAIEmbeddings(model="gemini-embedding-001")
return SemanticSimilarityExampleSelector.from_examples(
examples,
embeddings,
InMemoryVectorStore,
k=k,
input_keys=["message"], # compara só a mensagem, não a resposta
)
O prompt troca examples= por example_selector= e declara qual variável o seletor usa para comparar:
def create_dynamic_few_shot_prompt(selector):
few_shot = FewShotChatMessagePromptTemplate(
example_selector=selector,
example_prompt=EXAMPLE_PROMPT,
input_variables=["message"],
)
return _wrap(few_shot)
Com k=2, o modelo recebe 6 mensagens em vez de 14, e os dois exemplos são do mesmo assunto da mensagem. Nos testes do repositório, uma mensagem sobre cobrança em duplicidade puxou o exemplo de cobrança, um pedido de cancelamento puxou o de cancelamento e um problema de webhook puxou o de webhook.
Sobre o input_keys: sem ele, o seletor compara a mensagem nova com o exemplo inteiro, resposta incluída. Como a resposta não existe ainda na mensagem nova, comparar só o campo message dá resultados mais precisos.
O InMemoryVectorStore é suficiente para dezenas ou centenas de exemplos e não precisa de nenhum serviço externo. Os embeddings, por outro lado, são chamadas de API: os exemplos são vetorizados uma vez, quando o seletor é criado, e cada mensagem nova gera mais uma chamada.
Fixo ou dinâmico?
| Exemplos fixos | Seleção dinâmica | |
|---|---|---|
| Quando usar | Poucos exemplos (até uns 10) que valem para tudo | Muitos exemplos, cada um útil para um tipo de pedido |
| Tamanho do prompt | Cresce com cada exemplo novo | Fixo em k exemplos |
| Dependências | Nenhuma | Modelo de embeddings e base vetorial |
| Previsibilidade | O modelo sempre vê o mesmo contexto | O contexto muda a cada mensagem |
| Custo extra | Tokens dos exemplos em toda chamada | Uma chamada de embeddings por mensagem |
Comece com exemplos fixos. Passe para a seleção dinâmica quando a lista crescer a ponto de o prompt ficar grande, ou quando exemplos de um assunto começarem a "vazar" para respostas de outro.
Boas práticas e cuidados
- De 3 a 6 exemplos costumam bastar. Mais do que isso raramente melhora o estilo e sempre aumenta o prompt.
- Cubra a variedade, não repita o mesmo caso. Seis exemplos de cobrança ensinam a responder cobrança; um de cada assunto ensina o estilo.
- Mantenha o formato idêntico. Se uma resposta tem três frases e outra tem dez, o modelo não sabe qual seguir.
- Cuidado com fatos dentro dos exemplos. O modelo copia estilo, mas também pode copiar conteúdo: o "até 5 dias úteis" do exemplo de cobrança pode aparecer numa resposta onde o prazo é outro. Regras de negócio reais devem vir de dados ou da instrução de sistema, não só dos exemplos.
- Use exemplos reais. As melhores respostas que seu time já escreveu são exemplos melhores do que os inventados.
- Trate exemplos como código. Guarde-os versionados (como o
examples.py) e revise quando mudar uma política: um exemplo desatualizado ensina a resposta errada. - Combine com as outras partes. Few-shot funciona junto com Structured Output (exemplos de saída no formato do schema) e com o Router (um conjunto de exemplos por rota).
Resumo e referências
- Few-shot mostra o estilo com exemplos em vez de descrevê-lo em regras.
- Em modelos de chat, os exemplos entram como pares humano → IA antes da mensagem real.
FewShotChatMessagePromptTemplateaceita exemplos fixos ou um seletor.SemanticSimilarityExampleSelectorescolhe os exemplos mais parecidos com a mensagem, usando embeddings.- O modelo pode copiar fatos dos exemplos: dados de negócio devem vir de outra fonte.
Esse último ponto leva direto à Parte 7 (RAG): como buscar a informação certa em documentos e usá-la na resposta, em vez de depender do que está no prompt.