Conteúdo da trilha
PadrãoContexto e conhecimento

Reescrita de Consulta

Query Rewriting

Reescrita de Consulta (Query Rewriting) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Contexto e conhecimento. Transformar a pergunta do usuário em uma ou mais buscas melhores.

Elemento
14/70
Nível
Intermediário
Leitura
7 min
Atualizado
Nesta página 17
  1. 01Intenção
  2. 02Problema
  3. 03Solução
  4. 04Estrutura
  5. 05Pseudocódigo
  6. 06Cenários reais
  7. 07Aplicabilidade
  8. 08Quando não usar
  9. 09Como implementar
  10. 10Variações
  11. 11Custo, latência e falhas
  12. 12Como avaliar
  13. 13Em produção
  14. 14Segurança
  15. 15Prós e contras
  16. 16Código
  17. 17Teste seu entendimento
  18. ··Perguntas rápidas

Intenção

Transformar a pergunta do usuário numa ou mais buscas melhores antes de consultar o índice. Serve para sistemas de busca e RAG em que as perguntas chegam vagas, dependentes da conversa, com siglas ou misturando vários assuntos.

Em uma frase
O balconista que ouve “aquele remédio pra pressão que minha mãe toma” e procura pelo nome certo.

Problema

Rodrigo cuida do chat interno de uma empresa de energia. As perguntas raramente chegam prontas para busca. “E para quem é PJ?” só faz sentido depois da pergunta anterior, sobre reembolso de despesas. “Qual a regra do VR no home?” usa duas siglas e uma abreviação que nenhum documento usa. “Como funciona férias e o que acontece se eu pedir demissão no meio” tem dois assuntos que estão em documentos diferentes.

Mandar essas frases direto para o índice traz resultados ruins. A primeira não tem assunto; a segunda não tem as palavras que os documentos usam; a terceira acha um dos dois temas e ignora o outro.

A busca híbrida ajuda quando a consulta já está bem formada. Ela não inventa o assunto que a mensagem escondeu, nem separa duas perguntas coladas.

A pergunta que este padrão responde: como converter o jeito que as pessoas perguntam no jeito que os documentos estão escritos?

Solução

Antes da busca, um passo curto reescreve a pergunta com o contexto disponível:

  • Resolver referências: incorporar o que a conversa já disse (“e para PJ?” vira “reembolso de despesas para prestadores PJ”).
  • Expandir termos: trocar siglas e gírias internas pelos termos dos documentos, com a ajuda de um glossário.
  • Decompor: separar perguntas com vários assuntos em buscas independentes.
  • Gerar variações: duas ou três formulações diferentes, para aumentar o recall.

O resultado é estruturado (uma lista de buscas, cada uma com filtros opcionais) e vai para a busca. A pergunta original continua sendo a que o modelo principal responde.

Estrutura

flowchart LR
  Q(["1. Pergunta e histórico"]):::io --> RW(["2. Reescrever em buscas"]):::llm
  G[("Glossário interno")]:::store --> RW
  RW --> S1["3. Busca 1"]:::tool
  RW --> S2["3. Busca 2"]:::tool
  S1 --> U["4. Unir e deduplicar"]:::tool
  S2 --> U
  U --> M(["5. Responder a pergunta original"]):::llm
  1. A pergunta chega com as últimas mensagens da conversa.
  2. Um modelo pequeno reescreve em uma ou mais buscas, usando o glossário.
  3. As buscas rodam em paralelo.
  4. Os resultados são unidos, sem duplicados.
  5. O modelo principal responde a pergunta original, com os trechos encontrados.

Exemplo trabalhado

Histórico recente: “Como funciona o reembolso de despesas de viagem?”. Pergunta nova: “e para quem é PJ?”.

Sem reescrita, a busca híbrida procura PJ e devolve páginas sobre contratação de pessoa jurídica, nada de reembolso. Com o passo de reescrita e o glossário {PJ: prestador de serviço pessoa jurídica}, a saída estruturada fica:

