FMFelipe MiillerNotes on software & systems
HomeBlogAbout
GitHub

Keep building.

Felipe Miiller · © 2026

MailGitHubGitHubLinkedinGitHub
View source on GitHub
Back to blog

Qual arquitetura para qual base: as doze perguntas e a função que decide

04/10/2026
19 min de leitura
5403 palavras
RAGArquiteturaPython
  • 1. A pergunta errada, e por que ela não tem resposta
  • 2. As doze perguntas sobre a sua base
  • 3. A tabela de decisão
  • 4. A função `recomendar()`: o artigo sendo executável
  • As premissas que a função assume. Elas não são verdade: são as hipóteses que
  • você confirma (ou corrige) lendo a justificativa que ela devolve. Se uma
  • premissa é falsa para a sua base, a regra que depende dela está errada, e a
  • correção é na regra -- não em um número mágico dentro da função.
  • Um segundo já é aperto para uma tela de consulta: a pessoa já está esperando.
  • Abaixo disso, peça que soma latência (segunda busca, reordenação, segunda
  • chamada de modelo) precisa sair do caminho síncrono ou virar degradação.
  • Cada regra é (condição, arquitetura, motivo). A ordem é a ordem de decisão.
  • O que se adiciona à arquitetura escolhida. Aqui não há ordem: cada item é
  • uma condição independente, e a ordem de construção é da seção 7.
  • O que precisa estar pronto antes da arquitetura escolhida funcionar.
  • O que medir para saber se a escolha foi boa. Todos os nomes sao do artigo 11.
  • 5. Quatro bases, quatro rotas
  • Perfil 1: manual interno de uma empresa, uma pessoa mantendo, 900 documentos.
  • Perfil 2: catálogo com código de produto e número de contrato, cinco pessoas,
  • permissão por cliente, tela de busca com 800 ms de orçamento.
  • Perfil 3: normas e contratos que se referenciam, com pergunta por relação.
  • Perfil 4: suporte interno, onde a resposta exige buscar, filtrar por um
  • resultado e buscar de novo -- e o orçamento é de 40 dólares por mês.
  • 6. O que não fazer ainda
  • 7. A ordem de implantação, em quatro fases
  • 8. O custo operacional, honesto e qualitativo
  • TL;DR
  • Referências

Lede. "Qual é o melhor banco vetorial?" é a pergunta errada, e ela não tem resposta. A pergunta certa é "que tipo de pergunta o usuário faz sobre esta base". Este é o artigo que fecha a série: ele reúne as decisões dos treze em uma função executável, recomendar(), que recebe um perfil da sua base e devolve a arquitetura com a justificativa escrita. No fim, a ordem de implantação em quatro fases e o que medir no fim de cada uma. Onde você está na linha. Este é o passo 12 de 13 — o fechamento. Ele pressupõe que você leu a linha, ou pelo menos o 01, que dá o mapa e os quatro contratos. Se você chegou aqui sem ler nada: a função recomendar() funciona sozinha, e o artigo 13 fecha a série. Se você quer entender por que a função recomenda o que recomenda, ela cita o artigo de cada decisão.


1. A pergunta errada, e por que ela não tem resposta

Existe um post inteiro na internet sobre "o melhor banco vetorial em 2026", e ele

refaz a mesma lista todo ano: Qdrant, pgvector, Weaviate, Milvus, Chroma, um

índice aproximado, uma métrica de similaridade. A lista muda pouco, porque a

pergunta é quase sem conteúdo.

Um banco vetorial é uma peça. A peça não decide nada sozinha: ela responde "quais

trechos se parecem com esta pergunta", e a qualidade da resposta depende de como

os trechos foram cortados, do modelo que gerou os vetores, do filtro aplicado e do

que foi para a conversa. Trocar de banco sem mexer em nada disso entrega o mesmo

resultado com mais trabalho. O que muda o resultado é o formato da pergunta —

e é isso que a tabela da seção 3 lê.

