Trilha de Engenharia de Prompt

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.

Parte 6 de 11 da trilha ~12 min de leitura Python LangChain Embeddings

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:

prompt · tentando descrever o estilo
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:

um exemplo
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:

src/few_shot/examples.py
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:

src/few_shot/prompts.py
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:

src/few_shot/prompts.py
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:

src/few_shot/chain.py
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:

src/few_shot/chain.py
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:

src/few_shot/prompts.py
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 fixosSeleção dinâmica
Quando usarPoucos exemplos (até uns 10) que valem para tudoMuitos exemplos, cada um útil para um tipo de pedido
Tamanho do promptCresce com cada exemplo novoFixo em k exemplos
DependênciasNenhumaModelo de embeddings e base vetorial
PrevisibilidadeO modelo sempre vê o mesmo contextoO contexto muda a cada mensagem
Custo extraTokens dos exemplos em toda chamadaUma 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.
  • FewShotChatMessagePromptTemplate aceita exemplos fixos ou um seletor.
  • SemanticSimilarityExampleSelector escolhe 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.

Referências