{
  "queries": [
    { "text": "reembolso de despesas de viagem para prestador PJ", "area": "financeiro" }
  ],
  "needs_clarification": false
}

A busca agora acha a política certa. O modelo principal ainda vê a pergunta original (“e para quem é PJ?”) mais os trechos, então responde no tom da conversa, não com um parágrafo solto sobre a política.

Segundo caso, dois assuntos: “Como funciona férias e o que acontece se eu pedir demissão no meio”. A reescrita devolve duas buscas, férias CLT e demissão durante férias. Sem decompor, a híbrida tende a ficar só com o documento de férias, que é o mais parecido com a primeira metade.

Checagem +20 XP de primeiraA busca da pergunta 'e para PJ?' devolveu artigos de contratação de PJ, nenhum de reembolso. O histórico tinha a pergunta sobre reembolso de viagem. O que a reescrita deveria ter feito?

Pseudocódigo

schema Rewrite { queries: list[{ text: string, filter: { area: string? } }], needs_clarification: bool }

function retrieve(question, history):
    glossary = glossary_for(question)                                  # siglas encontradas
    rw = llm(REWRITE_PROMPT, question, history[-6:], glossary, model="small") -> Rewrite   # 2
    if rw.needs_clarification:
        return ask_user("Você quer saber sobre X ou Y?")
    parallel {                                                         # 3
        results = [hybrid_search(q.text, filters=q.filter, k=20) for q in rw.queries]
    }
    trace.span("rewrite", original=question, queries=rw.queries)
    return dedupe(flatten(results))                                    # 4

Cenários reais

  • Chat com a base de conhecimento interna. Siglas e apelidos internos viram os termos oficiais dos documentos; perguntas de continuação herdam o assunto da conversa.
  • Busca semântica em e-commerce. “Tênis pra correr na chuva até 400” vira busca por categoria, atributo (impermeável) e filtro de preço.
  • Bot de suporte com escalonamento humano. Mensagens longas e confusas viram uma ou duas buscas objetivas na central de ajuda.

Aplicabilidade

  • Perguntas em conversa, que dependem do que veio antes.
  • Vocabulário dos usuários diferente do vocabulário dos documentos.
  • Perguntas com mais de um assunto.
  • Filtros implícitos na frase (preço, data, área) que a busca pode aplicar.

Quando não usar

  • As perguntas já chegam bem formadas, como numa busca de catálogo por termo. A reescrita só acrescenta latência.
  • Latência muito apertada em cada consulta. Use um glossário em código para expandir siglas, sem modelo.
  • O problema é a ordenação, não a consulta. Veja Reordenação.

No RAG Agêntico, a reescrita deixa de ser um passo fixo: o agente decide se busca de novo. O padrão desta página é o passo fixo, barato, antes de uma busca só.

Checagem +20 XP de primeiraA caixa de busca do catálogo recebe 'SKU 88213' em 80% das consultas. Um colega quer reescrever toda consulta com um modelo. O que você faz?

Como implementar

  1. Junte perguntas reais que falham na busca e classifique o motivo: referência, vocabulário, vários assuntos, filtro implícito.
  2. Monte um glossário de siglas e apelidos internos.
  3. Escreva o prompt de reescrita com saída estruturada, pedindo poucas buscas.
  4. Passe as últimas mensagens da conversa, não o histórico inteiro.
  5. Rode as buscas em paralelo e una os resultados.
  6. Meça recall das buscas com e sem reescrita, por tipo de falha.
  7. Registre a pergunta original e as reescritas no trace.

O glossário começa pequeno: VR, PJ, home, N2, o apelido do sistema de RH. Cada pergunta sem resultado depois da reescrita é candidata a uma entrada nova, com revisão de quem conhece o domínio.