Formato, aqui, é uma de cinco coisas:

  • Local — a resposta está em um trecho: "qual o prazo de garantia".

  • Por termo — a resposta está em um trecho, mas a pergunta contém uma

    string exata que o modelo não vai acertar por significado: "o que diz o

    contrato E-4021", "qual o código do produto 88.310". Termo técnico:

    needle in a haystack é o caso extremo, em que a única pista é o termo.

  • Por relação — a resposta só existe se você seguir um caminho: "quem

    assinou o contrato que substituiu este". Nenhum trecho isolado responde isso.

    É o formato que o GraphRAG da Microsoft

    atende, e é o único em que o grafo ganha.

  • Global — a pergunta é sobre o acervo inteiro, não sobre um documento:

    "quais são os três temas dominantes desta base". Não há resposta pronta no

    acervo; ela precisa ser sintetizada antes da consulta, e essa síntese é o

    resumo de comunidade.

  • Multi-passo — a resposta exige mais de uma ação: buscar, filtrar por um

    resultado, buscar de novo, e só então responder.

Cada formato tem uma arquitetura que resolve, e as doze perguntas da seção 2 dizem

se você tem algum deles. Nem toda base tem os cinco. A maior parte tem um, e é o

único que importa.

⚠️ Arquitetura escolhida antes do perfil é o caminho mais curto para
um sistema que não mede

O sintoma é o time passar um trimestre construindo grafo de conhecimento para

uma base em que ninguém pergunta por relação. As peças de grafo não estão

erradas — estão respondendo a uma pergunta que ninguém fez. E como nada falha,

o sistema parece pronto. É por isso que a função desta seção é pura e testável:

ela devolve a decisão com a premissa que a justificou, e você pode

discordar dela em um teste.


2. As doze perguntas sobre a sua base

As doze perguntas não são uma entrevista de vendas: são os eixos que mudam a

arquitetura. Responda com um número ou um sim/não, não com adjetivo. "Base

grande" não é resposta; "quatrocentos mil trechos" é.

1. Que formato de pergunta domina? (seção 1). É a pergunta que mais pesa: os

outros onze apenas ajustam.

2. Quantos documentos, e quantos trechos depois do corte? A distinção importa

porque o banco indexa trecho, não documento. Mil documentos com corte de 200

caracteres já são vinte mil vetores — o volume que decide se busca exata ainda

vence, o assunto do artigo 03.

3. Com que frequência a base muda? Se o conteúdo muda uma vez por trimestre,

reindexar do zero é uma wget. Se muda toda hora, reindexar do zero é uma

tragédia, e você precisa de fila e de idempotência (reindexar não pode duplicar

nada) — assunto do artigo 13.

4. A permissão é por usuário, por time, ou não existe? Quando existe, ela

precisa estar dentro da consulta, e não depois. Um tenant é o nome do

cliente, da empresa ou da unidade: a fronteira que separa o que uma pessoa pode

ler do que ela não pode. Errar aqui não é resposta ruim, é resposta que não

deveria existir, e o artigo 01 mostra as duas

versões do código lado a lado.

5. Qual a latência que a tela aguenta? Metade de segundo e um segundo não

são a mesma promessa. Esse número decide se cabe um segundo modelo reordenando os

candidatos — o reranker,

a peça mais cara da recuperação em tempo, e a mais cara em chamada de modelo — e

se cabe chamada em segunda etapa.

6. Qual o orçamento mensal? Ele decide quantas chamadas de modelo por

requisição você aguenta. Cache e degradação são as duas alavancas, e ambas têm

custo de implementação.

7. Quantas pessoas vão manter isso? Uma pessoa e um time de cinco tomam

arquiteturas diferentes. Uma pessoa não mantém grafo de conhecimento, porque o

custo é manutenção, e não o dia em que ele ficou pronto.

8. A governança exige o quê? Dado pessoal no corpus, retenção, direito de

apagamento, auditoria de quem pediu o quê. Isso não muda a arquitetura: muda o

que entra no registro de observabilidade e o que sai da resposta, e é barato

resolver antes e caro resolver depois.

9. Quantos idiomas? Uma base em dois idiomas muda a normalização, muda o

indexador léxico e muda o modelo de vetor. Uma base em cinco muda a estratégia

inteira.

10. A resposta precisa ser auditável? Se alguém vai contestar o que o sistema

afirmou, a citação é obrigatória e o registro por requisição não é opcional — o

artigo 11 é a base disso.

11. Existe SLA? Um SLA é o combinado por escrito sobre disponibilidade e

tempo de resposta. A pergunta que importa não é "tem SLA?", é "o que eu

respondo quando o modelo cai e alguém cobra o SLA?". Se a resposta é "não sei",

você ainda não tem degradação, e degradação é feature (artigo 13).

12. Precisa de resposta sem modelo? Se sim — busca, e-mail automático, o

primeiro retorno de um atendimento —, você precisa do caminho que não chama

modelo nenhum. Esse caminho é mais barato, mais rápido e mais burro, e é ele

que segura o sistema quando o provedor está fora.

A melhor medida do formato de pergunta é uma lista de dez perguntas que as pessoas

realmente já fizeram. Formato ambíguo em documento pequeno quase sempre é, na

verdade, um formato local com vocabulário ruim. Formato ambíguo em base grande é

quase sempre relação — e ninguém escreve "por relação" num formulário.


3. A tabela de decisão

Agora que as doze têm resposta, a decisão cabe numa tabela. As colunas são as

quatro arquiteturas que esta série cobre; a tabela diz, para cada pergunta, o que

pesa a favor e o que pesa contra.

Sinal na sua baseVetorialHíbrido (vetorial + léxico)GrafoAgente
Pergunta local ("qual o prazo")resolve, com menos peçasresolve, com folganão ajudaé exagero
Pergunta com termo exato (E-4021)erra: vetor não distingue "4021" de "4022"resolve: BM25 acha a stringnão ajudanão resolve sozinho
Pergunta por relaçãonão respondenão responderesolve, é o motivo de existirresolve, e de mais jeito
Pergunta global ("quais são os temas")não respondenão responderesolve com resumo de comunidaderesolve com mais custo
Multi-passo (filtrar e buscar de novo)nãonãonãoresolve, é o motivo de existir
Volume alto (> 100 mil trechos)funciona, mas exige índice aproximadomelhora com fusão por ranking (RRF)escala pior: mais upkeepcusto por passo
Base muda muitoreindex caromesmo custocusto de construção do grafoidem
Permissão por usuáriofiltro no motor, obrigatóriomesmo filtro, dos dois ladosmesmo filtro, nos nósmesmo filtro, em toda ferramenta
Latência apertada (1 s ou menos)melhor casosoma o custo da segunda buscatravessia multi-hop custapior caso
Orçamento apertadomelhor casocusto quase igualcusto de construção altopior caso
Time pequenocabe no bolso de uma pessoacabenão cabenão cabe
Auditoria obrigatóriaexige citação e registroidemidemexige registrar cada passo
SLA com resposta garantidaprecisa de degradaçãoidemidema degradação é mais cara: cada passo pode falhar

Três leituras que a tabela dá de graça. A primeira: híbrido é o ponto de partida de quase tudo — ele nunca é o pior caso em nenhuma linha, e é o que resolve a

pergunta por termo exato, que é a falha mais visível e mais comum da busca por

vetor. A segunda: grafo e agente não são o próximo passo do vetorial. São

respostas a perguntas específicas, e a coluna deles está cheia de "não ajuda" —

isso é o sinal de que você não tem aquela pergunta. A terceira: as duas últimas colunas da tabela (auditoria e SLA) não escolhem arquitetura. Elas escolhem

obrigação, e as duas Cortam architecture só depois de ela existir.

⚠️ A linha da latência é a que trava a maior parte dos projetos
Não é o modelo que é lento: é o número de chamadas em série. Uma resposta que

busca, depois reordena, depois monta, depois gera, tem o custo somado. Baixar

o top-k é a alavanca mais barata que existe, e é a que o

Lost in the Middle dá apoio: o conteúdo que

sobrevive a um corte cego no meio do contexto tende a ser justamente o que não

responde à pergunta. Mais contexto não é mais resposta.


4. A função recomendar(): o artigo sendo executável

Uma tabela que você lê é uma opinião. Uma função é um contrato: ela recebe

fatos, devolve decisão e justificativa, e dá para testar. As regras abaixo estão em

uma lista, na ordem em que são avaliadas, e a primeira que bate decide. Isso

importa: sem ordem explícita, um dia alguém insere uma regra no meio e o

resultado muda para quem não mexeu.

A função é pura. Ela não lê arquivo, não chama modelo, não consulta banco. Dada a

mesma entrada, devolve a mesma saída — e é por isso que dá para escrever o teste