Erros comuns

  • Cinco variações para uma pergunta simples. O prompt precisa de um teto (3 é um começo) e de needs_clarification para ambiguidade de verdade.
  • Histórico demais. Vinte turnos antigos contaminam a pergunta nova. Seis recentes costumam bastar; um turno de “obrigado” não deve reabrir o assunto anterior.
  • Responder com a pergunta reescrita. O usuário perguntou “e para PJ?”. A resposta fala de “prestador de serviço pessoa jurídica” no tom de um manual. Reescreva para buscar, responda à fala original.
  • Filtro inventado. “até 400” virou max_price_cents: 400 em vez de 40000. Confira unidades no eval.

Variações

  • Expansão por glossário em código. Sem modelo: troca siglas conhecidas por termos oficiais. Barato e previsível.
  • Documento hipotético. O modelo escreve uma resposta imaginária e a busca usa o texto dela, que se parece mais com os documentos do que a pergunta. É a ideia do HyDE (Gao et al., 2022).
  • Pergunta de esclarecimento. Quando a pergunta é ambígua demais, o sistema pergunta ao usuário em vez de adivinhar.
  • Reescrita dentro do agente. No RAG Agêntico, o próprio agente decide e refaz as buscas.

Custo, latência e falhas

Aspecto O que acontece Como aparece no trace
Chamadas Uma chamada pequena por pergunta, mais uma busca por consulta gerada span rewrite com as consultas
Latência Soma a reescrita; buscas em paralelo duração de rewrite
Custo Modelo pequeno, entrada curta custo de rewrite por pergunta
Falha: reescrita muda o sentido A busca procura outra coisa consultas sem relação com a pergunta original
Falha: buscas demais Cinco variações para uma pergunta simples número médio de consultas alto
Falha: histórico errado Assunto antigo contamina a pergunta nova reescrita com termos de uma conversa já encerrada

Como avaliar

  • Recall da busca com e sem reescrita, por tipo de pergunta.
  • Fidelidade: uma pessoa ou um juiz confere se a reescrita mantém a intenção.
  • Qualidade final da resposta, no eval completo.
{ "history": ["Como funciona o reembolso de despesas de viagem?"], "question": "e para quem é PJ?", "expected_queries_contain": ["reembolso", "prestador PJ"] }

Inclua casos em que a reescrita deve recusar: “isso” sem histórico, ou duas leituras igualmente possíveis. needs_clarification=true é sucesso nesses casos, não falha.

Em produção

  • Span rewrite com a pergunta original, as consultas e os filtros.
  • Painel de perguntas sem resultado depois da reescrita, para alimentar o glossário.
  • Amostra de reescritas para revisão de fidelidade.

Se a consulta gerada não contém nenhum termo da original nem do glossário, trate como anomalia. É o sinal mais barato de distorção de sentido.

Segurança

O histórico da conversa pode trazer dados pessoais (nome, matrícula, valor de reembolso). A reescrita lê esse histórico. Mascare o que a busca não precisa antes de mandar ao modelo pequeno, e não grave o histórico cru no span se o trace for amplo demais.

A saída da reescrita vai para o índice, não para o usuário, mas um filtro inventado (area: rh quando a pergunta era financeira) muda quem pode ver o trecho se você usa o mesmo campo para permissão. Não reutilize filtro extraído como controle de acesso.

Prós e contras

Prós Contras
Perguntas em conversa passam a funcionar Uma chamada a mais por pergunta
Vocabulário interno vira o dos documentos Reescrita pode distorcer a intenção
Perguntas com vários assuntos acham os dois Mais buscas, mais custo de índice
Filtros implícitos viram filtros reais Glossário precisa de manutenção

Código

As duas abas reescrevem a pergunta com o histórico recente e um glossário, e rodam as buscas geradas em paralelo.

Python. Use quando o RAG roda em Python e você quer a reescrita com saída estruturada.

import asyncio

import anthropic
from pydantic import BaseModel

client = anthropic.AsyncAnthropic()

GLOSSARY = {"VR": "vale-refeição", "home": "trabalho remoto", "PJ": "prestador de serviço pessoa jurídica"}


class Query(BaseModel):
    text: str
    area: str | None = None


class Rewrite(BaseModel):
    queries: list[Query]
    needs_clarification: bool