antes do sistema existir.

from __future__ import annotations

from dataclasses import dataclass, field
from enum import StrEnum

# As premissas que a função assume. Elas não são verdade: são as hipóteses que
# você confirma (ou corrige) lendo a justificativa que ela devolve. Se uma
# premissa é falsa para a sua base, a regra que depende dela está errada, e a
# correção é na regra -- não em um número mágico dentro da função.
PREMISSAS: tuple[str, ...] = (
    "O corte do trecho já está resolvido; arquitetura não conserta corte ruim.",
    "Uma resposta com citação é exigível em qualquer arquitetura.",
    "Permissão por tenant é filtro dentro da consulta, nunca depois dela.",
    "Volume é número de trechos indexados, não de documentos.",
    "Uma pessoa sozinha não mantém grafo de conhecimento.",
)

# Um segundo já é aperto para uma tela de consulta: a pessoa já está esperando.
# Abaixo disso, peça que soma latência (segunda busca, reordenação, segunda
# chamada de modelo) precisa sair do caminho síncrono ou virar degradação.
LATENCIA_APERTADA_MS = 1000


class Arquitetura(StrEnum):
    """As quatro arquiteturas que esta série cobre."""

    VETORIAL = "vetorial"
    HIBRIDO = "hibrido"
    GRAFO = "grafo"
    AGENTE = "agente"


@dataclass(frozen=True)
class PerfilBase:
    """As doze perguntas, em um objeto só.

    Os nomes são autoexplicativos de propósito: este dataclass é a interface
    entre a conversa com o time e a decisão automática. Se um campo não pode ser
    preenchido com um número ou um sim/não, ele não deveria existir.
    """

    formato_dominante: str          # "local" | "termo" | "relacao" | "global" | "multi_passo"
    documentos: int
    atualizacoes_por_mes: int
    multi_tenant: bool
    latencia_maxima_ms: int
    orcamento_mensal_usd: float
    tamanho_do_time: int
    exige_auditoria: bool
    idiomas: int
    sla: bool
    precisa_sem_modelo: bool


@dataclass(frozen=True)
class Recomendacao:
    """A decisão, e tudo que vem junto dela para poder ser contestada."""

    arquitetura: Arquitetura
    extras: tuple[str, ...] = ()        # o que soma por cima da arquitetura
    justificativa: str = ""
    medir: tuple[str, ...] = ()         # o que medir antes de mudar qualquer coisa
    risco: str = ""                     # o conflito conhecido desta combinação
    bloqueadores: tuple[str, ...] = ()  # o que fazer ANTES da arquitetura


# Cada regra é (condição, arquitetura, motivo). A ordem é a ordem de decisão.
REGRAS: tuple[tuple, ...] = (
    (
        lambda p: p.formato_dominante == "relacao",
        Arquitetura.GRAFO,
        "A pergunta só se responde seguindo um caminho entre entidades, e "
        "nenhum trecho isolado contém o caminho.",
    ),
    (
        lambda p: p.formato_dominante == "global",
        Arquitetura.GRAFO,
        "A pergunta é sobre o acervo inteiro, então a resposta precisa ser "
        "sintetizada antes da consulta (resumo de comunidade).",
    ),
    (
        lambda p: p.formato_dominante == "multi_passo",
        Arquitetura.AGENTE,
        "A resposta exige mais de uma ação encadeada, com o resultado de uma "
        "virando entrada da próxima.",
    ),
    (
        lambda p: p.formato_dominante == "termo",
        Arquitetura.HIBRIDO,
        "A pergunta carrega uma string exata que similaridade de significado "
        "não distingue; a busca léxica acha a string, e a fusão por ranking "
        "junta as duas listas.",
    ),
    (
        lambda p: p.tamanho_do_time <= 1,
        Arquitetura.VETORIAL,
        "Uma pessoa não mantém grafo nem agente. Comece pelo que cabe.",
    ),
    (
        lambda p: p.latencia_maxima_ms <= LATENCIA_APERTADA_MS
        or p.orcamento_mensal_usd < 50,
        Arquitetura.VETORIAL,
        "Latência apertada ou orçamento apertado pedem o caminho mais curto "
        "entre a pergunta e a resposta.",
    ),
)

# O que se adiciona à arquitetura escolhida. Aqui não há ordem: cada item é
# uma condição independente, e a ordem de construção é da seção 7.
EXTRAS: tuple[tuple, ...] = (
    (
        lambda p: p.documentos >= 100_000,
        "reranker: com base grande, a ordenação inicial erra mais vezes",
    ),
    (
        lambda p: p.atualizacoes_por_mes >= 20,
        "fila de indexacao: reindexar do zero toda vez nao fecha",
    ),
    (
        lambda p: p.multi_tenant,
        "escopo obrigatorio na assinatura da funcao de busca",
    ),
    (
        lambda p: p.idiomas > 1,
        "indexador lexico por idioma e normalizacao no corte",
    ),
    (
        lambda p: p.sla,
        "degradacao declarada: o que a resposta faz sem modelo",
    ),
)

# O que precisa estar pronto antes da arquitetura escolhida funcionar.
BLOQUEADORES: tuple[tuple, ...] = (
    (lambda p: p.multi_tenant, "filtro por tenant dentro da consulta, testado"),
    (lambda p: p.exige_auditoria, "citacao obrigatoria no prompt"),
    (lambda p: p.sla, "caminho de resposta sem modelo, medido"),
    (lambda p: p.latencia_maxima_ms <= LATENCIA_APERTADA_MS, "orcamento de caracteres por bloco"),
)

# O que medir para saber se a escolha foi boa. Todos os nomes sao do artigo 11.
MEDIR: dict[Arquitetura, tuple[str, ...]] = {
    Arquitetura.VETORIAL: ("cobertura@10", "taxa de recusa", "recusa indevida"),
    Arquitetura.HIBRIDO: ("cobertura@10", "mrr", "fracao de acerto que veio do lexico"),
    Arquitetura.GRAFO: ("cobertura@10", "mrr", "profundidade media do caminho percorrido"),
    Arquitetura.AGENTE: ("passos por requisicao", "taxa de erro por passo", "custo por requisicao"),
}


def recomendar(perfil: PerfilBase) -> Recomendacao:
    """Transforma um perfil em arquitetura, com a justificativa e o risco.

    Função pura: mesma entrada, mesma saída. Se o resultado não faz sentido,
    o ajuste é em `REGRAS` ou em `PREMISSAS` -- nunca em um número dentro do
    corpo, porque aí a regra deixa de explicar o próprio resultado.
    """
    arquitetura, motivo = Arquitetura.VETORIAL, "nenhuma regra especial; padrão da série"
    for condicao, candidata, porque in REGRAS:
        if condicao(perfil):
            arquitetura, motivo = candidata, porque
            break

    extras = tuple(porque for condicao, porque in EXTRAS if condicao(perfil))
    bloqueadores = tuple(porque for condicao, porque in BLOQUEADORES if condicao(perfil))
    risco = _risco(perfil, arquitetura)
    return Recomendacao(
        arquitetura=arquitetura,
        extras=extras,
        justificativa=motivo,
        medir=MEDIR[arquitetura],
        risco=risco,
        bloqueadores=bloqueadores,
    )


def _risco(perfil: PerfilBase, arquitetura: Arquitetura) -> str:
    """O conflito conhecido desta combinação. Um por perfil, o mais caro.

    Conflito é quando duas coisas que você pediu se atrapalham. A tabela da
    seção 3 mostra as colunas; ela não mostra que a coluna do híbrido e a da
    latência apertada não podem ser compradas juntas sem custo.
    """
    if (
        perfil.latencia_maxima_ms <= LATENCIA_APERTADA_MS
        and perfil.documentos >= 100_000
    ):
        return ("latencia apertada com base grande: o reranker sai do caminho "
                "sincrono, ou a resposta passa a degradar")
    if arquitetura in (Arquitetura.GRAFO, Arquitetura.AGENTE) and perfil.tamanho_do_time <= 1:
        return "arquitetura que uma pessoa so nao mantem: o custo e manutencao, nao construcao"
    if arquitetura is Arquitetura.AGENTE and perfil.sla:
        return "cada passo do agente e um ponto de falha: o SLA precisa de corte, nao de sorte"
    if perfil.orcamento_mensal_usd < 50 and perfil.documentos >= 100_000:
        return "base grande com orcamento apertado: o custo esta na geracao, nao na busca"
    return "nenhum conflito conhecido: valide medindo"