async def rewrite(question: str, history: list[str]) -> Rewrite:
    terms = {k: v for k, v in GLOSSARY.items() if k.lower() in question.lower()}
    reply = await client.messages.parse(  # 2
        model="claude-haiku-4-5", max_tokens=500,
        system="Reescreva a pergunta em até 3 buscas autocontidas para a base de RH. "
               "Resolva referências à conversa, use os termos do glossário e separe assuntos diferentes.",
        messages=[{"role": "user", "content": f"Conversa recente: {history[-6:]}\nGlossário: {terms}\nPergunta: {question}"}],
        output_format=Rewrite,
    )
    return reply.parsed_output


async def retrieve(question: str, history: list[str]) -> list[dict]:
    rw = await rewrite(question, history)
    if rw.needs_clarification:
        return []  # o chamador pergunta ao usuário
    results = await asyncio.gather(*(search(q.text, area=q.area, k=20) for q in rw.queries))  # 3
    seen, merged = set(), []
    for doc in (d for r in results for d in r):  # 4
        if doc["id"] not in seen:
            seen.add(doc["id"])
            merged.append(doc)
    return merged

O que você ganhou: buscas autocontidas a partir de perguntas de conversa. O que você perdeu: uma chamada a mais em toda pergunta; pule a reescrita quando a pergunta já for autocontida e sem siglas.

TypeScript. Use quando a busca é de catálogo e a reescrita extrai filtros da frase.

import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";

const client = new Anthropic();

const ProductQuery = z.object({
  text: z.string(),
  category: z.enum(["tenis", "roupa", "acessorio", "outro"]).nullable(),
  max_price_cents: z.number().int().nullable(),
  attributes: z.array(z.string()),
});

export async function rewriteSearch(userText: string) {
  const { parsed_output } = await client.messages.parse({
    model: "claude-haiku-4-5",
    max_tokens: 300,
    system:
      "Converta a busca do cliente em consulta de catálogo. Extraia categoria, preço máximo em centavos e atributos " +
      "(ex.: impermeável, amortecimento). O campo text deve usar termos do catálogo, sem gíria.",
    messages: [{ role: "user", content: userText }],
    output_config: { format: zodOutputFormat(ProductQuery) },
  });
  if (!parsed_output) return { text: userText, category: null, max_price_cents: null, attributes: [] };
  return parsed_output; // "tênis pra correr na chuva até 400" -> { text: "tênis de corrida", category: "tenis", max_price_cents: 40000, attributes: ["impermeável"] }
}

O que você ganhou: filtros exatos aplicados pelo índice, em vez de depender do vetor para entender preço. O que você perdeu: em caso de falha, a busca cai para o texto original, sem filtros.

Teste seu entendimento

Checagem +20 XP de primeiraA reescrita transformou 'quero o mesmo, mas para estagiário' em uma busca sobre o programa de estágio, sem o assunto da política de home office da pergunta anterior. O que falhou?

Checagem +20 XP de primeiraO span rewrite mostra 5 consultas para 'qual o horário do refeitório?'. Recall não melhorou. Qual é o ajuste?

Checagem +20 XP de primeiraA pergunta é 'isso vale para o Norte também?', depois de um turno sobre tarifa de energia. Duas leituras são possíveis: região Norte ou horário noturno. O que o padrão pede?

Checagem +20 XP de primeiraRecall subiu depois da reescrita, mas as respostas ficaram formais demais para um chat. As consultas geradas estão corretas. Onde está o bug?

Perguntas rápidas

O que é Reescrita de Consulta?

Reescrita de Consulta (Query Rewriting) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Contexto e conhecimento. Transformar a pergunta do usuário em uma ou mais buscas melhores.

Onde Reescrita de Consulta aparece na prática?

Nos cenários reais Chat com a base de conhecimento interna, Busca semântica em e-commerce e Bot de suporte com escalonamento humano.

Para ir além

Relações

Aparece nas trilhas 1
Buscar