A função tem uma propriedade que vale mais do que as regras: ela devolve o risco junto com a decisão. Uma ferramenta que só diz "use híbrido" e engole o

conflito é metade da ferramenta. A sua diz "use híbrido, e a sua latência apertada

com base grande vai empurrar o reranker para fora da resposta".

A segunda propriedade é o campo bloqueadores: é o que separa "arquitetura

certa" de "arquitetura possível". Uma base multi-tenant com auditoria obrigatória

não tem um problema de arquitetura — tem dois bloqueios que precisam estar

resolvidos antes, e ambos custam menos que a arquitetura.

⚠️ recomendar() é heurística com premissas declaradas, e ela não
conhece a sua base

As cinco premissas em PREMISSAS são as únicas coisa que a função sabe. Se a

sua base tem corte ruim, a função vai recomendar uma arquitetura excelente

para um sistema que não acha nada. É por isso que o último campo de todo

resultado é sempre "meça": a função é o começo da conversa com a base, e a

última palavra é sempre do artigo 11.


5. Quatro bases, quatro rotas

A função só prova alguma coisa se a entrada variar. Estes quatro perfis caem em

rotas diferentes de propósito: o quarto é o caso em que a função recomenda contra o que a maioria quer, e é o mais instructive de ler.

# Perfil 1: manual interno de uma empresa, uma pessoa mantendo, 900 documentos.
MANUAL_INTERNO = PerfilBase(
    formato_dominante="local", documentos=900, atualizacoes_por_mes=2,
    multi_tenant=False, latencia_maxima_ms=1500, orcamento_mensal_usd=30,
    tamanho_do_time=1, exige_auditoria=False, idiomas=1, sla=False,
    precisa_sem_modelo=False,
)

# Perfil 2: catálogo com código de produto e número de contrato, cinco pessoas,
# permissão por cliente, tela de busca com 800 ms de orçamento.
CATALOGO_COM_CODIGO = PerfilBase(
    formato_dominante="termo", documentos=180_000, atualizacoes_por_mes=60,
    multi_tenant=True, latencia_maxima_ms=800, orcamento_mensal_usd=400,
    tamanho_do_time=5, exige_auditoria=True, idiomas=1, sla=True,
    precisa_sem_modelo=True,
)

# Perfil 3: normas e contratos que se referenciam, com pergunta por relação.
NORMAS_COM_REFERENCIA = PerfilBase(
    formato_dominante="relacao", documentos=6_000, atualizacoes_por_mes=30,
    multi_tenant=False, latencia_maxima_ms=3000, orcamento_mensal_usd=200,
    tamanho_do_time=2, exige_auditoria=True, idiomas=1, sla=False,
    precisa_sem_modelo=False,
)

# Perfil 4: suporte interno, onde a resposta exige buscar, filtrar por um
# resultado e buscar de novo -- e o orçamento é de 40 dólares por mês.
SUPORTE_MULTI_PASSO = PerfilBase(
    formato_dominante="multi_passo", documentos=15_000, atualizacoes_por_mes=90,
    multi_tenant=True, latencia_maxima_ms=12_000, orcamento_mensal_usd=40,
    tamanho_do_time=3, exige_auditoria=False, idiomas=2, sla=True,
    precisa_sem_modelo=True,
)

for nome, perfil in [
    ("manual interno", MANUAL_INTERNO),
    ("catalogo com codigo", CATALOGO_COM_CODIGO),
    ("normas com referencia", NORMAS_COM_REFERENCIA),
    ("suporte multi-passo", SUPORTE_MULTI_PASSO),
]:
    r = recomendar(perfil)
    print(f"\n== {nome}: {r.arquitetura}")
    print(f"   porque: {r.justificativa}")
    print(f"   extras : {', '.join(r.extras) or 'nenhum'}")
    print(f"   risco  : {r.risco}")
    print(f"   medir  : {', '.join(r.medir)}")
    print(f"   antes  : {', '.join(r.bloqueadores) or 'nada bloqueante'}")

Quatro saídas, e vale ler cada uma procurando o conflito:

  • manual interno sai como vetorial, por causa do tamanho do time. É a rota

    que uma pessoa aguenta, e a única das quatro em que o bloco "extras" é

    vazio. A justificativa é a segunda regra da lista, e é a que mais surpreende:

    o time decide, não o volume.

  • catálogo com código sai como híbrido, e é o caso em que os quatro extras

    aparecem de uma vez. Repare no risco: 800 ms com 180 mil documentos empurra o

    reranker para fora do caminho síncrono. A função avisa em vez de escolher.

  • normas com referência sai como grafo. Os bloqueadores incluem citação

    obrigatória, e o extras pede fila de indexação — porque grafo que se

    reconstrói do zero toda semana não é grafo, é um exercício.

  • suporte multi-passo sai como agente, e o risco muda: cada passo é um

    ponto de falha, e o SLA precisa de corte, não de sorte. É aqui que a pergunta

    12 ("precisa de resposta sem modelo?") deixa de ser recomendação e vira

    obrigação.

O que nenhum dos quatro perfis produziu é um agente para resolver uma pergunta

local, nem um grafo para uma base de manual. Isso não é falta de cobertura: é o

resultado que a coluna "não ajuda" da tabela produz quando ela é lida a sério.


6. O que não fazer ainda

A série inteira descreve peças que valem a pena. Isso não significa que você

precisa de todas, e a ordem de errar é previsível.

Não comece pelo agente. Um agente com busca ruim produz uma resposta ruim que

pode estar em três lugares diferentes. Aí você otimiza o prompt — a única parte

rápida de editar — e o prompt nunca foi o problema. O artigo 10

tem os três casos em que multiagente é erro, e a

API do LangGraph mostra

o que a coluna do agente exige de fato: estado explícito, checkpoint e retomada.

A frase que resume o resto: um agente raciocina sobre o que a busca devolveu, e se

a observação é ruim, o raciocínio é elegante sobre nada.

Não construa o grafo antes de ter a pergunta de relação. Construir o grafo

é um projeto de dois meses com a terceira pergunta respondida no fim. A

alternativa honesta é rodar a busca vetorial + léxica durante um mês, anotar as

perguntas que ficaram sem resposta, e ver se "quem assinou o que substituiu o quê"

aparece na lista. Se aparecer, o grafo se justifica com dados. Se não aparece,

você economiza dois meses.

Não comece pela reordenação. O reranker melhora a ordenação de uma lista que

a busca já montou. Se o trecho certo não está entre os 50 candidatos, a

reordenação escolhe melhor entre os errados — e o resultado fica um pouco melhor

sem que ninguém perceba que o defeito continua o mesmo. Meça a cobertura antes: se

ela está em 0,4, o problema é recuperação, e reordenar é enfeite.

Não multi-tenant por filtro em Python. Já está no artigo 01 e vale repetir

porque é o erro que custa mais caro: filtrar depois não vaza na resposta, vaza no

registro. O payload filter do Qdrant

é a cláusula WHERE dentro da mesma consulta que ordena o resultado.

💡 O que fazer enquanto não decide: uma coisa só, e medida
Indexe 50 documentos, mande cinco perguntas que você sabe responder, e meça a

cobertura. Isso cabe numa tarde, não em um trimestre, e o número que sai é a

única base honesta para qualquer uma das decisões desta seção.


7. A ordem de implantação, em quatro fases

A ordem abaixo não é a ordem dos artigos. É a ordem em que cada decisão fica

barata de revisar, porque cada fase tem um número que a justifica — e é esse

número, não a arquitetura preferida, que decide se você avança.

Fase 1 — vetorial, e nada mais. Indexe, corte, busque, responda, e meça

cobertura@10 com um conjunto de dez perguntas anotadas. Esta fase existe para

dar um número: sem ele, nenhuma fase depois tem critério de parada, e você vai

sentir que está melhorando sem saber. As métricas de trace e de avaliação que

medem isso estão no

guia de SDK do Langfuse,

no artigo 11.

Fase 2 — lexa, se a fase 1 não cobriu. É a fase da busca por palavra exata:

código, número de contrato, nome próprio. Meça de novo a mesma cobertura e o

mrr. Se o ganho for pequeno, a sua base não tem pergunta por termo — e a

pergunta 1 estava errada, o que é uma descoberta barata.

Fase 3 — corte, e depois o resto. A ordem aqui é contraintuitiva e é a que

mais economiza tempo: antes de adicionar peça, conserte a peça que existe. Corte

melhor muda mais a qualidade do que qualquer modelo de vetor. Só depois de

cobertura alta é que reranker, filtro por idioma e o resto entram.

Fase 4 — degradação, e só então as ramificações. Um caminho que responde sem

modelo, medido e com resposta, antes de grafo e antes de agente. A ordem não é

porque degradação é mais importante que grafo: é porque degradação é a única

fase que é obrigatória quando existe SLA, e ela é a mais barata de fazer bem.

O que a fase 4 decide é se o seu sistema precisa de grafo ou de agente — e essa

decisão, diferente das outras, não se responde com número. Responde-se com a lista

de perguntas que ficaram sem resposta depois das fases 1 a 3. Se a lista está

vazia, você terminou. Acabou cedo ser um resultado legítimo.


8. O custo operacional, honesto e qualitativo

O custo de uma arquitetura não é a diária do servidor. É quanto trabalho humano

ela pede por mês, depois de pronta — e é por isso que a ordem da seção 7 é esta.

Vetorial é o mais barato e o mais opaco. Um índice, um filtro, um

registrador. O custo que ninguém vê é o da qualidade: base grande com ordenação

ruim faz o usuário parar de confiar, e confiança não se recupera com modelo

melhor. Ele se recupera com citação correta.

Híbrido custa uma segunda consulta e um pouco de raciocínio. A segunda

busca é a parte barata; a parte cara é decidir a fusão, e a decisão errada (somar

escores de escalas diferentes) produz um resultado pior que vetorial puro sem

que nenhum alerta apareça. Custa uma métrica: a fração de acertos que veio do

léxico, que o artigo 11 já mede.

Grafo custa manutenção, que é o custo que não volta. Toda mudança no

documento pode virar aresta nova, e o grafo precisa de uma rotina que descubra

isso. Quem chama isso de "custo de construção" está descrevendo o primeiro mês.

Agente custa por requisição e por incidente. Cada passo é uma chance de

falhar, uma chance de gastar uma chamada de modelo, e uma chance de entrar em

ciclo. A métrica que importa não é "o agente acerta?", é "quantos passos por

requisição e quantos deles falharam" — o resto é otimização.

E há um custo que não aparece em planilha: a resposta errada. Uma resposta com

erro em base de política, contrato ou clínico não é um bug comum, é um incidente,

e o custo de responder errado depois de responder bem é assimétrico. Esse é o

argumento por trás de três decisões que parecem conservadoras neste artigo —

citação obrigatória, registro por requisição e degradação declarada. Nenhuma delas

aumenta a qualidade da resposta; as três diminuem a chance de você ficar sem

saber o que aconteceu.


TL;DR

  • A pergunta errada é "qual o melhor banco vetorial". A certa é "que tipo de

    pergunta o usuário faz", e existem cinco formatos: local, por termo, por

    relação, global e multi-passo.

  • Quatro arquiteturas, um formato de pergunta cada. Por relação é grafo; global

    é grafo com resumo de comunidade; multi-passo é agente; por termo é híbrido; o

    resto é vetorial, e time de uma pessoa decide vetorial.

  • A função recomendar() é o artigo executável: pura, testável, com a regra

    em ordem explícita e o risco no mesmo retorno da decisão.

  • Auditoria e SLA não escolhem arquitetura — escolhem obrigação. Citação

    obrigatória, registro por requisição e degradação declarada vêm antes de grafo e

    de agente.

  • A última resposta é sempre "meça". As cinco premissas da função são

    hipóteses, e a única que confirma ou derruba a recomendação é a cobertura do

    conjunto dourado.


Referências

  • Hybrid Queries — Qdrant — a fusão por ranking que sustenta a coluna do híbrido e o top-k do reranker
  • Payload filtering — Qdrant — o filtro dentro da consulta, que é bloqueador antes de qualquer arquitetura
  • Graph API — LangGraph — o que a coluna do agente exige de fato
  • GraphRAG — Microsoft — o resumo de comunidade que a pergunta global exige
  • Lost in the Middle: How Language Models Use Long Contexts — por que a latência apertada e o top-k grande são o mesmo problema
  • SDK overview — Langfuse — as métricas que a fase 1 usa para justificar a fase 2